From a0a322a7eef8c98f32c29fea8593aa943747f46f Mon Sep 17 00:00:00 2001 From: Keanu Date: Sat, 18 Jul 2020 23:16:27 +0200 Subject: [PATCH 001/178] Removed JS structure. --- .eslintrc.json | 3 - config.json.example | 5 - package-lock.json | 1228 ------------------------ package.json | 24 - src/Commands/Hello.js | 16 - src/Commands/Information/Botinfo.js | 49 - src/Commands/Information/Serverinfo.js | 90 -- src/Commands/Information/Userinfo.js | 60 -- src/Commands/Utilities/Help.js | 56 -- src/Commands/Utilities/Ping.js | 22 - src/Commands/Utilities/Uptime.js | 17 - src/Events/message/message.js | 25 - src/Events/ready.js | 19 - src/Structures/BotClient.js | 40 - src/Structures/Command.js | 17 - src/Structures/Event.js | 15 - src/Structures/Util.js | 81 -- src/index.js | 5 - 18 files changed, 1772 deletions(-) delete mode 100644 .eslintrc.json delete mode 100644 config.json.example delete mode 100644 package-lock.json delete mode 100644 package.json delete mode 100644 src/Commands/Hello.js delete mode 100644 src/Commands/Information/Botinfo.js delete mode 100644 src/Commands/Information/Serverinfo.js delete mode 100644 src/Commands/Information/Userinfo.js delete mode 100644 src/Commands/Utilities/Help.js delete mode 100644 src/Commands/Utilities/Ping.js delete mode 100644 src/Commands/Utilities/Uptime.js delete mode 100644 src/Events/message/message.js delete mode 100644 src/Events/ready.js delete mode 100644 src/Structures/BotClient.js delete mode 100644 src/Structures/Command.js delete mode 100644 src/Structures/Event.js delete mode 100644 src/Structures/Util.js delete mode 100644 src/index.js diff --git a/.eslintrc.json b/.eslintrc.json deleted file mode 100644 index 6962895..0000000 --- a/.eslintrc.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "extends": "tesseract" -} \ No newline at end of file diff --git a/config.json.example b/config.json.example deleted file mode 100644 index 968e009..0000000 --- a/config.json.example +++ /dev/null @@ -1,5 +0,0 @@ -{ - "prefix": "!!", // Bot Prefix - "token": "", // Bot Token - "owners": [""] // Array of bot owner IDs -} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index e7a067b..0000000 --- a/package-lock.json +++ /dev/null @@ -1,1228 +0,0 @@ -{ - "name": "d.js-v12-bot", - "version": "0.0.1", - "lockfileVersion": 1, - "requires": true, - "dependencies": { - "@babel/code-frame": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.1.tgz", - "integrity": "sha512-IGhtTmpjGbYzcEDOw7DcQtbQSXcG9ftmAXtWTu9V936vDye4xjjekktFAtgZsWpzTj/X01jocB46mTywm/4SZw==", - "dev": true, - "requires": { - "@babel/highlight": "^7.10.1" - } - }, - "@babel/helper-validator-identifier": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.1.tgz", - "integrity": "sha512-5vW/JXLALhczRCWP0PnFDMCJAchlBvM7f4uk/jXritBnIa6E1KmqmtrS3yn1LAnxFBypQ3eneLuXjsnfQsgILw==", - "dev": true - }, - "@babel/highlight": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.1.tgz", - "integrity": "sha512-8rMof+gVP8mxYZApLF/JgNDAkdKa+aJt3ZYxF8z6+j/hpeXL7iMsKCPHa2jNMHu/qqBwzQF4OHNoYi8dMA/rYg==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.10.1", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - }, - "dependencies": { - "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" - } - } - } - }, - "@discordjs/collection": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-0.1.5.tgz", - "integrity": "sha512-CU1q0UXQUpFNzNB7gufgoisDHP7n+T3tkqTsp3MNUkVJ5+hS3BCvME8uCXAUFlz+6T2FbTCu75A+yQ7HMKqRKw==" - }, - "@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" - } - }, - "@types/color-name": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", - "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==", - "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.2.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.2.0.tgz", - "integrity": "sha512-apwXVmYVpQ34m/i71vrApRrRKCWQnZZF1+npOD0WV5xZFfwWOmKGQ2RWlfdy9vWITsenisM8M0Qeq8agcFHNiQ==", - "dev": true - }, - "acorn-jsx": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.2.0.tgz", - "integrity": "sha512-HiUX/+K2YpkpJ+SzBffkM/AQ2YE03S0U1kjTLVpoJdhZMOWy8qvXVN9JdLqv2QsaQ6MPYQIuNmwD8zOiYUofLQ==", - "dev": true - }, - "ajv": { - "version": "6.12.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.2.tgz", - "integrity": "sha512-k+V+hzjm5q/Mr8ef/1Y9goCmlsK4I6Sm74teeyGvFk1XrOsbsKLjEdrvny42CZ+a8sXbk8KWpY/bDwS+FLL2UQ==", - "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-escapes": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.1.tgz", - "integrity": "sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA==", - "dev": true, - "requires": { - "type-fest": "^0.11.0" - }, - "dependencies": { - "type-fest": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.11.0.tgz", - "integrity": "sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==", - "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": "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" - } - }, - "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": "1.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", - "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", - "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.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "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.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.0.0.tgz", - "integrity": "sha512-N9oWFcegS0sFr9oh1oz2d7Npos6vNoWW9HvtCg5N1KRFpUhaAhvTv5Y58g880fZaEYSNm3qDz8SU1UrGvp+n7A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "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 - }, - "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 - }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "chardet": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", - "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", - "dev": true - }, - "cli-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", - "dev": true, - "requires": { - "restore-cursor": "^3.1.0" - } - }, - "cli-width": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.1.tgz", - "integrity": "sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw==", - "dev": true - }, - "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 - }, - "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.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "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.2.0", - "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-12.2.0.tgz", - "integrity": "sha512-Ueb/0SOsxXyqwvwFYFe0msMrGqH1OMqpp2Dpbplnlr4MzcRrFWwsBM9gKNZXPVBHWUKiQkwU8AihXBXIvTTSvg==", - "requires": { - "@discordjs/collection": "^0.1.5", - "@discordjs/form-data": "^3.0.1", - "abort-controller": "^3.0.0", - "node-fetch": "^2.6.0", - "prism-media": "^1.2.0", - "setimmediate": "^1.0.5", - "tweetnacl": "^1.0.3", - "ws": "^7.2.1" - } - }, - "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": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "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.1.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.1.0.tgz", - "integrity": "sha512-DfS3b8iHMK5z/YLSme8K5cge168I8j8o1uiVmFCgnnjxZQbCGyraF8bMl7Ju4yfBmCuxD7shOF7eqGkcuIHfsA==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "ajv": "^6.10.0", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.0.1", - "doctrine": "^3.0.0", - "eslint-scope": "^5.0.0", - "eslint-utils": "^2.0.0", - "eslint-visitor-keys": "^1.1.0", - "espree": "^7.0.0", - "esquery": "^1.2.0", - "esutils": "^2.0.2", - "file-entry-cache": "^5.0.1", - "functional-red-black-tree": "^1.0.1", - "glob-parent": "^5.0.0", - "globals": "^12.1.0", - "ignore": "^4.0.6", - "import-fresh": "^3.0.0", - "imurmurhash": "^0.1.4", - "inquirer": "^7.0.0", - "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.14", - "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": "^5.2.3", - "text-table": "^0.2.0", - "v8-compile-cache": "^2.0.3" - } - }, - "eslint-config-tesseract": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/eslint-config-tesseract/-/eslint-config-tesseract-0.0.2.tgz", - "integrity": "sha512-JNSZBJYEoD+R2SYlbf+6De1v7iYeMJ7BPX0AYpNLCX5I+9/vcDjejlcOrji3edOi+1bH82jHg3zpr3nsO0rzUg==", - "dev": true - }, - "eslint-scope": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.0.0.tgz", - "integrity": "sha512-oYrhJW7S0bxAFDvWqzvMPRm6pcgcnWc4QnofCAqRTRfQC0JcwenzGglTtsLyIuuWFfkqDG9vz67cnttSd53djw==", - "dev": true, - "requires": { - "esrecurse": "^4.1.0", - "estraverse": "^4.1.1" - } - }, - "eslint-utils": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.0.0.tgz", - "integrity": "sha512-0HCPuJv+7Wv1bACm8y5/ECVfYdfsAm9xmVb7saeFlxjPYALefjhbYoCkBjPdPzGH8wWyTpAez82Fh3VKYEZ8OA==", - "dev": true, - "requires": { - "eslint-visitor-keys": "^1.1.0" - } - }, - "eslint-visitor-keys": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz", - "integrity": "sha512-8y9YjtM1JBJU/A9Kc+SbaOV4y29sSWckBwMHa+FGtVj5gN/sbnKDf6xJUl+8g7FAij9LVaP8C24DUiH/f/2Z9A==", - "dev": true - }, - "espree": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-7.0.0.tgz", - "integrity": "sha512-/r2XEx5Mw4pgKdyb7GNLQNsu++asx/dltf/CI8RFi9oGHxmQFgvLbc5Op4U6i8Oaj+kdslhJtVlEZeAqH5qOTw==", - "dev": true, - "requires": { - "acorn": "^7.1.1", - "acorn-jsx": "^5.2.0", - "eslint-visitor-keys": "^1.1.0" - } - }, - "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.3.1", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.3.1.tgz", - "integrity": "sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ==", - "dev": true, - "requires": { - "estraverse": "^5.1.0" - }, - "dependencies": { - "estraverse": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.1.0.tgz", - "integrity": "sha512-FyohXK+R0vE+y1nHLoBM7ZTyqRpqAlhdZHCWIWEviFLiGB8b04H6bQs8G+XTthacvT8VuwvteiP7RJSxMs8UEw==", - "dev": true - } - } - }, - "esrecurse": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", - "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", - "dev": true, - "requires": { - "estraverse": "^4.1.0" - } - }, - "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==" - }, - "external-editor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", - "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", - "dev": true, - "requires": { - "chardet": "^0.7.0", - "iconv-lite": "^0.4.24", - "tmp": "^0.0.33" - } - }, - "fast-deep-equal": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz", - "integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA==", - "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 - }, - "figures": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", - "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", - "dev": true, - "requires": { - "escape-string-regexp": "^1.0.5" - } - }, - "file-entry-cache": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz", - "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==", - "dev": true, - "requires": { - "flat-cache": "^2.0.1" - } - }, - "flat-cache": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", - "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==", - "dev": true, - "requires": { - "flatted": "^2.0.0", - "rimraf": "2.6.3", - "write": "1.0.3" - } - }, - "flatted": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz", - "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==", - "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.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "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.1", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", - "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", - "dev": true, - "requires": { - "is-glob": "^4.0.1" - } - }, - "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" - } - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dev": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "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.2.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz", - "integrity": "sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ==", - "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 - }, - "inquirer": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.1.0.tgz", - "integrity": "sha512-5fJMWEmikSYu0nv/flMc475MhGbB7TSPd/2IpFV4I4rMklboCH2rQjYY5kKiYGHqUF9gvaambupcJFFG9dvReg==", - "dev": true, - "requires": { - "ansi-escapes": "^4.2.1", - "chalk": "^3.0.0", - "cli-cursor": "^3.1.0", - "cli-width": "^2.0.0", - "external-editor": "^3.0.3", - "figures": "^3.0.0", - "lodash": "^4.17.15", - "mute-stream": "0.0.8", - "run-async": "^2.4.0", - "rxjs": "^6.5.3", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0", - "through": "^2.3.6" - }, - "dependencies": { - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "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 - }, - "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 - }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "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.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz", - "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==", - "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.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", - "dev": true - }, - "mime-db": { - "version": "1.44.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", - "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==" - }, - "mime-types": { - "version": "2.1.27", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", - "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", - "requires": { - "mime-db": "1.44.0" - } - }, - "mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true - }, - "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" - } - }, - "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", - "dev": true - }, - "mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "dev": true, - "requires": { - "minimist": "^1.2.5" - } - }, - "moment": { - "version": "2.27.0", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.27.0.tgz", - "integrity": "sha512-al0MUK7cpIcglMv3YF13qSgdAIqxHTO7brRtaz3DlSULbqfazqkc5kEjNrLDOM7fsjshoFIihnU8snrP7zUvhQ==" - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "mute-stream": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", - "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", - "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.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz", - "integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==" - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, - "requires": { - "wrappy": "1" - } - }, - "onetime": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.0.tgz", - "integrity": "sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q==", - "dev": true, - "requires": { - "mimic-fn": "^2.1.0" - } - }, - "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" - } - }, - "os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", - "dev": true - }, - "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.2", - "resolved": "https://registry.npmjs.org/prism-media/-/prism-media-1.2.2.tgz", - "integrity": "sha512-I+nkWY212lJ500jLe4tN9tWO7nRiBAVdMv76P9kffZjYhw20raMlW1HSSvS+MLXC9MmbNZCazMrAr+5jEEgTuw==" - }, - "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 - }, - "regexpp": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz", - "integrity": "sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==", - "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 - }, - "restore-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", - "dev": true, - "requires": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" - } - }, - "rimraf": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", - "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "run-async": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", - "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", - "dev": true - }, - "rxjs": { - "version": "6.5.5", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.5.tgz", - "integrity": "sha512-WfQI+1gohdf0Dai/Bbmk5L5ItH5tYqm3ki2c5GdWhKjalzjg93N3avFjVStyZZz+A2Em+ZxKH5bNghw9UeylGQ==", - "dev": true, - "requires": { - "tslib": "^1.9.0" - } - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true - }, - "semver": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", - "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==", - "dev": true - }, - "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 - }, - "signal-exit": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", - "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", - "dev": true - }, - "slice-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", - "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.0", - "astral-regex": "^1.0.0", - "is-fullwidth-code-point": "^2.0.0" - }, - "dependencies": { - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - } - } - }, - "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.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", - "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - } - }, - "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.0", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.0.tgz", - "integrity": "sha512-e6/d0eBu7gHtdCqFt0xJr642LdToM5/cN4Qb9DbHjVx1CP5RyeM+zH7pbecEmDv/lBqb0QH+6Uqq75rxFPkM0w==", - "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" - } - }, - "table": { - "version": "5.4.6", - "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz", - "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==", - "dev": true, - "requires": { - "ajv": "^6.10.2", - "lodash": "^4.17.14", - "slice-ansi": "^2.1.0", - "string-width": "^3.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - }, - "emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - } - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - } - } - } - }, - "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 - }, - "through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", - "dev": true - }, - "tmp": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", - "dev": true, - "requires": { - "os-tmpdir": "~1.0.2" - } - }, - "tslib": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz", - "integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==", - "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.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", - "dev": true - }, - "uri-js": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", - "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", - "dev": true, - "requires": { - "punycode": "^2.1.0" - } - }, - "v8-compile-cache": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.1.tgz", - "integrity": "sha512-8OQ9CL+VWyt3JStj7HX7/ciTL2V3Rl1Wf5OL+SNTm0yK1KvtReVulksyeRnCANHHuUxHlQig+JJDlUhBt1NQDQ==", - "dev": true - }, - "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 - }, - "write": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz", - "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==", - "dev": true, - "requires": { - "mkdirp": "^0.5.1" - } - }, - "ws": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.3.0.tgz", - "integrity": "sha512-iFtXzngZVXPGgpTlP1rBqsUK82p9tKqsWRPg5L56egiljujJT3vGAYnHANvFxBieXrTFavhzhxW52jnaWV+w2w==" - } - } -} diff --git a/package.json b/package.json deleted file mode 100644 index 7e0e79b..0000000 --- a/package.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "name": "d.js-v12-bot", - "version": "0.0.1", - "description": "A Discord bot built on Discord.JS v12", - "main": "src/index", - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" - }, - "keywords": [ - "discord.js", - "bot" - ], - "author": "Keanu Timmermans", - "license": "Apache-2.0", - "dependencies": { - "discord.js": "^12.2.0", - "moment": "^2.27.0", - "ms": "^2.1.2" - }, - "devDependencies": { - "eslint": "^7.0.0", - "eslint-config-tesseract": "^0.0.2" - } -} diff --git a/src/Commands/Hello.js b/src/Commands/Hello.js deleted file mode 100644 index e1eb13e..0000000 --- a/src/Commands/Hello.js +++ /dev/null @@ -1,16 +0,0 @@ -/* eslint-disable no-unused-vars */ -const Command = require('./../Structures/Command.js'); - -module.exports = class extends Command { - - constructor(...args) { - super(...args, { - aliases: ['hallo'] - }); - } - - async run(message, args) { - message.channel.send('Hello'); - } - -}; diff --git a/src/Commands/Information/Botinfo.js b/src/Commands/Information/Botinfo.js deleted file mode 100644 index 5d35c75..0000000 --- a/src/Commands/Information/Botinfo.js +++ /dev/null @@ -1,49 +0,0 @@ -const Command = require('../../Structures/Command'); -const { MessageEmbed, version: djsversion } = require('discord.js'); -const { version } = require('../../../package.json'); -const { utc } = require('moment'); -const os = require('os'); -const ms = require('ms'); - -module.exports = class extends Command { - - constructor(...args) { - super(...args, { - aliases: ['info', 'bot', 'botinfo'], - category: 'Information' - }); - } - - run(message) { - const core = os.cpus()[0]; - const embed = new MessageEmbed() - .setThumbnail(this.client.user.displayAvatarURL()) - .setColor(message.guild.me.displayHexColor || 'BLUE') - .addField('General', [ - `**❯ Client:** ${this.client.user.tag} (${this.client.user.id})`, - `**❯ Commands:** ${this.client.commands.size}`, - `**❯ Servers:** ${this.client.guilds.cache.size.toLocaleString()}`, - `**❯ Users:** ${this.client.guilds.cache.reduce((a, b) => a + b.memberCount, 0).toLocaleString()}`, - `**❯ Channels:** ${this.client.channels.cache.size.toLocaleString()}`, - `**❯ Creation Date:** ${utc(this.client.user.createdTimestamp).format('Do MMMM YYYY HH:mm:ss')}`, - `**❯ Node.JS:** ${process.version}`, - `**❯ Version:** v${version}`, - `**❯ Discord.JS:** ${djsversion}`, - '\u200b' - ]) - .addField('System', [ - `**❯ Platform:** ${process.platform}`, - `**❯ Uptime:** ${ms(os.uptime() * 1000, { long: true })}`, - `**❯ CPU:**`, - `\u3000 • Cores: ${os.cpus().length}`, - `\u3000 • Model: ${core.model}`, - `\u3000 • Speed: ${core.speed}MHz`, - `**❯ Memory:**`, - `\u3000 • Total: ${this.client.utils.formatBytes(process.memoryUsage().heapTotal)}`, - `\u3000 • Used: ${this.client.utils.formatBytes(process.memoryUsage().heapTotal)}` - ]) - .setTimestamp(); - message.channel.send(embed); - } - -}; diff --git a/src/Commands/Information/Serverinfo.js b/src/Commands/Information/Serverinfo.js deleted file mode 100644 index 4247e6d..0000000 --- a/src/Commands/Information/Serverinfo.js +++ /dev/null @@ -1,90 +0,0 @@ -/* eslint-disable no-undef */ -/* eslint-disable no-warning-comments */ -const Command = require('../../Structures/Command'); -const { MessageEmbed } = require('discord.js'); -const moment = require('moment'); - -const filterLevels = { - DISABLED: 'Off', - MEMBERS_WITHOUT_ROLES: 'No Role', - ALL_MEMBERS: 'Everyone' -}; -const verificationLevels = { - NONE: 'None', - LOW: 'Low', - MEDIUM: 'Medium', - HIGH: '(╯°□°)╯︵ ┻━┻', - VERY_HIGH: '┻━┻ ミヽ(ಠ益ಠ)ノ彡┻━┻' -}; -const regions = { - brazil: 'Brazil', - europe: 'Europe', - hongkong: 'Hong Kong', - india: 'India', - japan: 'Japan', - russia: 'Russia', - singapore: 'Singapore', - southafrica: 'South Africa', - sydney: 'Sydney', - 'us-central': 'US Central', - 'us-east': 'US East', - 'us-west': 'US West', - 'us-south': 'US South' -}; - -module.exports = class extends Command { - - constructor(...args) { - super(...args, { - aliases: ['server', 'guild', 'guildinfo'], - category: 'Information' - }); - } - - async run(message) { - const roles = message.guild.roles.cache.sort((a, b) => b.position - a.position).map(role => role.toString()); - const members = message.guild.members.cache; - const channels = message.guild.channels.cache; - const emojis = message.guild.emojis.cache; - - const embed = new MessageEmbed() - .setDescription(`**Guild information for __${message.guild.name}__**`) - .setColor('BLUE') - .setThumbnail(message.guild.iconURL({ dynamic: true })) - .addField('General', [ - `**❯ Name:** ${message.guild.name}`, - `**❯ ID:** ${message.guild.id}`, - `**❯ Owner:** ${message.guild.owner.user.tag} (${message.guild.ownerID})`, - `**❯ Region:** ${regions[message.guild.region]}`, - `**❯ Boost Tier:** ${message.guild.premiumTier ? `Tier ${message.guild.premiumTier}` : 'None'}`, - `**❯ Explicit Filter:** ${filterLevels[message.guild.explicitContentFilter]}`, - `**❯ Verification Level:** ${verificationLevels[message.guild.verificationLevel]}`, - `**❯ Time Created:** ${moment(message.guild.createdTimestamp).format('LT')} ${moment(message.guild.createdTimestamp).format('LL')} ${moment(message.guild.createdTimestamp).fromNow()})`, - '\u200b' - ]) - .addField('Statistics', [ - `**❯ Role Count:** ${roles.length}`, - `**❯ Emoji Count:** ${emojis.size}`, - `**❯ Regular Emoji Count:** ${emojis.filter(emoji => !emoji.animated).size}`, - `**❯ Animated Emoji Count:** ${emojis.filter(emoji => emoji.animated).size}`, - `**❯ Member Count:** ${message.guild.memberCount}`, - `**❯ Humans:** ${members.filter(member => !member.user.bot).size}`, - `**❯ Bots:** ${members.filter(member => member.user.bot).size}`, - `**❯ Text Channels:** ${channels.filter(channel => channel.type === 'text')}`, - `**❯ Voice Channels:** ${channels.filter(channel => channel.type === 'voice')}`, - `**❯ Boost Count:** ${message.guild.premiumSubscriptionCount || '0'}`, - `\u200b` - ]) - .addField('Presence', [ - `**❯ Online:** ${members.filter(member => member.presence.status === 'online').size}`, - `**❯ Idle:** ${members.filter(member => member.presence.status === 'idle').size}`, - `**❯ Do Not Disturb:** ${members.filter(member => member.presence.stats === 'dnd').size}`, - `**❯ Offline:** ${members.filter(member => member.presence.status === 'offline').size}`, - '\u200b' - ]) - .addField(`Roles [${roles.length - 1}]`, roles.length < 10 ? roles.join(', ') : roles.length > 10 ? this.client.utils.trimArray(roles) : 'None') - .setTimestamp(); - message.channel.send(embed); - } - -}; diff --git a/src/Commands/Information/Userinfo.js b/src/Commands/Information/Userinfo.js deleted file mode 100644 index 919c183..0000000 --- a/src/Commands/Information/Userinfo.js +++ /dev/null @@ -1,60 +0,0 @@ -const Command = require('../../Structures/Command'); -const { MessageEmbed } = require('discord.js'); -const moment = require('moment'); - -const flags = { - DISCORD_EMPLOYEE: 'Discord Employee', - DISCORD_PARTNER: 'Discord Partner', - BUGHUNTER_LEVEL_1: 'Bug Hunter (Level 1)', - BUGHUNTER_LEVEL_2: 'Bug Hunter (Level 2)', - HYPESQUAD_EVENTS: 'HypeSquad Events', - HOUSE_BRAVERY: 'House of Bravery', - HOUSE_BRILLIANCE: 'House of Brilliance', - HOUSE_BALANCE: 'House of Balance', - EARLY_SUPPORTER: 'Early Supporter', - TEAM_USER: 'Team User', - SYSTEM: 'System', - VERIFIED_BOT: 'Verified Bot', - VERIFIED_DEVELOPER: 'Verified Bot Developer' -}; - -module.exports = class extends Command { - - constructor(...args) { - super(...args, { - aliases: ['user', 'ui'], - category: 'Information' - }); - } - - async run(message, [target]) { - const member = message.mentions.members.last() || message.guild.members.cache.get(target) || message.member; - const roles = member.roles.cache - .sort((a, b) => b.position - a.position) - .map(role => role.toString()) - .slice(0, -1); - const userFlags = member.user.flags.toArray(); - - const embed = new MessageEmbed() - .setThumbnail(member.user.displayAvatarURL({ dynamic: true, size: 512 })) - .setColor(member.displayHexColor || 'BLUE') - .addField('User', [ - `**❯ Username:** ${member.user.username}`, - `**❯ Discriminator:** ${member.user.discriminator}`, - `**❯ ID:** ${member.id}`, - `**❯ Flags:** ${userFlags.length ? userFlags.map(flag => flags[flag]).join(', ') : 'None'}`, - `**❯ Avatar:** [Link to avatar](${member.user.displayAvatarURL({ dynamic: true })})`, - `**❯ Time Created:** ${moment(member.user.createdTimestamp).format('LT')} ${moment(member.user.createdTimestamp).format('LL')} ${moment(member.user.createdTimestamp).fromNow()}`, - `**❯ Status:** ${member.user.presence.status}`, - `**❯ Game:** ${member.user.presence.game || 'Not playing a game.'}` - ]) - .addField('Member', [ - `**❯ Highest Role:** ${member.roles.highest.id === message.guild.id ? 'None' : member.roles.highest.name}`, - `**❯ Server Join Date:** ${moment(member.joinedAt).format('LL LTS')}`, - `**❯ Hoist Role:** ${member.roles.hoist ? member.roles.hoist.name : 'None'}`, - `**❯ Roles:** [${roles.length}]: ${roles.length < 10 ? roles.join(', ') : roles.length > 10 ? this.client.utils.trimArray(roles) : 'None'}`, - ]); - return message.channel.send(embed); - } - -}; diff --git a/src/Commands/Utilities/Help.js b/src/Commands/Utilities/Help.js deleted file mode 100644 index 25dd2e8..0000000 --- a/src/Commands/Utilities/Help.js +++ /dev/null @@ -1,56 +0,0 @@ -const Command = require('../../Structures/Command'); -const { MessageEmbed } = require('discord.js'); - -module.exports = class extends Command { - - constructor(...args) { - super(...args, { - aliases: ['help', 'halp'], - category: 'Utilities' - }); - } - - async run(message, [command]) { - const embed = new MessageEmbed() - .setColor('BLUE') - .setAuthor(`${message.guild.name} Help Menu`, message.guild.iconURL({ dynamic: true })) - .setThumbnail(this.client.user.displayAvatarURL()) - .setFooter(`Requested by ${message.author.username}`, message.author.displayAvatarURL({ dynamic: true })) - .setTimestamp(); - - if (command) { - const cmd = this.client.commands.get(command) || this.client.command.get(this.aliases.get(command)); - - if (!cmd) return message.channel.send(`\`${command}\` is not a valid command.`); - - embed.setAuthor(`${this.client.utils.capitalise(cmd.name)} Command Help`, this.client.user.displayAvatarURL()); - embed.setDescription([ - `**❯ Aliases:** ${cmd.aliases.length ? cmd.aliases.map(alias => `\`${alias}\``).join(' ') : 'No Aliases'}`, - `**❯ Description:** ${cmd.description}`, - `**❯ Category:** ${cmd.category}`, - `**❯ Usage:** ${cmd.usage}` - ]); - - return message.channel.send(embed); - } else { - embed.setDescription([ - `These are the available commands for ${message.guild.name}`, - `This bot's prefix is: ${this.client.prefix}`, - `Command Parameters: \`<>\` is a strict & \`[]\` is optional` - ]); - let categories; - if (!this.client.owners.includes(message.author.id)) { - categories = this.client.utils.removeDuplicates(this.client.commands.filter(cmd => cmd.category !== 'Owner').map(cmd => cmd.category)); - } else { - categories = this.client.utils.removeDuplicates(this.client.commands.map(cmd => cmd.category)); - } - - for (const category of categories) { - embed.addField(`**${this.client.utils.capitalise(category)}**`, this.client.commands.filter(cmd => - cmd.category === category).map(cmd => `\`${cmd.name}\``).join(' ')); - } - return message.channel.send(embed); - } - } - -}; diff --git a/src/Commands/Utilities/Ping.js b/src/Commands/Utilities/Ping.js deleted file mode 100644 index 17b75c1..0000000 --- a/src/Commands/Utilities/Ping.js +++ /dev/null @@ -1,22 +0,0 @@ -const Command = require('../../Structures/Command'); - -module.exports = class extends Command { - - constructor(...args) { - super(...args, { - aliases: ['pong'], - category: 'Utilities' - }); - } - - async run(message) { - const msg = await message.channel.send('Pinging...'); - - const latency = msg.createdTimestamp - message.createdTimestamp; - const choices = ['Is this really my ping?', "Is this okay? I can't look!", "I hope it isn't bad!"]; - const response = choices[Math.floor(Math.random() * choices.length)]; - - msg.edit(`${response} - Bot Latency: \`${latency}ms\`, API Latency: \`${Math.round(this.client.ws.ping)}ms\``); - } - -}; diff --git a/src/Commands/Utilities/Uptime.js b/src/Commands/Utilities/Uptime.js deleted file mode 100644 index 5608c44..0000000 --- a/src/Commands/Utilities/Uptime.js +++ /dev/null @@ -1,17 +0,0 @@ -const Command = require('../../Structures/Command.js'); -const ms = require('ms'); - -module.exports = class extends Command { - - constructor(...args) { - super(...args, { - aliases: ['uptime'], - category: 'Utilities' - }); - } - - async run(message) { - message.channel.send(`My uptime is \`${ms(this.client.uptime, { long: true })}\``); - } - -}; diff --git a/src/Events/message/message.js b/src/Events/message/message.js deleted file mode 100644 index c2a23b8..0000000 --- a/src/Events/message/message.js +++ /dev/null @@ -1,25 +0,0 @@ -const Event = require('../../Structures/Event'); - -module.exports = class extends Event { - - async run(message) { - const mentionRegex = RegExp(`^<@!${this.client.user.id}>$`); - const mentionRegexPrefix = RegExp(`^<@!${this.client.user.id}> `); - - if (!message.guild || message.author.bot) return; - - if (message.content.match(mentionRegex)) message.channel.send(`My prefix for ${message.guild.name} is \`${this.client.prefix}\`.`); - - const prefix = message.content.match(mentionRegexPrefix) ? - message.content.match(mentionRegexPrefix)[0] : this.client.prefix; - - // eslint-disable-next-line no-unused-vars - const [cmd, ...args] = message.content.slice(prefix.length).trim().split(/ +/g); - - const command = this.client.commands.get(cmd.toLowerCase()) || this.client.commands.get(this.client.aliases.get(cmd.toLowerCase())); - if (command) { - command.run(message, args); - } - } - -}; diff --git a/src/Events/ready.js b/src/Events/ready.js deleted file mode 100644 index 16dfe9f..0000000 --- a/src/Events/ready.js +++ /dev/null @@ -1,19 +0,0 @@ -const Event = require('../Structures/Event'); - -module.exports = class extends Event { - - constructor(...args) { - super(...args, { - once: true - }); - } - - run() { - console.log([ - `Logged in as ${this.client.user.tag}`, - `Loaded ${this.client.commands.size} commands.`, - `Loaded ${this.client.events.size} events.` - ].join('\n')); - } - -}; diff --git a/src/Structures/BotClient.js b/src/Structures/BotClient.js deleted file mode 100644 index 51a8326..0000000 --- a/src/Structures/BotClient.js +++ /dev/null @@ -1,40 +0,0 @@ -const { Client, Collection } = require('discord.js'); -const Util = require('./Util.js'); - -module.exports = class BotClient extends Client { - - constructor(options = {}) { - super({ - disableMentions: 'everyone' - }); - this.validate(options); - - this.commands = new Collection(); - - this.events = new Collection(); - - this.aliases = new Collection(); - - this.utils = new Util(this); - - this.owners = options.owners; - } - - validate(options) { - if (typeof options !== 'object') throw new TypeError('Options should be a type of Object.'); - - if (!options.token) throw new Error('You must pass a token for the client.'); - this.token = options.token; - - if (!options.prefix) throw new Error('You must pass a prefix for the client.'); - if (typeof options.prefix !== 'string') throw new TypeError('Prefix should be a type of String.'); - this.prefix = options.prefix; - } - - async start(token = this.token) { - this.utils.loadCommands(); - this.utils.loadEvents(); - super.login(token); - } - -}; diff --git a/src/Structures/Command.js b/src/Structures/Command.js deleted file mode 100644 index 02fb6f1..0000000 --- a/src/Structures/Command.js +++ /dev/null @@ -1,17 +0,0 @@ -module.exports = class Command { - - constructor(client, name, options = {}) { - this.client = client; - this.name = options.name || name; - this.aliases = options.aliases || []; - this.description = options.description || 'No description provided.'; - this.category = options.category || 'Miscellaneous'; - this.usage = `${this.client.prefix}${this.name} ${options.usage || ''}`.trim(); - } - - // eslint-disable-next-line no-unused-vars - async run(message, args) { - throw new Error(`Command ${this.name} doesn't provide a run method.`); - } - -}; diff --git a/src/Structures/Event.js b/src/Structures/Event.js deleted file mode 100644 index 5c816a1..0000000 --- a/src/Structures/Event.js +++ /dev/null @@ -1,15 +0,0 @@ -/* eslint-disable no-unused-vars */ -module.exports = class Event { - - constructor(client, name, options = {}) { - this.name = name; - this.client = client; - this.type = options.once ? 'once' : 'on'; - this.emitter = (typeof options.emitter === 'string' ? this.client[options.emitter] : options.emitter) || this.client; - } - - async run(...args) { - throw new Error(`The run method has not been implemented in ${this.name}`); - } - -}; diff --git a/src/Structures/Util.js b/src/Structures/Util.js deleted file mode 100644 index 40045cd..0000000 --- a/src/Structures/Util.js +++ /dev/null @@ -1,81 +0,0 @@ -const path = require('path'); -const { promisify } = require('util'); -const glob = promisify(require('glob')); -const Command = require('./Command.js'); -const Event = require('./Event.js'); - -module.exports = class Util { - - constructor(client) { - this.client = client; - } - - isClass(input) { - return typeof input === 'function' && - typeof input.prototype === 'object' && - input.toString().substring(0, 5) === 'class'; - } - - get directory() { - return `${path.dirname(require.main.filename)}${path.sep}`; - } - - trimArray(arr, maxLen = 10) { - if (arr.length > maxLen) { - const len = arr.length - maxLen; - arr = arr.slice(0, maxLen); - arr.push(`${len} more...`); - } - return arr; - } - - formatBytes(bytes) { - if (bytes === 0) return '0 Bytes'; - const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; - const i = Math.floor(Math.log(bytes) / Math.log(1024)); - return `${parseFloat((bytes / Math.pow(1024, i)).toFixed(2))} ${sizes[i]}`; - } - - removeDuplicates(arr) { - return [...new Set(arr)]; - } - - capitalise(string) { - return string.split(' ').map(str => str.slice(0, 1).toUpperCase() + str.slice(1)).join(' '); - } - - async loadCommands() { - return glob(`${this.directory}commands/**/*.js`).then(commands => { - for (const commandFile of commands) { - delete require.cache[commandFile]; - const { name } = path.parse(commandFile); - const File = require(commandFile); - if (!this.isClass(File)) throw new TypeError(`Command ${name} doesn't export a class.`); - const command = new File(this.client, name.toLowerCase()); - if (!(command instanceof Command)) throw new TypeError(`Command ${name} doesn't belong in commands.`); - this.client.commands.set(command.name, command); - if (command.aliases.length) { - for (const alias of command.aliases) { - this.client.aliases.set(alias, command.name); - } - } - } - }); - } - - async loadEvents() { - return glob(`${this.directory}events/**/*.js`).then(events => { - for (const eventFile of events) { - delete require.cache[eventFile]; - const { name } = path.parse(eventFile); - const File = require(eventFile); - if (!this.isClass(File)) throw new TypeError(`Event ${name} doesn't export a class!`); - const event = new File(this.client, name.toLowerCase()); - if (!(event instanceof Event)) throw new TypeError(`Event ${name} doesn't belong in the Events Directory.`); - this.client.events.set(event.name, event); - event.emitter[event.type](name, (...args) => event.run(...args)); - } - }); - } - -}; diff --git a/src/index.js b/src/index.js deleted file mode 100644 index 669f2a7..0000000 --- a/src/index.js +++ /dev/null @@ -1,5 +0,0 @@ -const BotClient = require('./Structures/BotClient'); -const config = require('../config.json'); - -const client = new BotClient(config); -client.start(); From b5e1ceaad31e5a5cb891561784df371e6c28b7f8 Mon Sep 17 00:00:00 2001 From: Keanu Date: Mon, 20 Jul 2020 10:53:51 +0200 Subject: [PATCH 002/178] Relicensed under MIT. --- LICENSE | 214 +++++--------------------------------------------------- 1 file changed, 17 insertions(+), 197 deletions(-) diff --git a/LICENSE b/LICENSE index 8f272d5..82dddca 100644 --- a/LICENSE +++ b/LICENSE @@ -1,201 +1,21 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ +MIT License - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION +Copyright (c) 2020 Keanu Timmermans - 1. Definitions. +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright 2020 Keanu Timmermans, Lexi Sother - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. \ No newline at end of file +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. From 295995aba2e7bd879cd8dd9b29ac29949161913c Mon Sep 17 00:00:00 2001 From: WatDuhHekBro <44940783+WatDuhHekBro@users.noreply.github.com> Date: Sat, 25 Jul 2020 03:15:26 -0500 Subject: [PATCH 003/178] Ported CrossExchange v1.0.1 and removed stonks --- .gitignore | 12 +- docs/CHANGELOG.md | 0 docs/Specifications.md | 31 +++ package-lock.json | 532 +++++++++++++++++++++++++++++++++++++++++ package.json | 31 +++ src/commands/admin.ts | 107 +++++++++ src/commands/help.ts | 102 ++++++++ src/commands/money.ts | 241 +++++++++++++++++++ src/core/command.ts | 145 +++++++++++ src/core/lib.ts | 374 +++++++++++++++++++++++++++++ src/core/storage.ts | 109 +++++++++ src/core/structures.ts | 113 +++++++++ src/core/wrappers.ts | 51 ++++ src/index.ts | 113 +++++++++ src/setup.ts | 48 ++++ tsconfig.json | 17 ++ 16 files changed, 2022 insertions(+), 4 deletions(-) create mode 100644 docs/CHANGELOG.md create mode 100644 docs/Specifications.md create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 src/commands/admin.ts create mode 100644 src/commands/help.ts create mode 100644 src/commands/money.ts create mode 100644 src/core/command.ts create mode 100644 src/core/lib.ts create mode 100644 src/core/storage.ts create mode 100644 src/core/structures.ts create mode 100644 src/core/wrappers.ts create mode 100644 src/index.ts create mode 100644 src/setup.ts create mode 100644 tsconfig.json diff --git a/.gitignore b/.gitignore index 89d4333..2a0491a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,13 +1,17 @@ +# Specific to this repository +dist/ +data/ +tmp/ +test* +*.bat +desktop.ini + # Logs logs *.log npm-debug.log* yarn-debug.log* yarn-error.log* -config.json -config.js -data/ -test.js # Runtime data pids diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md new file mode 100644 index 0000000..e69de29 diff --git a/docs/Specifications.md b/docs/Specifications.md new file mode 100644 index 0000000..9591e39 --- /dev/null +++ b/docs/Specifications.md @@ -0,0 +1,31 @@ +# Structure +The top-level directory is reserved for files that have to be there for it to work as well as configuration files. +- `src`: Contains all the code for the bot itself. Code in this directory is for independent tasks keeping the initialization out of the subdirectories. + - `core`: This is where core structures and critical functions for the bot go. + - `modules`: This is where modules go that accomplish one specific purpose but isn't so necessary for the bot to function. The goal is to be able to safely remove these without too much trouble. + - `commands`: Here's the place to store commands. The file name determines the command name. +- `dist`: This is where the runnable code in `src` compiles to. (The directory structure mirrors `src`.) +- `data`: Holds all the dynamic data used by the bot. This is what you modify if you want to change stuff for just your instance of the bot. +- `standard`: Contains all the standard data to be used with the project itself. It's part of the code and will not be checked for inaccuracies because it's not meant to be easily modified. +- `docs`: Used for information about the design of the project. + +# Specific Files +This list starts from `src`/`dist`. +- `index`: This is the entry point of the bot. Here is where all the initialization is done, because the idea is to keep repeatable code in separate modules while having code that runs only once here designating this is **the** starting point. +- `setup`: Used for the first time the bot is loaded, walking the user through setting up the bot. +- `core/lib`: Exports a function object which lets you wrap values letting you call special functions as well as calling utility functions common to all commands. +- `core/structures`: Contains all the structures that the dynamic data read from JSON files should follow. This exports instances of these classes. +- `core/command`: Contains the class used to instantiate commands. +- `core/storage`: Exports an object which handles everything related to files. +- `core/wrappers`: Contains classes that wrap around values and provide extra functionality. + +# Design Decisions +- All top-level files (relative to `src`/`dist`) should ideally be independent, one-time use scripts. This helps separate code that just initializes once and reusable code that forms the bulk of the main program itself. That's why all the file searching and loading commands/events will be done in `index`. +- Wrapper objects were designed with the idea of letting you assign functions directly to native objects [without the baggage of actually doing so](https://developer.mozilla.org/en-US/docs/Web/JavaScript/The_performance_hazards_of__%5B%5BPrototype%5D%5D_mutation). +- `test` should be a keyword for any file not tracked by git and generally be more flexible to play around with. It should also be automatically generated during initialization in `commands` so you can have templates ready for new commands. +- The storage module should not provide an auto-write feature. This would actually end up overcomplicating things especially when code isn't fully blocking. +- I think it's much easier to make a template system within the code itself. After all, the templates only change when the code changes to use new keys or remove old ones. You'll also be able to dedicate specific classes for the task rather than attaching meta tags to arrays and objects. +- I decided to forget about implementing dynamic events. I don't think it'll work with this setup. After all, there are only so many events you can use, whereas commands can have any number of ones, more suitable for dynamic loading. The main reasons were unsecure types and no easy way to access variables like the config or client. +- I want to make attaching subcommands more flexible, so you can either add subcommands in the constructor or by using a method. However, you have to add all other properties when instantiating a command. +- All commands should have only one parameter. This parameter is meant to be flexible so you can add properties without making a laundry list of parameters. It also has convenience functions too so you don't have to import the library for each command. +- The objects in `core/structures` are initialized into a special object once and then cached into memory automatically due to an import system. This means you don't have to keep loading JSON files over and over again even without the stack storage system. Because a JSON file resolves into an object, any extra keys are removed automatically (as it isn't initialized into the data) and any property that doesn't yet exist on the JSON object will be initialized into something. You can then have specific functions like `addUser` onto objects with a specific structure. \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..b60a197 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,532 @@ +{ + "name": "d.js-v12-bot", + "version": "0.0.1", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@discordjs/collection": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-0.1.5.tgz", + "integrity": "sha512-CU1q0UXQUpFNzNB7gufgoisDHP7n+T3tkqTsp3MNUkVJ5+hS3BCvME8uCXAUFlz+6T2FbTCu75A+yQ7HMKqRKw==" + }, + "@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" + } + }, + "@types/color-name": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", + "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==" + }, + "@types/inquirer": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@types/inquirer/-/inquirer-6.5.0.tgz", + "integrity": "sha512-rjaYQ9b9y/VFGOpqBEXRavc3jh0a+e6evAbI31tMda8VlPaSy0AZJfXsvmIe3wklc7W6C3zCSfleuMXR7NOyXw==", + "dev": true, + "requires": { + "@types/through": "*", + "rxjs": "^6.4.0" + } + }, + "@types/node": { + "version": "14.0.22", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.0.22.tgz", + "integrity": "sha512-emeGcJvdiZ4Z3ohbmw93E/64jRzUHAItSHt8nF7M4TGgQTiWqFVGB8KNpLGFmUHmHLvjvBgFwVlqNcq+VuGv9g==", + "dev": true + }, + "@types/through": { + "version": "0.0.30", + "resolved": "https://registry.npmjs.org/@types/through/-/through-0.0.30.tgz", + "integrity": "sha512-FvnCJljyxhPM3gkRgWmxmDZyAQSiBQQWLI0A0VFL0K7W1oRUrPJSqNO0NvTnLkBcotdlp3lKvaT0JrnyRDkzOg==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/ws": { + "version": "7.2.6", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-7.2.6.tgz", + "integrity": "sha512-Q07IrQUSNpr+cXU4E4LtkSIBPie5GLZyyMC1QtQYRLWz701+XcoVygGUZgvLqElq1nU4ICldMYPnexlBsg3dqQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "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" + } + }, + "ansi-escapes": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.1.tgz", + "integrity": "sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA==", + "requires": { + "type-fest": "^0.11.0" + } + }, + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" + }, + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==" + }, + "cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "requires": { + "restore-cursor": "^3.1.0" + } + }, + "cli-width": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", + "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==" + }, + "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==", + "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==" + }, + "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" + } + }, + "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" + } + }, + "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.2.0", + "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-12.2.0.tgz", + "integrity": "sha512-Ueb/0SOsxXyqwvwFYFe0msMrGqH1OMqpp2Dpbplnlr4MzcRrFWwsBM9gKNZXPVBHWUKiQkwU8AihXBXIvTTSvg==", + "requires": { + "@discordjs/collection": "^0.1.5", + "@discordjs/form-data": "^3.0.1", + "abort-controller": "^3.0.0", + "node-fetch": "^2.6.0", + "prism-media": "^1.2.0", + "setimmediate": "^1.0.5", + "tweetnacl": "^1.0.3", + "ws": "^7.2.1" + }, + "dependencies": { + "prism-media": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/prism-media/-/prism-media-1.2.2.tgz", + "integrity": "sha512-I+nkWY212lJ500jLe4tN9tWO7nRiBAVdMv76P9kffZjYhw20raMlW1HSSvS+MLXC9MmbNZCazMrAr+5jEEgTuw==" + }, + "ws": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.3.0.tgz", + "integrity": "sha512-iFtXzngZVXPGgpTlP1rBqsUK82p9tKqsWRPg5L56egiljujJT3vGAYnHANvFxBieXrTFavhzhxW52jnaWV+w2w==" + } + } + }, + "duplexer": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", + "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=", + "dev": true + }, + "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==" + }, + "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=" + }, + "event-stream": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz", + "integrity": "sha1-SrTJoPWlTbkzi0w02Gv86PSzVXE=", + "dev": true, + "requires": { + "duplexer": "~0.1.1", + "from": "~0", + "map-stream": "~0.1.0", + "pause-stream": "0.0.11", + "split": "0.3", + "stream-combiner": "~0.0.4", + "through": "~2.3.1" + } + }, + "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==" + }, + "external-editor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", + "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", + "requires": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + } + }, + "figures": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "requires": { + "escape-string-regexp": "^1.0.5" + } + }, + "from": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz", + "integrity": "sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4=", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "inquirer": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.3.1.tgz", + "integrity": "sha512-/+vOpHQHhoh90Znev8BXiuw1TDQ7IDxWsQnFafUEoK5+4uN5Eoz1p+3GqOj/NtzEi9VzWKQcV9Bm+i8moxedsA==", + "requires": { + "ansi-escapes": "^4.2.1", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-width": "^3.0.0", + "external-editor": "^3.0.3", + "figures": "^3.0.0", + "lodash": "^4.17.16", + "mute-stream": "0.0.8", + "run-async": "^2.4.0", + "rxjs": "^6.6.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0", + "through": "^2.3.6" + } + }, + "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==" + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "lodash": { + "version": "4.17.19", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz", + "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==" + }, + "map-stream": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz", + "integrity": "sha1-5WqpTEyAVaFkBKBnS3jyFffI4ZQ=", + "dev": true + }, + "mime-db": { + "version": "1.44.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", + "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==" + }, + "mime-types": { + "version": "2.1.27", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", + "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", + "requires": { + "mime-db": "1.44.0" + } + }, + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==" + }, + "mute-stream": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==" + }, + "node-cleanup": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/node-cleanup/-/node-cleanup-2.1.2.tgz", + "integrity": "sha1-esGavSl+Caf3KnFUXZUbUX5N3iw=", + "dev": true + }, + "node-fetch": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz", + "integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==" + }, + "onetime": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.0.tgz", + "integrity": "sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q==", + "requires": { + "mimic-fn": "^2.1.0" + } + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" + }, + "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 + }, + "pause-stream": { + "version": "0.0.11", + "resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", + "integrity": "sha1-/lo0sMvOErWqaitAPuLnO2AvFEU=", + "dev": true, + "requires": { + "through": "~2.3" + } + }, + "ps-tree": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/ps-tree/-/ps-tree-1.2.0.tgz", + "integrity": "sha512-0VnamPPYHl4uaU/nSFeZZpR21QAWRz+sRv4iW9+v/GS/J5U5iZB5BNN6J0RMoOvdx2gWM2+ZFMIm58q24e4UYA==", + "dev": true, + "requires": { + "event-stream": "=3.3.4" + } + }, + "restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "requires": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + } + }, + "run-async": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", + "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==" + }, + "rxjs": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.0.tgz", + "integrity": "sha512-3HMA8z/Oz61DUHe+SdOiQyzIf4tOx5oQHmMir7IZEu6TMqCLHT4LRcmNaUS0NwOz8VLvmmBduMsoaUvMaIiqzg==", + "requires": { + "tslib": "^1.9.0" + } + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "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 + }, + "signal-exit": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", + "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==" + }, + "split": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/split/-/split-0.3.3.tgz", + "integrity": "sha1-zQ7qXmOiEd//frDwkcQTPi0N0o8=", + "dev": true, + "requires": { + "through": "2" + } + }, + "stream-combiner": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz", + "integrity": "sha1-TV5DPBhSYd3mI8o/RMWGvPXErRQ=", + "dev": true, + "requires": { + "duplexer": "~0.1.1" + } + }, + "string-argv": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.1.2.tgz", + "integrity": "sha512-mBqPGEOMNJKXRo7z0keX0wlAhbBAjilUdPW13nN0PecVryZxdHIeM7TqbsSUA7VYuS00HGC6mojP7DlQzfa9ZA==", + "dev": true + }, + "string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "requires": { + "ansi-regex": "^5.0.0" + } + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "requires": { + "has-flag": "^4.0.0" + } + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" + }, + "tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "requires": { + "os-tmpdir": "~1.0.2" + } + }, + "tsc-watch": { + "version": "4.2.9", + "resolved": "https://registry.npmjs.org/tsc-watch/-/tsc-watch-4.2.9.tgz", + "integrity": "sha512-DlTaoDs74+KUpyWr7dCGhuscAUKCz6CiFduBN7R9RbLJSSN1moWdwoCLASE7+zLgGvV5AwXfYDiEMAsPGaO+Vw==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.3", + "node-cleanup": "^2.1.2", + "ps-tree": "^1.2.0", + "string-argv": "^0.1.1", + "strip-ansi": "^6.0.0" + } + }, + "tslib": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz", + "integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==" + }, + "tweetnacl": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", + "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==" + }, + "type-fest": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.11.0.tgz", + "integrity": "sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==" + }, + "typescript": { + "version": "3.9.6", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.6.tgz", + "integrity": "sha512-Pspx3oKAPJtjNwE92YS05HQoY7z2SFyOpHo9MqJor3BXAGNaPUs83CuVp9VISFkSjyRfiTpmKuAYGJB7S7hOxw==", + "dev": true + }, + "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" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..e92a1e8 --- /dev/null +++ b/package.json @@ -0,0 +1,31 @@ +{ + "name": "d.js-v12-bot", + "version": "0.0.1", + "description": "A Discord bot built on Discord.JS v12", + "main": "dist/index.js", + "dependencies": { + "chalk": "^4.1.0", + "discord.js": "^12.2.0", + "inquirer": "^7.3.1" + }, + "devDependencies": { + "@types/inquirer": "^6.5.0", + "@types/node": "^14.0.22", + "@types/ws": "^7.2.6", + "tsc-watch": "^4.2.9", + "typescript": "^3.9.6" + }, + "scripts": { + "build": "tsc", + "watch": "tsc --watch", + "autobuild": "tsc && npm start", + "start": "node dist/index.js", + "dev": "tsc-watch --onSuccess \"node dist/index.js dev\"" + }, + "keywords": [ + "discord.js", + "bot" + ], + "author": "Keanu Timmermans", + "license": "MIT" +} diff --git a/src/commands/admin.ts b/src/commands/admin.ts new file mode 100644 index 0000000..67a57df --- /dev/null +++ b/src/commands/admin.ts @@ -0,0 +1,107 @@ +import Command from "../core/command"; +import {CommonLibrary, logs} from "../core/lib"; +import {Config, Storage} from "../core/structures"; +import {Permissions} from "discord.js"; + +function authenticate($: CommonLibrary, customMessage = ""): boolean +{ + const hasAccess = Config.mechanics.includes($.author.id) || ($.member?.hasPermission(Permissions.FLAGS.ADMINISTRATOR) || false); + + if(!hasAccess) + { + if(customMessage !== "") + $.channel.send(customMessage); + else + { + $.channel.send(`${$.author.toString()}, you are not a server admin or one of the bot's mechanics. If you have access to the server files, add yourself to it manually in \`data/config.json\`. Your user ID should now be logged in the console.`); + $.debug($.author.id); + } + } + + return hasAccess; +} + +function getLogBuffer(type: string) +{ + return {files: [{ + attachment: Buffer.alloc(logs[type].length, logs[type]), + name: `${Date.now()}.${type}.log` + }]}; +} + +export default new Command({ + description: "An all-in-one command to do admin stuff. You need to be either an admin of the server or one of the bot's mechanics to use this command.", + async run($: CommonLibrary): Promise + { + const admin = $.member?.hasPermission(Permissions.FLAGS.ADMINISTRATOR) || false; + const mechanic = Config.mechanics.includes($.author.id); + let status = ""; + + if(admin && mechanic) + status = "a server admin and one of the bot's mechanics"; + else if(admin) + status = "a server admin"; + else if(mechanic) + status = "one of the bot's mechanics"; + + if(authenticate($)) + $.channel.send(`${$.author.toString()}, you are ${status}, meaning you can use this command.`); + }, + subcommands: + { + set: new Command({ + description: "Set different per-guild settings for the bot.", + run: "You have to specify the option you want to set.", + subcommands: + { + prefix: new Command({ + description: "Set a custom prefix for your guild. Removes your custom prefix if none is provided.", + usage: "()", + async run($: CommonLibrary): Promise + { + if(authenticate($)) + { + Storage.getGuild($.guild?.id || "N/A").prefix = null; + Storage.save(); + $.channel.send(`The custom prefix for this guild has been removed. My prefix is now back to \`${Config.prefix}\`.`); + } + }, + any: new Command({ + async run($: CommonLibrary): Promise + { + if(authenticate($)) + { + Storage.getGuild($.guild?.id || "N/A").prefix = $.args[0]; + Storage.save(); + $.channel.send(`The custom prefix for this guild is now \`${$.args[0]}\`.`); + } + } + }) + }) + } + }), + diag: new Command({ + description: "Requests a debug log with the \"info\" verbosity level.", + async run($: CommonLibrary): Promise + { + if(authenticate($)) + $.channel.send(getLogBuffer("info")); + }, + any: new Command({ + description: `Select a verbosity to listen to. Available levels: \`[${Object.keys(logs)}]\``, + async run($: CommonLibrary): Promise + { + if(authenticate($)) + { + const type = $.args[0]; + + if(type in logs) + $.channel.send(getLogBuffer(type)); + else + $.channel.send(`Couldn't find a verbosity level named \`${type}\`! The available types are \`[${Object.keys(logs)}]\`.`); + } + } + }) + }) + } +}); \ No newline at end of file diff --git a/src/commands/help.ts b/src/commands/help.ts new file mode 100644 index 0000000..aa97c65 --- /dev/null +++ b/src/commands/help.ts @@ -0,0 +1,102 @@ +import Command from "../core/command"; +import {CommonLibrary} from "../core/lib"; +import FileManager from "../core/storage"; + +const types = ["user", "number", "any"]; + +export default new Command({ + description: "Lists all commands. If a command is specified, their arguments are listed as well.", + usage: "([command, [subcommand/type], ...])", + async run($: CommonLibrary): Promise + { + const commands = await FileManager.loadCommands(); + const list: string[] = []; + + for(const [header, command] of commands) + if(header !== "test") + list.push(`- \`${header}\` - ${command.description}`); + + const outList = list.length > 0 ? `\n${list.join('\n')}` : " None"; + $.channel.send(`Legend: \`\`, \`[list/of/subcommands]\`, \`(optional)\`, \`()\`, \`([optional/list/...])\`\nCommands:${outList}`, {split: true}); + }, + any: new Command({ + async run($: CommonLibrary): Promise + { + const commands = await FileManager.loadCommands(); + let header = $.args.shift(); + let command = commands.get(header); + + if(!command || header === "test") + $.channel.send(`No command found by the name \`${header}\`!`); + else + { + let usage = command.usage; + + for(const param of $.args) + { + header += ` ${param}`; + + if(/<\w+>/g.test(param)) + { + const type = param.match(/\w+/g)[0]; + command = command[type]; + + if(types.includes(type) && command?.usage) + usage = command.usage; + else + { + command = undefined; + break; + } + } + else if(command?.subcommands?.[param]) + { + command = command.subcommands[param]; + + if(command.usage !== "") + usage = command.usage; + } + else + { + command = undefined; + break; + } + } + + if(!command) + return $.channel.send(`No command found by the name \`${header}\`!`); + + let append = ""; + + if(usage === "") + { + const list: string[] = []; + + for(const subtag in command.subcommands) + { + const subcmd = command.subcommands[subtag]; + const customUsage = subcmd.usage ? ` ${subcmd.usage}` : ""; + list.push(`- \`${header} ${subtag}${customUsage}\` - ${subcmd.description}`); + } + + for(const type of types) + { + if(command[type]) + { + const cmd = command[type]; + const customUsage = cmd.usage ? ` ${cmd.usage}` : ""; + list.push(`- \`${header} <${type}>${customUsage}\` - ${cmd.description}`); + } + } + + + append = "Usages:" + (list.length > 0 ? `\n${list.join('\n')}` : " None."); + } + else + append = `Usage: \`${header} ${usage}\``; + + $.channel.send(`Command: \`${header}\`\nDescription: ${command.description}\n${append}`, {split: true}); + } + } + }) +}); \ No newline at end of file diff --git a/src/commands/money.ts b/src/commands/money.ts new file mode 100644 index 0000000..332642b --- /dev/null +++ b/src/commands/money.ts @@ -0,0 +1,241 @@ +import Command from "../core/command"; +import $, {CommonLibrary} from "../core/lib"; +import {Storage} from "../core/structures"; +import {User} from "discord.js"; + +export function getMoneyEmbed(user: User): object +{ + const profile = Storage.getUser(user.id); + + return {embed: { + color: 0xFFFF00, + author: + { + name: user.username, + icon_url: user.displayAvatarURL({ + format: "png", + dynamic: true + }) + }, + fields: + [ + { + name: "Balance", + value: $(profile.money).pluralise("credit", "s") + } + ] + }}; +} + +function getSendEmbed(sender: User, receiver: User, amount: number): object +{ + return {embed: { + color: 0xFFFF00, + author: + { + name: sender.username, + icon_url: sender.displayAvatarURL({ + format: "png", + dynamic: true + }) + }, + title: "Transaction", + description: `${sender.toString()} has sent ${$(amount).pluralise("credit", "s")} to ${receiver.toString()}!`, + fields: + [ + { + name: `Sender: ${sender.username}#${sender.discriminator}`, + value: $(Storage.getUser(sender.id).money).pluralise("credit", "s") + }, + { + name: `Receiver: ${receiver.username}#${receiver.discriminator}`, + value: $(Storage.getUser(receiver.id).money).pluralise("credit", "s") + } + ], + footer: + { + text: receiver.username, + icon_url: receiver.displayAvatarURL({ + format: "png", + dynamic: true + }) + } + }}; +} + +export default new Command({ + description: "See how much money you have. Also provides other commands related to money.", + async run($: CommonLibrary): Promise + { + $.channel.send(getMoneyEmbed($.author)); + }, + subcommands: + { + get: new Command({ + description: "Pick up your daily credits. The cooldown is per user and every 22 hours to allow for some leeway.", + async run($: CommonLibrary): Promise + { + const user = Storage.getUser($.author.id); + const now = Date.now(); + + if(user.lastReceived === -1) + { + user.money = 100; + user.lastReceived = now; + Storage.save(); + $.channel.send("Here's 100 credits to get started, the price of a sandwich in Rookie Harbor.", getMoneyEmbed($.author)); + } + else if(now - user.lastReceived >= 79200000) + { + user.money += 25; + user.lastReceived = now; + Storage.save(); + $.channel.send("Here's your daily 25 credits.", getMoneyEmbed($.author)); + } + else + $.channel.send(`It's too soon to pick up your daily credits. You have about ${((user.lastReceived + 79200000 - now) / 3600000).toFixed(1)} hours to go.`); + } + }), + send: new Command({ + description: "Send money to someone.", + usage: " ", + run: "Who are you sending this money to?", + user: new Command({ + run: "You need to enter an amount you're sending!", + number: new Command({ + async run($: CommonLibrary): Promise + { + const amount = Math.floor($.args[1]); + const author = $.author; + const sender = Storage.getUser(author.id); + const target = $.args[0]; + const receiver = Storage.getUser(target.id); + + if(amount <= 0) + return $.channel.send("You must send at least one credit!"); + else if(sender.money < amount) + return $.channel.send("You don't have enough money to do that!", getMoneyEmbed(author)); + else if(target.id === author.id) + return $.channel.send("You can't send money to yourself!"); + else if(target.bot && process.argv[2] !== "dev") + return $.channel.send("You can't send money to a bot!"); + + sender.money -= amount; + receiver.money += amount; + Storage.save(); + $.channel.send(getSendEmbed(author, target, amount)); + } + }) + }), + number: new Command({ + run: "You must use the format `money send `!" + }), + any: new Command({ + async run($: CommonLibrary): Promise + { + const last = $.args.pop(); + + if(!/\d+/g.test(last) && $.args.length === 0) + return $.channel.send("You need to enter an amount you're sending!"); + + const amount = Math.floor(last); + const author = $.author; + const sender = Storage.getUser(author.id); + + if(amount <= 0) + return $.channel.send("You must send at least one credit!"); + else if(sender.money < amount) + return $.channel.send("You don't have enough money to do that!", getMoneyEmbed(author)); + else if(!$.guild) + return $.channel.send("You have to use this in a server if you want to send money with a username!"); + + const username = $.args.join(" "); + const member = (await $.guild.members.fetch({ + query: username, + limit: 1 + })).first(); + + if(!member) + return $.channel.send(`Couldn't find a user by the name of \`${username}\`! If you want to send money to someone in a different server, you have to use their user ID!`); + else if(member.user.id === author.id) + return $.channel.send("You can't send money to yourself!"); + else if(member.user.bot && process.argv[2] !== "dev") + return $.channel.send("You can't send money to a bot!"); + + const target = member.user; + + $.prompt(await $.channel.send(`Are you sure you want to send ${$(amount).pluralise("credit", "s")} to this person?\n*(This message will automatically be deleted after 10 seconds.)*`, {embed: { + color: "#ffff00", + author: + { + name: `${target.username}#${target.discriminator}`, + icon_url: target.displayAvatarURL({ + format: "png", + dynamic: true + }) + } + }}), $.author.id, () => { + const receiver = Storage.getUser(target.id); + sender.money -= amount; + receiver.money += amount; + Storage.save(); + $.channel.send(getSendEmbed(author, target, amount)); + }); + } + }) + }), + leaderboard: new Command({ + description: "See the richest players tracked by this bot (across servers).", + async run($: CommonLibrary): Promise + { + const users = Storage.users; + const ids = Object.keys(users); + ids.sort((a, b) => users[b].money - users[a].money); + const fields = []; + + for(let i = 0, limit = Math.min(10, ids.length); i < limit; i++) + { + const id = ids[i]; + const user = await $.client.users.fetch(id); + + fields.push({ + name: `#${i+1}. ${user.username}#${user.discriminator}`, + value: $(users[id].money).pluralise("credit", "s") + }); + } + + $.channel.send({embed: { + title: "Top 10 Richest Players", + color: "#ffff00", + fields: fields + }}); + } + }) + }, + user: new Command({ + description: "See how much money someone else has by using their user ID or pinging them.", + async run($: CommonLibrary): Promise + { + $.channel.send(getMoneyEmbed($.args[0])); + } + }), + any: new Command({ + description: "See how much money someone else has by using their username.", + async run($: CommonLibrary): Promise + { + if($.guild) + { + const username = $.args.join(" "); + const member = (await $.guild.members.fetch({ + query: username, + limit: 1 + })).first(); + + if(member) + $.channel.send(getMoneyEmbed(member.user)); + else + $.channel.send(`Couldn't find a user by the name of \`${username}\`!`); + } + } + }) +}); \ No newline at end of file diff --git a/src/core/command.ts b/src/core/command.ts new file mode 100644 index 0000000..0d81c84 --- /dev/null +++ b/src/core/command.ts @@ -0,0 +1,145 @@ +import {isType, parseVars, CommonLibrary} from "./lib"; + +// Permission levels starting from zero then increasing, allowing for numerical comparisons. +// Note: For my bot, there really isn't much purpose to doing so, as it's just one command. And plus, if you're doing stuff like moderation commands, it's probably better to make a permissions system that allows for you to separate permissions into different trees. After all, it'd be a really bad idea to allow a bot mechanic to ban users. +//enum PERMISSIONS {NONE, ADMIN, MECHANIC} + +interface CommandOptions +{ + description?: string; + endpoint?: boolean; + usage?: string; + //permissions?: number; + run?: Function|string; + subcommands?: {[key: string]: Command}; + user?: Command; + number?: Command; + any?: Command; +} + +export default class Command +{ + public readonly description: string; + public readonly endpoint: boolean; + public readonly usage: string; + //public readonly permissions: number; + private run: Function|string; + public subcommands: {[key: string]: Command}|null; + public user: Command|null; + public number: Command|null; + public any: Command|null; + //public static readonly PERMISSIONS = PERMISSIONS; + [key: string]: any; // Allow for dynamic indexing. The CommandOptions interface will still prevent users from adding unused properties though. + + constructor(options?: CommandOptions) + { + this.description = options?.description || "No description."; + this.endpoint = options?.endpoint || false; + this.usage = options?.usage || ""; + //this.permissions = options?.permissions || Command.PERMISSIONS.NONE; + this.run = options?.run || "No action was set on this command!"; + this.subcommands = options?.subcommands || null; + this.user = options?.user || null; + this.number = options?.number || null; + this.any = options?.any || null; + } + + public execute($: CommonLibrary) + { + if(isType(this.run, String)) + { + $.channel.send(parseVars(this.run as string, { + author: $.author.toString() + }, "???")); + } + else + (this.run as Function)($).catch($.handler.bind($)); + } + + /** + * Set what happens when the command is called. + * - If the command is a function, run it with one argument (the common library). + * - If the command is a string, it'll be sent as a message with %variables% replaced. + */ + public set(run: Function|string) + { + this.run = run; + } + + /** The safe way to attach a named subcommand. */ + public attach(key: string, command: Command) + { + if(!this.subcommands) + this.subcommands = {}; + this.subcommands[key] = command; + } + + /** See if a subcommand exists for the command. */ + /*public has(type: string): boolean + { + return this.subcommands && (type in this.subcommands) || false; + }*/ + + /** Get the requested subcommand if it exists. */ + /*public get(type: string): Command|null + { + return this.subcommands && this.subcommands[type] || null; + }*/ +} + +/*export function hasPermission(member: GuildMember, permission: number): boolean +{ + const length = Object.keys(PERMISSIONS).length / 2; + console.log(member, permission, length); + return true; +}*/ + +// The template should be built with a reductionist mentality. +// Provide everything the user needs and then let them remove whatever they want. +// That way, they aren't focusing on what's missing, but rather what they need for their command. +export const template = +`import Command from '../core/command'; +import {CommonLibrary} from '../core/lib'; + +export default new Command({ + description: "This is a template/testing command providing common functionality. Remove what you don't need, and rename/delete this file to generate a fresh command file here. This command should be automatically excluded from the help command. The \\"usage\\" parameter (string) overrides the default usage for the help command. The \\"endpoint\\" parameter (boolean) prevents further arguments from being passed. Also, as long as you keep the run function async, it'll return a promise allowing the program to automatically catch any synchronous errors. However, you'll have to do manual error handling if you go the then and catch route.", + endpoint: false, + usage: '', + async run($: CommonLibrary): Promise { + + }, + subcommands: { + layer: new Command({ + description: "This is a named subcommand, meaning that the key name is what determines the keyword to use. With default settings for example, \\"$test layer\\".", + endpoint: false, + usage: '', + async run($: CommonLibrary): Promise { + + } + }) + }, + user: new Command({ + description: "This is the subcommand for getting users by pinging them or copying their ID. With default settings for example, \\"$test 237359961842253835\\". The argument will be a user object and won't run if no user is found by that ID.", + endpoint: false, + usage: '', + async run($: CommonLibrary): Promise { + + } + }), + number: new Command({ + description: "This is a numeric subcommand, meaning that any type of number (excluding Infinity/NaN) will route to this command if present. With default settings for example, \\"$test -5.2\\". The argument with the number is already parsed so you can just use it without converting it.", + endpoint: false, + usage: '', + async run($: CommonLibrary): Promise { + + } + }), + any: new Command({ + description: "This is a generic subcommand, meaning that if there isn't a more specific subcommand that's called, it falls to this. With default settings for example, \\"$test reeee\\".", + endpoint: false, + usage: '', + async run($: CommonLibrary): Promise { + + } + }) +});`; \ No newline at end of file diff --git a/src/core/lib.ts b/src/core/lib.ts new file mode 100644 index 0000000..721c6e9 --- /dev/null +++ b/src/core/lib.ts @@ -0,0 +1,374 @@ +import {GenericWrapper, NumberWrapper, ArrayWrapper} from "./wrappers"; +import {Client, Message, TextChannel, DMChannel, NewsChannel, Guild, User, GuildMember, MessageReaction, PartialUser} from "discord.js"; +import chalk from "chalk"; +import FileManager from "./storage"; + +/** A type that describes what the library module does. */ +export interface CommonLibrary +{ + // Wrapper Object // + /** Wraps the value you enter with an object that provides extra functionality and provides common utility functions. */ + (value: number): NumberWrapper; + (value: T[]): ArrayWrapper; + (value: T): GenericWrapper; + + // Common Library Functions // + /** .catch($.handler.bind($)) or .catch(error => $.handler(error)) */ + handler: (error: Error) => void; + log: (...args: any[]) => void; + warn: (...args: any[]) => void; + error: (...args: any[]) => void; + debug: (...args: any[]) => void; + ready: (...args: any[]) => void; + paginate: (message: Message, senderID: string, total: number, callback: (page: number) => void, duration?: number) => void; + prompt: (message: Message, senderID: string, onConfirm: () => void, duration?: number) => void; + + // Dynamic Properties // + args: any[]; + client: Client; + message: Message; + channel: TextChannel|DMChannel|NewsChannel; + guild: Guild|null; + author: User; + member: GuildMember|null; +} + +export default function $(value: number): NumberWrapper; +export default function $(value: T[]): ArrayWrapper; +export default function $(value: T): GenericWrapper; +export default function $(value: any) +{ + if(isType(value, Number)) + return new NumberWrapper(value); + else if(isType(value, Array)) + return new ArrayWrapper(value); + else + return new GenericWrapper(value); +} + +// If you use promises, use this function to display the error in chat. +// Case #1: await $.channel.send(""); --> Automatically caught by Command.execute(). +// Case #2: $.channel.send("").catch($.handler.bind($)); --> Manually caught by the user. +$.handler = function(this: CommonLibrary, error: Error) +{ + if(this) + this.channel.send(`There was an error while trying to execute that command!\`\`\`${error.stack ?? error}\`\`\``); + else + $.warn("No context was attached to $.handler! Make sure to use .catch($.handler.bind($)) or .catch(error => $.handler(error)) instead!"); + + $.error(error); +}; + +// Logs with different levels of verbosity. +export const logs: {[type: string]: string} = { + error: "", + warn: "", + info: "", + verbose: "" +}; + +// The custom console. In order of verbosity, error, warn, log, and debug. Ready is a variation of log. +// General Purpose Logger +$.log = (...args: any[]) => { + console.log(chalk.white.bgGray(formatTimestamp()), chalk.black.bgWhite("INFO"), ...args); + const text = `[${formatUTCTimestamp()}] [INFO] ${args.join(" ")}\n`; + logs.info += text; + logs.verbose += text; +}; +// "It'll still work, but you should really check up on this." +$.warn = (...args: any[]) => { + console.warn(chalk.white.bgGray(formatTimestamp()), chalk.black.bgYellow("WARN"), ...args); + const text = `[${formatUTCTimestamp()}] [WARN] ${args.join(" ")}\n`; + logs.warn += text; + logs.info += text; + logs.verbose += text; +}; +// Used for anything which prevents the program from actually running. +$.error = (...args: any[]) => { + console.error(chalk.white.bgGray(formatTimestamp()), chalk.white.bgRed("ERROR"), ...args); + const text = `[${formatUTCTimestamp()}] [ERROR] ${args.join(" ")}\n`; + logs.error += text; + logs.warn += text; + logs.info += text; + logs.verbose += text; +}; +// Be as verbose as possible. If anything might help when debugging an error, then include it. This only shows in your console if you run this with "dev", but you can still get it from "logs.verbose". +$.debug = (...args: any[]) => { + if(process.argv[2] === "dev") + console.debug(chalk.white.bgGray(formatTimestamp()), chalk.white.bgBlue("DEBUG"), ...args); + const text = `[${formatUTCTimestamp()}] [DEBUG] ${args.join(" ")}\n`; + logs.verbose += text; +}; +// Used once at the start of the program when the bot loads. +$.ready = (...args: any[]) => { + console.log(chalk.white.bgGray(formatTimestamp()), chalk.black.bgGreen("READY"), ...args); + const text = `[${formatUTCTimestamp()}] [READY] ${args.join(" ")}\n`; + logs.info += text; + logs.verbose += text; +}; + +export function formatTimestamp(now = new Date()) +{ + const year = now.getFullYear(); + const month = (now.getMonth() + 1).toString().padStart(2, '0'); + const day = now.getDate().toString().padStart(2, '0'); + const hour = now.getHours().toString().padStart(2, '0'); + const minute = now.getMinutes().toString().padStart(2, '0'); + const second = now.getSeconds().toString().padStart(2, '0'); + return `${year}-${month}-${day} ${hour}:${minute}:${second}`; +} + +export function formatUTCTimestamp(now = new Date()) +{ + const year = now.getUTCFullYear(); + const month = (now.getUTCMonth() + 1).toString().padStart(2, '0'); + const day = now.getUTCDate().toString().padStart(2, '0'); + const hour = now.getUTCHours().toString().padStart(2, '0'); + const minute = now.getUTCMinutes().toString().padStart(2, '0'); + const second = now.getUTCSeconds().toString().padStart(2, '0'); + return `${year}-${month}-${day} ${hour}:${minute}:${second}`; +} + +// A list of message ID and callback pairs. You get the emote name and ID of the user reacting. +const eventListeners: Map void> = new Map(); + +// Pagination function that allows for customization via a callback. +// Define your own pages outside the function because this only manages the actual turning of pages. +$.paginate = async(message: Message, senderID: string, total: number, callback: (page: number) => void, duration = 60000) => { + let page = 0; + + const turn = (amount: number) => { + page += amount; + + if(page < 0) + page += total; + else if(page >= total) + page -= total; + + callback(page); + } + const handle = (emote: string, reacterID: string) => { + if(reacterID === senderID) + { + switch(emote) + { + case '⬅️': turn(-1); break; + case '➡️': turn(1); break; + } + } + }; + + // Listen for reactions and call the handler. + await message.react('⬅️'); + await message.react('➡️'); + eventListeners.set(message.id, handle); + await message.awaitReactions((reaction, user) => { + handle(reaction.emoji.name, user.id); + return false; + }, {time: duration}); + + // When time's up, remove the bot's own reactions. + eventListeners.delete(message.id); + message.reactions.cache.get('⬅️')?.users.remove(message.author); + message.reactions.cache.get('➡️')?.users.remove(message.author); +}; + +// Attached to the client, there can be one event listener attached to a message ID which is executed if present. +export function unreact(reaction: MessageReaction, user: User|PartialUser) +{ + const callback = eventListeners.get(reaction.message.id); + callback && callback(reaction.emoji.name, user.id); +} + +// Waits for the sender to either confirm an action or let it pass (and delete the message). +$.prompt = async(message: Message, senderID: string, onConfirm: () => void, duration = 10000) => { + let isDeleted = false; + + message.react('✅'); + await message.awaitReactions((reaction, user) => { + if(user.id === senderID) + { + if(reaction.emoji.name === '✅') + onConfirm(); + isDeleted = true; + message.delete(); + } + + // CollectorFilter requires a boolean to be returned. + // My guess is that the return value of awaitReactions can be altered by making a boolean filter. + // However, because that's not my concern with this command, I don't have to worry about it. + // May as well just set it to false because I'm not concerned with collecting any reactions. + return false; + }, {time: duration}); + + if(!isDeleted) + message.delete(); +}; + +/** + * Splits a command by spaces while accounting for quotes which capture string arguments. + * - `\"` = `"` + * - `\\` = `\` + */ +export function parseArgs(line: string): string[] +{ + let result = []; + let selection = ""; + let inString = false; + let isEscaped = false; + + for(let c of line) + { + if(isEscaped) + { + if(['"', '\\'].includes(c)) + selection += c; + else + selection += '\\' + c; + + isEscaped = false; + } + else if(c === '\\') + isEscaped = true; + else if(c === '"') + inString = !inString; + else if(c === ' ' && !inString) + { + result.push(selection); + selection = ""; + } + else + selection += c; + } + + if(selection.length > 0) + result.push(selection) + + return result; +} + +/** + * Allows you to store a template string with variable markers and parse it later. + * - Use `%name%` for variables + * - `%%` = `%` + * - If the invalid token is null/undefined, nothing is changed. + */ +export function parseVars(line: string, definitions: {[key: string]: string}, invalid: string|null|undefined = ""): string +{ + let result = ""; + let inVariable = false; + let token = ""; + + for(const c of line) + { + if(c === '%') + { + if(inVariable) + { + if(token === "") + result += '%'; + else + { + if(token in definitions) + result += definitions[token]; + else if(invalid === undefined || invalid === null) + result += `%${token}%`; + else + result += invalid; + + token = ""; + } + } + + inVariable = !inVariable; + } + else if(inVariable) + token += c; + else + result += c; + } + + return result; +} + +/** + * Split up an array into a specified length. + * [1,2,3,4,5,6,7,8,9,10] split by 3 = [[1,2,3],[4,5,6],[7,8,9],[10]] + */ +export function perforate(list: T[], lengthOfEachSection: number): T[][] +{ + const sections: T[][] = []; + const amountOfSections = Math.ceil(list.length / lengthOfEachSection); + + for(let index = 0; index < amountOfSections; index++) + sections.push(list.slice(index * lengthOfEachSection, (index + 1) * lengthOfEachSection)); + + return sections; +} + +export function isType(value: any, type: Function): boolean +{ + if(value === undefined && type === undefined) + return true; + else if(value === null && type === null) + return true; + else + return value !== undefined && value !== null && value.constructor === type; +} + +/** + * Checks a value to see if it matches the fallback's type, otherwise returns the fallback. + * For the purposes of the templates system, this function will only check array types, objects should be checked under their own type (as you'd do anyway with something like a User object). + * If at any point the value doesn't match the data structure provided, the fallback is returned. + * Warning: Type checking is based on the fallback's type. Be sure that the "type" parameter is accurate to this! + */ +export function select(value: any, fallback: T, type: Function, isArray = false): T +{ + if(isArray && isType(value, Array)) + { + for(let item of value) + if(!isType(item, type)) + return fallback; + return value; + } + else + { + if(isType(value, type)) + return value; + else + return fallback; + } +} + +export interface GenericJSON +{ + [key: string]: any; +} + +export abstract class GenericStructure +{ + protected __meta__ = "generic"; + + constructor(tag?: string) + { + this.__meta__ = tag || this.__meta__; + } + + public save(asynchronous = true) + { + const tag = this.__meta__; + delete this.__meta__; + FileManager.write(tag, this, asynchronous); + this.__meta__ = tag; + } +} + +// A 50% chance would be "Math.random() < 0.5" because Math.random() can be [0, 1), so to make two equal ranges, you'd need [0, 0.5)U[0.5, 1). +// Similar logic would follow for any other percentage. Math.random() < 1 is always true (100% chance) and Math.random() < 0 is always false (0% chance). +export const Random = { + num: (min: number, max: number) => (Math.random() * (max - min)) + min, + int: (min: number, max: number) => Math.floor(Random.num(min, max)), + chance: (decimal: number) => Math.random() < decimal, + sign: (number = 1) => number * (Random.chance(0.5) ? -1 : 1), + deviation: (base: number, deviation: number) => Random.num(base - deviation, base + deviation) +}; \ No newline at end of file diff --git a/src/core/storage.ts b/src/core/storage.ts new file mode 100644 index 0000000..cb7b821 --- /dev/null +++ b/src/core/storage.ts @@ -0,0 +1,109 @@ +import fs from "fs"; +import $ from "./lib"; +import {Collection} from "discord.js"; +import Command, {template} from "../core/command"; + +let commands: Collection|null = null; + +const Storage = { + read(header: string): object + { + this.open("data"); + const path = `data/${header}.json`; + let data = {}; + + if(fs.existsSync(path)) + { + const file = fs.readFileSync(path, "utf-8"); + + try + { + data = JSON.parse(file); + } + catch(error) + { + if(process.argv[2] !== "dev") + { + $.warn(`Malformed JSON data (header: ${header}), backing it up.`, file); + fs.writeFile(`${path}.backup`, file, generateHandler(`Backup file of "${header}" successfully written as ${file}.`)); + } + } + } + + return data; + }, + write(header: string, data: object, asynchronous = true) + { + this.open("data"); + const path = `data/${header}.json`; + + if(process.argv[2] === "dev" || header === "config") + { + const result = JSON.stringify(data, null, '\t'); + + if(asynchronous) + fs.writeFile(path, result, generateHandler(`"${header}" sucessfully spaced and written.`)); + else + fs.writeFileSync(path, result); + } + else + { + const result = JSON.stringify(data); + + if(asynchronous) + fs.writeFile(path, result, generateHandler(`"${header}" sucessfully written.`)); + else + fs.writeFileSync(path, result); + } + }, + open(path: string, filter?: (value: string, index: number, array: string[]) => unknown): string[] + { + if(!fs.existsSync(path)) + fs.mkdirSync(path); + + let directory = fs.readdirSync(path); + + if(filter) + directory = directory.filter(filter); + + return directory; + }, + close(path: string) + { + if(fs.existsSync(path) && fs.readdirSync(path).length === 0) + fs.rmdir(path, generateHandler(`"${path}" successfully closed.`)); + }, + /** Returns the cache of the commands if it exists and searches the directory if not. */ + async loadCommands(): Promise> + { + if(commands) + return commands; + + if(process.argv[2] === "dev" && !fs.existsSync("src/commands/test.ts")) + fs.writeFile("src/commands/test.ts", template, generateHandler('"test.ts" (testing/template command) successfully generated.')); + + commands = new Collection(); + + for(const file of Storage.open("dist/commands", (filename: string) => filename.endsWith(".js"))) + { + const header = file.substring(0, file.indexOf(".js")); + const command = (await import(`../commands/${header}`)).default; + commands.set(header, command); + $.log("Loading Command:", header); + } + + return commands; + } +}; + +function generateHandler(message: string) +{ + return (error: Error|null) => { + if(error) + $.error(error); + else + $.debug(message); + }; +}; + +export default Storage; \ No newline at end of file diff --git a/src/core/structures.ts b/src/core/structures.ts new file mode 100644 index 0000000..11f31a7 --- /dev/null +++ b/src/core/structures.ts @@ -0,0 +1,113 @@ +import FileManager from "./storage"; +import $, {select, GenericJSON, GenericStructure} from "./lib"; +import {watch} from "fs"; + +class ConfigStructure extends GenericStructure +{ + public token: string; + public prefix: string; + public mechanics: string[]; + + constructor(data: GenericJSON) + { + super("config"); + this.token = select(data.token, "", String); + this.prefix = select(data.prefix, "$", String); + this.mechanics = select(data.mechanics, [], String, true); + } +} + +class User +{ + public money: number; + public lastReceived: number; + + constructor(data?: GenericJSON) + { + this.money = select(data?.money, 0, Number); + this.lastReceived = select(data?.lastReceived, -1, Number); + } +} + +class Guild +{ + public prefix: string|null; + + constructor(data?: GenericJSON) + { + this.prefix = select(data?.prefix, null, String); + } +} + +class StorageStructure extends GenericStructure +{ + public users: {[id: string]: User}; + public guilds: {[id: string]: Guild}; + + constructor(data: GenericJSON) + { + super("storage"); + this.users = {}; + this.guilds = {}; + + for(let id in data.users) + if(/\d{17,19}/g.test(id)) + this.users[id] = new User(data.users[id]); + + for(let id in data.guilds) + if(/\d{17,19}/g.test(id)) + this.guilds[id] = new Guild(data.guilds[id]); + } + + /** Gets a user's profile if they exist and generate one if not. */ + public getUser(id: string): User + { + if(!/\d{17,19}/g.test(id)) + $.warn(`"${id}" is not a valid user ID! It will be erased when the data loads again.`); + + if(id in this.users) + return this.users[id]; + else + { + const user = new User(); + this.users[id] = user; + return user; + } + } + + /** Gets a guild's settings if they exist and generate one if not. */ + public getGuild(id: string): Guild + { + if(!/\d{17,19}/g.test(id)) + $.warn(`"${id}" is not a valid guild ID! It will be erased when the data loads again.`); + + if(id in this.guilds) + return this.guilds[id]; + else + { + const guild = new Guild(); + this.guilds[id] = guild; + return guild; + } + } +} + +// Exports instances. Don't worry, importing it from different files will load the same instance. +export let Config = new ConfigStructure(FileManager.read("config")); +export let Storage = new StorageStructure(FileManager.read("storage")); + +// This part will allow the user to manually edit any JSON files they want while the program is running which'll update the program's cache. +// However, fs.watch is a buggy mess that should be avoided in production. While it helps test out stuff for development, it's not a good idea to have it running outside of development as it causes all sorts of issues. +if(process.argv[2] === "dev") +{ + watch("data", (event, filename) => { + $.debug("File Watcher:", event, filename); + const header = filename.substring(0, filename.indexOf(".json")); + + switch(header) + { + case "config": Config = new ConfigStructure(FileManager.read("config")); break; + case "storage": Storage = new StorageStructure(FileManager.read("storage")); break; + } + }); +} \ No newline at end of file diff --git a/src/core/wrappers.ts b/src/core/wrappers.ts new file mode 100644 index 0000000..68a5dff --- /dev/null +++ b/src/core/wrappers.ts @@ -0,0 +1,51 @@ +export class GenericWrapper +{ + protected readonly value: T; + + public constructor(value: T) + { + this.value = value; + } +} + +export class NumberWrapper extends GenericWrapper +{ + /** + * Pluralises a word and chooses a suffix attached to the root provided. + * - pluralise("credit", "s") = credit/credits + * - pluralise("part", "ies", "y") = party/parties + * - pluralise("sheep") = sheep + */ + public pluralise(word: string, plural = "", singular = "", excludeNumber = false): string + { + let result = excludeNumber ? "" : `${this.value} `; + + if(this.value === 1) + result += word + singular; + else + result += word + plural; + + return result; + } + + /** + * Pluralises a word for changes. + * - (-1).pluraliseSigned() = '-1 credits' + * - (0).pluraliseSigned() = '+0 credits' + * - (1).pluraliseSigned() = '+1 credit' + */ + public pluraliseSigned(word: string, plural = "", singular = "", excludeNumber = false): string + { + const sign = this.value >= 0 ? '+' : ''; + return `${sign}${this.pluralise(word, plural, singular, excludeNumber)}`; + } +} + +export class ArrayWrapper extends GenericWrapper +{ + /** Returns a random element from this array. */ + public random(): T + { + return this.value[Math.floor(Math.random() * this.value.length)]; + } +} \ No newline at end of file diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..e64ae7d --- /dev/null +++ b/src/index.ts @@ -0,0 +1,113 @@ +import {Client, Permissions} from "discord.js"; +import $, {unreact} from "./core/lib"; +import setup from "./setup"; +import FileManager from "./core/storage"; +import {Config, Storage} from "./core/structures"; + +(async() => { + // Setup // + await setup.init(); + const client = new Client(); + const commands = await FileManager.loadCommands(); + client.login(Config.token).catch(setup.again); + + client.on("message", async message => { + // Message Setup // + if(message.author.bot) + return; + + const prefix = Storage.getGuild(message.guild?.id || "N/A").prefix || Config.prefix; + + if(!message.content.startsWith(prefix)) + return; + + const [header, ...args] = message.content.substring(prefix.length).split(/ +/); + + if(!commands.has(header)) + return; + if(message.channel.type === "text" && !message.channel.permissionsFor(client.user || "")?.has(Permissions.FLAGS.SEND_MESSAGES)) + { + let status; + + if(message.member?.hasPermission(Permissions.FLAGS.ADMINISTRATOR)) + status = "Because you're a server admin, you have the ability to change that channel's permissions to match if that's what you intended."; + else + status = "Try using a different channel or contacting a server admin to change permissions of that channel if you think something's wrong."; + + return message.author.send(`I don't have permission to send messages in ${message.channel.toString()}. ${status}`); + } + + $.log(`${message.author.username}#${message.author.discriminator} executed the command "${header}" with arguments "${args}".`); + + // Subcommand Recursion // + let command = commands.get(header); + if(!command) return $.warn(`Command "${header}" was called but for some reason it's still undefined!`); + const params: any[] = []; + let isEndpoint = false; + + for(let param of args) + { + if(command.endpoint) + { + if(command.subcommands || command.user || command.number || command.any) + $.warn(`An endpoint cannot have subcommands! Check ${prefix}${header} again.`); + isEndpoint = true; + break; + } + + if(command.subcommands?.[param]) + command = command.subcommands[param]; + // Any Discord ID format will automatically format to a user ID. + else if(command.user && (/\d{17,19}/.test(param))) + { + const id = param.match(/\d+/g)![0]; + command = command.user; + try {params.push(await client.users.fetch(id))} + catch(error) {return message.channel.send(`No user found by the ID \`${id}\`!`)} + } + // Disallow infinity and allow for 0. + else if(command.number && (Number(param) || param === "0") && !param.includes("Infinity")) + { + command = command.number; + params.push(Number(param)); + } + else if(command.any) + { + command = command.any; + params.push(param); + } + else + params.push(param); + } + + if(isEndpoint) + return message.channel.send("Too many arguments!"); + + // Execute with dynamic library attached. // + // The purpose of using $.bind($) is to clone the function so as to not modify the original $. + // The cloned function doesn't copy the properties, so Object.assign() is used. + // Object.assign() modifies the first element and returns that, the second element applies its properties and the third element applies its own overriding the second one. + command.execute(Object.assign($.bind($), { + args: params, + author: message.author, + channel: message.channel, + client: client, + guild: message.guild, + member: message.member, + message: message + }, $)); + }); + + client.once("ready", () => { + if(client.user) + { + $.ready(`Logged in as ${client.user.username}#${client.user.discriminator}.`); + client.user.setActivity({ + type: "LISTENING", + name: `${Config.prefix}help` + }); + } + }); + + client.on("messageReactionRemove", unreact); +})() \ No newline at end of file diff --git a/src/setup.ts b/src/setup.ts new file mode 100644 index 0000000..f36f1fb --- /dev/null +++ b/src/setup.ts @@ -0,0 +1,48 @@ +import {existsSync as exists} from "fs"; +import inquirer from "inquirer"; +import Storage from "./core/storage"; +import {Config} from "./core/structures"; +import $ from "./core/lib"; + +// This file is called (or at least should be called) automatically as long as a config file doesn't exist yet. +// And that file won't be written until the data is successfully initialized. +const prompts = [{ + type: "password", + name: "token", + message: "What's your bot's token?", + mask: true +}, { + type: "input", + name: "prefix", + message: "What do you want your bot's prefix to be?", + default: "$" +}, { + type: "input", + name: "mechanics", + message: "Enter a list of bot mechanics (by their IDs) separated by spaces." +}]; + +export default { + async init() + { + while(!exists("data/config.json")) + { + const answers = await inquirer.prompt(prompts); + Storage.open("data"); + Config.token = answers.token as string; + Config.prefix = answers.prefix as string; + const mechanics = (answers.mechanics as string); + Config.mechanics = mechanics !== "" ? mechanics.split(" ") : []; + Config.save(); + } + }, + /** Prompt the user to set their token again. */ + async again() + { + $.error("It seems that the token you provided is invalid."); + const answers = await inquirer.prompt(prompts.slice(0, 1)); + Config.token = answers.token as string; + Config.save(false); + process.exit(); + } +}; \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..8141b22 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": + { + "rootDir": "src", + "outDir": "dist", + "target": "ES6", + "module": "CommonJS", + "moduleResolution": "node", + "esModuleInterop": true, + "noImplicitAny": true, + "noImplicitReturns": true, + "strictNullChecks": true, + "strictFunctionTypes": true, + "strictPropertyInitialization": true, + "removeComments": true + } +} \ No newline at end of file From 072d2607d4c13b840492f0accd309adcd819a4ab Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 25 Jul 2020 08:37:05 +0000 Subject: [PATCH 004/178] Bump lodash from 4.17.15 to 4.17.19 Bumps [lodash](https://github.com/lodash/lodash) from 4.17.15 to 4.17.19. - [Release notes](https://github.com/lodash/lodash/releases) - [Commits](https://github.com/lodash/lodash/compare/4.17.15...4.17.19) Signed-off-by: dependabot[bot] --- package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index e7a067b..7a3968f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -774,9 +774,9 @@ } }, "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", + "version": "4.17.19", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz", + "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==", "dev": true }, "mime-db": { From 113fc965a970a743113e3bb1bc6228cb2968e209 Mon Sep 17 00:00:00 2001 From: WatDuhHekBro <44940783+WatDuhHekBro@users.noreply.github.com> Date: Sat, 25 Jul 2020 06:01:24 -0500 Subject: [PATCH 005/178] Added dynamically loaded events --- docs/Specifications.md | 2 + src/core/event.ts | 31 +++++++ src/core/lib.ts | 30 ++++--- src/core/storage.ts | 21 ++++- src/events/message.ts | 103 ++++++++++++++++++++++++ src/events/messageReactionRemove.ts | 14 ++++ src/events/ready.ts | 18 +++++ src/index.ts | 120 +++------------------------- src/setup.ts | 3 +- 9 files changed, 214 insertions(+), 128 deletions(-) create mode 100644 src/core/event.ts create mode 100644 src/events/message.ts create mode 100644 src/events/messageReactionRemove.ts create mode 100644 src/events/ready.ts diff --git a/docs/Specifications.md b/docs/Specifications.md index 9591e39..9a64ccf 100644 --- a/docs/Specifications.md +++ b/docs/Specifications.md @@ -4,6 +4,7 @@ The top-level directory is reserved for files that have to be there for it to wo - `core`: This is where core structures and critical functions for the bot go. - `modules`: This is where modules go that accomplish one specific purpose but isn't so necessary for the bot to function. The goal is to be able to safely remove these without too much trouble. - `commands`: Here's the place to store commands. The file name determines the command name. + - `events`: Here's the place to store events. The file name determines the event type. - `dist`: This is where the runnable code in `src` compiles to. (The directory structure mirrors `src`.) - `data`: Holds all the dynamic data used by the bot. This is what you modify if you want to change stuff for just your instance of the bot. - `standard`: Contains all the standard data to be used with the project itself. It's part of the code and will not be checked for inaccuracies because it's not meant to be easily modified. @@ -16,6 +17,7 @@ This list starts from `src`/`dist`. - `core/lib`: Exports a function object which lets you wrap values letting you call special functions as well as calling utility functions common to all commands. - `core/structures`: Contains all the structures that the dynamic data read from JSON files should follow. This exports instances of these classes. - `core/command`: Contains the class used to instantiate commands. +- `core/event`: Contains the class used to instantiate events. - `core/storage`: Exports an object which handles everything related to files. - `core/wrappers`: Contains classes that wrap around values and provide extra functionality. diff --git a/src/core/event.ts b/src/core/event.ts new file mode 100644 index 0000000..164d245 --- /dev/null +++ b/src/core/event.ts @@ -0,0 +1,31 @@ +import {Client} from "discord.js"; + +// Last Updated: Discord.js v12.2.0 +export const EVENTS = ["channelCreate", "channelDelete", "channelPinsUpdate", "channelUpdate", "debug", "warn", "disconnect", "emojiCreate", "emojiDelete", "emojiUpdate", "error", "guildBanAdd", "guildBanRemove", "guildCreate", "guildDelete", "guildUnavailable", "guildIntegrationsUpdate", "guildMemberAdd", "guildMemberAvailable", "guildMemberRemove", "guildMembersChunk", "guildMemberSpeaking", "guildMemberUpdate", "guildUpdate", "inviteCreate", "inviteDelete", "message", "messageDelete", "messageReactionRemoveAll", "messageReactionRemoveEmoji", "messageDeleteBulk", "messageReactionAdd", "messageReactionRemove", "messageUpdate", "presenceUpdate", "rateLimit", "ready", "invalidated", "roleCreate", "roleDelete", "roleUpdate", "typingStart", "userUpdate", "voiceStateUpdate", "webhookUpdate", "shardDisconnect", "shardError", "shardReady", "shardReconnecting", "shardResume"]; + +interface EventOptions +{ + readonly on?: Function; + readonly once?: Function; +} + +export default class Event +{ + private readonly on: Function|null; + private readonly once: Function|null; + + constructor(options: EventOptions) + { + this.on = options.on || null; + this.once = options.once || null; + } + + // For this function, I'm going to assume that the event is used with the correct arguments and that the event tag is checked in "storage.ts". + public attach(client: Client, event: string) + { + if(this.on) + client.on(event as any, this.on as any); + if(this.once) + client.once(event as any, this.once as any); + } +} \ No newline at end of file diff --git a/src/core/lib.ts b/src/core/lib.ts index 721c6e9..61db18d 100644 --- a/src/core/lib.ts +++ b/src/core/lib.ts @@ -1,7 +1,8 @@ import {GenericWrapper, NumberWrapper, ArrayWrapper} from "./wrappers"; -import {Client, Message, TextChannel, DMChannel, NewsChannel, Guild, User, GuildMember, MessageReaction, PartialUser} from "discord.js"; +import {Client, Message, TextChannel, DMChannel, NewsChannel, Guild, User, GuildMember} from "discord.js"; import chalk from "chalk"; import FileManager from "./storage"; +import {eventListeners} from "../events/messageReactionRemove"; /** A type that describes what the library module does. */ export interface CommonLibrary @@ -67,17 +68,22 @@ export const logs: {[type: string]: string} = { verbose: "" }; +let enabled = true; +export function setConsoleActivated(activated: boolean) {enabled = activated}; + // The custom console. In order of verbosity, error, warn, log, and debug. Ready is a variation of log. // General Purpose Logger $.log = (...args: any[]) => { - console.log(chalk.white.bgGray(formatTimestamp()), chalk.black.bgWhite("INFO"), ...args); + if(enabled) + console.log(chalk.white.bgGray(formatTimestamp()), chalk.black.bgWhite("INFO"), ...args); const text = `[${formatUTCTimestamp()}] [INFO] ${args.join(" ")}\n`; logs.info += text; logs.verbose += text; }; // "It'll still work, but you should really check up on this." $.warn = (...args: any[]) => { - console.warn(chalk.white.bgGray(formatTimestamp()), chalk.black.bgYellow("WARN"), ...args); + if(enabled) + console.warn(chalk.white.bgGray(formatTimestamp()), chalk.black.bgYellow("WARN"), ...args); const text = `[${formatUTCTimestamp()}] [WARN] ${args.join(" ")}\n`; logs.warn += text; logs.info += text; @@ -85,7 +91,8 @@ $.warn = (...args: any[]) => { }; // Used for anything which prevents the program from actually running. $.error = (...args: any[]) => { - console.error(chalk.white.bgGray(formatTimestamp()), chalk.white.bgRed("ERROR"), ...args); + if(enabled) + console.error(chalk.white.bgGray(formatTimestamp()), chalk.white.bgRed("ERROR"), ...args); const text = `[${formatUTCTimestamp()}] [ERROR] ${args.join(" ")}\n`; logs.error += text; logs.warn += text; @@ -94,14 +101,15 @@ $.error = (...args: any[]) => { }; // Be as verbose as possible. If anything might help when debugging an error, then include it. This only shows in your console if you run this with "dev", but you can still get it from "logs.verbose". $.debug = (...args: any[]) => { - if(process.argv[2] === "dev") + if(process.argv[2] === "dev" && enabled) console.debug(chalk.white.bgGray(formatTimestamp()), chalk.white.bgBlue("DEBUG"), ...args); const text = `[${formatUTCTimestamp()}] [DEBUG] ${args.join(" ")}\n`; logs.verbose += text; }; // Used once at the start of the program when the bot loads. $.ready = (...args: any[]) => { - console.log(chalk.white.bgGray(formatTimestamp()), chalk.black.bgGreen("READY"), ...args); + if(enabled) + console.log(chalk.white.bgGray(formatTimestamp()), chalk.black.bgGreen("READY"), ...args); const text = `[${formatUTCTimestamp()}] [READY] ${args.join(" ")}\n`; logs.info += text; logs.verbose += text; @@ -129,9 +137,6 @@ export function formatUTCTimestamp(now = new Date()) return `${year}-${month}-${day} ${hour}:${minute}:${second}`; } -// A list of message ID and callback pairs. You get the emote name and ID of the user reacting. -const eventListeners: Map void> = new Map(); - // Pagination function that allows for customization via a callback. // Define your own pages outside the function because this only manages the actual turning of pages. $.paginate = async(message: Message, senderID: string, total: number, callback: (page: number) => void, duration = 60000) => { @@ -173,13 +178,6 @@ $.paginate = async(message: Message, senderID: string, total: number, callback: message.reactions.cache.get('➡️')?.users.remove(message.author); }; -// Attached to the client, there can be one event listener attached to a message ID which is executed if present. -export function unreact(reaction: MessageReaction, user: User|PartialUser) -{ - const callback = eventListeners.get(reaction.message.id); - callback && callback(reaction.emoji.name, user.id); -} - // Waits for the sender to either confirm an action or let it pass (and delete the message). $.prompt = async(message: Message, senderID: string, onConfirm: () => void, duration = 10000) => { let isDeleted = false; diff --git a/src/core/storage.ts b/src/core/storage.ts index cb7b821..922e0fe 100644 --- a/src/core/storage.ts +++ b/src/core/storage.ts @@ -1,7 +1,8 @@ import fs from "fs"; import $ from "./lib"; -import {Collection} from "discord.js"; +import {Collection, Client} from "discord.js"; import Command, {template} from "../core/command"; +import {EVENTS} from "./event"; let commands: Collection|null = null; @@ -89,10 +90,26 @@ const Storage = { const header = file.substring(0, file.indexOf(".js")); const command = (await import(`../commands/${header}`)).default; commands.set(header, command); - $.log("Loading Command:", header); + $.log(`Loading Command: ${header}`); } return commands; + }, + async loadEvents(client: Client) + { + for(const file of Storage.open("dist/events", (filename: string) => filename.endsWith(".js"))) + { + const header = file.substring(0, file.indexOf(".js")); + const event = (await import(`../events/${header}`)).default; + + if(EVENTS.includes(header)) + { + event.attach(client, header); + $.log(`Loading Event: ${header}`); + } + else + $.warn(`"${header}" is not a valid event type! Did you misspell it? (Note: If you fixed the issue, delete "dist" because the compiler won't automatically delete any extra files.)`); + } } }; diff --git a/src/events/message.ts b/src/events/message.ts new file mode 100644 index 0000000..a63f6c8 --- /dev/null +++ b/src/events/message.ts @@ -0,0 +1,103 @@ +import Event from "../core/event"; +import Command from "../core/command"; +import $ from "../core/lib"; +import {Message, Permissions, Collection} from "discord.js"; +import FileManager from "../core/storage"; +import {Config, Storage} from "../core/structures"; + +// It's a rather hacky solution, but since there's no top-level await, I just have to make the loading conditional. +let commands: Collection|null = null; + +export default new Event({ + async on(message: Message) + { + // Load commands if it hasn't already done so. Luckily, it's called once at most. + if(!commands) + commands = await FileManager.loadCommands(); + + // Message Setup // + if(message.author.bot) + return; + + const prefix = Storage.getGuild(message.guild?.id || "N/A").prefix || Config.prefix; + + if(!message.content.startsWith(prefix)) + return; + + const [header, ...args] = message.content.substring(prefix.length).split(/ +/); + + if(!commands.has(header)) + return; + if(message.channel.type === "text" && !message.channel.permissionsFor(message.client.user || "")?.has(Permissions.FLAGS.SEND_MESSAGES)) + { + let status; + + if(message.member?.hasPermission(Permissions.FLAGS.ADMINISTRATOR)) + status = "Because you're a server admin, you have the ability to change that channel's permissions to match if that's what you intended."; + else + status = "Try using a different channel or contacting a server admin to change permissions of that channel if you think something's wrong."; + + return message.author.send(`I don't have permission to send messages in ${message.channel.toString()}. ${status}`); + } + + $.log(`${message.author.username}#${message.author.discriminator} executed the command "${header}" with arguments "${args}".`); + + // Subcommand Recursion // + let command = commands.get(header); + if(!command) return $.warn(`Command "${header}" was called but for some reason it's still undefined!`); + const params: any[] = []; + let isEndpoint = false; + + for(let param of args) + { + if(command.endpoint) + { + if(command.subcommands || command.user || command.number || command.any) + $.warn(`An endpoint cannot have subcommands! Check ${prefix}${header} again.`); + isEndpoint = true; + break; + } + + if(command.subcommands?.[param]) + command = command.subcommands[param]; + // Any Discord ID format will automatically format to a user ID. + else if(command.user && (/\d{17,19}/.test(param))) + { + const id = param.match(/\d+/g)![0]; + command = command.user; + try {params.push(await message.client.users.fetch(id))} + catch(error) {return message.channel.send(`No user found by the ID \`${id}\`!`)} + } + // Disallow infinity and allow for 0. + else if(command.number && (Number(param) || param === "0") && !param.includes("Infinity")) + { + command = command.number; + params.push(Number(param)); + } + else if(command.any) + { + command = command.any; + params.push(param); + } + else + params.push(param); + } + + if(isEndpoint) + return message.channel.send("Too many arguments!"); + + // Execute with dynamic library attached. // + // The purpose of using $.bind($) is to clone the function so as to not modify the original $. + // The cloned function doesn't copy the properties, so Object.assign() is used. + // Object.assign() modifies the first element and returns that, the second element applies its properties and the third element applies its own overriding the second one. + command.execute(Object.assign($.bind($), { + args: params, + author: message.author, + channel: message.channel, + client: message.client, + guild: message.guild, + member: message.member, + message: message + }, $)); + } +}); \ No newline at end of file diff --git a/src/events/messageReactionRemove.ts b/src/events/messageReactionRemove.ts new file mode 100644 index 0000000..60077e5 --- /dev/null +++ b/src/events/messageReactionRemove.ts @@ -0,0 +1,14 @@ +import Event from "../core/event"; +import {MessageReaction, User, PartialUser} from "discord.js"; + +// A list of message ID and callback pairs. You get the emote name and ID of the user reacting. +export const eventListeners: Map void> = new Map(); + +// Attached to the client, there can be one event listener attached to a message ID which is executed if present. +export default new Event({ + on(reaction: MessageReaction, user: User|PartialUser) + { + const callback = eventListeners.get(reaction.message.id); + callback && callback(reaction.emoji.name, user.id); + } +}); \ No newline at end of file diff --git a/src/events/ready.ts b/src/events/ready.ts new file mode 100644 index 0000000..7c4b78e --- /dev/null +++ b/src/events/ready.ts @@ -0,0 +1,18 @@ +import Event from "../core/event"; +import {client} from "../index"; +import $ from "../core/lib"; +import {Config} from "../core/structures"; + +export default new Event({ + once() + { + if(client.user) + { + $.ready(`Logged in as ${client.user.username}#${client.user.discriminator}.`); + client.user.setActivity({ + type: "LISTENING", + name: `${Config.prefix}help` + }); + } + } +}); \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index e64ae7d..f6114da 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,113 +1,15 @@ -import {Client, Permissions} from "discord.js"; -import $, {unreact} from "./core/lib"; +import {Client} from "discord.js"; import setup from "./setup"; import FileManager from "./core/storage"; -import {Config, Storage} from "./core/structures"; +import {Config} from "./core/structures"; -(async() => { - // Setup // - await setup.init(); - const client = new Client(); - const commands = await FileManager.loadCommands(); +// This is here in order to make it much less of a headache to access the client from other files. +// This of course won't actually do anything until the setup process is complete and it logs in. +export const client = new Client(); + +// Begin the command loading here rather than when it's needed like in the message event. +setup.init().then(() => { + FileManager.loadCommands(); + FileManager.loadEvents(client); client.login(Config.token).catch(setup.again); - - client.on("message", async message => { - // Message Setup // - if(message.author.bot) - return; - - const prefix = Storage.getGuild(message.guild?.id || "N/A").prefix || Config.prefix; - - if(!message.content.startsWith(prefix)) - return; - - const [header, ...args] = message.content.substring(prefix.length).split(/ +/); - - if(!commands.has(header)) - return; - if(message.channel.type === "text" && !message.channel.permissionsFor(client.user || "")?.has(Permissions.FLAGS.SEND_MESSAGES)) - { - let status; - - if(message.member?.hasPermission(Permissions.FLAGS.ADMINISTRATOR)) - status = "Because you're a server admin, you have the ability to change that channel's permissions to match if that's what you intended."; - else - status = "Try using a different channel or contacting a server admin to change permissions of that channel if you think something's wrong."; - - return message.author.send(`I don't have permission to send messages in ${message.channel.toString()}. ${status}`); - } - - $.log(`${message.author.username}#${message.author.discriminator} executed the command "${header}" with arguments "${args}".`); - - // Subcommand Recursion // - let command = commands.get(header); - if(!command) return $.warn(`Command "${header}" was called but for some reason it's still undefined!`); - const params: any[] = []; - let isEndpoint = false; - - for(let param of args) - { - if(command.endpoint) - { - if(command.subcommands || command.user || command.number || command.any) - $.warn(`An endpoint cannot have subcommands! Check ${prefix}${header} again.`); - isEndpoint = true; - break; - } - - if(command.subcommands?.[param]) - command = command.subcommands[param]; - // Any Discord ID format will automatically format to a user ID. - else if(command.user && (/\d{17,19}/.test(param))) - { - const id = param.match(/\d+/g)![0]; - command = command.user; - try {params.push(await client.users.fetch(id))} - catch(error) {return message.channel.send(`No user found by the ID \`${id}\`!`)} - } - // Disallow infinity and allow for 0. - else if(command.number && (Number(param) || param === "0") && !param.includes("Infinity")) - { - command = command.number; - params.push(Number(param)); - } - else if(command.any) - { - command = command.any; - params.push(param); - } - else - params.push(param); - } - - if(isEndpoint) - return message.channel.send("Too many arguments!"); - - // Execute with dynamic library attached. // - // The purpose of using $.bind($) is to clone the function so as to not modify the original $. - // The cloned function doesn't copy the properties, so Object.assign() is used. - // Object.assign() modifies the first element and returns that, the second element applies its properties and the third element applies its own overriding the second one. - command.execute(Object.assign($.bind($), { - args: params, - author: message.author, - channel: message.channel, - client: client, - guild: message.guild, - member: message.member, - message: message - }, $)); - }); - - client.once("ready", () => { - if(client.user) - { - $.ready(`Logged in as ${client.user.username}#${client.user.discriminator}.`); - client.user.setActivity({ - type: "LISTENING", - name: `${Config.prefix}help` - }); - } - }); - - client.on("messageReactionRemove", unreact); -})() \ No newline at end of file +}); \ No newline at end of file diff --git a/src/setup.ts b/src/setup.ts index f36f1fb..c373a72 100644 --- a/src/setup.ts +++ b/src/setup.ts @@ -2,7 +2,7 @@ import {existsSync as exists} from "fs"; import inquirer from "inquirer"; import Storage from "./core/storage"; import {Config} from "./core/structures"; -import $ from "./core/lib"; +import $, {setConsoleActivated} from "./core/lib"; // This file is called (or at least should be called) automatically as long as a config file doesn't exist yet. // And that file won't be written until the data is successfully initialized. @@ -40,6 +40,7 @@ export default { async again() { $.error("It seems that the token you provided is invalid."); + setConsoleActivated(false); const answers = await inquirer.prompt(prompts.slice(0, 1)); Config.token = answers.token as string; Config.save(false); From 14f78a91dcae4f8f6dc5f85d913702cde95245fa Mon Sep 17 00:00:00 2001 From: WatDuhHekBro <44940783+WatDuhHekBro@users.noreply.github.com> Date: Sat, 25 Jul 2020 18:32:49 -0500 Subject: [PATCH 006/178] Relocated loadCommands and loadEvents --- src/commands/help.ts | 6 +++--- src/core/command.ts | 30 ++++++++++++++++++++++++++++- src/core/event.ts | 21 ++++++++++++++++++++- src/core/storage.ts | 44 +------------------------------------------ src/events/message.ts | 4 ++-- src/index.ts | 7 ++++--- 6 files changed, 59 insertions(+), 53 deletions(-) diff --git a/src/commands/help.ts b/src/commands/help.ts index aa97c65..51c5eba 100644 --- a/src/commands/help.ts +++ b/src/commands/help.ts @@ -1,6 +1,6 @@ import Command from "../core/command"; import {CommonLibrary} from "../core/lib"; -import FileManager from "../core/storage"; +import {loadCommands} from "../core/command"; const types = ["user", "number", "any"]; @@ -9,7 +9,7 @@ export default new Command({ usage: "([command, [subcommand/type], ...])", async run($: CommonLibrary): Promise { - const commands = await FileManager.loadCommands(); + const commands = await loadCommands(); const list: string[] = []; for(const [header, command] of commands) @@ -22,7 +22,7 @@ export default new Command({ any: new Command({ async run($: CommonLibrary): Promise { - const commands = await FileManager.loadCommands(); + const commands = await loadCommands(); let header = $.args.shift(); let command = commands.get(header); diff --git a/src/core/command.ts b/src/core/command.ts index 0d81c84..fb844a9 100644 --- a/src/core/command.ts +++ b/src/core/command.ts @@ -1,4 +1,7 @@ -import {isType, parseVars, CommonLibrary} from "./lib"; +import $, {isType, parseVars, CommonLibrary} from "./lib"; +import {Collection} from "discord.js"; +import Storage, {generateHandler} from "./storage"; +import {existsSync, writeFile} from "fs"; // Permission levels starting from zero then increasing, allowing for numerical comparisons. // Note: For my bot, there really isn't much purpose to doing so, as it's just one command. And plus, if you're doing stuff like moderation commands, it's probably better to make a permissions system that allows for you to separate permissions into different trees. After all, it'd be a really bad idea to allow a bot mechanic to ban users. @@ -94,6 +97,31 @@ export default class Command return true; }*/ +let commands: Collection | null = null; + +/** Returns the cache of the commands if it exists and searches the directory if not. */ +// Fun, Miscellaneous (default), Music, System, Utility +export async function loadCommands(): Promise> +{ + if(commands) + return commands; + + if(process.argv[2] === "dev" && !existsSync("src/commands/test.ts")) + writeFile("src/commands/test.ts", template, generateHandler('"test.ts" (testing/template command) successfully generated.')); + + commands = new Collection(); + + for(const file of Storage.open("dist/commands", (filename: string) => filename.endsWith(".js"))) + { + const header = file.substring(0, file.indexOf(".js")); + const command = (await import(`../commands/${header}`)).default; + commands.set(header, command); + $.log(`Loading Command: ${header}`); + } + + return commands; +} + // The template should be built with a reductionist mentality. // Provide everything the user needs and then let them remove whatever they want. // That way, they aren't focusing on what's missing, but rather what they need for their command. diff --git a/src/core/event.ts b/src/core/event.ts index 164d245..6b2c813 100644 --- a/src/core/event.ts +++ b/src/core/event.ts @@ -1,7 +1,9 @@ import {Client} from "discord.js"; +import Storage from "./storage"; +import $ from "./lib"; // Last Updated: Discord.js v12.2.0 -export const EVENTS = ["channelCreate", "channelDelete", "channelPinsUpdate", "channelUpdate", "debug", "warn", "disconnect", "emojiCreate", "emojiDelete", "emojiUpdate", "error", "guildBanAdd", "guildBanRemove", "guildCreate", "guildDelete", "guildUnavailable", "guildIntegrationsUpdate", "guildMemberAdd", "guildMemberAvailable", "guildMemberRemove", "guildMembersChunk", "guildMemberSpeaking", "guildMemberUpdate", "guildUpdate", "inviteCreate", "inviteDelete", "message", "messageDelete", "messageReactionRemoveAll", "messageReactionRemoveEmoji", "messageDeleteBulk", "messageReactionAdd", "messageReactionRemove", "messageUpdate", "presenceUpdate", "rateLimit", "ready", "invalidated", "roleCreate", "roleDelete", "roleUpdate", "typingStart", "userUpdate", "voiceStateUpdate", "webhookUpdate", "shardDisconnect", "shardError", "shardReady", "shardReconnecting", "shardResume"]; +const EVENTS = ["channelCreate", "channelDelete", "channelPinsUpdate", "channelUpdate", "debug", "warn", "disconnect", "emojiCreate", "emojiDelete", "emojiUpdate", "error", "guildBanAdd", "guildBanRemove", "guildCreate", "guildDelete", "guildUnavailable", "guildIntegrationsUpdate", "guildMemberAdd", "guildMemberAvailable", "guildMemberRemove", "guildMembersChunk", "guildMemberSpeaking", "guildMemberUpdate", "guildUpdate", "inviteCreate", "inviteDelete", "message", "messageDelete", "messageReactionRemoveAll", "messageReactionRemoveEmoji", "messageDeleteBulk", "messageReactionAdd", "messageReactionRemove", "messageUpdate", "presenceUpdate", "rateLimit", "ready", "invalidated", "roleCreate", "roleDelete", "roleUpdate", "typingStart", "userUpdate", "voiceStateUpdate", "webhookUpdate", "shardDisconnect", "shardError", "shardReady", "shardReconnecting", "shardResume"]; interface EventOptions { @@ -28,4 +30,21 @@ export default class Event if(this.once) client.once(event as any, this.once as any); } +} + +export async function loadEvents(client: Client) +{ + for(const file of Storage.open("dist/events", (filename: string) => filename.endsWith(".js"))) + { + const header = file.substring(0, file.indexOf(".js")); + const event = (await import(`../events/${header}`)).default; + + if(EVENTS.includes(header)) + { + event.attach(client, header); + $.log(`Loading Event: ${header}`); + } + else + $.warn(`"${header}" is not a valid event type! Did you misspell it? (Note: If you fixed the issue, delete "dist" because the compiler won't automatically delete any extra files.)`); + } } \ No newline at end of file diff --git a/src/core/storage.ts b/src/core/storage.ts index 922e0fe..0bf790f 100644 --- a/src/core/storage.ts +++ b/src/core/storage.ts @@ -1,10 +1,5 @@ import fs from "fs"; import $ from "./lib"; -import {Collection, Client} from "discord.js"; -import Command, {template} from "../core/command"; -import {EVENTS} from "./event"; - -let commands: Collection|null = null; const Storage = { read(header: string): object @@ -73,47 +68,10 @@ const Storage = { { if(fs.existsSync(path) && fs.readdirSync(path).length === 0) fs.rmdir(path, generateHandler(`"${path}" successfully closed.`)); - }, - /** Returns the cache of the commands if it exists and searches the directory if not. */ - async loadCommands(): Promise> - { - if(commands) - return commands; - - if(process.argv[2] === "dev" && !fs.existsSync("src/commands/test.ts")) - fs.writeFile("src/commands/test.ts", template, generateHandler('"test.ts" (testing/template command) successfully generated.')); - - commands = new Collection(); - - for(const file of Storage.open("dist/commands", (filename: string) => filename.endsWith(".js"))) - { - const header = file.substring(0, file.indexOf(".js")); - const command = (await import(`../commands/${header}`)).default; - commands.set(header, command); - $.log(`Loading Command: ${header}`); - } - - return commands; - }, - async loadEvents(client: Client) - { - for(const file of Storage.open("dist/events", (filename: string) => filename.endsWith(".js"))) - { - const header = file.substring(0, file.indexOf(".js")); - const event = (await import(`../events/${header}`)).default; - - if(EVENTS.includes(header)) - { - event.attach(client, header); - $.log(`Loading Event: ${header}`); - } - else - $.warn(`"${header}" is not a valid event type! Did you misspell it? (Note: If you fixed the issue, delete "dist" because the compiler won't automatically delete any extra files.)`); - } } }; -function generateHandler(message: string) +export function generateHandler(message: string) { return (error: Error|null) => { if(error) diff --git a/src/events/message.ts b/src/events/message.ts index a63f6c8..613e602 100644 --- a/src/events/message.ts +++ b/src/events/message.ts @@ -2,8 +2,8 @@ import Event from "../core/event"; import Command from "../core/command"; import $ from "../core/lib"; import {Message, Permissions, Collection} from "discord.js"; -import FileManager from "../core/storage"; import {Config, Storage} from "../core/structures"; +import {loadCommands} from "../core/command"; // It's a rather hacky solution, but since there's no top-level await, I just have to make the loading conditional. let commands: Collection|null = null; @@ -13,7 +13,7 @@ export default new Event({ { // Load commands if it hasn't already done so. Luckily, it's called once at most. if(!commands) - commands = await FileManager.loadCommands(); + commands = await loadCommands(); // Message Setup // if(message.author.bot) diff --git a/src/index.ts b/src/index.ts index f6114da..0c2718a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,7 +1,8 @@ import {Client} from "discord.js"; import setup from "./setup"; -import FileManager from "./core/storage"; import {Config} from "./core/structures"; +import {loadCommands} from "./core/command"; +import {loadEvents} from "./core/event"; // This is here in order to make it much less of a headache to access the client from other files. // This of course won't actually do anything until the setup process is complete and it logs in. @@ -9,7 +10,7 @@ export const client = new Client(); // Begin the command loading here rather than when it's needed like in the message event. setup.init().then(() => { - FileManager.loadCommands(); - FileManager.loadEvents(client); + loadCommands(); + loadEvents(client); client.login(Config.token).catch(setup.again); }); \ No newline at end of file From 4b03912e89c73bedacea472d7100af9693cf2778 Mon Sep 17 00:00:00 2001 From: WatDuhHekBro <44940783+WatDuhHekBro@users.noreply.github.com> Date: Sat, 25 Jul 2020 20:14:11 -0500 Subject: [PATCH 007/178] Added command categories --- docs/Specifications.md | 3 ++ src/commands/help.ts | 27 ++++++++++---- src/core/command.ts | 80 +++++++++++++++++++++++++++++++++++++----- 3 files changed, 94 insertions(+), 16 deletions(-) diff --git a/docs/Specifications.md b/docs/Specifications.md index 9a64ccf..cc2b08e 100644 --- a/docs/Specifications.md +++ b/docs/Specifications.md @@ -4,6 +4,9 @@ The top-level directory is reserved for files that have to be there for it to wo - `core`: This is where core structures and critical functions for the bot go. - `modules`: This is where modules go that accomplish one specific purpose but isn't so necessary for the bot to function. The goal is to be able to safely remove these without too much trouble. - `commands`: Here's the place to store commands. The file name determines the command name. + - `subcommands/`: All commands here are ignored by the category loader. Here is where you can split commands into different files. Also works per directory, for example, `utility/subcommands/` is ignored. + - `/`: Specify a directory which'll group commands into a category. For example, a `utility` folder would make all commands inside have the `Utility` category. + - `.ts`: All commands at this level will have the `Miscellaneous` category. - `events`: Here's the place to store events. The file name determines the event type. - `dist`: This is where the runnable code in `src` compiles to. (The directory structure mirrors `src`.) - `data`: Holds all the dynamic data used by the bot. This is what you modify if you want to change stuff for just your instance of the bot. diff --git a/src/commands/help.ts b/src/commands/help.ts index 51c5eba..9c3e33a 100644 --- a/src/commands/help.ts +++ b/src/commands/help.ts @@ -1,6 +1,6 @@ import Command from "../core/command"; import {CommonLibrary} from "../core/lib"; -import {loadCommands} from "../core/command"; +import {loadCommands, categories} from "../core/command"; const types = ["user", "number", "any"]; @@ -10,14 +10,27 @@ export default new Command({ async run($: CommonLibrary): Promise { const commands = await loadCommands(); - const list: string[] = []; + let output = `Legend: \`\`, \`[list/of/subcommands]\`, \`(optional)\`, \`()\`, \`([optional/list/...])\``; - for(const [header, command] of commands) - if(header !== "test") - list.push(`- \`${header}\` - ${command.description}`); + for(const [category, headers] of categories) + { + output += `\n\n===[ ${category} ]===`; + + for(const header of headers) + { + if(header !== "test") + { + const command = commands.get(header); + + if(!command) + return $.warn(`Command "${header}" of category "${category}" unexpectedly doesn't exist!`); + + output += `\n- \`${header}\`: ${command.description}`; + } + } + } - const outList = list.length > 0 ? `\n${list.join('\n')}` : " None"; - $.channel.send(`Legend: \`\`, \`[list/of/subcommands]\`, \`(optional)\`, \`()\`, \`([optional/list/...])\`\nCommands:${outList}`, {split: true}); + $.channel.send(output, {split: true}); }, any: new Command({ async run($: CommonLibrary): Promise diff --git a/src/core/command.ts b/src/core/command.ts index fb844a9..5618ca9 100644 --- a/src/core/command.ts +++ b/src/core/command.ts @@ -1,7 +1,8 @@ import $, {isType, parseVars, CommonLibrary} from "./lib"; import {Collection} from "discord.js"; -import Storage, {generateHandler} from "./storage"; +import {generateHandler} from "./storage"; import {existsSync, writeFile} from "fs"; +import {promises as ffs} from "fs"; // Permission levels starting from zero then increasing, allowing for numerical comparisons. // Note: For my bot, there really isn't much purpose to doing so, as it's just one command. And plus, if you're doing stuff like moderation commands, it's probably better to make a permissions system that allows for you to separate permissions into different trees. After all, it'd be a really bad idea to allow a bot mechanic to ban users. @@ -97,10 +98,10 @@ export default class Command return true; }*/ -let commands: Collection | null = null; +let commands: Collection|null = null; +export const categories: Collection = new Collection(); /** Returns the cache of the commands if it exists and searches the directory if not. */ -// Fun, Miscellaneous (default), Music, System, Utility export async function loadCommands(): Promise> { if(commands) @@ -110,22 +111,83 @@ export async function loadCommands(): Promise> writeFile("src/commands/test.ts", template, generateHandler('"test.ts" (testing/template command) successfully generated.')); commands = new Collection(); + const dir = await ffs.opendir("dist/commands"); + const listMisc: string[] = []; + let selected; - for(const file of Storage.open("dist/commands", (filename: string) => filename.endsWith(".js"))) + // There will only be one level of directory searching (per category). + while(selected = await dir.read()) { - const header = file.substring(0, file.indexOf(".js")); - const command = (await import(`../commands/${header}`)).default; - commands.set(header, command); - $.log(`Loading Command: ${header}`); + if(selected.isDirectory()) + { + if(selected.name === "subcommands") + continue; + + const subdir = await ffs.opendir(`dist/commands/${selected.name}`); + const category = getTitleCase(selected.name); + const list: string[] = []; + let cmd; + + while(cmd = await subdir.read()) + { + if(cmd.isDirectory()) + { + if(cmd.name === "subcommands") + continue; + else + $.warn(`You can't have multiple levels of directories! From: "dist/commands/${cmd.name}"`); + } + else + { + const header = cmd.name.substring(0, cmd.name.indexOf(".js")); + const command = (await import(`../commands/${selected.name}/${header}`)).default; + list.push(header); + + if(commands.has(header)) + $.warn(`Command "${header}" already exists! Make sure to make each command uniquely identifiable across categories!`); + else + commands.set(header, command); + + $.log(`Loading Command: ${header} (${category})`); + } + } + + subdir.close(); + categories.set(category, list); + } + else + { + const header = selected.name.substring(0, selected.name.indexOf(".js")); + const command = (await import(`../commands/${header}`)).default; + listMisc.push(header); + + if(commands.has(header)) + $.warn(`Command "${header}" already exists! Make sure to make each command uniquely identifiable across categories.`); + else + commands.set(header, command); + + $.log(`Loading Command: ${header} (Miscellaneous)`); + } } + dir.close(); + categories.set("Miscellaneous", listMisc); + return commands; } +function getTitleCase(name: string): string +{ + if(name.length < 1) + return name; + const first = name[0].toUpperCase(); + return first + name.substring(1); +} + // The template should be built with a reductionist mentality. // Provide everything the user needs and then let them remove whatever they want. // That way, they aren't focusing on what's missing, but rather what they need for their command. -export const template = +const template = `import Command from '../core/command'; import {CommonLibrary} from '../core/lib'; From 248823959850c8eae5ecf43b89694f0265e7670b Mon Sep 17 00:00:00 2001 From: WatDuhHekBro <44940783+WatDuhHekBro@users.noreply.github.com> Date: Sat, 25 Jul 2020 21:35:53 -0500 Subject: [PATCH 008/178] Modularized command resolution --- src/commands/help.ts | 111 +++++++++++++++++++----------------------- src/core/command.ts | 40 ++++++++++++--- src/events/message.ts | 22 +++------ 3 files changed, 88 insertions(+), 85 deletions(-) diff --git a/src/commands/help.ts b/src/commands/help.ts index 9c3e33a..2bfa6e2 100644 --- a/src/commands/help.ts +++ b/src/commands/help.ts @@ -40,76 +40,65 @@ export default new Command({ let command = commands.get(header); if(!command || header === "test") - $.channel.send(`No command found by the name \`${header}\`!`); - else + return $.channel.send(`No command found by the name \`${header}\`!`); + + let usage = command.usage; + let invalid = false; + + for(const param of $.args) { - let usage = command.usage; + const type = command.resolve(param); - for(const param of $.args) + switch(type) { - header += ` ${param}`; - - if(/<\w+>/g.test(param)) - { - const type = param.match(/\w+/g)[0]; - command = command[type]; - - if(types.includes(type) && command?.usage) - usage = command.usage; - else - { - command = undefined; - break; - } - } - else if(command?.subcommands?.[param]) - { - command = command.subcommands[param]; - - if(command.usage !== "") - usage = command.usage; - } - else - { - command = undefined; - break; - } + case Command.TYPES.SUBCOMMAND: header += ` ${param}`; break; + case Command.TYPES.USER: header += " " ; break; + case Command.TYPES.NUMBER: header += " " ; break; + case Command.TYPES.ANY: header += " " ; break; + default: header += ` ${param}`; break; } - if(!command) - return $.channel.send(`No command found by the name \`${header}\`!`); - - let append = ""; - - if(usage === "") + if(type === Command.TYPES.NONE) { - const list: string[] = []; - - for(const subtag in command.subcommands) - { - const subcmd = command.subcommands[subtag]; - const customUsage = subcmd.usage ? ` ${subcmd.usage}` : ""; - list.push(`- \`${header} ${subtag}${customUsage}\` - ${subcmd.description}`); - } - - for(const type of types) - { - if(command[type]) - { - const cmd = command[type]; - const customUsage = cmd.usage ? ` ${cmd.usage}` : ""; - list.push(`- \`${header} <${type}>${customUsage}\` - ${cmd.description}`); - } - } - - - append = "Usages:" + (list.length > 0 ? `\n${list.join('\n')}` : " None."); + invalid = true; + break; } - else - append = `Usage: \`${header} ${usage}\``; - $.channel.send(`Command: \`${header}\`\nDescription: ${command.description}\n${append}`, {split: true}); + command = command.get(param); } + + if(invalid) + return $.channel.send(`No command found by the name \`${header}\`!`); + + let append = ""; + + if(usage === "") + { + const list: string[] = []; + + for(const subtag in command.subcommands) + { + const subcmd = command.subcommands[subtag]; + const customUsage = subcmd.usage ? ` ${subcmd.usage}` : ""; + list.push(`- \`${header} ${subtag}${customUsage}\` - ${subcmd.description}`); + } + + for(const type of types) + { + if(command[type]) + { + const cmd = command[type]; + const customUsage = cmd.usage ? ` ${cmd.usage}` : ""; + list.push(`- \`${header} <${type}>${customUsage}\` - ${cmd.description}`); + } + } + + append = "Usages:" + (list.length > 0 ? `\n${list.join('\n')}` : " None."); + } + else + append = `Usage: \`${header} ${usage}\``; + + $.channel.send(`Command: \`${header}\`\nDescription: ${command.description}\n${append}`, {split: true}); } }) }); \ No newline at end of file diff --git a/src/core/command.ts b/src/core/command.ts index 5618ca9..cab89e1 100644 --- a/src/core/command.ts +++ b/src/core/command.ts @@ -21,6 +21,8 @@ interface CommandOptions any?: Command; } +export enum TYPES {SUBCOMMAND, USER, NUMBER, ANY, NONE}; + export default class Command { public readonly description: string; @@ -34,6 +36,7 @@ export default class Command public any: Command|null; //public static readonly PERMISSIONS = PERMISSIONS; [key: string]: any; // Allow for dynamic indexing. The CommandOptions interface will still prevent users from adding unused properties though. + public static readonly TYPES = TYPES; constructor(options?: CommandOptions) { @@ -78,17 +81,38 @@ export default class Command this.subcommands[key] = command; } - /** See if a subcommand exists for the command. */ - /*public has(type: string): boolean + public resolve(param: string): TYPES { - return this.subcommands && (type in this.subcommands) || false; - }*/ + if(this.subcommands?.[param]) + return TYPES.SUBCOMMAND; + // Any Discord ID format will automatically format to a user ID. + else if(this.user && (/\d{17,19}/.test(param))) + return TYPES.USER; + // Disallow infinity and allow for 0. + else if(this.number && (Number(param) || param === "0") && !param.includes("Infinity")) + return TYPES.NUMBER; + else if(this.any) + return TYPES.ANY; + else + return TYPES.NONE; + } - /** Get the requested subcommand if it exists. */ - /*public get(type: string): Command|null + public get(param: string): Command { - return this.subcommands && this.subcommands[type] || null; - }*/ + const type = this.resolve(param); + let command; + + switch(type) + { + case TYPES.SUBCOMMAND: command = this.subcommands![param]; break; + case TYPES.USER: command = this.user as Command; break; + case TYPES.NUMBER: command = this.number as Command; break; + case TYPES.ANY: command = this.any as Command; break; + default: command = this; break; + } + + return command; + } } /*export function hasPermission(member: GuildMember, permission: number): boolean diff --git a/src/events/message.ts b/src/events/message.ts index 613e602..28252e4 100644 --- a/src/events/message.ts +++ b/src/events/message.ts @@ -58,28 +58,18 @@ export default new Event({ break; } - if(command.subcommands?.[param]) - command = command.subcommands[param]; - // Any Discord ID format will automatically format to a user ID. - else if(command.user && (/\d{17,19}/.test(param))) + const type = command.resolve(param); + command = command.get(param); + + if(type === Command.TYPES.USER) { const id = param.match(/\d+/g)![0]; - command = command.user; try {params.push(await message.client.users.fetch(id))} catch(error) {return message.channel.send(`No user found by the ID \`${id}\`!`)} } - // Disallow infinity and allow for 0. - else if(command.number && (Number(param) || param === "0") && !param.includes("Infinity")) - { - command = command.number; + else if(type === Command.TYPES.NUMBER) params.push(Number(param)); - } - else if(command.any) - { - command = command.any; - params.push(param); - } - else + else if(type !== Command.TYPES.SUBCOMMAND) params.push(param); } From bf84c2970dd9692069f51100dd2d5e06a4fa2f03 Mon Sep 17 00:00:00 2001 From: WatDuhHekBro <44940783+WatDuhHekBro@users.noreply.github.com> Date: Sun, 26 Jul 2020 05:02:35 -0500 Subject: [PATCH 009/178] Added a permissions system --- docs/Specifications.md | 1 + src/commands/admin.ts | 75 +++++++++++----------------------------- src/commands/help.ts | 5 ++- src/core/command.ts | 23 ++++--------- src/core/permissions.ts | 76 +++++++++++++++++++++++++++++++++++++++++ src/core/structures.ts | 8 +++-- src/events/message.ts | 13 +++++-- src/setup.ts | 19 ++++++++--- 8 files changed, 139 insertions(+), 81 deletions(-) create mode 100644 src/core/permissions.ts diff --git a/docs/Specifications.md b/docs/Specifications.md index cc2b08e..132e523 100644 --- a/docs/Specifications.md +++ b/docs/Specifications.md @@ -23,6 +23,7 @@ This list starts from `src`/`dist`. - `core/event`: Contains the class used to instantiate events. - `core/storage`: Exports an object which handles everything related to files. - `core/wrappers`: Contains classes that wrap around values and provide extra functionality. +- `core/permissions`: The file containing everything related to permissions. # Design Decisions - All top-level files (relative to `src`/`dist`) should ideally be independent, one-time use scripts. This helps separate code that just initializes once and reusable code that forms the bulk of the main program itself. That's why all the file searching and loading commands/events will be done in `index`. diff --git a/src/commands/admin.ts b/src/commands/admin.ts index 67a57df..e23cdea 100644 --- a/src/commands/admin.ts +++ b/src/commands/admin.ts @@ -1,25 +1,7 @@ import Command from "../core/command"; import {CommonLibrary, logs} from "../core/lib"; import {Config, Storage} from "../core/structures"; -import {Permissions} from "discord.js"; - -function authenticate($: CommonLibrary, customMessage = ""): boolean -{ - const hasAccess = Config.mechanics.includes($.author.id) || ($.member?.hasPermission(Permissions.FLAGS.ADMINISTRATOR) || false); - - if(!hasAccess) - { - if(customMessage !== "") - $.channel.send(customMessage); - else - { - $.channel.send(`${$.author.toString()}, you are not a server admin or one of the bot's mechanics. If you have access to the server files, add yourself to it manually in \`data/config.json\`. Your user ID should now be logged in the console.`); - $.debug($.author.id); - } - } - - return hasAccess; -} +import {PermissionNames, getPermissionLevel} from "../core/permissions"; function getLogBuffer(type: string) { @@ -33,25 +15,17 @@ export default new Command({ description: "An all-in-one command to do admin stuff. You need to be either an admin of the server or one of the bot's mechanics to use this command.", async run($: CommonLibrary): Promise { - const admin = $.member?.hasPermission(Permissions.FLAGS.ADMINISTRATOR) || false; - const mechanic = Config.mechanics.includes($.author.id); - let status = ""; - - if(admin && mechanic) - status = "a server admin and one of the bot's mechanics"; - else if(admin) - status = "a server admin"; - else if(mechanic) - status = "one of the bot's mechanics"; - - if(authenticate($)) - $.channel.send(`${$.author.toString()}, you are ${status}, meaning you can use this command.`); + if(!$.member) + return $.channel.send("Couldn't find a member object for you! Did you make sure you used this in a server?"); + const permLevel = getPermissionLevel($.member); + $.channel.send(`${$.author.toString()}, your permission level is \`${PermissionNames[permLevel]}\` (${permLevel}).`); }, subcommands: { set: new Command({ description: "Set different per-guild settings for the bot.", run: "You have to specify the option you want to set.", + permission: Command.PERMISSIONS.ADMIN, subcommands: { prefix: new Command({ @@ -59,22 +33,16 @@ export default new Command({ usage: "()", async run($: CommonLibrary): Promise { - if(authenticate($)) - { - Storage.getGuild($.guild?.id || "N/A").prefix = null; - Storage.save(); - $.channel.send(`The custom prefix for this guild has been removed. My prefix is now back to \`${Config.prefix}\`.`); - } + Storage.getGuild($.guild?.id || "N/A").prefix = null; + Storage.save(); + $.channel.send(`The custom prefix for this guild has been removed. My prefix is now back to \`${Config.prefix}\`.`); }, any: new Command({ async run($: CommonLibrary): Promise { - if(authenticate($)) - { - Storage.getGuild($.guild?.id || "N/A").prefix = $.args[0]; - Storage.save(); - $.channel.send(`The custom prefix for this guild is now \`${$.args[0]}\`.`); - } + Storage.getGuild($.guild?.id || "N/A").prefix = $.args[0]; + Storage.save(); + $.channel.send(`The custom prefix for this guild is now \`${$.args[0]}\`.`); } }) }) @@ -82,24 +50,21 @@ export default new Command({ }), diag: new Command({ description: "Requests a debug log with the \"info\" verbosity level.", + permission: Command.PERMISSIONS.BOT_SUPPORT, async run($: CommonLibrary): Promise { - if(authenticate($)) - $.channel.send(getLogBuffer("info")); + $.channel.send(getLogBuffer("info")); }, any: new Command({ description: `Select a verbosity to listen to. Available levels: \`[${Object.keys(logs)}]\``, async run($: CommonLibrary): Promise { - if(authenticate($)) - { - const type = $.args[0]; - - if(type in logs) - $.channel.send(getLogBuffer(type)); - else - $.channel.send(`Couldn't find a verbosity level named \`${type}\`! The available types are \`[${Object.keys(logs)}]\`.`); - } + const type = $.args[0]; + + if(type in logs) + $.channel.send(getLogBuffer(type)); + else + $.channel.send(`Couldn't find a verbosity level named \`${type}\`! The available types are \`[${Object.keys(logs)}]\`.`); } }) }) diff --git a/src/commands/help.ts b/src/commands/help.ts index 2bfa6e2..ba0a790 100644 --- a/src/commands/help.ts +++ b/src/commands/help.ts @@ -1,6 +1,7 @@ import Command from "../core/command"; import {CommonLibrary} from "../core/lib"; import {loadCommands, categories} from "../core/command"; +import {PermissionNames} from "../core/permissions"; const types = ["user", "number", "any"]; @@ -42,6 +43,7 @@ export default new Command({ if(!command || header === "test") return $.channel.send(`No command found by the name \`${header}\`!`); + let permLevel = command.permission ?? Command.PERMISSIONS.NONE; let usage = command.usage; let invalid = false; @@ -65,6 +67,7 @@ export default new Command({ } command = command.get(param); + permLevel = command.permission ?? permLevel; } if(invalid) @@ -98,7 +101,7 @@ export default new Command({ else append = `Usage: \`${header} ${usage}\``; - $.channel.send(`Command: \`${header}\`\nDescription: ${command.description}\n${append}`, {split: true}); + $.channel.send(`Command: \`${header}\`\nPermission Required: \`${PermissionNames[permLevel]}\` (${permLevel})\nDescription: ${command.description}\n${append}`, {split: true}); } }) }); \ No newline at end of file diff --git a/src/core/command.ts b/src/core/command.ts index cab89e1..3ea9a96 100644 --- a/src/core/command.ts +++ b/src/core/command.ts @@ -1,19 +1,15 @@ import $, {isType, parseVars, CommonLibrary} from "./lib"; import {Collection} from "discord.js"; import {generateHandler} from "./storage"; -import {existsSync, writeFile} from "fs"; -import {promises as ffs} from "fs"; - -// Permission levels starting from zero then increasing, allowing for numerical comparisons. -// Note: For my bot, there really isn't much purpose to doing so, as it's just one command. And plus, if you're doing stuff like moderation commands, it's probably better to make a permissions system that allows for you to separate permissions into different trees. After all, it'd be a really bad idea to allow a bot mechanic to ban users. -//enum PERMISSIONS {NONE, ADMIN, MECHANIC} +import {promises as ffs, existsSync, writeFile} from "fs"; +import {PERMISSIONS} from "./permissions"; interface CommandOptions { description?: string; endpoint?: boolean; usage?: string; - //permissions?: number; + permission?: PERMISSIONS; run?: Function|string; subcommands?: {[key: string]: Command}; user?: Command; @@ -28,22 +24,22 @@ export default class Command public readonly description: string; public readonly endpoint: boolean; public readonly usage: string; - //public readonly permissions: number; + public readonly permission: PERMISSIONS|null; private run: Function|string; public subcommands: {[key: string]: Command}|null; public user: Command|null; public number: Command|null; public any: Command|null; - //public static readonly PERMISSIONS = PERMISSIONS; [key: string]: any; // Allow for dynamic indexing. The CommandOptions interface will still prevent users from adding unused properties though. public static readonly TYPES = TYPES; + public static readonly PERMISSIONS = PERMISSIONS; constructor(options?: CommandOptions) { this.description = options?.description || "No description."; this.endpoint = options?.endpoint || false; this.usage = options?.usage || ""; - //this.permissions = options?.permissions || Command.PERMISSIONS.NONE; + this.permission = options?.permission ?? null; this.run = options?.run || "No action was set on this command!"; this.subcommands = options?.subcommands || null; this.user = options?.user || null; @@ -115,13 +111,6 @@ export default class Command } } -/*export function hasPermission(member: GuildMember, permission: number): boolean -{ - const length = Object.keys(PERMISSIONS).length / 2; - console.log(member, permission, length); - return true; -}*/ - let commands: Collection|null = null; export const categories: Collection = new Collection(); diff --git a/src/core/permissions.ts b/src/core/permissions.ts new file mode 100644 index 0000000..a69a944 --- /dev/null +++ b/src/core/permissions.ts @@ -0,0 +1,76 @@ +import {GuildMember, Permissions} from "discord.js"; +import {Config} from "./structures"; +import $ from "./lib"; + +export enum PERMISSIONS {NONE, MOD, ADMIN, OWNER, BOT_SUPPORT, BOT_ADMIN, BOT_OWNER}; +export const PermissionNames = ["User", "Moderator", "Administrator", "Server Owner", "Bot Support", "Bot Admin", "Bot Owner"]; + +// Here is where you enter in the functions that check for permissions. +const PermissionChecker: ((member: GuildMember) => boolean)[] = [ + // NONE // + () => true, + + // MOD // + (member: GuildMember) => + member.hasPermission(Permissions.FLAGS.MANAGE_ROLES) || + member.hasPermission(Permissions.FLAGS.MANAGE_MESSAGES) || + member.hasPermission(Permissions.FLAGS.KICK_MEMBERS) || + member.hasPermission(Permissions.FLAGS.BAN_MEMBERS), + + // ADMIN // + (member: GuildMember) => member.hasPermission(Permissions.FLAGS.ADMINISTRATOR), + + // OWNER // + (member: GuildMember) => member.guild.ownerID === member.id, + + // BOT_SUPPORT // + (member: GuildMember) => Config.support.includes(member.id), + + // BOT_ADMIN // + (member: GuildMember) => Config.admins.includes(member.id), + + // BOT_OWNER // + (member: GuildMember) => Config.owner === member.id +]; + +// After checking the lengths of these three objects, use this as the length for consistency. +const length = Object.keys(PERMISSIONS).length / 2; + +export function hasPermission(member: GuildMember, permission: PERMISSIONS): boolean +{ + if(permission === PERMISSIONS.NONE) + return true; + + for(let i = length-1; i >= permission; i--) + { + const condition = PermissionChecker[i](member); + + if(condition) + return true; + } + + return false; +} + +export function getPermissionLevel(member: GuildMember): number +{ + for(let i = length-1; i >= 0; i--) + { + const condition = PermissionChecker[i](member); + + if(condition) + return i; + } + + return 0; +} + +// Length Checking +(() => { + const lenNames = PermissionNames.length; + const lenChecker = PermissionChecker.length; + + // By transitive property, lenNames and lenChecker have to be equal to each other as well. + if(length !== lenNames || length !== lenChecker) + $.error(`Permission object lengths aren't equal! Enum Length (${length}), Names Length (${lenNames}), and Functions Length (${lenChecker}). This WILL cause problems!`); +})() \ No newline at end of file diff --git a/src/core/structures.ts b/src/core/structures.ts index 11f31a7..2a98839 100644 --- a/src/core/structures.ts +++ b/src/core/structures.ts @@ -6,14 +6,18 @@ class ConfigStructure extends GenericStructure { public token: string; public prefix: string; - public mechanics: string[]; + public owner: string; + public admins: string[]; + public support: string[]; constructor(data: GenericJSON) { super("config"); this.token = select(data.token, "", String); this.prefix = select(data.prefix, "$", String); - this.mechanics = select(data.mechanics, [], String, true); + this.owner = select(data.owner, "", String); + this.admins = select(data.admins, [], String, true); + this.support = select(data.support, [], String, true); } } diff --git a/src/events/message.ts b/src/events/message.ts index 28252e4..4771fb4 100644 --- a/src/events/message.ts +++ b/src/events/message.ts @@ -1,9 +1,9 @@ import Event from "../core/event"; -import Command from "../core/command"; +import Command, {loadCommands} from "../core/command"; +import {hasPermission, getPermissionLevel, PermissionNames} from "../core/permissions"; import $ from "../core/lib"; import {Message, Permissions, Collection} from "discord.js"; import {Config, Storage} from "../core/structures"; -import {loadCommands} from "../core/command"; // It's a rather hacky solution, but since there's no top-level await, I just have to make the loading conditional. let commands: Collection|null = null; @@ -47,6 +47,7 @@ export default new Event({ if(!command) return $.warn(`Command "${header}" was called but for some reason it's still undefined!`); const params: any[] = []; let isEndpoint = false; + let permLevel = command.permission ?? Command.PERMISSIONS.NONE; for(let param of args) { @@ -60,6 +61,7 @@ export default new Event({ const type = command.resolve(param); command = command.get(param); + permLevel = command.permission ?? permLevel; if(type === Command.TYPES.USER) { @@ -73,6 +75,13 @@ export default new Event({ params.push(param); } + if(!message.member) + return $.warn("This command was likely called from a DM channel meaning the member object is null."); + if(!hasPermission(message.member, permLevel)) + { + const userPermLevel = getPermissionLevel(message.member); + return message.channel.send(`You don't have access to this command! Your permission level is \`${PermissionNames[userPermLevel]}\` (${userPermLevel}), but this command requires a permission level of \`${PermissionNames[permLevel]}\` (${permLevel}).`); + } if(isEndpoint) return message.channel.send("Too many arguments!"); diff --git a/src/setup.ts b/src/setup.ts index c373a72..cfcfb5d 100644 --- a/src/setup.ts +++ b/src/setup.ts @@ -18,8 +18,16 @@ const prompts = [{ default: "$" }, { type: "input", - name: "mechanics", - message: "Enter a list of bot mechanics (by their IDs) separated by spaces." + name: "owner", + message: "Enter the owner's user ID here." +}, { + type: "input", + name: "admins", + message: "Enter a list of bot admins (by their IDs) separated by spaces." +}, { + type: "input", + name: "support", + message: "Enter a list of bot troubleshooters (by their IDs) separated by spaces." }]; export default { @@ -31,8 +39,11 @@ export default { Storage.open("data"); Config.token = answers.token as string; Config.prefix = answers.prefix as string; - const mechanics = (answers.mechanics as string); - Config.mechanics = mechanics !== "" ? mechanics.split(" ") : []; + Config.owner = answers.owner as string; + const admins = (answers.admins as string); + Config.admins = admins !== "" ? admins.split(" ") : []; + const support = (answers.support as string); + Config.support = support !== "" ? support.split(" ") : []; Config.save(); } }, From 48097b729d78539e7fa8aa200e71fcfd81d15cc1 Mon Sep 17 00:00:00 2001 From: Keanu Date: Wed, 5 Aug 2020 22:08:26 +0200 Subject: [PATCH 010/178] Added WIP info command. --- src/commands/info.ts | 122 +++++++++++++++++++++++++++++++++++++++++++ src/defs/info.ts | 45 ++++++++++++++++ 2 files changed, 167 insertions(+) create mode 100644 src/commands/info.ts create mode 100644 src/defs/info.ts diff --git a/src/commands/info.ts b/src/commands/info.ts new file mode 100644 index 0000000..e05a439 --- /dev/null +++ b/src/commands/info.ts @@ -0,0 +1,122 @@ +import { Guild, MessageEmbed } from "discord.js"; +import moment from "moment"; +import Command from "../core/command"; +import {CommonLibrary} from "../core/lib"; +import { verificationLevels, filterLevels, regions, flags } from "../defs/info"; + +export default new Command({ + description: "Command to provide all sorts of info about the current server, a user, etc.", + async run($: CommonLibrary): Promise + { + $.channel.send("Please provide an argument. `.help info`") + console.log(verificationLevels); + }, + subcommands: + { + avatar: new Command({ + description: "Shows your own, or another user's avatar.", + usage: "()", + async run($: CommonLibrary): Promise + { + $.channel.send($.author.displayAvatarURL({ dynamic: true, size: 2048 })) + }, + user: new Command({ + description: "Shows your own, or another user's avatar.", + async run($: CommonLibrary): Promise + { + $.channel.send($.args[0].displayAvatarURL({ dynamic: true, size: 2048 })) + } + }), + }), + + guild: new Command({ + description: "Displays info about the current guild.", + async run($: CommonLibrary): Promise + { + if ($.guild) { + const roles = $.guild.roles.cache.sort((a, b) => b.position - a.position).map(role => role.toString()); + const members = $.guild.members.cache; + const channels = $.guild.channels.cache; + const emojis = $.guild.emojis.cache; + + const iconURL = $.guild.iconURL({ dynamic: true }) + const embed = new MessageEmbed() + .setDescription(`**Guild information for __${$.guild.name}__**`) + .setColor('BLUE') + if (iconURL) embed.setThumbnail(iconURL) + .addField('General', [ + `**❯ Name:** ${$.guild.name}`, + `**❯ ID:** ${$.guild.id}`, + `**❯ Owner:** ${$.guild.owner?.user.tag} (${$.guild.ownerID})`, + `**❯ Region:** ${regions[$.guild.region]}`, + `**❯ Boost Tier:** ${$.guild.premiumTier ? `Tier ${$.guild.premiumTier}` : 'None'}`, + `**❯ Explicit Filter:** ${filterLevels[$.guild.explicitContentFilter]}`, + `**❯ Verification Level:** ${verificationLevels[$.guild.verificationLevel]}`, + `**❯ Time Created:** ${moment($.guild.createdTimestamp).format('LT')} ${moment($.guild.createdTimestamp).format('LL')} ${moment($.guild.createdTimestamp).fromNow()})`, + '\u200b' + ]) + .addField('Statistics', [ + `**❯ Role Count:** ${roles.length}`, + `**❯ Emoji Count:** ${emojis.size}`, + `**❯ Regular Emoji Count:** ${emojis.filter(emoji => !emoji.animated).size}`, + `**❯ Animated Emoji Count:** ${emojis.filter(emoji => emoji.animated).size}`, + `**❯ Member Count:** ${$.guild.memberCount}`, + `**❯ Humans:** ${members.filter(member => !member.user.bot).size}`, + `**❯ Bots:** ${members.filter(member => member.user.bot).size}`, + `**❯ Text Channels:** ${channels.filter(channel => channel.type === 'text').size}`, + `**❯ Voice Channels:** ${channels.filter(channel => channel.type === 'voice').size}`, + `**❯ Boost Count:** ${$.guild.premiumSubscriptionCount || '0'}`, + `\u200b` + ]) + .addField('Presence', [ + `**❯ Online:** ${members.filter(member => member.presence.status === 'online').size}`, + `**❯ Idle:** ${members.filter(member => member.presence.status === 'idle').size}`, + `**❯ Do Not Disturb:** ${members.filter(member => member.presence.status === 'dnd').size}`, + `**❯ Offline:** ${members.filter(member => member.presence.status === 'offline').size}`, + '\u200b' + ]) + .addField(`Roles [${roles.length - 1}]`, roles.length < 10 ? roles.join(', ') : roles.length > 10 ? this.client.utils.trimArray(roles) : 'None') + .setTimestamp(); + + $.channel.send(embed) + + } else { + $.channel.send("Please execute this command in a guild.") + } + } + }) + }, + user: new Command({ + description: "Displays info about mentioned user.", + async run($: CommonLibrary): Promise + { + const member = $.args[0] || $.args[0].mentions.members.last() || $.guild?.members.cache.get($.args[0]) || $.member; + const roles = member.roles.cache + .sort((a: { position: number; }, b: { position: number; }) => b.position - a.position) + .map((role: { toString: () => any; }) => role.toString()) + .slice(0, -1); + const userFlags = member.user.flags.toArray(); + + const embed = new MessageEmbed() + .setThumbnail(member.user.displayAvatarURL({ dynamic: true, size: 512 })) + .setColor(member.displayHexColor || 'BLUE') + .addField('User', [ + `**❯ Username:** ${member.user.username}`, + `**❯ Discriminator:** ${member.user.discriminator}`, + `**❯ ID:** ${member.id}`, + `**❯ Flags:** ${userFlags.length ? userFlags.map((flag: string | number) => flags[flag]).join(', ') : 'None'}`, + `**❯ Avatar:** [Link to avatar](${member.user.displayAvatarURL({ dynamic: true })})`, + `**❯ Time Created:** ${moment(member.user.createdTimestamp).format('LT')} ${moment(member.user.createdTimestamp).format('LL')} ${moment(member.user.createdTimestamp).fromNow()}`, + `**❯ Status:** ${member.user.presence.status}`, + `**❯ Game:** ${member.user.presence.game || 'Not playing a game.'}` + ]) + .addField('Member', [ + `**❯ Highest Role:** ${member.roles.highest.id === $.guild?.id ? 'None' : member.roles.highest.name}`, + `**❯ Server Join Date:** ${moment(member.joinedAt).format('LL LTS')}`, + `**❯ Hoist Role:** ${member.roles.hoist ? member.roles.hoist.name : 'None'}`, + `**❯ Roles:** [${roles.length}]: ${roles.length < 10 ? roles.join(', ') : roles.length > 10 ? this.client.utils.trimArray(roles) : 'None'}`, + ]); + $.channel.send(embed) + } + }) +}); \ No newline at end of file diff --git a/src/defs/info.ts b/src/defs/info.ts new file mode 100644 index 0000000..d560e2c --- /dev/null +++ b/src/defs/info.ts @@ -0,0 +1,45 @@ +// Flags a user can have. +// They're basically your profile badges. +export const flags: {[index: string]:any} = { + DISCORD_EMPLOYEE: 'Discord Employee', + DISCORD_PARTNER: 'Discord Partner', + BUGHUNTER_LEVEL_1: 'Bug Hunter (Level 1)', + BUGHUNTER_LEVEL_2: 'Bug Hunter (Level 2)', + HYPESQUAD_EVENTS: 'HypeSquad Events', + HOUSE_BRAVERY: 'House of Bravery', + HOUSE_BRILLIANCE: 'House of Brilliance', + HOUSE_BALANCE: 'House of Balance', + EARLY_SUPPORTER: 'Early Supporter', + TEAM_USER: 'Team User', + SYSTEM: 'System', + VERIFIED_BOT: 'Verified Bot', + VERIFIED_DEVELOPER: 'Verified Bot Developer', +}; + +export const filterLevels: {[index: string]:any} = { + DISABLED: 'Off', + MEMBERS_WITHOUT_ROLES: 'No Role', + ALL_MEMBERS: 'Everyone', +}; +export const verificationLevels: {[index: string]:any} = { + NONE: 'None', + LOW: 'Low', + MEDIUM: 'Medium', + HIGH: '(╯°□°)╯︵ ┻━┻', + VERY_HIGH: '┻━┻ ミヽ(ಠ益ಠ)ノ彡┻━┻', +}; +export const regions: {[index: string]:any} = { + brazil: 'Brazil', + europe: 'Europe', + hongkong: 'Hong Kong', + india: 'India', + japan: 'Japan', + russia: 'Russia', + singapore: 'Singapore', + southafrica: 'South Africa', + sydney: 'Sydney', + 'us-central': 'US Central', + 'us-east': 'US East', + 'us-west': 'US West', + 'us-south': 'US South', +}; \ No newline at end of file From 9816298f5523204e5adc568191688c4441f8c603 Mon Sep 17 00:00:00 2001 From: WatDuhHekBro <44940783+WatDuhHekBro@users.noreply.github.com> Date: Wed, 5 Aug 2020 22:11:11 -0500 Subject: [PATCH 011/178] Patched up the info command --- package-lock.json | 5 +++++ package.json | 3 ++- src/commands/info.ts | 13 +++++++++---- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index b60a197..1f86627 100644 --- a/package-lock.json +++ b/package-lock.json @@ -317,6 +317,11 @@ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==" }, + "moment": { + "version": "2.27.0", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.27.0.tgz", + "integrity": "sha512-al0MUK7cpIcglMv3YF13qSgdAIqxHTO7brRtaz3DlSULbqfazqkc5kEjNrLDOM7fsjshoFIihnU8snrP7zUvhQ==" + }, "mute-stream": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", diff --git a/package.json b/package.json index e92a1e8..c8dedf0 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,8 @@ "dependencies": { "chalk": "^4.1.0", "discord.js": "^12.2.0", - "inquirer": "^7.3.1" + "inquirer": "^7.3.1", + "moment": "^2.27.0" }, "devDependencies": { "@types/inquirer": "^6.5.0", diff --git a/src/commands/info.ts b/src/commands/info.ts index e05a439..01c94f3 100644 --- a/src/commands/info.ts +++ b/src/commands/info.ts @@ -90,12 +90,17 @@ export default new Command({ description: "Displays info about mentioned user.", async run($: CommonLibrary): Promise { - const member = $.args[0] || $.args[0].mentions.members.last() || $.guild?.members.cache.get($.args[0]) || $.member; + // Transforms the User object into a GuildMember object of the current guild. + const member = $.guild?.members.resolve($.args[0]); + + if(!member) + return $.channel.send("No member object was found by that user! Are you sure you used this command in a server?"); + const roles = member.roles.cache .sort((a: { position: number; }, b: { position: number; }) => b.position - a.position) .map((role: { toString: () => any; }) => role.toString()) .slice(0, -1); - const userFlags = member.user.flags.toArray(); + const userFlags = member.permissions.toArray(); const embed = new MessageEmbed() .setThumbnail(member.user.displayAvatarURL({ dynamic: true, size: 512 })) @@ -104,11 +109,11 @@ export default new Command({ `**❯ Username:** ${member.user.username}`, `**❯ Discriminator:** ${member.user.discriminator}`, `**❯ ID:** ${member.id}`, - `**❯ Flags:** ${userFlags.length ? userFlags.map((flag: string | number) => flags[flag]).join(', ') : 'None'}`, + `**❯ Flags:** ${userFlags.length ? userFlags.join(', ') : 'None'}`, `**❯ Avatar:** [Link to avatar](${member.user.displayAvatarURL({ dynamic: true })})`, `**❯ Time Created:** ${moment(member.user.createdTimestamp).format('LT')} ${moment(member.user.createdTimestamp).format('LL')} ${moment(member.user.createdTimestamp).fromNow()}`, `**❯ Status:** ${member.user.presence.status}`, - `**❯ Game:** ${member.user.presence.game || 'Not playing a game.'}` + `**❯ Game:** ${member.user.presence.activities || 'Not playing a game.'}` ]) .addField('Member', [ `**❯ Highest Role:** ${member.roles.highest.id === $.guild?.id ? 'None' : member.roles.highest.name}`, From 6744faa6e68972302bedcd3f2a67079f522cdb14 Mon Sep 17 00:00:00 2001 From: WatDuhHekBro <44940783+WatDuhHekBro@users.noreply.github.com> Date: Wed, 5 Aug 2020 22:21:36 -0500 Subject: [PATCH 012/178] Fixed userFlags --- src/commands/info.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/commands/info.ts b/src/commands/info.ts index 01c94f3..c858009 100644 --- a/src/commands/info.ts +++ b/src/commands/info.ts @@ -100,7 +100,8 @@ export default new Command({ .sort((a: { position: number; }, b: { position: number; }) => b.position - a.position) .map((role: { toString: () => any; }) => role.toString()) .slice(0, -1); - const userFlags = member.permissions.toArray(); + // @ts-ignore - Discord.js' typings seem to be outdated here. According to their v12 docs, it's User.fetchFlags() instead of User.flags. + const userFlags = (await member.user.fetchFlags() as UserFlags).toArray(); const embed = new MessageEmbed() .setThumbnail(member.user.displayAvatarURL({ dynamic: true, size: 512 })) From 9d0caaf976a9cf3519f36dedc13d638e4d04c84d Mon Sep 17 00:00:00 2001 From: Keanu Date: Wed, 12 Aug 2020 22:33:54 +0200 Subject: [PATCH 013/178] Added status command to admin. --- src/commands/admin.ts | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/commands/admin.ts b/src/commands/admin.ts index e23cdea..c693e1a 100644 --- a/src/commands/admin.ts +++ b/src/commands/admin.ts @@ -67,6 +67,26 @@ export default new Command({ $.channel.send(`Couldn't find a verbosity level named \`${type}\`! The available types are \`[${Object.keys(logs)}]\`.`); } }) + }), + status: new Command({ + description: "Changes the bot's status.", + permission: Command.PERMISSIONS.BOT_SUPPORT, + async run($: CommonLibrary): Promise + { + $.channel.send('Setting status to `online`...'); + }, + any: new Command({ + description: `Select a status to set to. Available statuses: \`online\`, \`idle\`, \`dnd\`, \`invisible\``, + async run($: CommonLibrary): Promise + { + let statuses = ['online', 'idle', 'dnd', 'invisible']; + if (!statuses.includes($.args[0])) return $.channel.send("That status doesn't exist!"); + else { + $.client.user?.setStatus($.args[0]); + $.channel.send(`Setting status to \`${$.args[0]}\`...`); + } + } + }) }) } }); \ No newline at end of file From facaf001ad622156674cca049116c57689f1bd97 Mon Sep 17 00:00:00 2001 From: Keanu Date: Thu, 13 Aug 2020 17:06:10 +0200 Subject: [PATCH 014/178] Added purge command to admin. --- src/commands/admin.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/commands/admin.ts b/src/commands/admin.ts index c693e1a..d113f83 100644 --- a/src/commands/admin.ts +++ b/src/commands/admin.ts @@ -87,6 +87,24 @@ export default new Command({ } } }) + }), + purge: new Command({ + description: "Purges bot messages.", + permission: Command.PERMISSIONS.BOT_SUPPORT, + async run($: CommonLibrary): Promise + { + $.message.delete(); + const msgs = await $.channel.messages.fetch({ + limit: 100 + }); + const travMessages = msgs.filter(m => m.author.id === $.client.user?.id); + + await $.message.channel.send(`Found ${travMessages.size} messages to delete.`) + .then(m => m.delete({ + timeout: 5000 + })); + await $.message.channel.bulkDelete(travMessages); + } }) } }); \ No newline at end of file From 979c00ddc8b32479703babcdc0c7c80e76def541 Mon Sep 17 00:00:00 2001 From: Keanu Date: Thu, 13 Aug 2020 17:57:22 +0200 Subject: [PATCH 015/178] Added nick command to admin. --- src/commands/admin.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/commands/admin.ts b/src/commands/admin.ts index d113f83..f9b0069 100644 --- a/src/commands/admin.ts +++ b/src/commands/admin.ts @@ -105,6 +105,23 @@ export default new Command({ })); await $.message.channel.bulkDelete(travMessages); } + }), + nick: new Command({ + description: "Change the bot's nickname.", + permission: Command.PERMISSIONS.BOT_SUPPORT, + async run($: CommonLibrary): Promise + { + try { + const nickName = $.args.join(" "); + const trav = $.guild?.members.cache.find(member => member.id === $.client.user?.id); + await trav?.setNickname(nickName); + $.message.delete({timeout: 5000}); + $.channel.send(`Nickname set to \`${nickName}\``) + .then(m => m.delete({timeout: 5000})); + } catch (e) { + console.log(e); + } + } }) } }); \ No newline at end of file From d85040b3137728324c36ee749c5647da6e6f8252 Mon Sep 17 00:00:00 2001 From: Keanu Date: Thu, 13 Aug 2020 18:13:55 +0200 Subject: [PATCH 016/178] Added guilds command to admin. --- src/commands/admin.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/commands/admin.ts b/src/commands/admin.ts index f9b0069..82e21b3 100644 --- a/src/commands/admin.ts +++ b/src/commands/admin.ts @@ -122,6 +122,16 @@ export default new Command({ console.log(e); } } + }), + guilds: new Command({ + description: "Shows a list of all guilds the bot is a member of.", + permission: Command.PERMISSIONS.BOT_SUPPORT, + async run($: CommonLibrary): Promise + { + const guildList = $.client.guilds.cache.array() + .map(e => e.name); + $.channel.send(guildList); + } }) } }); \ No newline at end of file From 6a628a4791859f8a8444d2ea223b14e00edee36a Mon Sep 17 00:00:00 2001 From: Keanu Date: Thu, 13 Aug 2020 18:49:55 +0200 Subject: [PATCH 017/178] Added activity command to admin. --- src/commands/admin.ts | 67 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/src/commands/admin.ts b/src/commands/admin.ts index 82e21b3..9f506b5 100644 --- a/src/commands/admin.ts +++ b/src/commands/admin.ts @@ -132,6 +132,73 @@ export default new Command({ .map(e => e.name); $.channel.send(guildList); } + }), + activity: new Command({ + description: "Set the activity of the bot.", + permission: Command.PERMISSIONS.BOT_SUPPORT, + usage: " ", + async run($: CommonLibrary): Promise + { + // if ($.args[0]) { + // $.message.delete(); + // $.client.user?.setActivity($.args.join(" ")); + // } else { + // $.message.delete(); + // $.client.user?.setActivity(".help", { + // type: "LISTENING" + // }); + // } + $.client.user?.setActivity(".help", { + type: "LISTENING" + }); + }, + subcommands: + { + playing: new Command({ + description: "Set the \`Playing\` status for the bot.", + usage: "", + async run($: CommonLibrary): Promise + { + $.client.user?.setActivity($.args.join(" "), { + type: "PLAYING" + }) + $.channel.send(`Set status to \`Playing ${$.args.join(" ")}\``) + } + }), + streaming: new Command({ + description: "Set the \`Streaming\` status for the bot.", + usage: "", + async run($: CommonLibrary): Promise + { + $.client.user?.setActivity($.args.join(" "), { + type: "STREAMING" + }) + $.channel.send(`Set status to \`Streaming ${$.args.join(" ")}\``) + } + }), + listening: new Command({ + description: "Set the \`Listening to\` status for the bot.", + usage: "", + async run($: CommonLibrary): Promise + { + $.client.user?.setActivity($.args.join(" "), { + type: "LISTENING" + }) + $.channel.send(`Set status to \`Listening to ${$.args.join(" ")}\``) + } + }), + watching: new Command({ + description: "Set the \`Watching\` status for the bot.", + usage: "", + async run($: CommonLibrary): Promise + { + $.client.user?.setActivity($.args.join(" "), { + type: "WATCHING" + }) + $.channel.send(`Set status to \`Watching ${$.args.join(" ")}\``) + } + }) + } }) } }); \ No newline at end of file From 8dd87c89a9a845d8587216ab5a00928e6bda901c Mon Sep 17 00:00:00 2001 From: Keanu Date: Thu, 13 Aug 2020 20:11:14 +0200 Subject: [PATCH 018/178] Largely optimized activity command. --- src/commands/admin.ts | 76 ++++++++++++------------------------------- 1 file changed, 21 insertions(+), 55 deletions(-) diff --git a/src/commands/admin.ts b/src/commands/admin.ts index 9f506b5..ff65330 100644 --- a/src/commands/admin.ts +++ b/src/commands/admin.ts @@ -11,6 +11,13 @@ function getLogBuffer(type: string) }]}; } +const activities: { [type: string]: string } = { + playing: "", + listening: "", + streaming: "", + watching: "" +}; + export default new Command({ description: "An all-in-one command to do admin stuff. You need to be either an admin of the server or one of the bot's mechanics to use this command.", async run($: CommonLibrary): Promise @@ -139,66 +146,25 @@ export default new Command({ usage: " ", async run($: CommonLibrary): Promise { - // if ($.args[0]) { - // $.message.delete(); - // $.client.user?.setActivity($.args.join(" ")); - // } else { - // $.message.delete(); - // $.client.user?.setActivity(".help", { - // type: "LISTENING" - // }); - // } $.client.user?.setActivity(".help", { type: "LISTENING" }); + $.channel.send("Activity set to default.") }, - subcommands: - { - playing: new Command({ - description: "Set the \`Playing\` status for the bot.", - usage: "", - async run($: CommonLibrary): Promise - { - $.client.user?.setActivity($.args.join(" "), { - type: "PLAYING" - }) - $.channel.send(`Set status to \`Playing ${$.args.join(" ")}\``) + any: new Command({ + description: `Select an activity type to set. Available levels: \`[${Object.keys(activities)}]\``, + async run($: CommonLibrary): Promise + { + const type = $.args[0]; + + if(type in activities) { + $.client.user?.setActivity($.args.slice(1).join(" "), {type: $.args[0].toUpperCase()}) + $.channel.send(`Set activity to \`${$.args[0].toUpperCase()}\` \`${$.args.slice(1).join(" ")}\`.`) } - }), - streaming: new Command({ - description: "Set the \`Streaming\` status for the bot.", - usage: "", - async run($: CommonLibrary): Promise - { - $.client.user?.setActivity($.args.join(" "), { - type: "STREAMING" - }) - $.channel.send(`Set status to \`Streaming ${$.args.join(" ")}\``) - } - }), - listening: new Command({ - description: "Set the \`Listening to\` status for the bot.", - usage: "", - async run($: CommonLibrary): Promise - { - $.client.user?.setActivity($.args.join(" "), { - type: "LISTENING" - }) - $.channel.send(`Set status to \`Listening to ${$.args.join(" ")}\``) - } - }), - watching: new Command({ - description: "Set the \`Watching\` status for the bot.", - usage: "", - async run($: CommonLibrary): Promise - { - $.client.user?.setActivity($.args.join(" "), { - type: "WATCHING" - }) - $.channel.send(`Set status to \`Watching ${$.args.join(" ")}\``) - } - }) - } + else + $.channel.send(`Couldn't find an activity type named \`${type}\`! The available types are \`[${Object.keys(activities)}]\`.`); + } + }) }) } }); \ No newline at end of file From ed14ccefca4541d5ad7991c61f7c8225490ca26b Mon Sep 17 00:00:00 2001 From: Keanu Date: Thu, 13 Aug 2020 20:58:41 +0200 Subject: [PATCH 019/178] Added 8ball command to fun. --- src/commands/fun.ts | 48 ++++++++++++++++++++++++++++++++++++++++++++ src/commands/info.ts | 6 +----- 2 files changed, 49 insertions(+), 5 deletions(-) create mode 100644 src/commands/fun.ts diff --git a/src/commands/fun.ts b/src/commands/fun.ts new file mode 100644 index 0000000..6932a6d --- /dev/null +++ b/src/commands/fun.ts @@ -0,0 +1,48 @@ +import Command from "../core/command"; +import {CommonLibrary} from "../core/lib"; + +const responses = [ + "Most likely,", + "It is certain,", + "It is decidedly so,", + "Without a doubt,", + "Definitely,", + "You may rely on it,", + "As I see it, yes,", + "Outlook good,", + "Yes,", + "Signs point to yes,", + "Reply hazy, try again,", + "Ask again later,", + "Better not tell you now,", + "Cannot predict now,", + "Concentrate and ask again,", + "Don't count on it,", + "My reply is no,", + "My sources say no,", + "Outlook not so good,", + "Very doubtful," +]; + +export default new Command({ + description: "Fun commands.", + endpoint: false, + run: "Please provide an argument.\nFor help, run `.help fun`.", + subcommands: + { + "8ball": new Command({ + description: "Answers your question in an 8-ball manner.", + endpoint: false, + usage: "", + run: "Please provide a question.", + any: new Command({ + description: "Question to ask the 8 Ball.", + async run($: CommonLibrary): Promise + { + const sender = $.message.author; + $.channel.send(responses[Math.floor(Math.random() * responses.length)] + ` <@${sender.id}>`); + } + }) + }) + } +}); \ No newline at end of file diff --git a/src/commands/info.ts b/src/commands/info.ts index c858009..739c8ad 100644 --- a/src/commands/info.ts +++ b/src/commands/info.ts @@ -6,11 +6,7 @@ import { verificationLevels, filterLevels, regions, flags } from "../defs/info"; export default new Command({ description: "Command to provide all sorts of info about the current server, a user, etc.", - async run($: CommonLibrary): Promise - { - $.channel.send("Please provide an argument. `.help info`") - console.log(verificationLevels); - }, + run: "Please provide an argument.\nFor help, run `.help info`.", subcommands: { avatar: new Command({ From 10f4f30137e2aab52c2c8d7f40571d3f6858bf8f Mon Sep 17 00:00:00 2001 From: Keanu Date: Thu, 13 Aug 2020 22:03:18 +0200 Subject: [PATCH 020/178] Added poll to fun. --- src/commands/fun.ts | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/commands/fun.ts b/src/commands/fun.ts index 6932a6d..68d63c1 100644 --- a/src/commands/fun.ts +++ b/src/commands/fun.ts @@ -1,3 +1,4 @@ +import { MessageEmbed } from "discord.js"; import Command from "../core/command"; import {CommonLibrary} from "../core/lib"; @@ -43,6 +44,28 @@ export default new Command({ $.channel.send(responses[Math.floor(Math.random() * responses.length)] + ` <@${sender.id}>`); } }) + }), + poll: new Command({ + description: "Create a poll.", + usage: "", + run: "Please provide a question.", + any: new Command({ + description: "Question for the poll.", + async run($: CommonLibrary): Promise + { + const embed = new MessageEmbed() + .setAuthor(`Poll created by ${$.message.author.username}`, $.message.guild?.iconURL({ dynamic: true }) ?? undefined) + .setColor(0xffffff) + .setFooter("React to vote.") + .setDescription($.args.join(" ")); + const msg = await $.channel.send(embed); + await msg.react("✅"); + await msg.react("⛔"); + $.message.delete({ + timeout: 1000 + }); + } + }) }) } }); \ No newline at end of file From a86e11ed23a83bd292076f18cbe842ea5311267f Mon Sep 17 00:00:00 2001 From: Keanu Date: Thu, 13 Aug 2020 22:39:10 +0200 Subject: [PATCH 021/178] Added lsemotes command to util. --- src/commands/util.ts | 72 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 src/commands/util.ts diff --git a/src/commands/util.ts b/src/commands/util.ts new file mode 100644 index 0000000..b1e6f81 --- /dev/null +++ b/src/commands/util.ts @@ -0,0 +1,72 @@ +import { MessageEmbed } from "discord.js"; +import Command from '../core/command'; +import {CommonLibrary} from '../core/lib'; + +export default new Command({ + description: "", + endpoint: false, + usage: '', + async run($: CommonLibrary): Promise { + + }, + subcommands: { + lsemotes: new Command({ + description: "Lists all emotes the bot has in it's registry,", + endpoint: true, + async run($: CommonLibrary): Promise { + const nsfw: string | string[] = []; + const list = $.client.emojis.cache.filter(x => !nsfw.includes(x.guild.id), this) + .array(); + let page = 1; + const epg = 20; + let content = ""; + const left = "⬅", + right = "➡"; + var embed = new MessageEmbed() + .setTitle("**Emoji list!**") + .setColor("AQUA"); + let owo = list.slice((page - 1) * epg, page * epg); + owo.forEach(q => (content += q.toString() + " | " + q.name + "\n")); + embed.setDescription(content); + const msg = await $.channel.send({ + embed + }); + if (list.length < epg) return; + await msg.react("⬅"); + await msg.react("➡"); + const backwardsfilter = (reaction: { emoji: { name: string; }; }, user: { id: any; }) => reaction.emoji.name == left && user.id == $.message.author.id; + const forwardsfilter = (reaction: { emoji: { name: string; }; }, user: { id: any; }) => reaction.emoji.name == right && user.id == $.message.author.id; + const backwards = msg.createReactionCollector(backwardsfilter, { + time: 300000 + }); + const forwards = msg.createReactionCollector(forwardsfilter, { + time: 300000 + }); + backwards.on("collect", () => { + if (page < 2) return; + // @ts-ignore + msg.reactions.cache.find((uwu: { emoji: { name: string; }; }) => (uwu.emoji.name = "⬅")) + .users.remove($.message.author) + page--; + owo = list.slice((page - 1) * epg, page * epg); + content = ""; + owo.forEach(q => (content += q.toString() + " | " + q.name + "\n")); + embed.setDescription(content); + msg.edit(embed); + }); + forwards.on("collect", () => { + if (page > Math.ceil(list.length / epg)) return; + page++; + // @ts-ignore + msg.reactions.cache.find((uwu: { emoji: { name: string; }; }) => uwu.emoji.name == "➡") + .users.remove($.message.author) + owo = list.slice((page - 1) * epg, page * epg); + content = ""; + owo.forEach(q => (content += q.toString() + " | " + q.name + "\n")); + embed.setDescription(content); + msg.edit(embed); + }); + } + }) + } +}); \ No newline at end of file From 77422538df9f496c83840bbd6a1e3af9b7b6abc9 Mon Sep 17 00:00:00 2001 From: WatDuhHekBro <44940783+WatDuhHekBro@users.noreply.github.com> Date: Fri, 14 Aug 2020 04:43:45 -0500 Subject: [PATCH 022/178] Added pinging bot for prefix and var string prefix --- src/commands/fun.ts | 2 +- src/core/command.ts | 4 +++- src/core/structures.ts | 6 ++++++ src/events/message.ts | 8 ++++++-- 4 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/commands/fun.ts b/src/commands/fun.ts index 68d63c1..99834d3 100644 --- a/src/commands/fun.ts +++ b/src/commands/fun.ts @@ -28,7 +28,7 @@ const responses = [ export default new Command({ description: "Fun commands.", endpoint: false, - run: "Please provide an argument.\nFor help, run `.help fun`.", + run: "Please provide an argument.\nFor help, run `%prefix%help fun`.", subcommands: { "8ball": new Command({ diff --git a/src/core/command.ts b/src/core/command.ts index 3ea9a96..a26e4dc 100644 --- a/src/core/command.ts +++ b/src/core/command.ts @@ -3,6 +3,7 @@ import {Collection} from "discord.js"; import {generateHandler} from "./storage"; import {promises as ffs, existsSync, writeFile} from "fs"; import {PERMISSIONS} from "./permissions"; +import {getPrefix} from "../core/structures"; interface CommandOptions { @@ -52,7 +53,8 @@ export default class Command if(isType(this.run, String)) { $.channel.send(parseVars(this.run as string, { - author: $.author.toString() + author: $.author.toString(), + prefix: getPrefix($.guild) }, "???")); } else diff --git a/src/core/structures.ts b/src/core/structures.ts index 2a98839..4bf75fa 100644 --- a/src/core/structures.ts +++ b/src/core/structures.ts @@ -1,6 +1,7 @@ import FileManager from "./storage"; import $, {select, GenericJSON, GenericStructure} from "./lib"; import {watch} from "fs"; +import {Guild as DiscordGuild} from "discord.js"; class ConfigStructure extends GenericStructure { @@ -114,4 +115,9 @@ if(process.argv[2] === "dev") case "storage": Storage = new StorageStructure(FileManager.read("storage")); break; } }); +} + +export function getPrefix(guild: DiscordGuild|null): string +{ + return Storage.getGuild(guild?.id || "N/A").prefix ?? Config.prefix; } \ No newline at end of file diff --git a/src/events/message.ts b/src/events/message.ts index 4771fb4..becc9bd 100644 --- a/src/events/message.ts +++ b/src/events/message.ts @@ -3,7 +3,7 @@ import Command, {loadCommands} from "../core/command"; import {hasPermission, getPermissionLevel, PermissionNames} from "../core/permissions"; import $ from "../core/lib"; import {Message, Permissions, Collection} from "discord.js"; -import {Config, Storage} from "../core/structures"; +import {getPrefix} from "../core/structures"; // It's a rather hacky solution, but since there's no top-level await, I just have to make the loading conditional. let commands: Collection|null = null; @@ -19,10 +19,14 @@ export default new Event({ if(message.author.bot) return; - const prefix = Storage.getGuild(message.guild?.id || "N/A").prefix || Config.prefix; + const prefix = getPrefix(message.guild); if(!message.content.startsWith(prefix)) + { + if(message.client.user && message.mentions.has(message.client.user)) + message.channel.send(`${message.author.toString()}, my prefix on this guild is \`${prefix}\`.`); return; + } const [header, ...args] = message.content.substring(prefix.length).split(/ +/); From 53705e76c56a2708d81ca7dd78fb8afbd80cc6b6 Mon Sep 17 00:00:00 2001 From: WatDuhHekBro <44940783+WatDuhHekBro@users.noreply.github.com> Date: Fri, 14 Aug 2020 05:21:19 -0500 Subject: [PATCH 023/178] Added removing emotes in paginate if possible --- src/core/lib.ts | 11 +++++++++-- src/events/messageReactionRemove.ts | 12 +++++++++--- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/src/core/lib.ts b/src/core/lib.ts index 61db18d..a052276 100644 --- a/src/core/lib.ts +++ b/src/core/lib.ts @@ -1,8 +1,9 @@ import {GenericWrapper, NumberWrapper, ArrayWrapper} from "./wrappers"; -import {Client, Message, TextChannel, DMChannel, NewsChannel, Guild, User, GuildMember} from "discord.js"; +import {Client, Message, TextChannel, DMChannel, NewsChannel, Guild, User, GuildMember, Permissions} from "discord.js"; import chalk from "chalk"; import FileManager from "./storage"; import {eventListeners} from "../events/messageReactionRemove"; +import {client} from "../index"; /** A type that describes what the library module does. */ export interface CommonLibrary @@ -141,7 +142,6 @@ export function formatUTCTimestamp(now = new Date()) // Define your own pages outside the function because this only manages the actual turning of pages. $.paginate = async(message: Message, senderID: string, total: number, callback: (page: number) => void, duration = 60000) => { let page = 0; - const turn = (amount: number) => { page += amount; @@ -168,7 +168,14 @@ $.paginate = async(message: Message, senderID: string, total: number, callback: await message.react('➡️'); eventListeners.set(message.id, handle); await message.awaitReactions((reaction, user) => { + // The reason this is inside the call is because it's possible to switch a user's permissions halfway and suddenly throw an error. + // This will dynamically adjust for that, switching modes depending on whether it currently has the "Manage Messages" permission. + const canDeleteEmotes = !!(client.user && message.guild?.members.resolve(client.user)?.hasPermission(Permissions.FLAGS.MANAGE_MESSAGES)); handle(reaction.emoji.name, user.id); + + if(canDeleteEmotes && user.id !== client.user?.id) + reaction.users.remove(user); + return false; }, {time: duration}); diff --git a/src/events/messageReactionRemove.ts b/src/events/messageReactionRemove.ts index 60077e5..986e94e 100644 --- a/src/events/messageReactionRemove.ts +++ b/src/events/messageReactionRemove.ts @@ -1,5 +1,6 @@ import Event from "../core/event"; -import {MessageReaction, User, PartialUser} from "discord.js"; +import {MessageReaction, User, PartialUser, Permissions} from "discord.js"; +import {client} from "../index"; // A list of message ID and callback pairs. You get the emote name and ID of the user reacting. export const eventListeners: Map void> = new Map(); @@ -8,7 +9,12 @@ export const eventListeners: Map void> = export default new Event({ on(reaction: MessageReaction, user: User|PartialUser) { - const callback = eventListeners.get(reaction.message.id); - callback && callback(reaction.emoji.name, user.id); + const canDeleteEmotes = !!(client.user && reaction.message.guild?.members.resolve(client.user)?.hasPermission(Permissions.FLAGS.MANAGE_MESSAGES)); + + if(!canDeleteEmotes) + { + const callback = eventListeners.get(reaction.message.id); + callback && callback(reaction.emoji.name, user.id); + } } }); \ No newline at end of file From 139630ce9fc607bcf1b066e8562355f5a50d57eb Mon Sep 17 00:00:00 2001 From: WatDuhHekBro <44940783+WatDuhHekBro@users.noreply.github.com> Date: Fri, 14 Aug 2020 07:50:24 -0500 Subject: [PATCH 024/178] Various small changes/fixes --- src/commands/admin.ts | 36 ++++++++++++----------------- src/commands/fun.ts | 2 +- src/commands/info.ts | 2 +- src/core/event.ts | 31 +++++++++++-------------- src/core/lib.ts | 12 +++++----- src/core/permissions.ts | 17 ++------------ src/events/message.ts | 8 +++---- src/events/messageReactionRemove.ts | 10 ++++---- src/events/ready.ts | 2 +- src/index.ts | 9 ++++++-- src/setup.ts | 2 +- 11 files changed, 57 insertions(+), 74 deletions(-) diff --git a/src/commands/admin.ts b/src/commands/admin.ts index ff65330..fc9a15c 100644 --- a/src/commands/admin.ts +++ b/src/commands/admin.ts @@ -2,6 +2,8 @@ import Command from "../core/command"; import {CommonLibrary, logs} from "../core/lib"; import {Config, Storage} from "../core/structures"; import {PermissionNames, getPermissionLevel} from "../core/permissions"; +import {botHasPermission} from "../index"; +import {Permissions} from "discord.js"; function getLogBuffer(type: string) { @@ -11,12 +13,7 @@ function getLogBuffer(type: string) }]}; } -const activities: { [type: string]: string } = { - playing: "", - listening: "", - streaming: "", - watching: "" -}; +const activities = ["playing", "listening", "streaming", "watching"]; export default new Command({ description: "An all-in-one command to do admin stuff. You need to be either an admin of the server or one of the bot's mechanics to use this command.", @@ -63,7 +60,7 @@ export default new Command({ $.channel.send(getLogBuffer("info")); }, any: new Command({ - description: `Select a verbosity to listen to. Available levels: \`[${Object.keys(logs)}]\``, + description: `Select a verbosity to listen to. Available levels: \`[${Object.keys(logs).join(", ")}]\``, async run($: CommonLibrary): Promise { const type = $.args[0]; @@ -71,7 +68,7 @@ export default new Command({ if(type in logs) $.channel.send(getLogBuffer(type)); else - $.channel.send(`Couldn't find a verbosity level named \`${type}\`! The available types are \`[${Object.keys(logs)}]\`.`); + $.channel.send(`Couldn't find a verbosity level named \`${type}\`! The available types are \`[${Object.keys(logs).join(", ")}]\`.`); } }) }), @@ -118,16 +115,13 @@ export default new Command({ permission: Command.PERMISSIONS.BOT_SUPPORT, async run($: CommonLibrary): Promise { - try { - const nickName = $.args.join(" "); - const trav = $.guild?.members.cache.find(member => member.id === $.client.user?.id); - await trav?.setNickname(nickName); - $.message.delete({timeout: 5000}); - $.channel.send(`Nickname set to \`${nickName}\``) - .then(m => m.delete({timeout: 5000})); - } catch (e) { - console.log(e); - } + const nickName = $.args.join(" "); + const trav = $.guild?.members.cache.find(member => member.id === $.client.user?.id); + await trav?.setNickname(nickName); + if(botHasPermission($.guild, Permissions.FLAGS.MANAGE_MESSAGES)) + $.message.delete({timeout: 5000}).catch($.handler.bind($)); + $.channel.send(`Nickname set to \`${nickName}\``) + .then(m => m.delete({timeout: 5000})); } }), guilds: new Command({ @@ -152,17 +146,17 @@ export default new Command({ $.channel.send("Activity set to default.") }, any: new Command({ - description: `Select an activity type to set. Available levels: \`[${Object.keys(activities)}]\``, + description: `Select an activity type to set. Available levels: \`[${activities.join(", ")}]\``, async run($: CommonLibrary): Promise { const type = $.args[0]; - if(type in activities) { + if(activities.includes(type)) { $.client.user?.setActivity($.args.slice(1).join(" "), {type: $.args[0].toUpperCase()}) $.channel.send(`Set activity to \`${$.args[0].toUpperCase()}\` \`${$.args.slice(1).join(" ")}\`.`) } else - $.channel.send(`Couldn't find an activity type named \`${type}\`! The available types are \`[${Object.keys(activities)}]\`.`); + $.channel.send(`Couldn't find an activity type named \`${type}\`! The available types are \`[${activities.join(", ")}]\`.`); } }) }) diff --git a/src/commands/fun.ts b/src/commands/fun.ts index 99834d3..35d7b51 100644 --- a/src/commands/fun.ts +++ b/src/commands/fun.ts @@ -41,7 +41,7 @@ export default new Command({ async run($: CommonLibrary): Promise { const sender = $.message.author; - $.channel.send(responses[Math.floor(Math.random() * responses.length)] + ` <@${sender.id}>`); + $.channel.send($(responses).random() + ` <@${sender.id}>`); } }) }), diff --git a/src/commands/info.ts b/src/commands/info.ts index 739c8ad..7a00557 100644 --- a/src/commands/info.ts +++ b/src/commands/info.ts @@ -6,7 +6,7 @@ import { verificationLevels, filterLevels, regions, flags } from "../defs/info"; export default new Command({ description: "Command to provide all sorts of info about the current server, a user, etc.", - run: "Please provide an argument.\nFor help, run `.help info`.", + run: "Please provide an argument.\nFor help, run `%prefix%help info`.", subcommands: { avatar: new Command({ diff --git a/src/core/event.ts b/src/core/event.ts index 6b2c813..4ede652 100644 --- a/src/core/event.ts +++ b/src/core/event.ts @@ -1,34 +1,31 @@ -import {Client} from "discord.js"; +import {Client, ClientEvents, Constants} from "discord.js"; import Storage from "./storage"; import $ from "./lib"; -// Last Updated: Discord.js v12.2.0 -const EVENTS = ["channelCreate", "channelDelete", "channelPinsUpdate", "channelUpdate", "debug", "warn", "disconnect", "emojiCreate", "emojiDelete", "emojiUpdate", "error", "guildBanAdd", "guildBanRemove", "guildCreate", "guildDelete", "guildUnavailable", "guildIntegrationsUpdate", "guildMemberAdd", "guildMemberAvailable", "guildMemberRemove", "guildMembersChunk", "guildMemberSpeaking", "guildMemberUpdate", "guildUpdate", "inviteCreate", "inviteDelete", "message", "messageDelete", "messageReactionRemoveAll", "messageReactionRemoveEmoji", "messageDeleteBulk", "messageReactionAdd", "messageReactionRemove", "messageUpdate", "presenceUpdate", "rateLimit", "ready", "invalidated", "roleCreate", "roleDelete", "roleUpdate", "typingStart", "userUpdate", "voiceStateUpdate", "webhookUpdate", "shardDisconnect", "shardError", "shardReady", "shardReconnecting", "shardResume"]; - -interface EventOptions +interface EventOptions { - readonly on?: Function; - readonly once?: Function; + readonly on?: (...args: ClientEvents[K]) => void; + readonly once?: (...args: ClientEvents[K]) => void; } -export default class Event +export default class Event { - private readonly on: Function|null; - private readonly once: Function|null; + private readonly on?: (...args: ClientEvents[K]) => void; + private readonly once?: (...args: ClientEvents[K]) => void; - constructor(options: EventOptions) + constructor(options: EventOptions) { - this.on = options.on || null; - this.once = options.once || null; + this.on = options.on; + this.once = options.once; } // For this function, I'm going to assume that the event is used with the correct arguments and that the event tag is checked in "storage.ts". - public attach(client: Client, event: string) + public attach(client: Client, event: K) { if(this.on) - client.on(event as any, this.on as any); + client.on(event, this.on); if(this.once) - client.once(event as any, this.once as any); + client.once(event, this.once); } } @@ -39,7 +36,7 @@ export async function loadEvents(client: Client) const header = file.substring(0, file.indexOf(".js")); const event = (await import(`../events/${header}`)).default; - if(EVENTS.includes(header)) + if((Object.values(Constants.Events) as string[]).includes(header)) { event.attach(client, header); $.log(`Loading Event: ${header}`); diff --git a/src/core/lib.ts b/src/core/lib.ts index a052276..e22e354 100644 --- a/src/core/lib.ts +++ b/src/core/lib.ts @@ -3,7 +3,7 @@ import {Client, Message, TextChannel, DMChannel, NewsChannel, Guild, User, Guild import chalk from "chalk"; import FileManager from "./storage"; import {eventListeners} from "../events/messageReactionRemove"; -import {client} from "../index"; +import {botHasPermission} from "../index"; /** A type that describes what the library module does. */ export interface CommonLibrary @@ -170,10 +170,10 @@ $.paginate = async(message: Message, senderID: string, total: number, callback: await message.awaitReactions((reaction, user) => { // The reason this is inside the call is because it's possible to switch a user's permissions halfway and suddenly throw an error. // This will dynamically adjust for that, switching modes depending on whether it currently has the "Manage Messages" permission. - const canDeleteEmotes = !!(client.user && message.guild?.members.resolve(client.user)?.hasPermission(Permissions.FLAGS.MANAGE_MESSAGES)); + const canDeleteEmotes = botHasPermission(message.guild, Permissions.FLAGS.MANAGE_MESSAGES); handle(reaction.emoji.name, user.id); - if(canDeleteEmotes && user.id !== client.user?.id) + if(canDeleteEmotes) reaction.users.remove(user); return false; @@ -258,7 +258,7 @@ export function parseArgs(line: string): string[] * - `%%` = `%` * - If the invalid token is null/undefined, nothing is changed. */ -export function parseVars(line: string, definitions: {[key: string]: string}, invalid: string|null|undefined = ""): string +export function parseVars(line: string, definitions: {[key: string]: string}, invalid: string|null = ""): string { let result = ""; let inVariable = false; @@ -276,7 +276,7 @@ export function parseVars(line: string, definitions: {[key: string]: string}, in { if(token in definitions) result += definitions[token]; - else if(invalid === undefined || invalid === null) + else if(invalid === null) result += `%${token}%`; else result += invalid; @@ -352,7 +352,7 @@ export interface GenericJSON export abstract class GenericStructure { - protected __meta__ = "generic"; + private __meta__ = "generic"; constructor(tag?: string) { diff --git a/src/core/permissions.ts b/src/core/permissions.ts index a69a944..56ea4d9 100644 --- a/src/core/permissions.ts +++ b/src/core/permissions.ts @@ -38,30 +38,17 @@ const length = Object.keys(PERMISSIONS).length / 2; export function hasPermission(member: GuildMember, permission: PERMISSIONS): boolean { - if(permission === PERMISSIONS.NONE) - return true; - for(let i = length-1; i >= permission; i--) - { - const condition = PermissionChecker[i](member); - - if(condition) + if(PermissionChecker[i](member)) return true; - } - return false; } export function getPermissionLevel(member: GuildMember): number { for(let i = length-1; i >= 0; i--) - { - const condition = PermissionChecker[i](member); - - if(condition) + if(PermissionChecker[i](member)) return i; - } - return 0; } diff --git a/src/events/message.ts b/src/events/message.ts index becc9bd..a7eb4cd 100644 --- a/src/events/message.ts +++ b/src/events/message.ts @@ -1,15 +1,15 @@ import Event from "../core/event"; import Command, {loadCommands} from "../core/command"; import {hasPermission, getPermissionLevel, PermissionNames} from "../core/permissions"; -import $ from "../core/lib"; -import {Message, Permissions, Collection} from "discord.js"; +import {Permissions, Collection} from "discord.js"; import {getPrefix} from "../core/structures"; +import $ from "../core/lib"; // It's a rather hacky solution, but since there's no top-level await, I just have to make the loading conditional. let commands: Collection|null = null; -export default new Event({ - async on(message: Message) +export default new Event<"message">({ + async on(message) { // Load commands if it hasn't already done so. Luckily, it's called once at most. if(!commands) diff --git a/src/events/messageReactionRemove.ts b/src/events/messageReactionRemove.ts index 986e94e..4304d27 100644 --- a/src/events/messageReactionRemove.ts +++ b/src/events/messageReactionRemove.ts @@ -1,15 +1,15 @@ import Event from "../core/event"; -import {MessageReaction, User, PartialUser, Permissions} from "discord.js"; -import {client} from "../index"; +import {Permissions} from "discord.js"; +import {botHasPermission} from "../index"; // A list of message ID and callback pairs. You get the emote name and ID of the user reacting. export const eventListeners: Map void> = new Map(); // Attached to the client, there can be one event listener attached to a message ID which is executed if present. -export default new Event({ - on(reaction: MessageReaction, user: User|PartialUser) +export default new Event<"messageReactionRemove">({ + on(reaction, user) { - const canDeleteEmotes = !!(client.user && reaction.message.guild?.members.resolve(client.user)?.hasPermission(Permissions.FLAGS.MANAGE_MESSAGES)); + const canDeleteEmotes = botHasPermission(reaction.message.guild, Permissions.FLAGS.MANAGE_MESSAGES); if(!canDeleteEmotes) { diff --git a/src/events/ready.ts b/src/events/ready.ts index 7c4b78e..a7ff1e8 100644 --- a/src/events/ready.ts +++ b/src/events/ready.ts @@ -3,7 +3,7 @@ import {client} from "../index"; import $ from "../core/lib"; import {Config} from "../core/structures"; -export default new Event({ +export default new Event<"ready">({ once() { if(client.user) diff --git a/src/index.ts b/src/index.ts index 0c2718a..39c2b29 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,4 @@ -import {Client} from "discord.js"; +import {Client, Guild} from "discord.js"; import setup from "./setup"; import {Config} from "./core/structures"; import {loadCommands} from "./core/command"; @@ -13,4 +13,9 @@ setup.init().then(() => { loadCommands(); loadEvents(client); client.login(Config.token).catch(setup.again); -}); \ No newline at end of file +}); + +export function botHasPermission(guild: Guild|null, permission: number): boolean +{ + return !!(client.user && guild?.members.resolve(client.user)?.hasPermission(permission)) +} \ No newline at end of file diff --git a/src/setup.ts b/src/setup.ts index cfcfb5d..7f08a93 100644 --- a/src/setup.ts +++ b/src/setup.ts @@ -44,7 +44,7 @@ export default { Config.admins = admins !== "" ? admins.split(" ") : []; const support = (answers.support as string); Config.support = support !== "" ? support.split(" ") : []; - Config.save(); + Config.save(false); } }, /** Prompt the user to set their token again. */ From 877a41fac23c1cf722ec34a4ad64bedca315eb77 Mon Sep 17 00:00:00 2001 From: WatDuhHekBro <44940783+WatDuhHekBro@users.noreply.github.com> Date: Fri, 14 Aug 2020 11:35:53 -0500 Subject: [PATCH 025/178] Added command aliases --- docs/Specifications.md | 7 ++- src/commands/help.ts | 76 +++++++++++++++------- src/core/command.ts | 140 +++++++++++++++++++++++++---------------- src/core/lib.ts | 8 ++- src/core/wrappers.ts | 18 ++++++ src/events/message.ts | 5 +- 6 files changed, 174 insertions(+), 80 deletions(-) diff --git a/docs/Specifications.md b/docs/Specifications.md index 132e523..5ba8043 100644 --- a/docs/Specifications.md +++ b/docs/Specifications.md @@ -34,4 +34,9 @@ This list starts from `src`/`dist`. - I decided to forget about implementing dynamic events. I don't think it'll work with this setup. After all, there are only so many events you can use, whereas commands can have any number of ones, more suitable for dynamic loading. The main reasons were unsecure types and no easy way to access variables like the config or client. - I want to make attaching subcommands more flexible, so you can either add subcommands in the constructor or by using a method. However, you have to add all other properties when instantiating a command. - All commands should have only one parameter. This parameter is meant to be flexible so you can add properties without making a laundry list of parameters. It also has convenience functions too so you don't have to import the library for each command. -- The objects in `core/structures` are initialized into a special object once and then cached into memory automatically due to an import system. This means you don't have to keep loading JSON files over and over again even without the stack storage system. Because a JSON file resolves into an object, any extra keys are removed automatically (as it isn't initialized into the data) and any property that doesn't yet exist on the JSON object will be initialized into something. You can then have specific functions like `addUser` onto objects with a specific structure. \ No newline at end of file +- The objects in `core/structures` are initialized into a special object once and then cached into memory automatically due to an import system. This means you don't have to keep loading JSON files over and over again even without the stack storage system. Because a JSON file resolves into an object, any extra keys are removed automatically (as it isn't initialized into the data) and any property that doesn't yet exist on the JSON object will be initialized into something. You can then have specific functions like `addUser` onto objects with a specific structure. +- There were several possible ways to go about implementing aliases and subaliases. + - Two properties on the `Command` class, `aliases: string[]` and `subaliases: {[key: string]: string[]}`. + - Exporting a const named `aliases` which would handle top-level aliases. + - For `subaliases`, either making subaliases work like redirects (Instead of doing `new Command(...)`, you'd do `"nameOfSubcommand"`), or define properties on `Command`. +- What I ended up doing for aliases is making an `aliases` property on `Command` and then converting those string arrays to a more usable structure with strings pointing to the original commands. `aliases` at the very top will determine global aliases and is pretty much the exception in the program's logic. `aliases` elsewhere will provide aliases per subcommand. For anything other than the top-level or `subcommands`, `aliases` does nothing (plus you'll get a warning about it). \ No newline at end of file diff --git a/src/commands/help.ts b/src/commands/help.ts index ba0a790..b6166a3 100644 --- a/src/commands/help.ts +++ b/src/commands/help.ts @@ -3,15 +3,14 @@ import {CommonLibrary} from "../core/lib"; import {loadCommands, categories} from "../core/command"; import {PermissionNames} from "../core/permissions"; -const types = ["user", "number", "any"]; - export default new Command({ description: "Lists all commands. If a command is specified, their arguments are listed as well.", usage: "([command, [subcommand/type], ...])", + aliases: ["h"], async run($: CommonLibrary): Promise { const commands = await loadCommands(); - let output = `Legend: \`\`, \`[list/of/subcommands]\`, \`(optional)\`, \`()\`, \`([optional/list/...])\``; + let output = `Legend: \`\`, \`[list/of/stuff]\`, \`(optional)\`, \`()\`, \`([optional/list/...])\``; for(const [category, headers] of categories) { @@ -37,23 +36,43 @@ export default new Command({ async run($: CommonLibrary): Promise { const commands = await loadCommands(); - let header = $.args.shift(); + let header = $.args.shift() as string; let command = commands.get(header); if(!command || header === "test") return $.channel.send(`No command found by the name \`${header}\`!`); + + if(command.originalCommandName) + header = command.originalCommandName; + else + $.warn(`originalCommandName isn't defined for ${header}?!`); let permLevel = command.permission ?? Command.PERMISSIONS.NONE; let usage = command.usage; let invalid = false; + let selectedCategory = "Unknown"; + + for(const [category, headers] of categories) + { + if(headers.includes(header)) + { + if(selectedCategory !== "Unknown") + $.warn(`Command "${header}" is somehow in multiple categories. This means that the command loading stage probably failed in properly adding categories.`); + else + selectedCategory = category; + } + } + for(const param of $.args) { const type = command.resolve(param); + command = command.get(param); + permLevel = command.permission ?? permLevel; switch(type) { - case Command.TYPES.SUBCOMMAND: header += ` ${param}`; break; + case Command.TYPES.SUBCOMMAND: header += ` ${command.originalCommandName}`; break; case Command.TYPES.USER: header += " " ; break; case Command.TYPES.NUMBER: header += " " ; break; case Command.TYPES.ANY: header += " " ; break; @@ -65,9 +84,6 @@ export default new Command({ invalid = true; break; } - - command = command.get(param); - permLevel = command.permission ?? permLevel; } if(invalid) @@ -79,29 +95,47 @@ export default new Command({ { const list: string[] = []; - for(const subtag in command.subcommands) - { - const subcmd = command.subcommands[subtag]; - const customUsage = subcmd.usage ? ` ${subcmd.usage}` : ""; - list.push(`- \`${header} ${subtag}${customUsage}\` - ${subcmd.description}`); - } + command.subcommands.forEach((subcmd, subtag) => { + // Don't capture duplicates generated from aliases. + if(subcmd.originalCommandName === subtag) { + const customUsage = subcmd.usage ? ` ${subcmd.usage}` : ""; + list.push(`- \`${header} ${subtag}${customUsage}\` - ${subcmd.description}`); + } + }); - for(const type of types) - { - if(command[type]) - { - const cmd = command[type]; + const addDynamicType = (cmd: Command|null, type: string) => { + if(cmd) { const customUsage = cmd.usage ? ` ${cmd.usage}` : ""; list.push(`- \`${header} <${type}>${customUsage}\` - ${cmd.description}`); } - } + }; + + addDynamicType(command.user, "user"); + addDynamicType(command.number, "number"); + addDynamicType(command.any, "any"); append = "Usages:" + (list.length > 0 ? `\n${list.join('\n')}` : " None."); } else append = `Usage: \`${header} ${usage}\``; - $.channel.send(`Command: \`${header}\`\nPermission Required: \`${PermissionNames[permLevel]}\` (${permLevel})\nDescription: ${command.description}\n${append}`, {split: true}); + let aliases = "None"; + + if(command.aliases.length > 0) + { + aliases = ""; + + for(let i = 0; i < command.aliases.length; i++) + { + const alias = command.aliases[i]; + aliases += `\`${alias}\``; + + if(i !== command.aliases.length-1) + aliases += ", "; + } + } + + $.channel.send(`Command: \`${header}\`\nAliases: ${aliases}\nCategory: \`${selectedCategory}\`\nPermission Required: \`${PermissionNames[permLevel]}\` (${permLevel})\nDescription: ${command.description}\n${append}`, {split: true}); } }) }); \ No newline at end of file diff --git a/src/core/command.ts b/src/core/command.ts index a26e4dc..d6aabeb 100644 --- a/src/core/command.ts +++ b/src/core/command.ts @@ -10,7 +10,8 @@ interface CommandOptions description?: string; endpoint?: boolean; usage?: string; - permission?: PERMISSIONS; + permission?: PERMISSIONS|null; + aliases?: string[]; run?: Function|string; subcommands?: {[key: string]: Command}; user?: Command; @@ -26,12 +27,13 @@ export default class Command public readonly endpoint: boolean; public readonly usage: string; public readonly permission: PERMISSIONS|null; + public readonly aliases: string[]; // This is to keep the array intact for parent Command instances to use. It'll also be used when loading top-level aliases. + public originalCommandName: string|null; // If the command is an alias, what's the original name? private run: Function|string; - public subcommands: {[key: string]: Command}|null; + public readonly subcommands: Collection; // This is the final data structure you'll actually use to work with the commands the aliases point to. public user: Command|null; public number: Command|null; public any: Command|null; - [key: string]: any; // Allow for dynamic indexing. The CommandOptions interface will still prevent users from adding unused properties though. public static readonly TYPES = TYPES; public static readonly PERMISSIONS = PERMISSIONS; @@ -41,11 +43,48 @@ export default class Command this.endpoint = options?.endpoint || false; this.usage = options?.usage || ""; this.permission = options?.permission ?? null; + this.aliases = options?.aliases ?? []; + this.originalCommandName = null; this.run = options?.run || "No action was set on this command!"; - this.subcommands = options?.subcommands || null; + this.subcommands = new Collection(); // Populate this collection after setting subcommands. this.user = options?.user || null; this.number = options?.number || null; this.any = options?.any || null; + + if(options?.subcommands) + { + const baseSubcommands = Object.keys(options.subcommands); + + // Loop once to set the base subcommands. + for(const name in options.subcommands) + this.subcommands.set(name, options.subcommands[name]); + + // Then loop again to make aliases point to the base subcommands and warn if something's not right. + // This shouldn't be a problem because I'm hoping that JS stores these as references that point to the same object. + for(const name in options.subcommands) + { + const subcmd = options.subcommands[name]; + subcmd.originalCommandName = name; + const aliases = subcmd.aliases; + + for(const alias of aliases) + { + if(baseSubcommands.includes(alias)) + $.warn(`"${alias}" in subcommand "${name}" was attempted to be declared as an alias but it already exists in the base commands! (Look at the next "Loading Command" line to see which command is affected.)`); + else if(this.subcommands.has(alias)) + $.warn(`Duplicate alias "${alias}" at subcommand "${name}"! (Look at the next "Loading Command" line to see which command is affected.)`); + else + this.subcommands.set(alias, subcmd); + } + } + } + + if(this.user && this.user.aliases.length > 0) + $.warn(`There are aliases defined for a "user"-type subcommand, but those aliases won't be used. (Look at the next "Loading Command" line to see which command is affected.)`); + if(this.number && this.number.aliases.length > 0) + $.warn(`There are aliases defined for a "number"-type subcommand, but those aliases won't be used. (Look at the next "Loading Command" line to see which command is affected.)`); + if(this.any && this.any.aliases.length > 0) + $.warn(`There are aliases defined for an "any"-type subcommand, but those aliases won't be used. (Look at the next "Loading Command" line to see which command is affected.)`); } public execute($: CommonLibrary) @@ -61,27 +100,9 @@ export default class Command (this.run as Function)($).catch($.handler.bind($)); } - /** - * Set what happens when the command is called. - * - If the command is a function, run it with one argument (the common library). - * - If the command is a string, it'll be sent as a message with %variables% replaced. - */ - public set(run: Function|string) - { - this.run = run; - } - - /** The safe way to attach a named subcommand. */ - public attach(key: string, command: Command) - { - if(!this.subcommands) - this.subcommands = {}; - this.subcommands[key] = command; - } - public resolve(param: string): TYPES { - if(this.subcommands?.[param]) + if(this.subcommands.has(param)) return TYPES.SUBCOMMAND; // Any Discord ID format will automatically format to a user ID. else if(this.user && (/\d{17,19}/.test(param))) @@ -98,11 +119,11 @@ export default class Command public get(param: string): Command { const type = this.resolve(param); - let command; + let command: Command; switch(type) { - case TYPES.SUBCOMMAND: command = this.subcommands![param]; break; + case TYPES.SUBCOMMAND: command = this.subcommands.get(param) as Command; break; case TYPES.USER: command = this.user as Command; break; case TYPES.NUMBER: command = this.number as Command; break; case TYPES.ANY: command = this.any as Command; break; @@ -115,6 +136,7 @@ export default class Command let commands: Collection|null = null; export const categories: Collection = new Collection(); +export const aliases: Collection = new Collection(); // Top-level aliases only. /** Returns the cache of the commands if it exists and searches the directory if not. */ export async function loadCommands(): Promise> @@ -139,7 +161,7 @@ export async function loadCommands(): Promise> continue; const subdir = await ffs.opendir(`dist/commands/${selected.name}`); - const category = getTitleCase(selected.name); + const category = $(selected.name).toTitleCase(); const list: string[] = []; let cmd; @@ -153,36 +175,14 @@ export async function loadCommands(): Promise> $.warn(`You can't have multiple levels of directories! From: "dist/commands/${cmd.name}"`); } else - { - const header = cmd.name.substring(0, cmd.name.indexOf(".js")); - const command = (await import(`../commands/${selected.name}/${header}`)).default; - list.push(header); - - if(commands.has(header)) - $.warn(`Command "${header}" already exists! Make sure to make each command uniquely identifiable across categories!`); - else - commands.set(header, command); - - $.log(`Loading Command: ${header} (${category})`); - } + loadCommand(cmd.name, list, selected.name); } subdir.close(); categories.set(category, list); } else - { - const header = selected.name.substring(0, selected.name.indexOf(".js")); - const command = (await import(`../commands/${header}`)).default; - listMisc.push(header); - - if(commands.has(header)) - $.warn(`Command "${header}" already exists! Make sure to make each command uniquely identifiable across categories.`); - else - commands.set(header, command); - - $.log(`Loading Command: ${header} (Miscellaneous)`); - } + loadCommand(selected.name, listMisc); } dir.close(); @@ -191,12 +191,35 @@ export async function loadCommands(): Promise> return commands; } -function getTitleCase(name: string): string +async function loadCommand(filename: string, list: string[], category?: string) { - if(name.length < 1) - return name; - const first = name[0].toUpperCase(); - return first + name.substring(1); + if(!commands) + return $.error(`Function "loadCommand" was called without first initializing commands!`); + + const prefix = category ?? ""; + const header = filename.substring(0, filename.indexOf(".js")); + const command = (await import(`../commands/${prefix}/${header}`)).default as Command|undefined; + + if(!command) + return $.warn(`Command "${header}" has no default export which is a Command instance!`); + + command.originalCommandName = header; + list.push(header); + + if(commands.has(header)) + $.warn(`Command "${header}" already exists! Make sure to make each command uniquely identifiable across categories!`); + else + commands.set(header, command); + + for(const alias of command.aliases) + { + if(commands.has(alias)) + $.warn(`Top-level alias "${alias}" from command "${header}" already exists either as a command or alias!`); + else + commands.set(alias, command); + } + + $.log(`Loading Command: ${header} (${category ? $(category).toTitleCase() : "Miscellaneous"})`); } // The template should be built with a reductionist mentality. @@ -210,6 +233,8 @@ export default new Command({ description: "This is a template/testing command providing common functionality. Remove what you don't need, and rename/delete this file to generate a fresh command file here. This command should be automatically excluded from the help command. The \\"usage\\" parameter (string) overrides the default usage for the help command. The \\"endpoint\\" parameter (boolean) prevents further arguments from being passed. Also, as long as you keep the run function async, it'll return a promise allowing the program to automatically catch any synchronous errors. However, you'll have to do manual error handling if you go the then and catch route.", endpoint: false, usage: '', + permission: null, + aliases: [], async run($: CommonLibrary): Promise { }, @@ -218,6 +243,8 @@ export default new Command({ description: "This is a named subcommand, meaning that the key name is what determines the keyword to use. With default settings for example, \\"$test layer\\".", endpoint: false, usage: '', + permission: null, + aliases: [], async run($: CommonLibrary): Promise { } @@ -227,6 +254,7 @@ export default new Command({ description: "This is the subcommand for getting users by pinging them or copying their ID. With default settings for example, \\"$test 237359961842253835\\". The argument will be a user object and won't run if no user is found by that ID.", endpoint: false, usage: '', + permission: null, async run($: CommonLibrary): Promise { } @@ -235,6 +263,7 @@ export default new Command({ description: "This is a numeric subcommand, meaning that any type of number (excluding Infinity/NaN) will route to this command if present. With default settings for example, \\"$test -5.2\\". The argument with the number is already parsed so you can just use it without converting it.", endpoint: false, usage: '', + permission: null, async run($: CommonLibrary): Promise { } @@ -243,6 +272,7 @@ export default new Command({ description: "This is a generic subcommand, meaning that if there isn't a more specific subcommand that's called, it falls to this. With default settings for example, \\"$test reeee\\".", endpoint: false, usage: '', + permission: null, async run($: CommonLibrary): Promise { } diff --git a/src/core/lib.ts b/src/core/lib.ts index e22e354..9a1a6cb 100644 --- a/src/core/lib.ts +++ b/src/core/lib.ts @@ -1,4 +1,4 @@ -import {GenericWrapper, NumberWrapper, ArrayWrapper} from "./wrappers"; +import {GenericWrapper, NumberWrapper, StringWrapper, ArrayWrapper} from "./wrappers"; import {Client, Message, TextChannel, DMChannel, NewsChannel, Guild, User, GuildMember, Permissions} from "discord.js"; import chalk from "chalk"; import FileManager from "./storage"; @@ -11,6 +11,7 @@ export interface CommonLibrary // Wrapper Object // /** Wraps the value you enter with an object that provides extra functionality and provides common utility functions. */ (value: number): NumberWrapper; + (value: string): StringWrapper; (value: T[]): ArrayWrapper; (value: T): GenericWrapper; @@ -36,12 +37,15 @@ export interface CommonLibrary } export default function $(value: number): NumberWrapper; +export default function $(value: string): StringWrapper; export default function $(value: T[]): ArrayWrapper; export default function $(value: T): GenericWrapper; export default function $(value: any) { if(isType(value, Number)) return new NumberWrapper(value); + else if(isType(value, String)) + return new StringWrapper(value); else if(isType(value, Array)) return new ArrayWrapper(value); else @@ -311,7 +315,7 @@ export function perforate(list: T[], lengthOfEachSection: number): T[][] return sections; } -export function isType(value: any, type: Function): boolean +export function isType(value: any, type: any): boolean { if(value === undefined && type === undefined) return true; diff --git a/src/core/wrappers.ts b/src/core/wrappers.ts index 68a5dff..adbadb0 100644 --- a/src/core/wrappers.ts +++ b/src/core/wrappers.ts @@ -41,6 +41,24 @@ export class NumberWrapper extends GenericWrapper } } +export class StringWrapper extends GenericWrapper +{ + public replaceAll(before: string, after: string): string + { + let result = this.value; + + while(result.indexOf(before) !== -1) + result = result.replace(before, after); + + return result; + } + + public toTitleCase(): string + { + return this.value.replace(/([^\W_]+[^\s-]*) */g, txt => txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase()); + } +} + export class ArrayWrapper extends GenericWrapper { /** Returns a random element from this array. */ diff --git a/src/events/message.ts b/src/events/message.ts index a7eb4cd..4a20047 100644 --- a/src/events/message.ts +++ b/src/events/message.ts @@ -32,6 +32,7 @@ export default new Event<"message">({ if(!commands.has(header)) return; + if(message.channel.type === "text" && !message.channel.permissionsFor(message.client.user || "")?.has(Permissions.FLAGS.SEND_MESSAGES)) { let status; @@ -57,7 +58,7 @@ export default new Event<"message">({ { if(command.endpoint) { - if(command.subcommands || command.user || command.number || command.any) + if(command.subcommands.size > 0 || command.user || command.number || command.any) $.warn(`An endpoint cannot have subcommands! Check ${prefix}${header} again.`); isEndpoint = true; break; @@ -81,11 +82,13 @@ export default new Event<"message">({ if(!message.member) return $.warn("This command was likely called from a DM channel meaning the member object is null."); + if(!hasPermission(message.member, permLevel)) { const userPermLevel = getPermissionLevel(message.member); return message.channel.send(`You don't have access to this command! Your permission level is \`${PermissionNames[userPermLevel]}\` (${userPermLevel}), but this command requires a permission level of \`${PermissionNames[permLevel]}\` (${permLevel}).`); } + if(isEndpoint) return message.channel.send("Too many arguments!"); From 32256f50fe764811285e8b143e1fd7bc361d090a Mon Sep 17 00:00:00 2001 From: WatDuhHekBro <44940783+WatDuhHekBro@users.noreply.github.com> Date: Fri, 14 Aug 2020 12:51:27 -0500 Subject: [PATCH 026/178] Ported the scanemotes command --- src/commands/scanemotes.ts | 191 +++++++++++++++++++++++++++++++++++++ 1 file changed, 191 insertions(+) create mode 100644 src/commands/scanemotes.ts diff --git a/src/commands/scanemotes.ts b/src/commands/scanemotes.ts new file mode 100644 index 0000000..7d33969 --- /dev/null +++ b/src/commands/scanemotes.ts @@ -0,0 +1,191 @@ +import Command from '../core/command'; +import {CommonLibrary} from '../core/lib'; +import moment from "moment"; +import {Collection, TextChannel} from 'discord.js'; + +const lastUsedTimestamps: {[id: string]: number} = {}; + +export default new Command({ + description: "Scans all text channels in the current guild and returns the number of times each emoji specific to the guild has been used. Has a cooldown of 24 hours per guild.", + async run($: CommonLibrary): Promise + { + if(!$.guild) + return $.channel.send(`You must use this command on a server!`); + + // Test if the command is on cooldown. This isn't the strictest cooldown possible, because in the event that the bot crashes, the cooldown will be reset. But for all intends and purposes, it's a good enough cooldown. It's a per-server cooldown. + const startTime = Date.now(); + const cooldown = 86400000; // 24 hours + const lastUsedTimestamp = lastUsedTimestamps[$.guild.id] ?? 0; + const difference = startTime - lastUsedTimestamp; + const howLong = moment(startTime).to(lastUsedTimestamp + cooldown); + + // If it's been less than an hour since the command was last used, prevent it from executing. + if(difference < cooldown) + return $.channel.send(`This command requires a day to cooldown. You'll be able to activate this command ${howLong}.`); + else + lastUsedTimestamps[$.guild.id] = startTime; + + const stats: {[id: string]: { + name: string, + formatted: string, + users: number, + bots: number + }} = {}; + let totalUserEmoteUsage = 0; + // IMPORTANT: You MUST check if the bot actually has access to the channel in the first place. It will get the list of all channels, but that doesn't mean it has access to every channel. Without this, it'll require admin access and throw an annoying unhelpful DiscordAPIError: Missing Access otherwise. + const allTextChannelsInCurrentGuild = $.guild.channels.cache.filter(channel => channel.type === "text" && channel.viewable) as Collection; + let messagesSearched = 0; + let channelsSearched = 0; + let currentChannelName = ""; + const totalChannels = allTextChannelsInCurrentGuild.size; + const statusMessage = await $.channel.send("Gathering emotes..."); + let warnings = 0; + $.channel.startTyping(); + + // Initialize the emote stats object with every emote in the current guild. + // The goal here is to cut the need to access guild.emojis.get() which'll make it faster and easier to work with. + for(let emote of $.guild.emojis.cache.values()) + { + // If you don't include the "a" for animated emotes, it'll not only not show up, but also cause all other emotes in the same message to not show up. The emote name is self-correcting but it's better to keep the right value since it'll be used to calculate message lengths that fit. + stats[emote.id] = { + name: emote.name, + formatted: `<${emote.animated ? "a" : ""}:${emote.name}:${emote.id}>`, + users: 0, + bots: 0 + }; + }; + + const interval = setInterval(() => { + statusMessage.edit(`Searching channel \`${currentChannelName}\`... (${messagesSearched} messages scanned, ${channelsSearched}/${totalChannels} channels scanned)`); + }, 5000); + + for(const channel of allTextChannelsInCurrentGuild.values()) + { + currentChannelName = channel.name; + let selected = channel.lastMessageID ?? $.message.id; + let continueLoop = true; + + while(continueLoop) + { + // Unfortunately, any kind of .fetch call is limited to 100 items at once by Discord's API. + const messages = await channel.messages.fetch({ + limit: 100, + before: selected + }); + + if(messages.size > 0) + { + for(const msg of messages.values()) + { + // It's very important to not capture an array of matches then do \d+ on each item because emote names can have numbers in them, causing the bot to not count them correctly. + const search = //g; + const text = msg.content; + let match: RegExpExecArray|null; + + while(match = search.exec(text)) + { + const emoteID = match[1]; + + if(emoteID in stats) + { + if(msg.author.bot) + stats[emoteID].bots++; + else + { + stats[emoteID].users++; + totalUserEmoteUsage++; + } + } + } + + for(const reaction of msg.reactions.cache.values()) + { + const emoteID = reaction.emoji.id; + let continueReactionLoop = true; + let lastUserID: string|undefined; + let userReactions = 0; + let botReactions = 0; + + // An emote's ID will be null if it's a unicode emote. + if(emoteID && emoteID in stats) + { + // There is a simple count property on a reaction, but that doesn't separate users from bots. + // So instead, I'll use that property to check for inconsistencies. + while(continueReactionLoop) + { + // After logging users, it seems like the order is strictly numerical. As long as that stays consistent, this should work fine. + const users = await reaction.users.fetch({ + limit: 100, + after: lastUserID + }); + + if(users.size > 0) + { + for(const user of users.values()) + { + if(user.bot) + { + stats[emoteID].bots++; + botReactions++; + } + else + { + stats[emoteID].users++; + totalUserEmoteUsage++; + userReactions++; + } + + lastUserID = user.id; + }; + } + else + { + // Then halt the loop and send warnings of any inconsistencies. + continueReactionLoop = false; + + if(reaction.count !== userReactions + botReactions) + { + $.warn(`[Channel: ${channel.id}, Message: ${msg.id}] A reaction count of ${reaction.count} was expected but was given ${userReactions} user reactions and ${botReactions} bot reactions.`); + warnings++; + } + } + } + } + }; + + selected = msg.id; + messagesSearched++; + }; + } + else + { + continueLoop = false; + channelsSearched++; + } + } + } + + // Mark the operation as ended. + const finishTime = Date.now(); + clearInterval(interval); + statusMessage.edit(`Finished operation in ${moment.duration(finishTime - startTime).humanize()} with ${$(warnings).pluralise("inconsistenc", "ies", "y")}.`); + $.log(`Finished operation in ${finishTime - startTime} ms.`); + $.channel.stopTyping(); + + // Display stats on emote usage. + // This can work outside the loop now that it's synchronous, and now it's clearer what code is meant to execute at the end. + let sortedEmoteIDs = Object.keys(stats).sort((a, b) => stats[b].users - stats[a].users); + const lines: string[] = []; + let rank = 1; + + // It's better to send all the lines at once rather than paginate the data because it's quite a memory-intensive task to search all the messages in a server for it, and I wouldn't want to activate the command again just to get to another page. + for(const emoteID of sortedEmoteIDs) + { + const emote = stats[emoteID]; + const botInfo = emote.bots > 0 ? ` (Bots: ${emote.bots})` : ""; + lines.push(`\`#${rank++}\` ${emote.formatted} x ${emote.users} - ${((emote.users / totalUserEmoteUsage * 100) || 0).toFixed(3)}%` + botInfo); + } + + $.channel.send(lines, {split: true}).catch($.handler.bind($)); + } +}); \ No newline at end of file From 5abda092e03b39dbb12465688e9116f6802aa482 Mon Sep 17 00:00:00 2001 From: WatDuhHekBro <44940783+WatDuhHekBro@users.noreply.github.com> Date: Fri, 14 Aug 2020 13:31:05 -0500 Subject: [PATCH 027/178] Moved core/lib/perforate to ArrayWrapper/split --- src/core/lib.ts | 15 --------------- src/core/wrappers.ts | 15 +++++++++++++++ 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/core/lib.ts b/src/core/lib.ts index 9a1a6cb..85110ee 100644 --- a/src/core/lib.ts +++ b/src/core/lib.ts @@ -300,21 +300,6 @@ export function parseVars(line: string, definitions: {[key: string]: string}, in return result; } -/** - * Split up an array into a specified length. - * [1,2,3,4,5,6,7,8,9,10] split by 3 = [[1,2,3],[4,5,6],[7,8,9],[10]] - */ -export function perforate(list: T[], lengthOfEachSection: number): T[][] -{ - const sections: T[][] = []; - const amountOfSections = Math.ceil(list.length / lengthOfEachSection); - - for(let index = 0; index < amountOfSections; index++) - sections.push(list.slice(index * lengthOfEachSection, (index + 1) * lengthOfEachSection)); - - return sections; -} - export function isType(value: any, type: any): boolean { if(value === undefined && type === undefined) diff --git a/src/core/wrappers.ts b/src/core/wrappers.ts index adbadb0..fbda382 100644 --- a/src/core/wrappers.ts +++ b/src/core/wrappers.ts @@ -66,4 +66,19 @@ export class ArrayWrapper extends GenericWrapper { return this.value[Math.floor(Math.random() * this.value.length)]; } + + /** + * Splits up this array into a specified length. + * `$([1,2,3,4,5,6,7,8,9,10]).split(3)` = `[[1,2,3],[4,5,6],[7,8,9],[10]]` + */ + public split(lengthOfEachSection: number): T[][] + { + const amountOfSections = Math.ceil(this.value.length / lengthOfEachSection); + const sections: T[][] = new Array(amountOfSections); + + for(let index = 0; index < amountOfSections; index++) + sections[index] = this.value.slice(index * lengthOfEachSection, (index + 1) * lengthOfEachSection); + + return sections; + } } \ No newline at end of file From 5b3df90067b6586e7595fe49157683dfe61b842a Mon Sep 17 00:00:00 2001 From: Keanu Date: Sat, 15 Aug 2020 21:02:58 +0000 Subject: [PATCH 028/178] Moved 8ball to Fun category. --- src/commands/fun.ts | 39 +-------------------------------------- src/commands/fun/8ball.ts | 39 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 38 deletions(-) create mode 100644 src/commands/fun/8ball.ts diff --git a/src/commands/fun.ts b/src/commands/fun.ts index 35d7b51..0250179 100644 --- a/src/commands/fun.ts +++ b/src/commands/fun.ts @@ -2,49 +2,12 @@ import { MessageEmbed } from "discord.js"; import Command from "../core/command"; import {CommonLibrary} from "../core/lib"; -const responses = [ - "Most likely,", - "It is certain,", - "It is decidedly so,", - "Without a doubt,", - "Definitely,", - "You may rely on it,", - "As I see it, yes,", - "Outlook good,", - "Yes,", - "Signs point to yes,", - "Reply hazy, try again,", - "Ask again later,", - "Better not tell you now,", - "Cannot predict now,", - "Concentrate and ask again,", - "Don't count on it,", - "My reply is no,", - "My sources say no,", - "Outlook not so good,", - "Very doubtful," -]; - export default new Command({ description: "Fun commands.", endpoint: false, run: "Please provide an argument.\nFor help, run `%prefix%help fun`.", subcommands: { - "8ball": new Command({ - description: "Answers your question in an 8-ball manner.", - endpoint: false, - usage: "", - run: "Please provide a question.", - any: new Command({ - description: "Question to ask the 8 Ball.", - async run($: CommonLibrary): Promise - { - const sender = $.message.author; - $.channel.send($(responses).random() + ` <@${sender.id}>`); - } - }) - }), poll: new Command({ description: "Create a poll.", usage: "", @@ -68,4 +31,4 @@ export default new Command({ }) }) } -}); \ No newline at end of file +}); diff --git a/src/commands/fun/8ball.ts b/src/commands/fun/8ball.ts new file mode 100644 index 0000000..86a6e62 --- /dev/null +++ b/src/commands/fun/8ball.ts @@ -0,0 +1,39 @@ +import Command from "../../core/command"; +import {CommonLibrary} from "../../core/lib"; +const responses = [ + "Most likely,", + "It is certain,", + "It is decidedly so,", + "Without a doubt,", + "Definitely,", + "You may rely on it,", + "As I see it, yes,", + "Outlook good,", + "Yes,", + "Signs point to yes,", + "Reply hazy, try again,", + "Ask again later,", + "Better not tell you now,", + "Cannot predict now,", + "Concentrate and ask again,", + "Don't count on it,", + "My reply is no,", + "My sources say no,", + "Outlook not so good,", + "Very doubtful," +]; + +export default new Command({ + description: "Answers your question in an 8-ball manner.", + endpoint: false, + usage: "", + run: "Please provide a question.", + any: new Command({ + description: "Question to ask the 8-ball.", + async run($: CommonLibrary): Promise + { + const sender = $.message.author; + $.channel.send($(responses).random() + ` <@${sender.id}>`); + } + }) +}) From bd0984eb69f1c9e6c8bb98bd73cc31d12f1a4eec Mon Sep 17 00:00:00 2001 From: Keanu Date: Mon, 17 Aug 2020 17:38:15 +0000 Subject: [PATCH 029/178] Moved poll command to fun category. --- .gitignore | 3 ++- src/commands/fun/poll.ts | 26 ++++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 src/commands/fun/poll.ts diff --git a/.gitignore b/.gitignore index 2a0491a..f15e293 100644 --- a/.gitignore +++ b/.gitignore @@ -65,4 +65,5 @@ typings/ # dotenv environment variables file .env -config.json \ No newline at end of file +config.json +.vscode/ \ No newline at end of file diff --git a/src/commands/fun/poll.ts b/src/commands/fun/poll.ts new file mode 100644 index 0000000..d1f0e15 --- /dev/null +++ b/src/commands/fun/poll.ts @@ -0,0 +1,26 @@ +import { MessageEmbed } from "discord.js"; +import Command from "../../core/command"; +import {CommonLibrary} from "../../core/lib"; + +export default new Command({ + description: "Create a poll.", + usage: "", + run: "Please provide a question.", + any: new Command({ + description: "Question for the poll.", + async run($: CommonLibrary): Promise + { + const embed = new MessageEmbed() + .setAuthor(`Poll created by ${$.message.author.username}`, $.message.guild?.iconURL({ dynamic: true }) ?? undefined) + .setColor(0xffffff) + .setFooter("React to vote.") + .setDescription($.args.join(" ")); + const msg = await $.channel.send(embed); + await msg.react("✅"); + await msg.react("⛔"); + $.message.delete({ + timeout: 1000 + }); + } + }) +}) From eed1438fb2cd3f947fd874f5ee57c7fa6fd3491a Mon Sep 17 00:00:00 2001 From: Keanu Date: Tue, 18 Aug 2020 09:22:07 +0000 Subject: [PATCH 030/178] Removed fun.ts --- src/commands/fun.ts | 34 ---------------------------------- 1 file changed, 34 deletions(-) delete mode 100644 src/commands/fun.ts diff --git a/src/commands/fun.ts b/src/commands/fun.ts deleted file mode 100644 index 0250179..0000000 --- a/src/commands/fun.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { MessageEmbed } from "discord.js"; -import Command from "../core/command"; -import {CommonLibrary} from "../core/lib"; - -export default new Command({ - description: "Fun commands.", - endpoint: false, - run: "Please provide an argument.\nFor help, run `%prefix%help fun`.", - subcommands: - { - poll: new Command({ - description: "Create a poll.", - usage: "", - run: "Please provide a question.", - any: new Command({ - description: "Question for the poll.", - async run($: CommonLibrary): Promise - { - const embed = new MessageEmbed() - .setAuthor(`Poll created by ${$.message.author.username}`, $.message.guild?.iconURL({ dynamic: true }) ?? undefined) - .setColor(0xffffff) - .setFooter("React to vote.") - .setDescription($.args.join(" ")); - const msg = await $.channel.send(embed); - await msg.react("✅"); - await msg.react("⛔"); - $.message.delete({ - timeout: 1000 - }); - } - }) - }) - } -}); From 4a6754d21eb0f6df7d3749a0986cd7209129e121 Mon Sep 17 00:00:00 2001 From: Keanu Date: Thu, 20 Aug 2020 16:35:35 +0200 Subject: [PATCH 031/178] Added CodeQL. (#5) --- .github/codeql/codeql-config.yml | 6 ++++ .github/workflows/codeql-analysis.yml | 41 +++++++++++++++++++++++++++ 2 files changed, 47 insertions(+) create mode 100644 .github/codeql/codeql-config.yml create mode 100644 .github/workflows/codeql-analysis.yml diff --git a/.github/codeql/codeql-config.yml b/.github/codeql/codeql-config.yml new file mode 100644 index 0000000..e3dae69 --- /dev/null +++ b/.github/codeql/codeql-config.yml @@ -0,0 +1,6 @@ +name: "CodeQL Config" + +queries: + - uses: security-and-quality +paths: + - dist diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 0000000..f22f74b --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,41 @@ +name: "CodeQL" + +on: + push: + branches: [typescript] + pull_request: + branches: [typescript] + schedule: + - cron: '0 5 * * 1' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + with: + fetch-depth: 2 + + - run: git checkout HEAD^2 + if: ${{ github.event_name == 'pull_request' }} + + - name: Setup Node.JS + uses: actions/setup-node@v2-beta + with: + node-version: '12' + - run: npm ci + + - name: Build codebase + run: npm run build + + - name: Initialize CodeQL + uses: github/codeql-action/init@v1 + with: + config-file: ./.github/codeql/codeql-config.yml + languages: javascript + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v1 From 8ca171e924b972717b22be179ad56f99b9c824d0 Mon Sep 17 00:00:00 2001 From: WatDuhHekBro <44940783+WatDuhHekBro@users.noreply.github.com> Date: Tue, 25 Aug 2020 21:49:08 -0500 Subject: [PATCH 032/178] Added test suite and production builder --- .gitignore | 1 + docs/GettingStarted.md | 16 + docs/Specifications.md | 1 + package-lock.json | 1183 ++++++++++++++++++++++++++++++++++++++++ package.json | 11 +- src/core/lib.ts | 2 + test/wrappers.ts | 69 +++ tsconfig.json | 3 +- 8 files changed, 1281 insertions(+), 5 deletions(-) create mode 100644 docs/GettingStarted.md create mode 100644 test/wrappers.ts diff --git a/.gitignore b/.gitignore index f15e293..2f75f8b 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ dist/ data/ tmp/ test* +!test/ *.bat desktop.ini diff --git a/docs/GettingStarted.md b/docs/GettingStarted.md new file mode 100644 index 0000000..3bfb7e1 --- /dev/null +++ b/docs/GettingStarted.md @@ -0,0 +1,16 @@ +# Getting Started +1. `npm install` +2. `npm run build` +3. `npm start` + +# Getting Started (Developers) +1. `npm install` +2. `npm run dev` +3. Familiarize yourself with the [project's structure](Specifications.md). +4. Make sure to avoid using `npm run build`! This will remove all your dev dependencies (in order to reduce space used). Instead, use `npm run once` to compile and build in non-dev mode. +5. Begin developing. + +## Don't forget to... +- ...update the [changelog](CHANGELOG.md) and any other necessary docs. +- ...update the version numbers in `package.json` and `package-lock.json`. +- ...make sure the test suite passes by running `npm test`. \ No newline at end of file diff --git a/docs/Specifications.md b/docs/Specifications.md index 5ba8043..ffbc451 100644 --- a/docs/Specifications.md +++ b/docs/Specifications.md @@ -9,6 +9,7 @@ The top-level directory is reserved for files that have to be there for it to wo - `.ts`: All commands at this level will have the `Miscellaneous` category. - `events`: Here's the place to store events. The file name determines the event type. - `dist`: This is where the runnable code in `src` compiles to. (The directory structure mirrors `src`.) +- `test`: Used for all the unit tests. - `data`: Holds all the dynamic data used by the bot. This is what you modify if you want to change stuff for just your instance of the bot. - `standard`: Contains all the standard data to be used with the project itself. It's part of the code and will not be checked for inaccuracies because it's not meant to be easily modified. - `docs`: Used for information about the design of the project. diff --git a/package-lock.json b/package-lock.json index 1f86627..64cb614 100644 --- a/package-lock.json +++ b/package-lock.json @@ -34,6 +34,12 @@ "rxjs": "^6.4.0" } }, + "@types/mocha": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-8.0.3.tgz", + "integrity": "sha512-vyxR57nv8NfcU0GZu8EUXZLTbCMupIUwy95LJ6lllN+JRPG25CwMHoB1q5xKh8YKhQnHYRAn4yW2yuHbf/5xgg==", + "dev": true + }, "@types/node": { "version": "14.0.22", "resolved": "https://registry.npmjs.org/@types/node/-/node-14.0.22.tgz", @@ -66,6 +72,12 @@ "event-target-shim": "^5.0.0" } }, + "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-escapes": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.1.tgz", @@ -88,11 +100,97 @@ "color-convert": "^2.0.1" } }, + "anymatch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", + "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "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" + } + }, + "array.prototype.map": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array.prototype.map/-/array.prototype.map-1.0.2.tgz", + "integrity": "sha512-Az3OYxgsa1g7xDYp86l0nnN4bcmuEITGe1rbdEBVkrqkzMgDcbdQ2R7r41pNzti+4NMces3H8gMmuioZUilLgw==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1", + "es-array-method-boxes-properly": "^1.0.0", + "is-string": "^1.0.4" + } + }, "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "binary-extensions": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz", + "integrity": "sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==", + "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" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, "chalk": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", @@ -107,6 +205,22 @@ "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==" }, + "chokidar": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.2.tgz", + "integrity": "sha512-IZHaDeBeI+sZJRX7lGcXsdzgvZqKv6sECqsbErJA4mHWfpRrD8B97kSFN4cQz6nGBGiuFia1MKR4d6c1o8Cv7A==", + "dev": true, + "requires": { + "anymatch": "~3.1.1", + "braces": "~3.0.2", + "fsevents": "~2.1.2", + "glob-parent": "~5.1.0", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.4.0" + } + }, "cli-cursor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", @@ -120,6 +234,57 @@ "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==" }, + "cliui": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "dev": true, + "requires": { + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, "color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -141,6 +306,12 @@ "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", @@ -152,11 +323,41 @@ "which": "^2.0.1" } }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true + }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dev": true, + "requires": { + "object-keys": "^1.0.12" + } + }, "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" }, + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true + }, "discord.js": { "version": "12.2.0", "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-12.2.0.tgz", @@ -195,11 +396,68 @@ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, + "es-abstract": { + "version": "1.17.6", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz", + "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.0", + "is-regex": "^1.1.0", + "object-inspect": "^1.7.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + }, + "es-array-method-boxes-properly": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz", + "integrity": "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==", + "dev": true + }, + "es-get-iterator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.0.tgz", + "integrity": "sha512-UfrmHuWQlNMTs35e1ypnvikg6jCz3SK8v8ImvmDsh36fCVUR1MqoFDiyn0/k52C8NqO3YsO8Oe0azeesNuqSsQ==", + "dev": true, + "requires": { + "es-abstract": "^1.17.4", + "has-symbols": "^1.0.1", + "is-arguments": "^1.0.4", + "is-map": "^2.0.1", + "is-set": "^2.0.1", + "is-string": "^1.0.5", + "isarray": "^2.0.5" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, "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=" }, + "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 + }, "event-stream": { "version": "3.3.4", "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz", @@ -238,17 +496,120 @@ "escape-string-regexp": "^1.0.5" } }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, + "flat": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/flat/-/flat-4.1.0.tgz", + "integrity": "sha512-Px/TiLIznH7gEDlPXcUD4KnBusa6kR6ayRUVcnEAbreRIuhkqow/mun59BuRXwoYk7ZQOLW1ZM05ilIvK38hFw==", + "dev": true, + "requires": { + "is-buffer": "~2.0.3" + } + }, "from": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz", "integrity": "sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4=", "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 + }, + "fsevents": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", + "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", + "dev": true, + "optional": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true + }, + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "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.1", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", + "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "dev": true + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" }, + "has-symbols": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", + "dev": true + }, + "he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true + }, "iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -257,6 +618,22 @@ "safer-buffer": ">= 2.1.2 < 3" } }, + "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 + }, "inquirer": { "version": "7.3.1", "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.3.1.tgz", @@ -277,22 +654,174 @@ "through": "^2.3.6" } }, + "is-arguments": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.0.4.tgz", + "integrity": "sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA==", + "dev": true + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-buffer": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz", + "integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A==", + "dev": true + }, + "is-callable": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.0.tgz", + "integrity": "sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw==", + "dev": true + }, + "is-date-object": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", + "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==", + "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==" }, + "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" + } + }, + "is-map": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.1.tgz", + "integrity": "sha512-T/S49scO8plUiAOA2DBTBG3JHpn1yiw0kRp6dgiZ0v2/6twi5eiB0rHtHFH9ZIrvlWc6+4O+m4zg5+Z833aXgw==", + "dev": true + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=", + "dev": true + }, + "is-regex": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", + "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", + "dev": true, + "requires": { + "has-symbols": "^1.0.1" + } + }, + "is-set": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.1.tgz", + "integrity": "sha512-eJEzOtVyenDs1TMzSQ3kU3K+E0GUS9sno+F0OBT97xsgcJsF9nXMBtkT9/kut5JEpM7oL7X/0qxR17K3mcwIAA==", + "dev": true + }, + "is-string": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.5.tgz", + "integrity": "sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==", + "dev": true + }, + "is-symbol": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", + "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", + "dev": true, + "requires": { + "has-symbols": "^1.0.1" + } + }, + "isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + }, "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", "dev": true }, + "iterate-iterator": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/iterate-iterator/-/iterate-iterator-1.0.1.tgz", + "integrity": "sha512-3Q6tudGN05kbkDQDI4CqjaBf4qf85w6W6GnuZDtUVYwKgtC1q8yxYX7CZed7N+tLzQqS6roujWvszf13T+n9aw==", + "dev": true + }, + "iterate-value": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/iterate-value/-/iterate-value-1.0.2.tgz", + "integrity": "sha512-A6fMAio4D2ot2r/TYzr4yUWrmwNdsN5xL7+HUiyACE4DXm+q8HtPcnFTp+NnW3k4N05tZ7FVYFFb2CR13NxyHQ==", + "dev": true, + "requires": { + "es-get-iterator": "^1.0.2", + "iterate-iterator": "^1.0.1" + } + }, + "js-yaml": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz", + "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "requires": { + "p-locate": "^5.0.0" + } + }, "lodash": { "version": "4.17.19", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz", "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==" }, + "log-symbols": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.0.0.tgz", + "integrity": "sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA==", + "dev": true, + "requires": { + "chalk": "^4.0.0" + } + }, + "make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, "map-stream": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz", @@ -317,11 +846,67 @@ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==" }, + "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" + } + }, + "mocha": { + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-8.1.2.tgz", + "integrity": "sha512-I8FRAcuACNMLQn3lS4qeWLxXqLvGf6r2CaLstDpZmMUUSmvW6Cnm1AuHxgbc7ctZVRcfwspCRbDHymPsi3dkJw==", + "dev": true, + "requires": { + "ansi-colors": "4.1.1", + "browser-stdout": "1.3.1", + "chokidar": "3.4.2", + "debug": "4.1.1", + "diff": "4.0.2", + "escape-string-regexp": "4.0.0", + "find-up": "5.0.0", + "glob": "7.1.6", + "growl": "1.10.5", + "he": "1.2.0", + "js-yaml": "3.14.0", + "log-symbols": "4.0.0", + "minimatch": "3.0.4", + "ms": "2.1.2", + "object.assign": "4.1.0", + "promise.allsettled": "1.0.2", + "serialize-javascript": "4.0.0", + "strip-json-comments": "3.0.1", + "supports-color": "7.1.0", + "which": "2.0.2", + "wide-align": "1.1.3", + "workerpool": "6.0.0", + "yargs": "13.3.2", + "yargs-parser": "13.1.2", + "yargs-unparser": "1.6.1" + }, + "dependencies": { + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + } + } + }, "moment": { "version": "2.27.0", "resolved": "https://registry.npmjs.org/moment/-/moment-2.27.0.tgz", "integrity": "sha512-al0MUK7cpIcglMv3YF13qSgdAIqxHTO7brRtaz3DlSULbqfazqkc5kEjNrLDOM7fsjshoFIihnU8snrP7zUvhQ==" }, + "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 + }, "mute-stream": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", @@ -338,6 +923,45 @@ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz", "integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==" }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "object-inspect": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz", + "integrity": "sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==", + "dev": true + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true + }, + "object.assign": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", + "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.0", + "object-keys": "^1.0.11" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, "onetime": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.0.tgz", @@ -351,6 +975,42 @@ "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" }, + "p-limit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.0.2.tgz", + "integrity": "sha512-iwqZSOoWIW+Ew4kAGUlN16J4M7OB3ysMLSZtnhmqx7njIHFPlxWBX8xo3lVTyFVq6mI/lL9qt2IsN1sHwaxJkg==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "requires": { + "p-limit": "^3.0.2" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "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", @@ -366,6 +1026,25 @@ "through": "~2.3" } }, + "picomatch": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", + "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", + "dev": true + }, + "promise.allsettled": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/promise.allsettled/-/promise.allsettled-1.0.2.tgz", + "integrity": "sha512-UpcYW5S1RaNKT6pd+s9jp9K9rlQge1UXKskec0j6Mmuq7UJCvlS2J2/s/yuPN8ehftf9HXMxWlKiPbGGUzpoRg==", + "dev": true, + "requires": { + "array.prototype.map": "^1.0.1", + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1", + "function-bind": "^1.1.1", + "iterate-value": "^1.0.0" + } + }, "ps-tree": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/ps-tree/-/ps-tree-1.2.0.tgz", @@ -375,6 +1054,36 @@ "event-stream": "=3.3.4" } }, + "randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.0" + } + }, + "readdirp": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.4.0.tgz", + "integrity": "sha512-0xe001vZBnJEK+uKcj8qOhyAKPzIT+gStxWr3LCB0DwcXR5NZJ3IaC+yGnHCYzB/S7ov3m3EEbZI2zeNvX+hGQ==", + "dev": true, + "requires": { + "picomatch": "^2.2.1" + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true + }, + "require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true + }, "restore-cursor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", @@ -397,11 +1106,32 @@ "tslib": "^1.9.0" } }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + }, "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, + "serialize-javascript": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", + "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==", + "dev": true, + "requires": { + "randombytes": "^2.1.0" + } + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true + }, "setimmediate": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", @@ -427,6 +1157,22 @@ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==" }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "source-map-support": { + "version": "0.5.19", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", + "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, "split": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/split/-/split-0.3.3.tgz", @@ -436,6 +1182,12 @@ "through": "2" } }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, "stream-combiner": { "version": "0.0.4", "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz", @@ -461,6 +1213,26 @@ "strip-ansi": "^6.0.0" } }, + "string.prototype.trimend": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz", + "integrity": "sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "string.prototype.trimstart": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz", + "integrity": "sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, "strip-ansi": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", @@ -469,6 +1241,12 @@ "ansi-regex": "^5.0.0" } }, + "strip-json-comments": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.0.1.tgz", + "integrity": "sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw==", + "dev": true + }, "supports-color": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", @@ -490,6 +1268,28 @@ "os-tmpdir": "~1.0.2" } }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "ts-node": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-9.0.0.tgz", + "integrity": "sha512-/TqB4SnererCDR/vb4S/QvSZvzQMJN8daAslg7MeaiHvD8rDZsSfXmNeNumyZZzMned72Xoq/isQljYSt8Ynfg==", + "dev": true, + "requires": { + "arg": "^4.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "source-map-support": "^0.5.17", + "yn": "3.1.1" + } + }, "tsc-watch": { "version": "4.2.9", "resolved": "https://registry.npmjs.org/tsc-watch/-/tsc-watch-4.2.9.tgz", @@ -532,6 +1332,389 @@ "requires": { "isexe": "^2.0.0" } + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true + }, + "wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "dev": true, + "requires": { + "string-width": "^1.0.2 || 2" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "workerpool": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.0.0.tgz", + "integrity": "sha512-fU2OcNA/GVAJLLyKUoHkAgIhKb0JoCpSjLC/G2vYKxUjVmQwGbRVeoPJ1a8U4pnVofz4AQV5Y/NEw8oKqxEBtA==", + "dev": true + }, + "wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "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" + } + }, + "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 + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "y18n": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", + "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", + "dev": true + }, + "yargs": { + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", + "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", + "dev": true, + "requires": { + "cliui": "^5.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.1.2" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "yargs-parser": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", + "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + }, + "yargs-unparser": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.6.1.tgz", + "integrity": "sha512-qZV14lK9MWsGCmcr7u5oXGH0dbGqZAIxTDrWXZDo5zUr6b6iUmelNKO6x6R1dQT24AH3LgRxJpr8meWy2unolA==", + "dev": true, + "requires": { + "camelcase": "^5.3.1", + "decamelize": "^1.2.0", + "flat": "^4.1.0", + "is-plain-obj": "^1.1.0", + "yargs": "^14.2.3" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + }, + "yargs": { + "version": "14.2.3", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-14.2.3.tgz", + "integrity": "sha512-ZbotRWhF+lkjijC/VhmOT9wSgyBQ7+zr13+YLkhfsSiTriYsMzkTUFP18pFhWwBeMa5gUc1MzbhrO6/VB7c9Xg==", + "dev": true, + "requires": { + "cliui": "^5.0.0", + "decamelize": "^1.2.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^15.0.1" + } + }, + "yargs-parser": { + "version": "15.0.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-15.0.1.tgz", + "integrity": "sha512-0OAMV2mAZQrs3FkNpDQcBk1x5HXb8X4twADss4S0Iuk+2dGnLOE/fRHrsYm542GduMveyA77OF4wrNJuanRCWw==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } + } + }, + "yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true } } } diff --git a/package.json b/package.json index c8dedf0..2c85b2a 100644 --- a/package.json +++ b/package.json @@ -11,17 +11,20 @@ }, "devDependencies": { "@types/inquirer": "^6.5.0", + "@types/mocha": "^8.0.3", "@types/node": "^14.0.22", "@types/ws": "^7.2.6", + "mocha": "^8.1.2", + "ts-node": "^9.0.0", "tsc-watch": "^4.2.9", "typescript": "^3.9.6" }, "scripts": { - "build": "tsc", - "watch": "tsc --watch", - "autobuild": "tsc && npm start", + "build": "tsc && npm prune --production", "start": "node dist/index.js", - "dev": "tsc-watch --onSuccess \"node dist/index.js dev\"" + "once": "tsc && npm start", + "dev": "tsc-watch --onSuccess \"node dist/index.js dev\"", + "test": "mocha --require ts-node/register --extension ts --recursive" }, "keywords": [ "discord.js", diff --git a/src/core/lib.ts b/src/core/lib.ts index 85110ee..a92ff9a 100644 --- a/src/core/lib.ts +++ b/src/core/lib.ts @@ -105,6 +105,8 @@ $.error = (...args: any[]) => { logs.verbose += text; }; // Be as verbose as possible. If anything might help when debugging an error, then include it. This only shows in your console if you run this with "dev", but you can still get it from "logs.verbose". +// $.debug(`core/lib::parseArgs("testing \"in progress\"") = ["testing", "in progress"]`) --> /::(.)() = +// Would probably be more suited for debugging program logic rather than function logic, which can be checked using unit tests. $.debug = (...args: any[]) => { if(process.argv[2] === "dev" && enabled) console.debug(chalk.white.bgGray(formatTimestamp()), chalk.white.bgBlue("DEBUG"), ...args); diff --git a/test/wrappers.ts b/test/wrappers.ts new file mode 100644 index 0000000..260c229 --- /dev/null +++ b/test/wrappers.ts @@ -0,0 +1,69 @@ +import {strict as assert} from "assert"; +import {NumberWrapper, StringWrapper, ArrayWrapper} from "../src/core/wrappers"; + +// I can't figure out a way to run the test suite while running the bot. +describe("Wrappers", () => { + describe("NumberWrapper", () => { + describe("#pluralise()", () => { + it('should return "5 credits"', () => { + assert.equal(new NumberWrapper(5).pluralise("credit", "s"), "5 credits"); + }) + + it('should return "1 credit"', () => { + assert.equal(new NumberWrapper(1).pluralise("credit", "s"), "1 credit"); + }) + + it('should return "-1 credits"', () => { + assert.equal(new NumberWrapper(-1).pluralise("credit", "s"), "-1 credits"); + }) + + it('should be able to work with a plural suffix', () => { + assert.equal(new NumberWrapper(2).pluralise("part", "ies", "y"), "2 parties"); + }) + + it('should be able to work with a singular suffix', () => { + assert.equal(new NumberWrapper(1).pluralise("part", "ies", "y"), "1 party"); + }) + + it('should be able to exclude the number', () => { + assert.equal(new NumberWrapper(1).pluralise("credit", "s", "", true), "credit"); + }) + }) + + describe("#pluraliseSigned()", () => { + it('should return "-1 credits"', () => { + assert.equal(new NumberWrapper(-1).pluraliseSigned("credit", "s"), "-1 credits"); + }) + + it('should return "+0 credits"', () => { + assert.equal(new NumberWrapper(0).pluraliseSigned("credit", "s"), "+0 credits"); + }) + + it('should return "+1 credit"', () => { + assert.equal(new NumberWrapper(1).pluraliseSigned("credit", "s"), "+1 credit"); + }) + }) + }) + + describe("StringWrapper", () => { + describe("#replaceAll()", () => { + it('should convert "test" to "zesz"', () => { + assert.equal(new StringWrapper("test").replaceAll('t', 'z'), "zesz"); + }) + }) + + describe("#toTitleCase()", () => { + it('should capitalize the first letter of each word', () => { + assert.equal(new StringWrapper("yeetus deletus find salvation from jesus").toTitleCase(), "Yeetus Deletus Find Salvation From Jesus"); + }) + }) + }) + + describe("ArrayWrapper", () => { + describe("#split()", () => { + it('should split [1,2,3,4,5,6,7,8,9,10] into [[1,2,3],[4,5,6],[7,8,9],[10]]', () => { + assert.deepEqual(new ArrayWrapper([1,2,3,4,5,6,7,8,9,10]).split(3), [[1,2,3],[4,5,6],[7,8,9],[10]]); + }) + }) + }) +}) \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 8141b22..92f72c4 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -13,5 +13,6 @@ "strictFunctionTypes": true, "strictPropertyInitialization": true, "removeComments": true - } + }, + "exclude": ["test"] } \ No newline at end of file From 3767a108291cf639c8948e534398ce92565a48ca Mon Sep 17 00:00:00 2001 From: WatDuhHekBro <44940783+WatDuhHekBro@users.noreply.github.com> Date: Thu, 27 Aug 2020 22:46:40 -0500 Subject: [PATCH 033/178] Modularized finding members by their username --- src/commands/money.ts | 16 +++------------- src/core/lib.ts | 27 +++++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 13 deletions(-) diff --git a/src/commands/money.ts b/src/commands/money.ts index 332642b..23b55b7 100644 --- a/src/commands/money.ts +++ b/src/commands/money.ts @@ -223,19 +223,9 @@ export default new Command({ description: "See how much money someone else has by using their username.", async run($: CommonLibrary): Promise { - if($.guild) - { - const username = $.args.join(" "); - const member = (await $.guild.members.fetch({ - query: username, - limit: 1 - })).first(); - - if(member) - $.channel.send(getMoneyEmbed(member.user)); - else - $.channel.send(`Couldn't find a user by the name of \`${username}\`!`); - } + $.callMemberByUsername($.message, $.args.join(" "), member => { + $.channel.send(getMoneyEmbed(member.user)); + }); } }) }); \ No newline at end of file diff --git a/src/core/lib.ts b/src/core/lib.ts index a92ff9a..77fa0ab 100644 --- a/src/core/lib.ts +++ b/src/core/lib.ts @@ -25,6 +25,8 @@ export interface CommonLibrary ready: (...args: any[]) => void; paginate: (message: Message, senderID: string, total: number, callback: (page: number) => void, duration?: number) => void; prompt: (message: Message, senderID: string, onConfirm: () => void, duration?: number) => void; + getMemberByUsername: (guild: Guild, username: string) => Promise; + callMemberByUsername: (message: Message, username: string, onSuccess: (member: GuildMember) => void) => Promise; // Dynamic Properties // args: any[]; @@ -216,6 +218,31 @@ $.prompt = async(message: Message, senderID: string, onConfirm: () => void, dura message.delete(); }; +$.getMemberByUsername = async(guild: Guild, username: string) => { + return (await guild.members.fetch({ + query: username, + limit: 1 + })).first(); +}; + +/** Convenience function to handle false cases automatically. */ +$.callMemberByUsername = async(message: Message, username: string, onSuccess: (member: GuildMember) => void) => { + const guild = message.guild; + const send = message.channel.send; + + if(guild) + { + const member = await $.getMemberByUsername(guild, username); + + if(member) + onSuccess(member); + else + send(`Couldn't find a user by the name of \`${username}\`!`); + } + else + send("You must execute this command in a server!"); +}; + /** * Splits a command by spaces while accounting for quotes which capture string arguments. * - `\"` = `"` From b3209d1cf19aa429b85c62ecc4bf7eb526f1f8b5 Mon Sep 17 00:00:00 2001 From: WatDuhHekBro <44940783+WatDuhHekBro@users.noreply.github.com> Date: Sun, 30 Aug 2020 16:26:18 -0500 Subject: [PATCH 034/178] Added documentation + misc patches --- docs/Documentation.md | 234 ++++++++++++++++++++++++++++ docs/GettingStarted.md | 2 +- src/commands/admin.ts | 3 +- src/core/command.ts | 4 +- src/core/lib.ts | 7 +- src/core/permissions.ts | 12 +- src/events/messageReactionRemove.ts | 2 +- src/index.ts | 9 +- 8 files changed, 253 insertions(+), 20 deletions(-) create mode 100644 docs/Documentation.md diff --git a/docs/Documentation.md b/docs/Documentation.md new file mode 100644 index 0000000..bcfed75 --- /dev/null +++ b/docs/Documentation.md @@ -0,0 +1,234 @@ +# What this is +This is a user-friendly version of the project's structure (compared to the amalgamation that has become [specifications](Specifications.md) which is just a list of design decisions and isn't actually helpful at all for understanding the code). This will follow the line of logic that the program would run through. + +# Building/Setup +- `npm run dev` runs the TypeScript compiler in watch mode, meaning that any changes you make to the code will automatically reload the bot. +- This will take all the files in `src` (where all the code is) and compile it into `dist` which is what the program actually uses. + - If there's a runtime error, `dist\commands\test.js:25:30` for example, then you have to into `dist` instead of `src`, then find the line that corresponds. + +# Launching +When you start the program, it'll run the code in `index` (meaning both `src/index.ts` and `dist/index.js`, they're the same except that `dist/<...>.js` is compiled). The code in `index` will call `setup` and check if `data/config.json` exists, prompting you if it doesn't. It'll then run initialization code. + +# Structure +- `commands` contains all the commands. +- `defs` contains static definitions. +- `core` contains the foundation of the program. You won't need to worry about this unless you're modifying pre-existing behavior of the `Command` class for example or add a function to the library. +- `events` contains all the events. Again, you generally won't need to touch this unless you're listening for a new Discord event. + +# The Command Class +A valid command file must be located in `commands` and export a default `Command` instance. Assume that we're working with `commands/money.ts`. +```js +import Command from '../core/command'; + +export default new Command({ + //... +}); +``` +The `run` property can be either a function or a string. If it's a function, you get one parameter, `$` which represents the common library (see below). If it's a string, it's a variable string. +- `%author%` pings the person who sent the message. +- `%prefix%` gets the bot's current prefix in the selected server. +```js +import Command from '../core/command'; +import {CommonLibrary} from '../core/lib'; + +export default new Command({ + run: "%author%, make sure to use the prefix! (%prefix)" +}); +``` +...is equal to... +```js +import Command from '../core/command'; +import {CommonLibrary} from '../core/lib'; +import {getPrefix} from "../core/structures"; + +export default new Command({ + async run($: CommonLibrary): Promise { + $.channel.send(`${$.author.toString()}, make sure to use the prefix! (${getPrefix($.guild)})`); + } +}); +``` +Now here's where it gets fun. The `Command` class is a recursive structure, containing other `Command` instances as properties. +- `subcommands` is used for specific keywords for accessing a certain command. For example, `$eco pay` has a subcommand of `pay`. +```js +import Command from '../core/command'; +import {CommonLibrary} from '../core/lib'; + +export default new Command({ + subcommands: { + pay: new Command({ + //... + }) + } +}); +``` +There's also `user` which listens for a ping or a Discord ID, `<@237359961842253835>` and `237359961842253835` respectively. The argument will be a `User` object. +```js +import Command from '../core/command'; +import {CommonLibrary} from '../core/lib'; + +export default new Command({ + user: new Command({ + async run($: CommonLibrary): Promise { + $.debug($.args[0].username); // "WatDuhHekBro" + } + }) +}); +``` +There's also `number` which checks for any number type except `Infinity`, converting the argument to a `number` type. +```js +import Command from '../core/command'; +import {CommonLibrary} from '../core/lib'; + +export default new Command({ + number: new Command({ + async run($: CommonLibrary): Promise { + $.debug($.args[0] + 5); + } + }) +}); +``` +And then there's `any` which catches everything else that doesn't fall into the above categories. The argument will be a `string`. +```js +import Command from '../core/command'; +import {CommonLibrary} from '../core/lib'; + +export default new Command({ + any: new Command({ + async run($: CommonLibrary): Promise { + $.debug($.args[0].toUpperCase()); + } + }) +}); +``` +Of course, maybe you just want to get string arguments regardless, and since everything is an optional property, so you'd then just include `any` and not `subcommands`, `user`, or `number`. + +## Other Properties +- `description`: The description for that specific command. +- `endpoint`: A `boolean` determining whether or not to prevent any further arguments. For example, you could prevent `$money daily too many arguments`. +- `usage`: Provide a custom usage for the help menu. Do note that this is relative to the subcommand, so the below will result in `$money pay `. +```js +import Command from '../core/command'; +import {CommonLibrary} from '../core/lib'; + +export default new Command({ + subcommands: { + pay: new Command({ + usage: " " + }) + } +}); +``` +- `permission`: The permission to restrict the current command to. You can specify it for certain subcommands, useful for having `$money` be open to anyone but not `$money admin`. If it's `null` (default), the permission will inherit whatever was declared before (if any). The default value is NOT the same as `Command.PERMISSIONS.NONE`. +- `aliases`: A list of aliases (if any). + +## Alternatives to Nesting +For a lot of the metadata properties like `description`, you must provide them when creating a new `Command` instance. However, you can freely modify and attach subcommands, useful for splitting a command into multiple files. +```js +import pay from "./subcommands/pay"; + +const cmd = new Command({ + description: "Handle your money." +}); +cmd.subcommands.set("pay", pay); +cmd.run = async($: CommonLibrary): Promise { + $.debug($.args); +}; +cmd.any = new Command({ + //... +}); + +export default cmd; +``` + +## Error Handling +Any errors caused when using `await` or just regular synchronous functions will be automatically caught, you don't need to worry about those. However, promises must be caught manually. For example, `$.channel.send("")` will throw an error because you can't send empty messages to Discord, but since it's a promise, it'll just fade without throwing an error. There are two ways to do this: +- `$.channel.send("").catch($.handler.bind($))` +- `$.channel.send("").catch(error => $.handler(error))` + +# The Common Library +This is the container of functions available without having to import `core/lib`, usually as `$`. When accessing this from a command's `run` function, it'll also come with shortcuts to other properties. + +## Custom Wrappers +- `$(5)` = `new NumberWrapper(5)` +- `$("text")` = `new StringWrapper("text")` +- `$([1,2,3])` = `new ArrayWrapper([1,2,3])` + +## Custom Logger +- `$.log(...)` +- `$.warn(...)` +- `$.error(...)` +- `$.debug(...)` +- `$.ready(...)` (only meant to be used once at the start of the program) + +## Convenience Functions +This modularizes certain patterns of code to make things easier. +- `$.paginate()`: Takes a message and some additional parameters and makes a reaction page with it. All the pagination logic is taken care of but nothing more, the page index is returned and you have to send a callback to do something with it. +```js +const pages = ["one", "two", "three"]; +const msg = await $.channel.send(pages[0]); + +$.paginate(msg, $.author.id, pages.length, page => { + msg.edit(pages[page]); +}); +``` +- `$.prompt()`: Prompts the user about a decision before following through. +```js +const msg = await $.channel.send("Are you sure you want to delete this?"); + +$.prompt(msg, $.author.id, () => { + delete this; // Replace this with actual code. +}); +``` +- `$.getMemberByUsername()`: Gets a user by their username. Gets the first one then rolls with it. +- `$.callMemberByUsername()`: Convenience function to handle cases where someone isn't found by a username automatically. +```js +$.callMemberByUsername($.message, $.args.join(" "), member => { + $.channel.send(`Your nickname is ${member.nickname}.`); +}); +``` + +## Dynamic Properties +These will be accessible only inside a `Command` and will change per message. +- `$.args`: A list of arguments in the command. It's relative to the subcommand, so if you do `$test this 5`, `5` becomes `$.args[0]` if `this` is a subcommand. Args are already converted, so a `number` subcommand would return a number rather than a string. +- `$.client`: `message.client` +- `$.message`: `message` +- `$.channel`: `message.channel` +- `$.guild`: `message.guild` +- `$.author`: `message.author` +- `$.member`: `message.member` + +# Wrappers +This is similar to modifying a primitive object's `prototype` without actually doing so. + +## NumberWrapper +- `.pluralise()`: A substitute for not having to do `amount === 1 ? "singular" : "plural"`. For example, `$(x).pluralise("credit", "s")` will return `"1 credit"` and/or `"5 credits"` respectively. +- `.pluraliseSigned()`: This builds on `.pluralise()` and adds a sign at the beginning for marking changes/differences. `$(0).pluraliseSigned("credit", "s")` will return `"+0 credits"`. + +## StringWrapper +- `.replaceAll()`: A non-regex alternative to replacing everything in a string. `$("test").replaceAll('t', 'z')` = `"zesz"`. +- `.toTitleCase()`: Capitalizes the first letter of each word. `$("this is some text").toTitleCase()` = `"This Is Some Text"`. + +## ArrayWrapper +- `.random()`: Returns a random element from an array. `$([1,2,3]).random()` could be any one of those elements. +- `.split()`: Splits an array into different arrays by a specified length. `$([1,2,3,4,5,6,7,8,9,10]).split(3)` = `[[1,2,3],[4,5,6],[7,8,9],[10]]`. + +# Other Library Functions +These do have to be manually imported, which are used more on a case-by-case basis. +- `formatTimestamp()`: Formats a `Date` object into your system's time. `YYYY-MM-DD HH:MM:SS` +- `formatUTCTimestamp()`: Formats a `Date` object into UTC time. `YYYY-MM-DD HH:MM:SS` +- `botHasPermission()`: Tests if a bot has a certain permission in a specified guild. +- `parseArgs()`: Turns `call test "args with spaces" "even more spaces"` into `["call", "test", "args with spaces", "even more spaces"]`, inspired by the command line. +- `parseVars()`: Replaces all `%` args in a string with stuff you specify. For example, you can replace all `nop` with `asm`, and `register %nop%` will turn into `register asm`. Useful for storing strings with variables in one place them accessing them in another place. +- `isType()`: Used for type-checking. Useful for testing `any` types. +- `select()`: Checks if a variable matches a certain type and uses the fallback value if not. (Warning: Type checking is based on the fallback's type. Be sure that the "type" parameter is accurate to this!) +- `Random`: An object of functions containing stuff related to randomness. `Random.num` is a random decimal, `Random.int` is a random integer, `Random.chance` takes a number ranging from `0` to `1` as a percentage. `Random.sign` takes a number and has a 50-50 chance to be negative or positive. `Random.deviation` takes a number and a magnitude and produces a random number within those confines. `(5, 2)` would produce any number between `3` and `7`. + +# Other Core Functions +- `permissions::hasPermission()`: Checks if a `Member` has a certain permission. +- `permissions::getPermissionLevel()`: Gets a `Member`'s permission level according to the permissions enum defined in the file. +- `structures::getPrefix()`: Get the current prefix of the guild or the bot's prefix if none is found. + +# The other core files +- `core/permissions`: Contains all the permission roles and checking functions. +- `core/structures`: Contains all the code handling dynamic JSON data. Has a one-to-one connection with each file generated, for example, `Config` which calls `super("config")` meaning it writes to `data/config.json`. +- `core/storage`: Handles most of the file system operations, all of the ones related to `data` at least. \ No newline at end of file diff --git a/docs/GettingStarted.md b/docs/GettingStarted.md index 3bfb7e1..04d3396 100644 --- a/docs/GettingStarted.md +++ b/docs/GettingStarted.md @@ -6,7 +6,7 @@ # Getting Started (Developers) 1. `npm install` 2. `npm run dev` -3. Familiarize yourself with the [project's structure](Specifications.md). +3. Familiarize yourself with the [project's structure](Documentation.md). 4. Make sure to avoid using `npm run build`! This will remove all your dev dependencies (in order to reduce space used). Instead, use `npm run once` to compile and build in non-dev mode. 5. Begin developing. diff --git a/src/commands/admin.ts b/src/commands/admin.ts index fc9a15c..651704a 100644 --- a/src/commands/admin.ts +++ b/src/commands/admin.ts @@ -1,8 +1,7 @@ import Command from "../core/command"; -import {CommonLibrary, logs} from "../core/lib"; +import {CommonLibrary, logs, botHasPermission} from "../core/lib"; import {Config, Storage} from "../core/structures"; import {PermissionNames, getPermissionLevel} from "../core/permissions"; -import {botHasPermission} from "../index"; import {Permissions} from "discord.js"; function getLogBuffer(type: string) diff --git a/src/core/command.ts b/src/core/command.ts index d6aabeb..b495154 100644 --- a/src/core/command.ts +++ b/src/core/command.ts @@ -12,7 +12,7 @@ interface CommandOptions usage?: string; permission?: PERMISSIONS|null; aliases?: string[]; - run?: Function|string; + run?: (($: CommonLibrary) => Promise)|string; subcommands?: {[key: string]: Command}; user?: Command; number?: Command; @@ -29,7 +29,7 @@ export default class Command public readonly permission: PERMISSIONS|null; public readonly aliases: string[]; // This is to keep the array intact for parent Command instances to use. It'll also be used when loading top-level aliases. public originalCommandName: string|null; // If the command is an alias, what's the original name? - private run: Function|string; + public run: (($: CommonLibrary) => Promise)|string; public readonly subcommands: Collection; // This is the final data structure you'll actually use to work with the commands the aliases point to. public user: Command|null; public number: Command|null; diff --git a/src/core/lib.ts b/src/core/lib.ts index 77fa0ab..81b960d 100644 --- a/src/core/lib.ts +++ b/src/core/lib.ts @@ -3,7 +3,7 @@ import {Client, Message, TextChannel, DMChannel, NewsChannel, Guild, User, Guild import chalk from "chalk"; import FileManager from "./storage"; import {eventListeners} from "../events/messageReactionRemove"; -import {botHasPermission} from "../index"; +import {client} from "../index"; /** A type that describes what the library module does. */ export interface CommonLibrary @@ -146,6 +146,11 @@ export function formatUTCTimestamp(now = new Date()) return `${year}-${month}-${day} ${hour}:${minute}:${second}`; } +export function botHasPermission(guild: Guild|null, permission: number): boolean +{ + return !!(client.user && guild?.members.resolve(client.user)?.hasPermission(permission)) +} + // Pagination function that allows for customization via a callback. // Define your own pages outside the function because this only manages the actual turning of pages. $.paginate = async(message: Message, senderID: string, total: number, callback: (page: number) => void, duration = 60000) => { diff --git a/src/core/permissions.ts b/src/core/permissions.ts index 56ea4d9..8935b6c 100644 --- a/src/core/permissions.ts +++ b/src/core/permissions.ts @@ -11,26 +11,26 @@ const PermissionChecker: ((member: GuildMember) => boolean)[] = [ () => true, // MOD // - (member: GuildMember) => + member => member.hasPermission(Permissions.FLAGS.MANAGE_ROLES) || member.hasPermission(Permissions.FLAGS.MANAGE_MESSAGES) || member.hasPermission(Permissions.FLAGS.KICK_MEMBERS) || member.hasPermission(Permissions.FLAGS.BAN_MEMBERS), // ADMIN // - (member: GuildMember) => member.hasPermission(Permissions.FLAGS.ADMINISTRATOR), + member => member.hasPermission(Permissions.FLAGS.ADMINISTRATOR), // OWNER // - (member: GuildMember) => member.guild.ownerID === member.id, + member => member.guild.ownerID === member.id, // BOT_SUPPORT // - (member: GuildMember) => Config.support.includes(member.id), + member => Config.support.includes(member.id), // BOT_ADMIN // - (member: GuildMember) => Config.admins.includes(member.id), + member => Config.admins.includes(member.id), // BOT_OWNER // - (member: GuildMember) => Config.owner === member.id + member => Config.owner === member.id ]; // After checking the lengths of these three objects, use this as the length for consistency. diff --git a/src/events/messageReactionRemove.ts b/src/events/messageReactionRemove.ts index 4304d27..4463350 100644 --- a/src/events/messageReactionRemove.ts +++ b/src/events/messageReactionRemove.ts @@ -1,6 +1,6 @@ import Event from "../core/event"; import {Permissions} from "discord.js"; -import {botHasPermission} from "../index"; +import {botHasPermission} from "../core/lib"; // A list of message ID and callback pairs. You get the emote name and ID of the user reacting. export const eventListeners: Map void> = new Map(); diff --git a/src/index.ts b/src/index.ts index 39c2b29..0c2718a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,4 @@ -import {Client, Guild} from "discord.js"; +import {Client} from "discord.js"; import setup from "./setup"; import {Config} from "./core/structures"; import {loadCommands} from "./core/command"; @@ -13,9 +13,4 @@ setup.init().then(() => { loadCommands(); loadEvents(client); client.login(Config.token).catch(setup.again); -}); - -export function botHasPermission(guild: Guild|null, permission: number): boolean -{ - return !!(client.user && guild?.members.resolve(client.user)?.hasPermission(permission)) -} \ No newline at end of file +}); \ No newline at end of file From 19010e7e59f52d68955893e7f2a29f30e16e287d Mon Sep 17 00:00:00 2001 From: Keanu Date: Mon, 31 Aug 2020 21:15:41 +0200 Subject: [PATCH 035/178] Attempt #1 at adding Docker. --- Dockerfile | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 Dockerfile diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..35723a0 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,7 @@ +FROM node:current-alpine + +COPY . . + +RUN npm run build + +CMD ["npm", "start"] \ No newline at end of file From a829e3d0bd46e1cb8762d7d3ee143775200e7601 Mon Sep 17 00:00:00 2001 From: Keanu Date: Mon, 31 Aug 2020 21:17:22 +0200 Subject: [PATCH 036/178] Create docker.yml --- .github/workflows/docker.yml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 .github/workflows/docker.yml diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml new file mode 100644 index 0000000..781891f --- /dev/null +++ b/.github/workflows/docker.yml @@ -0,0 +1,18 @@ +name: Build Docker Image +on: + push: + branches: ["typescript", "docker"] +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v2 + - name: Build and push Docker images + uses: docker/build-push-action@v1 + with: + username: keanucode + password: ${{ secrets.DOCKER_PASS }} + registry: docker.pkg.github.com + repository: keanucode/travbot-v3 + tags: latest From f8cd32914cb3c2626106cf92759af269cceee8bf Mon Sep 17 00:00:00 2001 From: Keanu Date: Mon, 31 Aug 2020 21:18:44 +0200 Subject: [PATCH 037/178] Added missing npm i to Dockerfile. --- Dockerfile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Dockerfile b/Dockerfile index 35723a0..ab33a22 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,6 +2,8 @@ FROM node:current-alpine COPY . . +RUN npm i + RUN npm run build CMD ["npm", "start"] \ No newline at end of file From 863057f4969e547f07c2d4b9f5d0c612e7546a04 Mon Sep 17 00:00:00 2001 From: Keanu Date: Mon, 31 Aug 2020 21:21:02 +0200 Subject: [PATCH 038/178] Updated Docker repo --- .github/workflows/docker.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 781891f..08b6b63 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -14,5 +14,5 @@ jobs: username: keanucode password: ${{ secrets.DOCKER_PASS }} registry: docker.pkg.github.com - repository: keanucode/travbot-v3 + repository: keanucode/travbot-v3/travbot-v3 tags: latest From 8293dc3925fc61e00ae11d1ee2d3fd16ba5be32d Mon Sep 17 00:00:00 2001 From: Keanu Date: Mon, 31 Aug 2020 21:28:50 +0200 Subject: [PATCH 039/178] Fixed password. --- .github/workflows/docker.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 08b6b63..ecd3358 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -12,7 +12,7 @@ jobs: uses: docker/build-push-action@v1 with: username: keanucode - password: ${{ secrets.DOCKER_PASS }} + password: ${{ secrets.GH_TOKEN }} registry: docker.pkg.github.com repository: keanucode/travbot-v3/travbot-v3 tags: latest From 61825e5fff85dcd90a9b2cafc33698a02cd7fd30 Mon Sep 17 00:00:00 2001 From: Keanu Date: Mon, 31 Aug 2020 21:33:35 +0200 Subject: [PATCH 040/178] Fixed username. --- .github/workflows/docker.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index ecd3358..ed941a3 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -11,7 +11,7 @@ jobs: - name: Build and push Docker images uses: docker/build-push-action@v1 with: - username: keanucode + username: keanuplayz password: ${{ secrets.GH_TOKEN }} registry: docker.pkg.github.com repository: keanucode/travbot-v3/travbot-v3 From 7f811800d704f2a26ffc1ce14d47a939401d521d Mon Sep 17 00:00:00 2001 From: Keanu Date: Wed, 9 Sep 2020 13:11:17 +0200 Subject: [PATCH 041/178] Removed broken Docker workflow. This has been moved to Docker Hub, since it builds atomatically. --- .github/workflows/docker.yml | 18 ------------------ 1 file changed, 18 deletions(-) delete mode 100644 .github/workflows/docker.yml diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml deleted file mode 100644 index ed941a3..0000000 --- a/.github/workflows/docker.yml +++ /dev/null @@ -1,18 +0,0 @@ -name: Build Docker Image -on: - push: - branches: ["typescript", "docker"] -jobs: - build: - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v2 - - name: Build and push Docker images - uses: docker/build-push-action@v1 - with: - username: keanuplayz - password: ${{ secrets.GH_TOKEN }} - registry: docker.pkg.github.com - repository: keanucode/travbot-v3/travbot-v3 - tags: latest From 4da672f419c069b8bf81931dc6cd67c2a311597a Mon Sep 17 00:00:00 2001 From: Keanu Date: Thu, 10 Sep 2020 18:51:01 +0200 Subject: [PATCH 042/178] Create Dockerfile.armhf --- Dockerfile.armhf | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 Dockerfile.armhf diff --git a/Dockerfile.armhf b/Dockerfile.armhf new file mode 100644 index 0000000..4eec205 --- /dev/null +++ b/Dockerfile.armhf @@ -0,0 +1,9 @@ +FROM node:current-alpine + +COPY . . + +RUN npm i + +RUN npm run build + +CMD ["npm", "start"] From e6c435fa45e67bfb99f2662e3b1a590922665786 Mon Sep 17 00:00:00 2001 From: Keanu Date: Thu, 10 Sep 2020 20:38:33 +0200 Subject: [PATCH 043/178] Attempt to add Docker image build. --- .github/workflows/image.yml | 24 ++++++++++++++++++++++++ Dockerfile.armhf | 9 --------- 2 files changed, 24 insertions(+), 9 deletions(-) create mode 100644 .github/workflows/image.yml delete mode 100644 Dockerfile.armhf diff --git a/.github/workflows/image.yml b/.github/workflows/image.yml new file mode 100644 index 0000000..7279695 --- /dev/null +++ b/.github/workflows/image.yml @@ -0,0 +1,24 @@ +name: Build Docker Image + Push + +on: + push: + branches: master + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v2 + - name: Install Docker BuildX + id: buildx + uses: crazy-max/ghaction-docker-buildx@v1 + with: + version: latest + - name: Login to Docker Hub + run: echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u "${{ secrets.DOCKER_USERNAME }}" --password-stdin + - name: Build the image + run: | + docker buildx build \ + --tag keanucode/travbot-v3:latest \ + --platform linux/amd64,linux/arm/v8,linux/arm64 . diff --git a/Dockerfile.armhf b/Dockerfile.armhf deleted file mode 100644 index 4eec205..0000000 --- a/Dockerfile.armhf +++ /dev/null @@ -1,9 +0,0 @@ -FROM node:current-alpine - -COPY . . - -RUN npm i - -RUN npm run build - -CMD ["npm", "start"] From 005713efcacfeaf63a8ad02a3e46f62c91599b61 Mon Sep 17 00:00:00 2001 From: Keanu Date: Thu, 10 Sep 2020 21:03:44 +0200 Subject: [PATCH 044/178] Attempt to fix Docker action. --- .github/workflows/image.yml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/workflows/image.yml b/.github/workflows/image.yml index 7279695..d16ded3 100644 --- a/.github/workflows/image.yml +++ b/.github/workflows/image.yml @@ -1,8 +1,9 @@ name: Build Docker Image + Push - on: push: - branches: master + branches: + - typescript + - docker jobs: build: @@ -16,9 +17,9 @@ jobs: with: version: latest - name: Login to Docker Hub - run: echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u "${{ secrets.DOCKER_USERNAME }}" --password-stdin + run: echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u "${{ secrets.DOCKER_USERNAME }}" --password-stdin - name: Build the image - run: | - docker buildx build \ + run: | + docker buildx build \ --tag keanucode/travbot-v3:latest \ --platform linux/amd64,linux/arm/v8,linux/arm64 . From 369937ad2b1fde4ecfceb15f5afd64aef86288aa Mon Sep 17 00:00:00 2001 From: Keanu Date: Thu, 10 Sep 2020 21:05:33 +0200 Subject: [PATCH 045/178] Changed v8 to v7. --- .github/workflows/image.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/image.yml b/.github/workflows/image.yml index d16ded3..deec229 100644 --- a/.github/workflows/image.yml +++ b/.github/workflows/image.yml @@ -22,4 +22,4 @@ jobs: run: | docker buildx build \ --tag keanucode/travbot-v3:latest \ - --platform linux/amd64,linux/arm/v8,linux/arm64 . + --platform linux/amd64,linux/arm/v7,linux/arm64 . From 2960639dbd9a1300ad1d7d1f1d122761caacaac4 Mon Sep 17 00:00:00 2001 From: Keanu Date: Thu, 10 Sep 2020 21:11:32 +0200 Subject: [PATCH 046/178] Added --push flag --- .github/workflows/image.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/image.yml b/.github/workflows/image.yml index deec229..953adcc 100644 --- a/.github/workflows/image.yml +++ b/.github/workflows/image.yml @@ -22,4 +22,4 @@ jobs: run: | docker buildx build \ --tag keanucode/travbot-v3:latest \ - --platform linux/amd64,linux/arm/v7,linux/arm64 . + --platform linux/amd64,linux/arm/v7,linux/arm64 --push . From cbab1c34358e205773e257e8fd63734554dc4f90 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 11 Sep 2020 19:29:43 +0000 Subject: [PATCH 047/178] Bump node-fetch from 2.6.0 to 2.6.1 Bumps [node-fetch](https://github.com/bitinn/node-fetch) from 2.6.0 to 2.6.1. - [Release notes](https://github.com/bitinn/node-fetch/releases) - [Changelog](https://github.com/node-fetch/node-fetch/blob/master/docs/CHANGELOG.md) - [Commits](https://github.com/bitinn/node-fetch/compare/v2.6.0...v2.6.1) Signed-off-by: dependabot[bot] --- package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 64cb614..458d55e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -919,9 +919,9 @@ "dev": true }, "node-fetch": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz", - "integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==" + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", + "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==" }, "normalize-path": { "version": "3.0.0", From 57a4c9f52380056b4abfaedc4cd6d3197ed64e53 Mon Sep 17 00:00:00 2001 From: Lexi Date: Tue, 13 Oct 2020 22:11:26 +0200 Subject: [PATCH 048/178] Added WIP emote list. @WatDuhHekBro also fixed the paginate function in lib.ts. This had some inconsistencies. Aside from that, to reduce image size, docker now ignores node_modules. Co-authored-by: WatDuhHekBro --- .dockerignore | 1 + src/commands/util.ts | 68 ++++++++++++++------------------------------ src/core/lib.ts | 52 +++++++++++++++++---------------- 3 files changed, 50 insertions(+), 71 deletions(-) create mode 100644 .dockerignore diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..c2658d7 --- /dev/null +++ b/.dockerignore @@ -0,0 +1 @@ +node_modules/ diff --git a/src/commands/util.ts b/src/commands/util.ts index b1e6f81..ed129dc 100644 --- a/src/commands/util.ts +++ b/src/commands/util.ts @@ -15,58 +15,34 @@ export default new Command({ endpoint: true, async run($: CommonLibrary): Promise { const nsfw: string | string[] = []; - const list = $.client.emojis.cache.filter(x => !nsfw.includes(x.guild.id), this) - .array(); - let page = 1; - const epg = 20; - let content = ""; - const left = "⬅", - right = "➡"; + const pages = $.client.emojis.cache.filter(x => !nsfw.includes(x.guild.id), this).array(); + // $.log(pages); var embed = new MessageEmbed() .setTitle("**Emoji list!**") .setColor("AQUA"); - let owo = list.slice((page - 1) * epg, page * epg); - owo.forEach(q => (content += q.toString() + " | " + q.name + "\n")); - embed.setDescription(content); - const msg = await $.channel.send({ - embed - }); - if (list.length < epg) return; - await msg.react("⬅"); - await msg.react("➡"); - const backwardsfilter = (reaction: { emoji: { name: string; }; }, user: { id: any; }) => reaction.emoji.name == left && user.id == $.message.author.id; - const forwardsfilter = (reaction: { emoji: { name: string; }; }, user: { id: any; }) => reaction.emoji.name == right && user.id == $.message.author.id; - const backwards = msg.createReactionCollector(backwardsfilter, { - time: 300000 - }); - const forwards = msg.createReactionCollector(forwardsfilter, { - time: 300000 - }); - backwards.on("collect", () => { - if (page < 2) return; - // @ts-ignore - msg.reactions.cache.find((uwu: { emoji: { name: string; }; }) => (uwu.emoji.name = "⬅")) - .users.remove($.message.author) - page--; - owo = list.slice((page - 1) * epg, page * epg); - content = ""; - owo.forEach(q => (content += q.toString() + " | " + q.name + "\n")); - embed.setDescription(content); - msg.edit(embed); - }); - forwards.on("collect", () => { - if (page > Math.ceil(list.length / epg)) return; - page++; - // @ts-ignore - msg.reactions.cache.find((uwu: { emoji: { name: string; }; }) => uwu.emoji.name == "➡") - .users.remove($.message.author) - owo = list.slice((page - 1) * epg, page * epg); - content = ""; - owo.forEach(q => (content += q.toString() + " | " + q.name + "\n")); - embed.setDescription(content); + const msg = await $.channel.send({embed}); + + $.paginate(msg, $.author.id, pages.length, page => { + embed.setDescription(`${pages[page]} | ${pages[page].name}`); msg.edit(embed); }); } + }), + emote: new Command({ + description: "Send the specified emote.", + run: "Please provide a command name.", + any: new Command({ + description: "The emote to send.", + usage: "", + async run($: CommonLibrary): Promise + { + const search = $.args[0].toLowerCase(); + const emote = $.client.emojis.cache.find(emote => emote.name.toLowerCase().includes(search)); + if (!emote) return $.channel.send("That's not a valid emote name!"); + $.message.delete(); + $.channel.send(`${emote}`); + } + }) }) } }); \ No newline at end of file diff --git a/src/core/lib.ts b/src/core/lib.ts index 81b960d..40dde69 100644 --- a/src/core/lib.ts +++ b/src/core/lib.ts @@ -166,31 +166,33 @@ $.paginate = async(message: Message, senderID: string, total: number, callback: callback(page); } const handle = (emote: string, reacterID: string) => { - if(reacterID === senderID) - { - switch(emote) - { - case '⬅️': turn(-1); break; - case '➡️': turn(1); break; - } - } - }; - - // Listen for reactions and call the handler. - await message.react('⬅️'); - await message.react('➡️'); - eventListeners.set(message.id, handle); - await message.awaitReactions((reaction, user) => { - // The reason this is inside the call is because it's possible to switch a user's permissions halfway and suddenly throw an error. - // This will dynamically adjust for that, switching modes depending on whether it currently has the "Manage Messages" permission. - const canDeleteEmotes = botHasPermission(message.guild, Permissions.FLAGS.MANAGE_MESSAGES); - handle(reaction.emoji.name, user.id); - - if(canDeleteEmotes) - reaction.users.remove(user); - - return false; - }, {time: duration}); + switch(emote) + { + case '⬅️': turn(-1); break; + case '➡️': turn(1); break; + } +}; + +// Listen for reactions and call the handler. +await message.react('⬅️'); +await message.react('➡️'); +eventListeners.set(message.id, handle); +await message.awaitReactions((reaction, user) => { + if(user.id === senderID) + { + // The reason this is inside the call is because it's possible to switch a user's permissions halfway and suddenly throw an error. + // This will dynamically adjust for that, switching modes depending on whether it currently has the "Manage Messages" permission. + const canDeleteEmotes = botHasPermission(message.guild, Permissions.FLAGS.MANAGE_MESSAGES); + handle(reaction.emoji.name, user.id); + + if(canDeleteEmotes) + reaction.users.remove(user); + } + + return false; +}, {time: duration}); + + $.log("removal") // When time's up, remove the bot's own reactions. eventListeners.delete(message.id); From 57433cc5946404a7ff8644caa06e8242f34dd08c Mon Sep 17 00:00:00 2001 From: Keanu Date: Thu, 15 Oct 2020 09:02:24 +0000 Subject: [PATCH 049/178] Completed lsemotes. Various other changes. - Replaced deprecated `version` input with `dockerx-version` - Replaced deprecated `equals` function with `strictEquals` - Fixed indentation on paginate function. --- .github/workflows/image.yml | 2 +- src/commands/util.ts | 18 +++++++++++--- src/core/lib.ts | 49 +++++++++++++++++-------------------- test/wrappers.ts | 24 +++++++++--------- 4 files changed, 50 insertions(+), 43 deletions(-) diff --git a/.github/workflows/image.yml b/.github/workflows/image.yml index 953adcc..f17915c 100644 --- a/.github/workflows/image.yml +++ b/.github/workflows/image.yml @@ -15,7 +15,7 @@ jobs: id: buildx uses: crazy-max/ghaction-docker-buildx@v1 with: - version: latest + buildx-version: latest - name: Login to Docker Hub run: echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u "${{ secrets.DOCKER_USERNAME }}" --password-stdin - name: Build the image diff --git a/src/commands/util.ts b/src/commands/util.ts index ed129dc..1070979 100644 --- a/src/commands/util.ts +++ b/src/commands/util.ts @@ -3,7 +3,7 @@ import Command from '../core/command'; import {CommonLibrary} from '../core/lib'; export default new Command({ - description: "", + description: "Various utilities.", endpoint: false, usage: '', async run($: CommonLibrary): Promise { @@ -16,14 +16,24 @@ export default new Command({ async run($: CommonLibrary): Promise { const nsfw: string | string[] = []; const pages = $.client.emojis.cache.filter(x => !nsfw.includes(x.guild.id), this).array(); - // $.log(pages); + const pagesSplit = $(pages).split(20); + $.log(pagesSplit) var embed = new MessageEmbed() .setTitle("**Emoji list!**") - .setColor("AQUA"); + .setColor("AQUA") + let desc = "" + for (const emote of pagesSplit[0]) { + desc += `${emote} | ${emote.name}\n` + } + embed.setDescription(desc) const msg = await $.channel.send({embed}); $.paginate(msg, $.author.id, pages.length, page => { - embed.setDescription(`${pages[page]} | ${pages[page].name}`); + let desc = "" + for(const emote of pagesSplit[page]) { + desc += `${emote} | ${emote.name}\n` + } + embed.setDescription(desc) msg.edit(embed); }); } diff --git a/src/core/lib.ts b/src/core/lib.ts index 40dde69..78e1ff9 100644 --- a/src/core/lib.ts +++ b/src/core/lib.ts @@ -166,34 +166,30 @@ $.paginate = async(message: Message, senderID: string, total: number, callback: callback(page); } const handle = (emote: string, reacterID: string) => { - switch(emote) - { - case '⬅️': turn(-1); break; - case '➡️': turn(1); break; - } -}; + switch(emote) + { + case '⬅️': turn(-1); break; + case '➡️': turn(1); break; + } + }; -// Listen for reactions and call the handler. -await message.react('⬅️'); -await message.react('➡️'); -eventListeners.set(message.id, handle); -await message.awaitReactions((reaction, user) => { - if(user.id === senderID) - { - // The reason this is inside the call is because it's possible to switch a user's permissions halfway and suddenly throw an error. - // This will dynamically adjust for that, switching modes depending on whether it currently has the "Manage Messages" permission. - const canDeleteEmotes = botHasPermission(message.guild, Permissions.FLAGS.MANAGE_MESSAGES); - handle(reaction.emoji.name, user.id); + // Listen for reactions and call the handler. + await message.react('⬅️'); + await message.react('➡️'); + eventListeners.set(message.id, handle); + await message.awaitReactions((reaction, user) => { + if(user.id === senderID) + { + // The reason this is inside the call is because it's possible to switch a user's permissions halfway and suddenly throw an error. + // This will dynamically adjust for that, switching modes depending on whether it currently has the "Manage Messages" permission. + const canDeleteEmotes = botHasPermission(message.guild, Permissions.FLAGS.MANAGE_MESSAGES); + handle(reaction.emoji.name, user.id); - if(canDeleteEmotes) - reaction.users.remove(user); - } - - return false; -}, {time: duration}); - - $.log("removal") - + if(canDeleteEmotes) + reaction.users.remove(user); + } + return false; + }, {time: duration}); // When time's up, remove the bot's own reactions. eventListeners.delete(message.id); message.reactions.cache.get('⬅️')?.users.remove(message.author); @@ -387,6 +383,7 @@ export abstract class GenericStructure public save(asynchronous = true) { const tag = this.__meta__; + /// @ts-ignore delete this.__meta__; FileManager.write(tag, this, asynchronous); this.__meta__ = tag; diff --git a/test/wrappers.ts b/test/wrappers.ts index 260c229..e6699d9 100644 --- a/test/wrappers.ts +++ b/test/wrappers.ts @@ -6,41 +6,41 @@ describe("Wrappers", () => { describe("NumberWrapper", () => { describe("#pluralise()", () => { it('should return "5 credits"', () => { - assert.equal(new NumberWrapper(5).pluralise("credit", "s"), "5 credits"); + assert.strictEqual(new NumberWrapper(5).pluralise("credit", "s"), "5 credits"); }) it('should return "1 credit"', () => { - assert.equal(new NumberWrapper(1).pluralise("credit", "s"), "1 credit"); + assert.strictEqual(new NumberWrapper(1).pluralise("credit", "s"), "1 credit"); }) it('should return "-1 credits"', () => { - assert.equal(new NumberWrapper(-1).pluralise("credit", "s"), "-1 credits"); + assert.strictEqual(new NumberWrapper(-1).pluralise("credit", "s"), "-1 credits"); }) it('should be able to work with a plural suffix', () => { - assert.equal(new NumberWrapper(2).pluralise("part", "ies", "y"), "2 parties"); + assert.strictEqual(new NumberWrapper(2).pluralise("part", "ies", "y"), "2 parties"); }) it('should be able to work with a singular suffix', () => { - assert.equal(new NumberWrapper(1).pluralise("part", "ies", "y"), "1 party"); + assert.strictEqual(new NumberWrapper(1).pluralise("part", "ies", "y"), "1 party"); }) it('should be able to exclude the number', () => { - assert.equal(new NumberWrapper(1).pluralise("credit", "s", "", true), "credit"); + assert.strictEqual(new NumberWrapper(1).pluralise("credit", "s", "", true), "credit"); }) }) describe("#pluraliseSigned()", () => { it('should return "-1 credits"', () => { - assert.equal(new NumberWrapper(-1).pluraliseSigned("credit", "s"), "-1 credits"); + assert.strictEqual(new NumberWrapper(-1).pluraliseSigned("credit", "s"), "-1 credits"); }) it('should return "+0 credits"', () => { - assert.equal(new NumberWrapper(0).pluraliseSigned("credit", "s"), "+0 credits"); + assert.strictEqual(new NumberWrapper(0).pluraliseSigned("credit", "s"), "+0 credits"); }) it('should return "+1 credit"', () => { - assert.equal(new NumberWrapper(1).pluraliseSigned("credit", "s"), "+1 credit"); + assert.strictEqual(new NumberWrapper(1).pluraliseSigned("credit", "s"), "+1 credit"); }) }) }) @@ -48,13 +48,13 @@ describe("Wrappers", () => { describe("StringWrapper", () => { describe("#replaceAll()", () => { it('should convert "test" to "zesz"', () => { - assert.equal(new StringWrapper("test").replaceAll('t', 'z'), "zesz"); + assert.strictEqual(new StringWrapper("test").replaceAll('t', 'z'), "zesz"); }) }) describe("#toTitleCase()", () => { it('should capitalize the first letter of each word', () => { - assert.equal(new StringWrapper("yeetus deletus find salvation from jesus").toTitleCase(), "Yeetus Deletus Find Salvation From Jesus"); + assert.strictEqual(new StringWrapper("yeetus deletus find salvation from jesus").toTitleCase(), "Yeetus Deletus Find Salvation From Jesus"); }) }) }) @@ -62,7 +62,7 @@ describe("Wrappers", () => { describe("ArrayWrapper", () => { describe("#split()", () => { it('should split [1,2,3,4,5,6,7,8,9,10] into [[1,2,3],[4,5,6],[7,8,9],[10]]', () => { - assert.deepEqual(new ArrayWrapper([1,2,3,4,5,6,7,8,9,10]).split(3), [[1,2,3],[4,5,6],[7,8,9],[10]]); + assert.deepStrictEqual(new ArrayWrapper([1,2,3,4,5,6,7,8,9,10]).split(3), [[1,2,3],[4,5,6],[7,8,9],[10]]); }) }) }) From 74b4d4272cd78bb1eb4ab698d6674908aecebd55 Mon Sep 17 00:00:00 2001 From: Keanu Date: Thu, 15 Oct 2020 09:23:24 +0000 Subject: [PATCH 050/178] Added husky. --- .github/codeql/codeql-config.yml | 2 +- .github/workflows/codeql-analysis.yml | 42 +- .github/workflows/image.yml | 2 +- .husky/.gitignore | 1 + .husky/pre-commit | 4 + .prettierignore | 71 +++ README.md | 2 +- docs/Documentation.md | 146 ++++-- docs/GettingStarted.md | 5 +- docs/Specifications.md | 27 +- package-lock.json | 12 + package.json | 4 + prettier.config.js | 13 + src/commands/admin.ts | 339 +++++++------ src/commands/fun/8ball.ts | 69 ++- src/commands/fun/poll.ts | 50 +- src/commands/help.ts | 280 +++++------ src/commands/info.ts | 307 +++++++----- src/commands/money.ts | 479 +++++++++--------- src/commands/scanemotes.ts | 374 +++++++-------- src/commands/util.ts | 109 ++--- src/core/command.ts | 455 +++++++++--------- src/core/event.ts | 78 ++- src/core/lib.ts | 667 ++++++++++++++------------ src/core/permissions.ts | 113 +++-- src/core/storage.ts | 159 +++--- src/core/structures.ts | 205 ++++---- src/core/wrappers.ts | 159 +++--- src/defs/info.ts | 78 +-- src/events/message.ts | 240 +++++---- src/events/messageReactionRemove.ts | 36 +- src/events/ready.ts | 34 +- src/index.ts | 18 +- src/setup.ts | 114 ++--- test/wrappers.ts | 176 ++++--- tsconfig.json | 33 +- 36 files changed, 2677 insertions(+), 2226 deletions(-) create mode 100644 .husky/.gitignore create mode 100755 .husky/pre-commit create mode 100644 .prettierignore create mode 100644 prettier.config.js diff --git a/.github/codeql/codeql-config.yml b/.github/codeql/codeql-config.yml index e3dae69..08ced75 100644 --- a/.github/codeql/codeql-config.yml +++ b/.github/codeql/codeql-config.yml @@ -1,4 +1,4 @@ -name: "CodeQL Config" +name: 'CodeQL Config' queries: - uses: security-and-quality diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index f22f74b..f28a185 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -1,4 +1,4 @@ -name: "CodeQL" +name: 'CodeQL' on: push: @@ -14,28 +14,28 @@ jobs: runs-on: ubuntu-latest steps: - - name: Checkout repository - uses: actions/checkout@v2 - with: - fetch-depth: 2 + - name: Checkout repository + uses: actions/checkout@v2 + with: + fetch-depth: 2 - - run: git checkout HEAD^2 - if: ${{ github.event_name == 'pull_request' }} + - run: git checkout HEAD^2 + if: ${{ github.event_name == 'pull_request' }} - - name: Setup Node.JS - uses: actions/setup-node@v2-beta - with: - node-version: '12' - - run: npm ci + - name: Setup Node.JS + uses: actions/setup-node@v2-beta + with: + node-version: '12' + - run: npm ci - - name: Build codebase - run: npm run build + - name: Build codebase + run: npm run build - - name: Initialize CodeQL - uses: github/codeql-action/init@v1 - with: - config-file: ./.github/codeql/codeql-config.yml - languages: javascript + - name: Initialize CodeQL + uses: github/codeql-action/init@v1 + with: + config-file: ./.github/codeql/codeql-config.yml + languages: javascript - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v1 + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v1 diff --git a/.github/workflows/image.yml b/.github/workflows/image.yml index f17915c..d6650c9 100644 --- a/.github/workflows/image.yml +++ b/.github/workflows/image.yml @@ -1,7 +1,7 @@ name: Build Docker Image + Push on: push: - branches: + branches: - typescript - docker diff --git a/.husky/.gitignore b/.husky/.gitignore new file mode 100644 index 0000000..31354ec --- /dev/null +++ b/.husky/.gitignore @@ -0,0 +1 @@ +_ diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100755 index 0000000..beaf4ba --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,4 @@ +#!/bin/sh +. "$(dirname $0)/_/husky.sh" + +npm test && npx prettier --write . \ No newline at end of file diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..3b0f1ed --- /dev/null +++ b/.prettierignore @@ -0,0 +1,71 @@ +# Specific to this repository +dist/ +data/ +docs/ +tmp/ +test* +!test/ +*.bat +desktop.ini + +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (http://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Typescript v1 declaration files +typings/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env + +config.json +.vscode/ \ No newline at end of file diff --git a/README.md b/README.md index a098939..292e3f9 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ Thank you for coming on this journey with me, but it is time to put the big chan Special thanks to: - Lexi Sother (TravBot v2, structural overhaul. Reviewing PRs.) -- WatDuhHekBro (a *lot* of contributions to TravBot v2) +- WatDuhHekBro (a _lot_ of contributions to TravBot v2) - Zeehondie (Ideas for various commands.) ### License diff --git a/docs/Documentation.md b/docs/Documentation.md index bcfed75..c720942 100644 --- a/docs/Documentation.md +++ b/docs/Documentation.md @@ -1,128 +1,158 @@ # What this is + This is a user-friendly version of the project's structure (compared to the amalgamation that has become [specifications](Specifications.md) which is just a list of design decisions and isn't actually helpful at all for understanding the code). This will follow the line of logic that the program would run through. # Building/Setup + - `npm run dev` runs the TypeScript compiler in watch mode, meaning that any changes you make to the code will automatically reload the bot. - This will take all the files in `src` (where all the code is) and compile it into `dist` which is what the program actually uses. - - If there's a runtime error, `dist\commands\test.js:25:30` for example, then you have to into `dist` instead of `src`, then find the line that corresponds. + - If there's a runtime error, `dist\commands\test.js:25:30` for example, then you have to into `dist` instead of `src`, then find the line that corresponds. # Launching + When you start the program, it'll run the code in `index` (meaning both `src/index.ts` and `dist/index.js`, they're the same except that `dist/<...>.js` is compiled). The code in `index` will call `setup` and check if `data/config.json` exists, prompting you if it doesn't. It'll then run initialization code. # Structure + - `commands` contains all the commands. - `defs` contains static definitions. - `core` contains the foundation of the program. You won't need to worry about this unless you're modifying pre-existing behavior of the `Command` class for example or add a function to the library. - `events` contains all the events. Again, you generally won't need to touch this unless you're listening for a new Discord event. # The Command Class + A valid command file must be located in `commands` and export a default `Command` instance. Assume that we're working with `commands/money.ts`. + ```js import Command from '../core/command'; export default new Command({ - //... + //... }); ``` + The `run` property can be either a function or a string. If it's a function, you get one parameter, `$` which represents the common library (see below). If it's a string, it's a variable string. + - `%author%` pings the person who sent the message. - `%prefix%` gets the bot's current prefix in the selected server. + ```js import Command from '../core/command'; -import {CommonLibrary} from '../core/lib'; +import { CommonLibrary } from '../core/lib'; export default new Command({ - run: "%author%, make sure to use the prefix! (%prefix)" + run: '%author%, make sure to use the prefix! (%prefix)', }); ``` + ...is equal to... + ```js import Command from '../core/command'; -import {CommonLibrary} from '../core/lib'; -import {getPrefix} from "../core/structures"; +import { CommonLibrary } from '../core/lib'; +import { getPrefix } from '../core/structures'; export default new Command({ - async run($: CommonLibrary): Promise { - $.channel.send(`${$.author.toString()}, make sure to use the prefix! (${getPrefix($.guild)})`); - } + async run($: CommonLibrary): Promise { + $.channel.send( + `${$.author.toString()}, make sure to use the prefix! (${getPrefix( + $.guild, + )})`, + ); + }, }); ``` + Now here's where it gets fun. The `Command` class is a recursive structure, containing other `Command` instances as properties. + - `subcommands` is used for specific keywords for accessing a certain command. For example, `$eco pay` has a subcommand of `pay`. + ```js import Command from '../core/command'; -import {CommonLibrary} from '../core/lib'; +import { CommonLibrary } from '../core/lib'; export default new Command({ - subcommands: { - pay: new Command({ - //... - }) - } + subcommands: { + pay: new Command({ + //... + }), + }, }); ``` + There's also `user` which listens for a ping or a Discord ID, `<@237359961842253835>` and `237359961842253835` respectively. The argument will be a `User` object. + ```js import Command from '../core/command'; -import {CommonLibrary} from '../core/lib'; +import { CommonLibrary } from '../core/lib'; export default new Command({ - user: new Command({ - async run($: CommonLibrary): Promise { - $.debug($.args[0].username); // "WatDuhHekBro" - } - }) + user: new Command({ + async run($: CommonLibrary): Promise { + $.debug($.args[0].username); // "WatDuhHekBro" + }, + }), }); ``` + There's also `number` which checks for any number type except `Infinity`, converting the argument to a `number` type. + ```js import Command from '../core/command'; -import {CommonLibrary} from '../core/lib'; +import { CommonLibrary } from '../core/lib'; export default new Command({ - number: new Command({ - async run($: CommonLibrary): Promise { - $.debug($.args[0] + 5); - } - }) + number: new Command({ + async run($: CommonLibrary): Promise { + $.debug($.args[0] + 5); + }, + }), }); ``` + And then there's `any` which catches everything else that doesn't fall into the above categories. The argument will be a `string`. + ```js import Command from '../core/command'; -import {CommonLibrary} from '../core/lib'; +import { CommonLibrary } from '../core/lib'; export default new Command({ - any: new Command({ - async run($: CommonLibrary): Promise { - $.debug($.args[0].toUpperCase()); - } - }) + any: new Command({ + async run($: CommonLibrary): Promise { + $.debug($.args[0].toUpperCase()); + }, + }), }); ``` + Of course, maybe you just want to get string arguments regardless, and since everything is an optional property, so you'd then just include `any` and not `subcommands`, `user`, or `number`. ## Other Properties + - `description`: The description for that specific command. - `endpoint`: A `boolean` determining whether or not to prevent any further arguments. For example, you could prevent `$money daily too many arguments`. - `usage`: Provide a custom usage for the help menu. Do note that this is relative to the subcommand, so the below will result in `$money pay `. + ```js import Command from '../core/command'; -import {CommonLibrary} from '../core/lib'; +import { CommonLibrary } from '../core/lib'; export default new Command({ - subcommands: { - pay: new Command({ - usage: " " - }) - } + subcommands: { + pay: new Command({ + usage: ' ', + }), + }, }); ``` + - `permission`: The permission to restrict the current command to. You can specify it for certain subcommands, useful for having `$money` be open to anyone but not `$money admin`. If it's `null` (default), the permission will inherit whatever was declared before (if any). The default value is NOT the same as `Command.PERMISSIONS.NONE`. - `aliases`: A list of aliases (if any). ## Alternatives to Nesting + For a lot of the metadata properties like `description`, you must provide them when creating a new `Command` instance. However, you can freely modify and attach subcommands, useful for splitting a command into multiple files. + ```js import pay from "./subcommands/pay"; @@ -141,19 +171,24 @@ export default cmd; ``` ## Error Handling + Any errors caused when using `await` or just regular synchronous functions will be automatically caught, you don't need to worry about those. However, promises must be caught manually. For example, `$.channel.send("")` will throw an error because you can't send empty messages to Discord, but since it's a promise, it'll just fade without throwing an error. There are two ways to do this: + - `$.channel.send("").catch($.handler.bind($))` - `$.channel.send("").catch(error => $.handler(error))` # The Common Library + This is the container of functions available without having to import `core/lib`, usually as `$`. When accessing this from a command's `run` function, it'll also come with shortcuts to other properties. ## Custom Wrappers + - `$(5)` = `new NumberWrapper(5)` - `$("text")` = `new StringWrapper("text")` - `$([1,2,3])` = `new ArrayWrapper([1,2,3])` ## Custom Logger + - `$.log(...)` - `$.warn(...)` - `$.error(...)` @@ -161,34 +196,43 @@ This is the container of functions available without having to import `core/lib` - `$.ready(...)` (only meant to be used once at the start of the program) ## Convenience Functions + This modularizes certain patterns of code to make things easier. + - `$.paginate()`: Takes a message and some additional parameters and makes a reaction page with it. All the pagination logic is taken care of but nothing more, the page index is returned and you have to send a callback to do something with it. + ```js -const pages = ["one", "two", "three"]; +const pages = ['one', 'two', 'three']; const msg = await $.channel.send(pages[0]); -$.paginate(msg, $.author.id, pages.length, page => { - msg.edit(pages[page]); +$.paginate(msg, $.author.id, pages.length, (page) => { + msg.edit(pages[page]); }); ``` + - `$.prompt()`: Prompts the user about a decision before following through. + ```js -const msg = await $.channel.send("Are you sure you want to delete this?"); +const msg = await $.channel.send('Are you sure you want to delete this?'); $.prompt(msg, $.author.id, () => { - delete this; // Replace this with actual code. + delete this; // Replace this with actual code. }); ``` + - `$.getMemberByUsername()`: Gets a user by their username. Gets the first one then rolls with it. - `$.callMemberByUsername()`: Convenience function to handle cases where someone isn't found by a username automatically. + ```js -$.callMemberByUsername($.message, $.args.join(" "), member => { - $.channel.send(`Your nickname is ${member.nickname}.`); +$.callMemberByUsername($.message, $.args.join(' '), (member) => { + $.channel.send(`Your nickname is ${member.nickname}.`); }); ``` ## Dynamic Properties + These will be accessible only inside a `Command` and will change per message. + - `$.args`: A list of arguments in the command. It's relative to the subcommand, so if you do `$test this 5`, `5` becomes `$.args[0]` if `this` is a subcommand. Args are already converted, so a `number` subcommand would return a number rather than a string. - `$.client`: `message.client` - `$.message`: `message` @@ -198,22 +242,28 @@ These will be accessible only inside a `Command` and will change per message. - `$.member`: `message.member` # Wrappers + This is similar to modifying a primitive object's `prototype` without actually doing so. ## NumberWrapper + - `.pluralise()`: A substitute for not having to do `amount === 1 ? "singular" : "plural"`. For example, `$(x).pluralise("credit", "s")` will return `"1 credit"` and/or `"5 credits"` respectively. - `.pluraliseSigned()`: This builds on `.pluralise()` and adds a sign at the beginning for marking changes/differences. `$(0).pluraliseSigned("credit", "s")` will return `"+0 credits"`. ## StringWrapper + - `.replaceAll()`: A non-regex alternative to replacing everything in a string. `$("test").replaceAll('t', 'z')` = `"zesz"`. - `.toTitleCase()`: Capitalizes the first letter of each word. `$("this is some text").toTitleCase()` = `"This Is Some Text"`. ## ArrayWrapper + - `.random()`: Returns a random element from an array. `$([1,2,3]).random()` could be any one of those elements. - `.split()`: Splits an array into different arrays by a specified length. `$([1,2,3,4,5,6,7,8,9,10]).split(3)` = `[[1,2,3],[4,5,6],[7,8,9],[10]]`. # Other Library Functions + These do have to be manually imported, which are used more on a case-by-case basis. + - `formatTimestamp()`: Formats a `Date` object into your system's time. `YYYY-MM-DD HH:MM:SS` - `formatUTCTimestamp()`: Formats a `Date` object into UTC time. `YYYY-MM-DD HH:MM:SS` - `botHasPermission()`: Tests if a bot has a certain permission in a specified guild. @@ -224,11 +274,13 @@ These do have to be manually imported, which are used more on a case-by-case bas - `Random`: An object of functions containing stuff related to randomness. `Random.num` is a random decimal, `Random.int` is a random integer, `Random.chance` takes a number ranging from `0` to `1` as a percentage. `Random.sign` takes a number and has a 50-50 chance to be negative or positive. `Random.deviation` takes a number and a magnitude and produces a random number within those confines. `(5, 2)` would produce any number between `3` and `7`. # Other Core Functions + - `permissions::hasPermission()`: Checks if a `Member` has a certain permission. - `permissions::getPermissionLevel()`: Gets a `Member`'s permission level according to the permissions enum defined in the file. - `structures::getPrefix()`: Get the current prefix of the guild or the bot's prefix if none is found. # The other core files + - `core/permissions`: Contains all the permission roles and checking functions. - `core/structures`: Contains all the code handling dynamic JSON data. Has a one-to-one connection with each file generated, for example, `Config` which calls `super("config")` meaning it writes to `data/config.json`. -- `core/storage`: Handles most of the file system operations, all of the ones related to `data` at least. \ No newline at end of file +- `core/storage`: Handles most of the file system operations, all of the ones related to `data` at least. diff --git a/docs/GettingStarted.md b/docs/GettingStarted.md index 04d3396..398cc33 100644 --- a/docs/GettingStarted.md +++ b/docs/GettingStarted.md @@ -1,9 +1,11 @@ # Getting Started + 1. `npm install` 2. `npm run build` 3. `npm start` # Getting Started (Developers) + 1. `npm install` 2. `npm run dev` 3. Familiarize yourself with the [project's structure](Documentation.md). @@ -11,6 +13,7 @@ 5. Begin developing. ## Don't forget to... + - ...update the [changelog](CHANGELOG.md) and any other necessary docs. - ...update the version numbers in `package.json` and `package-lock.json`. -- ...make sure the test suite passes by running `npm test`. \ No newline at end of file +- ...make sure the test suite passes by running `npm test`. diff --git a/docs/Specifications.md b/docs/Specifications.md index ffbc451..49bef9d 100644 --- a/docs/Specifications.md +++ b/docs/Specifications.md @@ -1,13 +1,15 @@ # Structure + The top-level directory is reserved for files that have to be there for it to work as well as configuration files. + - `src`: Contains all the code for the bot itself. Code in this directory is for independent tasks keeping the initialization out of the subdirectories. - - `core`: This is where core structures and critical functions for the bot go. - - `modules`: This is where modules go that accomplish one specific purpose but isn't so necessary for the bot to function. The goal is to be able to safely remove these without too much trouble. - - `commands`: Here's the place to store commands. The file name determines the command name. - - `subcommands/`: All commands here are ignored by the category loader. Here is where you can split commands into different files. Also works per directory, for example, `utility/subcommands/` is ignored. - - `/`: Specify a directory which'll group commands into a category. For example, a `utility` folder would make all commands inside have the `Utility` category. - - `.ts`: All commands at this level will have the `Miscellaneous` category. - - `events`: Here's the place to store events. The file name determines the event type. + - `core`: This is where core structures and critical functions for the bot go. + - `modules`: This is where modules go that accomplish one specific purpose but isn't so necessary for the bot to function. The goal is to be able to safely remove these without too much trouble. + - `commands`: Here's the place to store commands. The file name determines the command name. + - `subcommands/`: All commands here are ignored by the category loader. Here is where you can split commands into different files. Also works per directory, for example, `utility/subcommands/` is ignored. + - `/`: Specify a directory which'll group commands into a category. For example, a `utility` folder would make all commands inside have the `Utility` category. + - `.ts`: All commands at this level will have the `Miscellaneous` category. + - `events`: Here's the place to store events. The file name determines the event type. - `dist`: This is where the runnable code in `src` compiles to. (The directory structure mirrors `src`.) - `test`: Used for all the unit tests. - `data`: Holds all the dynamic data used by the bot. This is what you modify if you want to change stuff for just your instance of the bot. @@ -15,7 +17,9 @@ The top-level directory is reserved for files that have to be there for it to wo - `docs`: Used for information about the design of the project. # Specific Files + This list starts from `src`/`dist`. + - `index`: This is the entry point of the bot. Here is where all the initialization is done, because the idea is to keep repeatable code in separate modules while having code that runs only once here designating this is **the** starting point. - `setup`: Used for the first time the bot is loaded, walking the user through setting up the bot. - `core/lib`: Exports a function object which lets you wrap values letting you call special functions as well as calling utility functions common to all commands. @@ -27,6 +31,7 @@ This list starts from `src`/`dist`. - `core/permissions`: The file containing everything related to permissions. # Design Decisions + - All top-level files (relative to `src`/`dist`) should ideally be independent, one-time use scripts. This helps separate code that just initializes once and reusable code that forms the bulk of the main program itself. That's why all the file searching and loading commands/events will be done in `index`. - Wrapper objects were designed with the idea of letting you assign functions directly to native objects [without the baggage of actually doing so](https://developer.mozilla.org/en-US/docs/Web/JavaScript/The_performance_hazards_of__%5B%5BPrototype%5D%5D_mutation). - `test` should be a keyword for any file not tracked by git and generally be more flexible to play around with. It should also be automatically generated during initialization in `commands` so you can have templates ready for new commands. @@ -37,7 +42,7 @@ This list starts from `src`/`dist`. - All commands should have only one parameter. This parameter is meant to be flexible so you can add properties without making a laundry list of parameters. It also has convenience functions too so you don't have to import the library for each command. - The objects in `core/structures` are initialized into a special object once and then cached into memory automatically due to an import system. This means you don't have to keep loading JSON files over and over again even without the stack storage system. Because a JSON file resolves into an object, any extra keys are removed automatically (as it isn't initialized into the data) and any property that doesn't yet exist on the JSON object will be initialized into something. You can then have specific functions like `addUser` onto objects with a specific structure. - There were several possible ways to go about implementing aliases and subaliases. - - Two properties on the `Command` class, `aliases: string[]` and `subaliases: {[key: string]: string[]}`. - - Exporting a const named `aliases` which would handle top-level aliases. - - For `subaliases`, either making subaliases work like redirects (Instead of doing `new Command(...)`, you'd do `"nameOfSubcommand"`), or define properties on `Command`. -- What I ended up doing for aliases is making an `aliases` property on `Command` and then converting those string arrays to a more usable structure with strings pointing to the original commands. `aliases` at the very top will determine global aliases and is pretty much the exception in the program's logic. `aliases` elsewhere will provide aliases per subcommand. For anything other than the top-level or `subcommands`, `aliases` does nothing (plus you'll get a warning about it). \ No newline at end of file + - Two properties on the `Command` class, `aliases: string[]` and `subaliases: {[key: string]: string[]}`. + - Exporting a const named `aliases` which would handle top-level aliases. + - For `subaliases`, either making subaliases work like redirects (Instead of doing `new Command(...)`, you'd do `"nameOfSubcommand"`), or define properties on `Command`. +- What I ended up doing for aliases is making an `aliases` property on `Command` and then converting those string arrays to a more usable structure with strings pointing to the original commands. `aliases` at the very top will determine global aliases and is pretty much the exception in the program's logic. `aliases` elsewhere will provide aliases per subcommand. For anything other than the top-level or `subcommands`, `aliases` does nothing (plus you'll get a warning about it). diff --git a/package-lock.json b/package-lock.json index 458d55e..5c0cf99 100644 --- a/package-lock.json +++ b/package-lock.json @@ -610,6 +610,12 @@ "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", "dev": true }, + "husky": { + "version": "5.0.0-alpha.6", + "resolved": "https://registry.npmjs.org/husky/-/husky-5.0.0-alpha.6.tgz", + "integrity": "sha512-Ofqq0oHLCO0r8hTb/1PQ3FAfsW945adUli8jFTeXdOIK8gCUmxY9A0BI0mG9oGboPf+Y53bvEmX6ljdrz+yV6w==", + "dev": true + }, "iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -1032,6 +1038,12 @@ "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", "dev": true }, + "prettier": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.1.2.tgz", + "integrity": "sha512-16c7K+x4qVlJg9rEbXl7HEGmQyZlG4R9AgP+oHKRMsMsuk8s+ATStlf1NpDqyBI1HpVyfjLOeMhH2LvuNvV5Vg==", + "dev": true + }, "promise.allsettled": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/promise.allsettled/-/promise.allsettled-1.0.2.tgz", diff --git a/package.json b/package.json index 2c85b2a..8c53960 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,7 @@ "version": "0.0.1", "description": "A Discord bot built on Discord.JS v12", "main": "dist/index.js", + "private": true, "dependencies": { "chalk": "^4.1.0", "discord.js": "^12.2.0", @@ -14,12 +15,15 @@ "@types/mocha": "^8.0.3", "@types/node": "^14.0.22", "@types/ws": "^7.2.6", + "husky": "^5.0.0-alpha.6", "mocha": "^8.1.2", + "prettier": "2.1.2", "ts-node": "^9.0.0", "tsc-watch": "^4.2.9", "typescript": "^3.9.6" }, "scripts": { + "postinstall": "husky install", "build": "tsc && npm prune --production", "start": "node dist/index.js", "once": "tsc && npm start", diff --git a/prettier.config.js b/prettier.config.js new file mode 100644 index 0000000..7584823 --- /dev/null +++ b/prettier.config.js @@ -0,0 +1,13 @@ +module.exports = { + printWidth: 80, + tabWidth: 2, + useTabs: false, + semi: true, + singleQuote: true, + quoteProps: 'as-needed', + jsxSingleQuote: false, + trailingComma: 'all', + bracketSpacing: true, + jsxBracketSameLine: true, + arrowParens: 'always', +}; diff --git a/src/commands/admin.ts b/src/commands/admin.ts index 651704a..f60f1c5 100644 --- a/src/commands/admin.ts +++ b/src/commands/admin.ts @@ -1,163 +1,188 @@ -import Command from "../core/command"; -import {CommonLibrary, logs, botHasPermission} from "../core/lib"; -import {Config, Storage} from "../core/structures"; -import {PermissionNames, getPermissionLevel} from "../core/permissions"; -import {Permissions} from "discord.js"; +import Command from '../core/command'; +import { CommonLibrary, logs, botHasPermission } from '../core/lib'; +import { Config, Storage } from '../core/structures'; +import { PermissionNames, getPermissionLevel } from '../core/permissions'; +import { Permissions } from 'discord.js'; -function getLogBuffer(type: string) -{ - return {files: [{ - attachment: Buffer.alloc(logs[type].length, logs[type]), - name: `${Date.now()}.${type}.log` - }]}; +function getLogBuffer(type: string) { + return { + files: [ + { + attachment: Buffer.alloc(logs[type].length, logs[type]), + name: `${Date.now()}.${type}.log`, + }, + ], + }; } -const activities = ["playing", "listening", "streaming", "watching"]; +const activities = ['playing', 'listening', 'streaming', 'watching']; export default new Command({ - description: "An all-in-one command to do admin stuff. You need to be either an admin of the server or one of the bot's mechanics to use this command.", - async run($: CommonLibrary): Promise - { - if(!$.member) - return $.channel.send("Couldn't find a member object for you! Did you make sure you used this in a server?"); - const permLevel = getPermissionLevel($.member); - $.channel.send(`${$.author.toString()}, your permission level is \`${PermissionNames[permLevel]}\` (${permLevel}).`); - }, - subcommands: - { - set: new Command({ - description: "Set different per-guild settings for the bot.", - run: "You have to specify the option you want to set.", - permission: Command.PERMISSIONS.ADMIN, - subcommands: - { - prefix: new Command({ - description: "Set a custom prefix for your guild. Removes your custom prefix if none is provided.", - usage: "()", - async run($: CommonLibrary): Promise - { - Storage.getGuild($.guild?.id || "N/A").prefix = null; - Storage.save(); - $.channel.send(`The custom prefix for this guild has been removed. My prefix is now back to \`${Config.prefix}\`.`); - }, - any: new Command({ - async run($: CommonLibrary): Promise - { - Storage.getGuild($.guild?.id || "N/A").prefix = $.args[0]; - Storage.save(); - $.channel.send(`The custom prefix for this guild is now \`${$.args[0]}\`.`); - } - }) - }) - } - }), - diag: new Command({ - description: "Requests a debug log with the \"info\" verbosity level.", - permission: Command.PERMISSIONS.BOT_SUPPORT, - async run($: CommonLibrary): Promise - { - $.channel.send(getLogBuffer("info")); - }, - any: new Command({ - description: `Select a verbosity to listen to. Available levels: \`[${Object.keys(logs).join(", ")}]\``, - async run($: CommonLibrary): Promise - { - const type = $.args[0]; - - if(type in logs) - $.channel.send(getLogBuffer(type)); - else - $.channel.send(`Couldn't find a verbosity level named \`${type}\`! The available types are \`[${Object.keys(logs).join(", ")}]\`.`); - } - }) - }), - status: new Command({ - description: "Changes the bot's status.", - permission: Command.PERMISSIONS.BOT_SUPPORT, - async run($: CommonLibrary): Promise - { - $.channel.send('Setting status to `online`...'); - }, - any: new Command({ - description: `Select a status to set to. Available statuses: \`online\`, \`idle\`, \`dnd\`, \`invisible\``, - async run($: CommonLibrary): Promise - { - let statuses = ['online', 'idle', 'dnd', 'invisible']; - if (!statuses.includes($.args[0])) return $.channel.send("That status doesn't exist!"); - else { - $.client.user?.setStatus($.args[0]); - $.channel.send(`Setting status to \`${$.args[0]}\`...`); - } - } - }) - }), - purge: new Command({ - description: "Purges bot messages.", - permission: Command.PERMISSIONS.BOT_SUPPORT, - async run($: CommonLibrary): Promise - { - $.message.delete(); - const msgs = await $.channel.messages.fetch({ - limit: 100 - }); - const travMessages = msgs.filter(m => m.author.id === $.client.user?.id); + description: + "An all-in-one command to do admin stuff. You need to be either an admin of the server or one of the bot's mechanics to use this command.", + async run($: CommonLibrary): Promise { + if (!$.member) + return $.channel.send( + "Couldn't find a member object for you! Did you make sure you used this in a server?", + ); + const permLevel = getPermissionLevel($.member); + $.channel.send( + `${$.author.toString()}, your permission level is \`${ + PermissionNames[permLevel] + }\` (${permLevel}).`, + ); + }, + subcommands: { + set: new Command({ + description: 'Set different per-guild settings for the bot.', + run: 'You have to specify the option you want to set.', + permission: Command.PERMISSIONS.ADMIN, + subcommands: { + prefix: new Command({ + description: + 'Set a custom prefix for your guild. Removes your custom prefix if none is provided.', + usage: '()', + async run($: CommonLibrary): Promise { + Storage.getGuild($.guild?.id || 'N/A').prefix = null; + Storage.save(); + $.channel.send( + `The custom prefix for this guild has been removed. My prefix is now back to \`${Config.prefix}\`.`, + ); + }, + any: new Command({ + async run($: CommonLibrary): Promise { + Storage.getGuild($.guild?.id || 'N/A').prefix = $.args[0]; + Storage.save(); + $.channel.send( + `The custom prefix for this guild is now \`${$.args[0]}\`.`, + ); + }, + }), + }), + }, + }), + diag: new Command({ + description: 'Requests a debug log with the "info" verbosity level.', + permission: Command.PERMISSIONS.BOT_SUPPORT, + async run($: CommonLibrary): Promise { + $.channel.send(getLogBuffer('info')); + }, + any: new Command({ + description: `Select a verbosity to listen to. Available levels: \`[${Object.keys( + logs, + ).join(', ')}]\``, + async run($: CommonLibrary): Promise { + const type = $.args[0]; - await $.message.channel.send(`Found ${travMessages.size} messages to delete.`) - .then(m => m.delete({ - timeout: 5000 - })); - await $.message.channel.bulkDelete(travMessages); - } - }), - nick: new Command({ - description: "Change the bot's nickname.", - permission: Command.PERMISSIONS.BOT_SUPPORT, - async run($: CommonLibrary): Promise - { - const nickName = $.args.join(" "); - const trav = $.guild?.members.cache.find(member => member.id === $.client.user?.id); - await trav?.setNickname(nickName); - if(botHasPermission($.guild, Permissions.FLAGS.MANAGE_MESSAGES)) - $.message.delete({timeout: 5000}).catch($.handler.bind($)); - $.channel.send(`Nickname set to \`${nickName}\``) - .then(m => m.delete({timeout: 5000})); - } - }), - guilds: new Command({ - description: "Shows a list of all guilds the bot is a member of.", - permission: Command.PERMISSIONS.BOT_SUPPORT, - async run($: CommonLibrary): Promise - { - const guildList = $.client.guilds.cache.array() - .map(e => e.name); - $.channel.send(guildList); - } - }), - activity: new Command({ - description: "Set the activity of the bot.", - permission: Command.PERMISSIONS.BOT_SUPPORT, - usage: " ", - async run($: CommonLibrary): Promise - { - $.client.user?.setActivity(".help", { - type: "LISTENING" - }); - $.channel.send("Activity set to default.") - }, - any: new Command({ - description: `Select an activity type to set. Available levels: \`[${activities.join(", ")}]\``, - async run($: CommonLibrary): Promise - { - const type = $.args[0]; - - if(activities.includes(type)) { - $.client.user?.setActivity($.args.slice(1).join(" "), {type: $.args[0].toUpperCase()}) - $.channel.send(`Set activity to \`${$.args[0].toUpperCase()}\` \`${$.args.slice(1).join(" ")}\`.`) - } - else - $.channel.send(`Couldn't find an activity type named \`${type}\`! The available types are \`[${activities.join(", ")}]\`.`); - } - }) - }) - } -}); \ No newline at end of file + if (type in logs) $.channel.send(getLogBuffer(type)); + else + $.channel.send( + `Couldn't find a verbosity level named \`${type}\`! The available types are \`[${Object.keys( + logs, + ).join(', ')}]\`.`, + ); + }, + }), + }), + status: new Command({ + description: "Changes the bot's status.", + permission: Command.PERMISSIONS.BOT_SUPPORT, + async run($: CommonLibrary): Promise { + $.channel.send('Setting status to `online`...'); + }, + any: new Command({ + description: `Select a status to set to. Available statuses: \`online\`, \`idle\`, \`dnd\`, \`invisible\``, + async run($: CommonLibrary): Promise { + let statuses = ['online', 'idle', 'dnd', 'invisible']; + if (!statuses.includes($.args[0])) + return $.channel.send("That status doesn't exist!"); + else { + $.client.user?.setStatus($.args[0]); + $.channel.send(`Setting status to \`${$.args[0]}\`...`); + } + }, + }), + }), + purge: new Command({ + description: 'Purges bot messages.', + permission: Command.PERMISSIONS.BOT_SUPPORT, + async run($: CommonLibrary): Promise { + $.message.delete(); + const msgs = await $.channel.messages.fetch({ + limit: 100, + }); + const travMessages = msgs.filter( + (m) => m.author.id === $.client.user?.id, + ); + + await $.message.channel + .send(`Found ${travMessages.size} messages to delete.`) + .then((m) => + m.delete({ + timeout: 5000, + }), + ); + await $.message.channel.bulkDelete(travMessages); + }, + }), + nick: new Command({ + description: "Change the bot's nickname.", + permission: Command.PERMISSIONS.BOT_SUPPORT, + async run($: CommonLibrary): Promise { + const nickName = $.args.join(' '); + const trav = $.guild?.members.cache.find( + (member) => member.id === $.client.user?.id, + ); + await trav?.setNickname(nickName); + if (botHasPermission($.guild, Permissions.FLAGS.MANAGE_MESSAGES)) + $.message.delete({ timeout: 5000 }).catch($.handler.bind($)); + $.channel + .send(`Nickname set to \`${nickName}\``) + .then((m) => m.delete({ timeout: 5000 })); + }, + }), + guilds: new Command({ + description: 'Shows a list of all guilds the bot is a member of.', + permission: Command.PERMISSIONS.BOT_SUPPORT, + async run($: CommonLibrary): Promise { + const guildList = $.client.guilds.cache.array().map((e) => e.name); + $.channel.send(guildList); + }, + }), + activity: new Command({ + description: 'Set the activity of the bot.', + permission: Command.PERMISSIONS.BOT_SUPPORT, + usage: ' ', + async run($: CommonLibrary): Promise { + $.client.user?.setActivity('.help', { + type: 'LISTENING', + }); + $.channel.send('Activity set to default.'); + }, + any: new Command({ + description: `Select an activity type to set. Available levels: \`[${activities.join( + ', ', + )}]\``, + async run($: CommonLibrary): Promise { + const type = $.args[0]; + + if (activities.includes(type)) { + $.client.user?.setActivity($.args.slice(1).join(' '), { + type: $.args[0].toUpperCase(), + }); + $.channel.send( + `Set activity to \`${$.args[0].toUpperCase()}\` \`${$.args + .slice(1) + .join(' ')}\`.`, + ); + } else + $.channel.send( + `Couldn't find an activity type named \`${type}\`! The available types are \`[${activities.join( + ', ', + )}]\`.`, + ); + }, + }), + }), + }, +}); diff --git a/src/commands/fun/8ball.ts b/src/commands/fun/8ball.ts index 86a6e62..ebd2a35 100644 --- a/src/commands/fun/8ball.ts +++ b/src/commands/fun/8ball.ts @@ -1,39 +1,38 @@ -import Command from "../../core/command"; -import {CommonLibrary} from "../../core/lib"; +import Command from '../../core/command'; +import { CommonLibrary } from '../../core/lib'; const responses = [ - "Most likely,", - "It is certain,", - "It is decidedly so,", - "Without a doubt,", - "Definitely,", - "You may rely on it,", - "As I see it, yes,", - "Outlook good,", - "Yes,", - "Signs point to yes,", - "Reply hazy, try again,", - "Ask again later,", - "Better not tell you now,", - "Cannot predict now,", - "Concentrate and ask again,", - "Don't count on it,", - "My reply is no,", - "My sources say no,", - "Outlook not so good,", - "Very doubtful," + 'Most likely,', + 'It is certain,', + 'It is decidedly so,', + 'Without a doubt,', + 'Definitely,', + 'You may rely on it,', + 'As I see it, yes,', + 'Outlook good,', + 'Yes,', + 'Signs point to yes,', + 'Reply hazy, try again,', + 'Ask again later,', + 'Better not tell you now,', + 'Cannot predict now,', + 'Concentrate and ask again,', + "Don't count on it,", + 'My reply is no,', + 'My sources say no,', + 'Outlook not so good,', + 'Very doubtful,', ]; export default new Command({ - description: "Answers your question in an 8-ball manner.", - endpoint: false, - usage: "", - run: "Please provide a question.", - any: new Command({ - description: "Question to ask the 8-ball.", - async run($: CommonLibrary): Promise - { - const sender = $.message.author; - $.channel.send($(responses).random() + ` <@${sender.id}>`); - } - }) -}) + description: 'Answers your question in an 8-ball manner.', + endpoint: false, + usage: '', + run: 'Please provide a question.', + any: new Command({ + description: 'Question to ask the 8-ball.', + async run($: CommonLibrary): Promise { + const sender = $.message.author; + $.channel.send($(responses).random() + ` <@${sender.id}>`); + }, + }), +}); diff --git a/src/commands/fun/poll.ts b/src/commands/fun/poll.ts index d1f0e15..0e46f90 100644 --- a/src/commands/fun/poll.ts +++ b/src/commands/fun/poll.ts @@ -1,26 +1,28 @@ -import { MessageEmbed } from "discord.js"; -import Command from "../../core/command"; -import {CommonLibrary} from "../../core/lib"; +import { MessageEmbed } from 'discord.js'; +import Command from '../../core/command'; +import { CommonLibrary } from '../../core/lib'; export default new Command({ - description: "Create a poll.", - usage: "", - run: "Please provide a question.", - any: new Command({ - description: "Question for the poll.", - async run($: CommonLibrary): Promise - { - const embed = new MessageEmbed() - .setAuthor(`Poll created by ${$.message.author.username}`, $.message.guild?.iconURL({ dynamic: true }) ?? undefined) - .setColor(0xffffff) - .setFooter("React to vote.") - .setDescription($.args.join(" ")); - const msg = await $.channel.send(embed); - await msg.react("✅"); - await msg.react("⛔"); - $.message.delete({ - timeout: 1000 - }); - } - }) -}) + description: 'Create a poll.', + usage: '', + run: 'Please provide a question.', + any: new Command({ + description: 'Question for the poll.', + async run($: CommonLibrary): Promise { + const embed = new MessageEmbed() + .setAuthor( + `Poll created by ${$.message.author.username}`, + $.message.guild?.iconURL({ dynamic: true }) ?? undefined, + ) + .setColor(0xffffff) + .setFooter('React to vote.') + .setDescription($.args.join(' ')); + const msg = await $.channel.send(embed); + await msg.react('✅'); + await msg.react('⛔'); + $.message.delete({ + timeout: 1000, + }); + }, + }), +}); diff --git a/src/commands/help.ts b/src/commands/help.ts index b6166a3..3150df7 100644 --- a/src/commands/help.ts +++ b/src/commands/help.ts @@ -1,141 +1,145 @@ -import Command from "../core/command"; -import {CommonLibrary} from "../core/lib"; -import {loadCommands, categories} from "../core/command"; -import {PermissionNames} from "../core/permissions"; +import Command from '../core/command'; +import { CommonLibrary } from '../core/lib'; +import { loadCommands, categories } from '../core/command'; +import { PermissionNames } from '../core/permissions'; export default new Command({ - description: "Lists all commands. If a command is specified, their arguments are listed as well.", - usage: "([command, [subcommand/type], ...])", - aliases: ["h"], - async run($: CommonLibrary): Promise - { - const commands = await loadCommands(); - let output = `Legend: \`\`, \`[list/of/stuff]\`, \`(optional)\`, \`()\`, \`([optional/list/...])\``; - - for(const [category, headers] of categories) - { - output += `\n\n===[ ${category} ]===`; - - for(const header of headers) - { - if(header !== "test") - { - const command = commands.get(header); - - if(!command) - return $.warn(`Command "${header}" of category "${category}" unexpectedly doesn't exist!`); - - output += `\n- \`${header}\`: ${command.description}`; - } - } - } - - $.channel.send(output, {split: true}); - }, - any: new Command({ - async run($: CommonLibrary): Promise - { - const commands = await loadCommands(); - let header = $.args.shift() as string; - let command = commands.get(header); - - if(!command || header === "test") - return $.channel.send(`No command found by the name \`${header}\`!`); + description: + 'Lists all commands. If a command is specified, their arguments are listed as well.', + usage: '([command, [subcommand/type], ...])', + aliases: ['h'], + async run($: CommonLibrary): Promise { + const commands = await loadCommands(); + let output = `Legend: \`\`, \`[list/of/stuff]\`, \`(optional)\`, \`()\`, \`([optional/list/...])\``; - if(command.originalCommandName) - header = command.originalCommandName; - else - $.warn(`originalCommandName isn't defined for ${header}?!`); - - let permLevel = command.permission ?? Command.PERMISSIONS.NONE; - let usage = command.usage; - let invalid = false; - - let selectedCategory = "Unknown"; - - for(const [category, headers] of categories) - { - if(headers.includes(header)) - { - if(selectedCategory !== "Unknown") - $.warn(`Command "${header}" is somehow in multiple categories. This means that the command loading stage probably failed in properly adding categories.`); - else - selectedCategory = category; - } - } - - for(const param of $.args) - { - const type = command.resolve(param); - command = command.get(param); - permLevel = command.permission ?? permLevel; - - switch(type) - { - case Command.TYPES.SUBCOMMAND: header += ` ${command.originalCommandName}`; break; - case Command.TYPES.USER: header += " " ; break; - case Command.TYPES.NUMBER: header += " " ; break; - case Command.TYPES.ANY: header += " " ; break; - default: header += ` ${param}`; break; - } - - if(type === Command.TYPES.NONE) - { - invalid = true; - break; - } - } - - if(invalid) - return $.channel.send(`No command found by the name \`${header}\`!`); - - let append = ""; - - if(usage === "") - { - const list: string[] = []; - - command.subcommands.forEach((subcmd, subtag) => { - // Don't capture duplicates generated from aliases. - if(subcmd.originalCommandName === subtag) { - const customUsage = subcmd.usage ? ` ${subcmd.usage}` : ""; - list.push(`- \`${header} ${subtag}${customUsage}\` - ${subcmd.description}`); - } - }); - - const addDynamicType = (cmd: Command|null, type: string) => { - if(cmd) { - const customUsage = cmd.usage ? ` ${cmd.usage}` : ""; - list.push(`- \`${header} <${type}>${customUsage}\` - ${cmd.description}`); - } - }; - - addDynamicType(command.user, "user"); - addDynamicType(command.number, "number"); - addDynamicType(command.any, "any"); - - append = "Usages:" + (list.length > 0 ? `\n${list.join('\n')}` : " None."); - } - else - append = `Usage: \`${header} ${usage}\``; - - let aliases = "None"; - - if(command.aliases.length > 0) - { - aliases = ""; - - for(let i = 0; i < command.aliases.length; i++) - { - const alias = command.aliases[i]; - aliases += `\`${alias}\``; - - if(i !== command.aliases.length-1) - aliases += ", "; - } - } - - $.channel.send(`Command: \`${header}\`\nAliases: ${aliases}\nCategory: \`${selectedCategory}\`\nPermission Required: \`${PermissionNames[permLevel]}\` (${permLevel})\nDescription: ${command.description}\n${append}`, {split: true}); - } - }) -}); \ No newline at end of file + for (const [category, headers] of categories) { + output += `\n\n===[ ${category} ]===`; + + for (const header of headers) { + if (header !== 'test') { + const command = commands.get(header); + + if (!command) + return $.warn( + `Command "${header}" of category "${category}" unexpectedly doesn't exist!`, + ); + + output += `\n- \`${header}\`: ${command.description}`; + } + } + } + + $.channel.send(output, { split: true }); + }, + any: new Command({ + async run($: CommonLibrary): Promise { + const commands = await loadCommands(); + let header = $.args.shift() as string; + let command = commands.get(header); + + if (!command || header === 'test') + return $.channel.send(`No command found by the name \`${header}\`!`); + + if (command.originalCommandName) header = command.originalCommandName; + else $.warn(`originalCommandName isn't defined for ${header}?!`); + + let permLevel = command.permission ?? Command.PERMISSIONS.NONE; + let usage = command.usage; + let invalid = false; + + let selectedCategory = 'Unknown'; + + for (const [category, headers] of categories) { + if (headers.includes(header)) { + if (selectedCategory !== 'Unknown') + $.warn( + `Command "${header}" is somehow in multiple categories. This means that the command loading stage probably failed in properly adding categories.`, + ); + else selectedCategory = category; + } + } + + for (const param of $.args) { + const type = command.resolve(param); + command = command.get(param); + permLevel = command.permission ?? permLevel; + + switch (type) { + case Command.TYPES.SUBCOMMAND: + header += ` ${command.originalCommandName}`; + break; + case Command.TYPES.USER: + header += ' '; + break; + case Command.TYPES.NUMBER: + header += ' '; + break; + case Command.TYPES.ANY: + header += ' '; + break; + default: + header += ` ${param}`; + break; + } + + if (type === Command.TYPES.NONE) { + invalid = true; + break; + } + } + + if (invalid) + return $.channel.send(`No command found by the name \`${header}\`!`); + + let append = ''; + + if (usage === '') { + const list: string[] = []; + + command.subcommands.forEach((subcmd, subtag) => { + // Don't capture duplicates generated from aliases. + if (subcmd.originalCommandName === subtag) { + const customUsage = subcmd.usage ? ` ${subcmd.usage}` : ''; + list.push( + `- \`${header} ${subtag}${customUsage}\` - ${subcmd.description}`, + ); + } + }); + + const addDynamicType = (cmd: Command | null, type: string) => { + if (cmd) { + const customUsage = cmd.usage ? ` ${cmd.usage}` : ''; + list.push( + `- \`${header} <${type}>${customUsage}\` - ${cmd.description}`, + ); + } + }; + + addDynamicType(command.user, 'user'); + addDynamicType(command.number, 'number'); + addDynamicType(command.any, 'any'); + + append = + 'Usages:' + (list.length > 0 ? `\n${list.join('\n')}` : ' None.'); + } else append = `Usage: \`${header} ${usage}\``; + + let aliases = 'None'; + + if (command.aliases.length > 0) { + aliases = ''; + + for (let i = 0; i < command.aliases.length; i++) { + const alias = command.aliases[i]; + aliases += `\`${alias}\``; + + if (i !== command.aliases.length - 1) aliases += ', '; + } + } + + $.channel.send( + `Command: \`${header}\`\nAliases: ${aliases}\nCategory: \`${selectedCategory}\`\nPermission Required: \`${PermissionNames[permLevel]}\` (${permLevel})\nDescription: ${command.description}\n${append}`, + { split: true }, + ); + }, + }), +}); diff --git a/src/commands/info.ts b/src/commands/info.ts index 7a00557..6ea85f9 100644 --- a/src/commands/info.ts +++ b/src/commands/info.ts @@ -1,124 +1,197 @@ -import { Guild, MessageEmbed } from "discord.js"; -import moment from "moment"; -import Command from "../core/command"; -import {CommonLibrary} from "../core/lib"; -import { verificationLevels, filterLevels, regions, flags } from "../defs/info"; +import { Guild, MessageEmbed } from 'discord.js'; +import moment from 'moment'; +import Command from '../core/command'; +import { CommonLibrary } from '../core/lib'; +import { verificationLevels, filterLevels, regions, flags } from '../defs/info'; export default new Command({ - description: "Command to provide all sorts of info about the current server, a user, etc.", - run: "Please provide an argument.\nFor help, run `%prefix%help info`.", - subcommands: - { - avatar: new Command({ - description: "Shows your own, or another user's avatar.", - usage: "()", - async run($: CommonLibrary): Promise - { - $.channel.send($.author.displayAvatarURL({ dynamic: true, size: 2048 })) - }, - user: new Command({ - description: "Shows your own, or another user's avatar.", - async run($: CommonLibrary): Promise - { - $.channel.send($.args[0].displayAvatarURL({ dynamic: true, size: 2048 })) - } - }), - }), - - guild: new Command({ - description: "Displays info about the current guild.", - async run($: CommonLibrary): Promise - { - if ($.guild) { - const roles = $.guild.roles.cache.sort((a, b) => b.position - a.position).map(role => role.toString()); - const members = $.guild.members.cache; - const channels = $.guild.channels.cache; - const emojis = $.guild.emojis.cache; - - const iconURL = $.guild.iconURL({ dynamic: true }) - const embed = new MessageEmbed() - .setDescription(`**Guild information for __${$.guild.name}__**`) - .setColor('BLUE') - if (iconURL) embed.setThumbnail(iconURL) - .addField('General', [ - `**❯ Name:** ${$.guild.name}`, - `**❯ ID:** ${$.guild.id}`, - `**❯ Owner:** ${$.guild.owner?.user.tag} (${$.guild.ownerID})`, - `**❯ Region:** ${regions[$.guild.region]}`, - `**❯ Boost Tier:** ${$.guild.premiumTier ? `Tier ${$.guild.premiumTier}` : 'None'}`, - `**❯ Explicit Filter:** ${filterLevels[$.guild.explicitContentFilter]}`, - `**❯ Verification Level:** ${verificationLevels[$.guild.verificationLevel]}`, - `**❯ Time Created:** ${moment($.guild.createdTimestamp).format('LT')} ${moment($.guild.createdTimestamp).format('LL')} ${moment($.guild.createdTimestamp).fromNow()})`, - '\u200b' - ]) - .addField('Statistics', [ - `**❯ Role Count:** ${roles.length}`, - `**❯ Emoji Count:** ${emojis.size}`, - `**❯ Regular Emoji Count:** ${emojis.filter(emoji => !emoji.animated).size}`, - `**❯ Animated Emoji Count:** ${emojis.filter(emoji => emoji.animated).size}`, - `**❯ Member Count:** ${$.guild.memberCount}`, - `**❯ Humans:** ${members.filter(member => !member.user.bot).size}`, - `**❯ Bots:** ${members.filter(member => member.user.bot).size}`, - `**❯ Text Channels:** ${channels.filter(channel => channel.type === 'text').size}`, - `**❯ Voice Channels:** ${channels.filter(channel => channel.type === 'voice').size}`, - `**❯ Boost Count:** ${$.guild.premiumSubscriptionCount || '0'}`, - `\u200b` - ]) - .addField('Presence', [ - `**❯ Online:** ${members.filter(member => member.presence.status === 'online').size}`, - `**❯ Idle:** ${members.filter(member => member.presence.status === 'idle').size}`, - `**❯ Do Not Disturb:** ${members.filter(member => member.presence.status === 'dnd').size}`, - `**❯ Offline:** ${members.filter(member => member.presence.status === 'offline').size}`, - '\u200b' - ]) - .addField(`Roles [${roles.length - 1}]`, roles.length < 10 ? roles.join(', ') : roles.length > 10 ? this.client.utils.trimArray(roles) : 'None') - .setTimestamp(); + description: + 'Command to provide all sorts of info about the current server, a user, etc.', + run: 'Please provide an argument.\nFor help, run `%prefix%help info`.', + subcommands: { + avatar: new Command({ + description: "Shows your own, or another user's avatar.", + usage: '()', + async run($: CommonLibrary): Promise { + $.channel.send( + $.author.displayAvatarURL({ dynamic: true, size: 2048 }), + ); + }, + user: new Command({ + description: "Shows your own, or another user's avatar.", + async run($: CommonLibrary): Promise { + $.channel.send( + $.args[0].displayAvatarURL({ dynamic: true, size: 2048 }), + ); + }, + }), + }), - $.channel.send(embed) + guild: new Command({ + description: 'Displays info about the current guild.', + async run($: CommonLibrary): Promise { + if ($.guild) { + const roles = $.guild.roles.cache + .sort((a, b) => b.position - a.position) + .map((role) => role.toString()); + const members = $.guild.members.cache; + const channels = $.guild.channels.cache; + const emojis = $.guild.emojis.cache; - } else { - $.channel.send("Please execute this command in a guild.") - } - } - }) - }, - user: new Command({ - description: "Displays info about mentioned user.", - async run($: CommonLibrary): Promise - { - // Transforms the User object into a GuildMember object of the current guild. - const member = $.guild?.members.resolve($.args[0]); + const iconURL = $.guild.iconURL({ dynamic: true }); + const embed = new MessageEmbed() + .setDescription(`**Guild information for __${$.guild.name}__**`) + .setColor('BLUE'); + if (iconURL) + embed + .setThumbnail(iconURL) + .addField('General', [ + `**❯ Name:** ${$.guild.name}`, + `**❯ ID:** ${$.guild.id}`, + `**❯ Owner:** ${$.guild.owner?.user.tag} (${$.guild.ownerID})`, + `**❯ Region:** ${regions[$.guild.region]}`, + `**❯ Boost Tier:** ${ + $.guild.premiumTier ? `Tier ${$.guild.premiumTier}` : 'None' + }`, + `**❯ Explicit Filter:** ${ + filterLevels[$.guild.explicitContentFilter] + }`, + `**❯ Verification Level:** ${ + verificationLevels[$.guild.verificationLevel] + }`, + `**❯ Time Created:** ${moment($.guild.createdTimestamp).format( + 'LT', + )} ${moment($.guild.createdTimestamp).format('LL')} ${moment( + $.guild.createdTimestamp, + ).fromNow()})`, + '\u200b', + ]) + .addField('Statistics', [ + `**❯ Role Count:** ${roles.length}`, + `**❯ Emoji Count:** ${emojis.size}`, + `**❯ Regular Emoji Count:** ${ + emojis.filter((emoji) => !emoji.animated).size + }`, + `**❯ Animated Emoji Count:** ${ + emojis.filter((emoji) => emoji.animated).size + }`, + `**❯ Member Count:** ${$.guild.memberCount}`, + `**❯ Humans:** ${ + members.filter((member) => !member.user.bot).size + }`, + `**❯ Bots:** ${ + members.filter((member) => member.user.bot).size + }`, + `**❯ Text Channels:** ${ + channels.filter((channel) => channel.type === 'text').size + }`, + `**❯ Voice Channels:** ${ + channels.filter((channel) => channel.type === 'voice').size + }`, + `**❯ Boost Count:** ${$.guild.premiumSubscriptionCount || '0'}`, + `\u200b`, + ]) + .addField('Presence', [ + `**❯ Online:** ${ + members.filter( + (member) => member.presence.status === 'online', + ).size + }`, + `**❯ Idle:** ${ + members.filter((member) => member.presence.status === 'idle') + .size + }`, + `**❯ Do Not Disturb:** ${ + members.filter((member) => member.presence.status === 'dnd') + .size + }`, + `**❯ Offline:** ${ + members.filter( + (member) => member.presence.status === 'offline', + ).size + }`, + '\u200b', + ]) + .addField( + `Roles [${roles.length - 1}]`, + roles.length < 10 + ? roles.join(', ') + : roles.length > 10 + ? this.client.utils.trimArray(roles) + : 'None', + ) + .setTimestamp(); - if(!member) - return $.channel.send("No member object was found by that user! Are you sure you used this command in a server?"); + $.channel.send(embed); + } else { + $.channel.send('Please execute this command in a guild.'); + } + }, + }), + }, + user: new Command({ + description: 'Displays info about mentioned user.', + async run($: CommonLibrary): Promise { + // Transforms the User object into a GuildMember object of the current guild. + const member = $.guild?.members.resolve($.args[0]); - const roles = member.roles.cache - .sort((a: { position: number; }, b: { position: number; }) => b.position - a.position) - .map((role: { toString: () => any; }) => role.toString()) - .slice(0, -1); - // @ts-ignore - Discord.js' typings seem to be outdated here. According to their v12 docs, it's User.fetchFlags() instead of User.flags. - const userFlags = (await member.user.fetchFlags() as UserFlags).toArray(); + if (!member) + return $.channel.send( + 'No member object was found by that user! Are you sure you used this command in a server?', + ); - const embed = new MessageEmbed() - .setThumbnail(member.user.displayAvatarURL({ dynamic: true, size: 512 })) - .setColor(member.displayHexColor || 'BLUE') - .addField('User', [ - `**❯ Username:** ${member.user.username}`, - `**❯ Discriminator:** ${member.user.discriminator}`, - `**❯ ID:** ${member.id}`, - `**❯ Flags:** ${userFlags.length ? userFlags.join(', ') : 'None'}`, - `**❯ Avatar:** [Link to avatar](${member.user.displayAvatarURL({ dynamic: true })})`, - `**❯ Time Created:** ${moment(member.user.createdTimestamp).format('LT')} ${moment(member.user.createdTimestamp).format('LL')} ${moment(member.user.createdTimestamp).fromNow()}`, - `**❯ Status:** ${member.user.presence.status}`, - `**❯ Game:** ${member.user.presence.activities || 'Not playing a game.'}` - ]) - .addField('Member', [ - `**❯ Highest Role:** ${member.roles.highest.id === $.guild?.id ? 'None' : member.roles.highest.name}`, - `**❯ Server Join Date:** ${moment(member.joinedAt).format('LL LTS')}`, - `**❯ Hoist Role:** ${member.roles.hoist ? member.roles.hoist.name : 'None'}`, - `**❯ Roles:** [${roles.length}]: ${roles.length < 10 ? roles.join(', ') : roles.length > 10 ? this.client.utils.trimArray(roles) : 'None'}`, - ]); - $.channel.send(embed) - } - }) -}); \ No newline at end of file + const roles = member.roles.cache + .sort( + (a: { position: number }, b: { position: number }) => + b.position - a.position, + ) + .map((role: { toString: () => any }) => role.toString()) + .slice(0, -1); + // @ts-ignore - Discord.js' typings seem to be outdated here. According to their v12 docs, it's User.fetchFlags() instead of User.flags. + const userFlags = ((await member.user.fetchFlags()) as UserFlags).toArray(); + + const embed = new MessageEmbed() + .setThumbnail( + member.user.displayAvatarURL({ dynamic: true, size: 512 }), + ) + .setColor(member.displayHexColor || 'BLUE') + .addField('User', [ + `**❯ Username:** ${member.user.username}`, + `**❯ Discriminator:** ${member.user.discriminator}`, + `**❯ ID:** ${member.id}`, + `**❯ Flags:** ${userFlags.length ? userFlags.join(', ') : 'None'}`, + `**❯ Avatar:** [Link to avatar](${member.user.displayAvatarURL({ + dynamic: true, + })})`, + `**❯ Time Created:** ${moment(member.user.createdTimestamp).format( + 'LT', + )} ${moment(member.user.createdTimestamp).format('LL')} ${moment( + member.user.createdTimestamp, + ).fromNow()}`, + `**❯ Status:** ${member.user.presence.status}`, + `**❯ Game:** ${ + member.user.presence.activities || 'Not playing a game.' + }`, + ]) + .addField('Member', [ + `**❯ Highest Role:** ${ + member.roles.highest.id === $.guild?.id + ? 'None' + : member.roles.highest.name + }`, + `**❯ Server Join Date:** ${moment(member.joinedAt).format('LL LTS')}`, + `**❯ Hoist Role:** ${ + member.roles.hoist ? member.roles.hoist.name : 'None' + }`, + `**❯ Roles:** [${roles.length}]: ${ + roles.length < 10 + ? roles.join(', ') + : roles.length > 10 + ? this.client.utils.trimArray(roles) + : 'None' + }`, + ]); + $.channel.send(embed); + }, + }), +}); diff --git a/src/commands/money.ts b/src/commands/money.ts index 23b55b7..7abffb9 100644 --- a/src/commands/money.ts +++ b/src/commands/money.ts @@ -1,231 +1,260 @@ -import Command from "../core/command"; -import $, {CommonLibrary} from "../core/lib"; -import {Storage} from "../core/structures"; -import {User} from "discord.js"; +import Command from '../core/command'; +import $, { CommonLibrary } from '../core/lib'; +import { Storage } from '../core/structures'; +import { User } from 'discord.js'; -export function getMoneyEmbed(user: User): object -{ - const profile = Storage.getUser(user.id); - - return {embed: { - color: 0xFFFF00, - author: - { - name: user.username, - icon_url: user.displayAvatarURL({ - format: "png", - dynamic: true - }) - }, - fields: - [ - { - name: "Balance", - value: $(profile.money).pluralise("credit", "s") - } - ] - }}; +export function getMoneyEmbed(user: User): object { + const profile = Storage.getUser(user.id); + + return { + embed: { + color: 0xffff00, + author: { + name: user.username, + icon_url: user.displayAvatarURL({ + format: 'png', + dynamic: true, + }), + }, + fields: [ + { + name: 'Balance', + value: $(profile.money).pluralise('credit', 's'), + }, + ], + }, + }; } -function getSendEmbed(sender: User, receiver: User, amount: number): object -{ - return {embed: { - color: 0xFFFF00, - author: - { - name: sender.username, - icon_url: sender.displayAvatarURL({ - format: "png", - dynamic: true - }) - }, - title: "Transaction", - description: `${sender.toString()} has sent ${$(amount).pluralise("credit", "s")} to ${receiver.toString()}!`, - fields: - [ - { - name: `Sender: ${sender.username}#${sender.discriminator}`, - value: $(Storage.getUser(sender.id).money).pluralise("credit", "s") - }, - { - name: `Receiver: ${receiver.username}#${receiver.discriminator}`, - value: $(Storage.getUser(receiver.id).money).pluralise("credit", "s") - } - ], - footer: - { - text: receiver.username, - icon_url: receiver.displayAvatarURL({ - format: "png", - dynamic: true - }) - } - }}; +function getSendEmbed(sender: User, receiver: User, amount: number): object { + return { + embed: { + color: 0xffff00, + author: { + name: sender.username, + icon_url: sender.displayAvatarURL({ + format: 'png', + dynamic: true, + }), + }, + title: 'Transaction', + description: `${sender.toString()} has sent ${$(amount).pluralise( + 'credit', + 's', + )} to ${receiver.toString()}!`, + fields: [ + { + name: `Sender: ${sender.username}#${sender.discriminator}`, + value: $(Storage.getUser(sender.id).money).pluralise('credit', 's'), + }, + { + name: `Receiver: ${receiver.username}#${receiver.discriminator}`, + value: $(Storage.getUser(receiver.id).money).pluralise('credit', 's'), + }, + ], + footer: { + text: receiver.username, + icon_url: receiver.displayAvatarURL({ + format: 'png', + dynamic: true, + }), + }, + }, + }; } export default new Command({ - description: "See how much money you have. Also provides other commands related to money.", - async run($: CommonLibrary): Promise - { - $.channel.send(getMoneyEmbed($.author)); - }, - subcommands: - { - get: new Command({ - description: "Pick up your daily credits. The cooldown is per user and every 22 hours to allow for some leeway.", - async run($: CommonLibrary): Promise - { - const user = Storage.getUser($.author.id); - const now = Date.now(); - - if(user.lastReceived === -1) - { - user.money = 100; - user.lastReceived = now; - Storage.save(); - $.channel.send("Here's 100 credits to get started, the price of a sandwich in Rookie Harbor.", getMoneyEmbed($.author)); - } - else if(now - user.lastReceived >= 79200000) - { - user.money += 25; - user.lastReceived = now; - Storage.save(); - $.channel.send("Here's your daily 25 credits.", getMoneyEmbed($.author)); - } - else - $.channel.send(`It's too soon to pick up your daily credits. You have about ${((user.lastReceived + 79200000 - now) / 3600000).toFixed(1)} hours to go.`); - } - }), - send: new Command({ - description: "Send money to someone.", - usage: " ", - run: "Who are you sending this money to?", - user: new Command({ - run: "You need to enter an amount you're sending!", - number: new Command({ - async run($: CommonLibrary): Promise - { - const amount = Math.floor($.args[1]); - const author = $.author; - const sender = Storage.getUser(author.id); - const target = $.args[0]; - const receiver = Storage.getUser(target.id); - - if(amount <= 0) - return $.channel.send("You must send at least one credit!"); - else if(sender.money < amount) - return $.channel.send("You don't have enough money to do that!", getMoneyEmbed(author)); - else if(target.id === author.id) - return $.channel.send("You can't send money to yourself!"); - else if(target.bot && process.argv[2] !== "dev") - return $.channel.send("You can't send money to a bot!"); - - sender.money -= amount; - receiver.money += amount; - Storage.save(); - $.channel.send(getSendEmbed(author, target, amount)); - } - }) - }), - number: new Command({ - run: "You must use the format `money send `!" - }), - any: new Command({ - async run($: CommonLibrary): Promise - { - const last = $.args.pop(); - - if(!/\d+/g.test(last) && $.args.length === 0) - return $.channel.send("You need to enter an amount you're sending!"); - - const amount = Math.floor(last); - const author = $.author; - const sender = Storage.getUser(author.id); - - if(amount <= 0) - return $.channel.send("You must send at least one credit!"); - else if(sender.money < amount) - return $.channel.send("You don't have enough money to do that!", getMoneyEmbed(author)); - else if(!$.guild) - return $.channel.send("You have to use this in a server if you want to send money with a username!"); - - const username = $.args.join(" "); - const member = (await $.guild.members.fetch({ - query: username, - limit: 1 - })).first(); - - if(!member) - return $.channel.send(`Couldn't find a user by the name of \`${username}\`! If you want to send money to someone in a different server, you have to use their user ID!`); - else if(member.user.id === author.id) - return $.channel.send("You can't send money to yourself!"); - else if(member.user.bot && process.argv[2] !== "dev") - return $.channel.send("You can't send money to a bot!"); - - const target = member.user; - - $.prompt(await $.channel.send(`Are you sure you want to send ${$(amount).pluralise("credit", "s")} to this person?\n*(This message will automatically be deleted after 10 seconds.)*`, {embed: { - color: "#ffff00", - author: - { - name: `${target.username}#${target.discriminator}`, - icon_url: target.displayAvatarURL({ - format: "png", - dynamic: true - }) - } - }}), $.author.id, () => { - const receiver = Storage.getUser(target.id); - sender.money -= amount; - receiver.money += amount; - Storage.save(); - $.channel.send(getSendEmbed(author, target, amount)); - }); - } - }) - }), - leaderboard: new Command({ - description: "See the richest players tracked by this bot (across servers).", - async run($: CommonLibrary): Promise - { - const users = Storage.users; - const ids = Object.keys(users); - ids.sort((a, b) => users[b].money - users[a].money); - const fields = []; - - for(let i = 0, limit = Math.min(10, ids.length); i < limit; i++) - { - const id = ids[i]; - const user = await $.client.users.fetch(id); - - fields.push({ - name: `#${i+1}. ${user.username}#${user.discriminator}`, - value: $(users[id].money).pluralise("credit", "s") - }); - } - - $.channel.send({embed: { - title: "Top 10 Richest Players", - color: "#ffff00", - fields: fields - }}); - } - }) - }, - user: new Command({ - description: "See how much money someone else has by using their user ID or pinging them.", - async run($: CommonLibrary): Promise - { - $.channel.send(getMoneyEmbed($.args[0])); - } - }), - any: new Command({ - description: "See how much money someone else has by using their username.", - async run($: CommonLibrary): Promise - { - $.callMemberByUsername($.message, $.args.join(" "), member => { - $.channel.send(getMoneyEmbed(member.user)); - }); - } - }) -}); \ No newline at end of file + description: + 'See how much money you have. Also provides other commands related to money.', + async run($: CommonLibrary): Promise { + $.channel.send(getMoneyEmbed($.author)); + }, + subcommands: { + get: new Command({ + description: + 'Pick up your daily credits. The cooldown is per user and every 22 hours to allow for some leeway.', + async run($: CommonLibrary): Promise { + const user = Storage.getUser($.author.id); + const now = Date.now(); + + if (user.lastReceived === -1) { + user.money = 100; + user.lastReceived = now; + Storage.save(); + $.channel.send( + "Here's 100 credits to get started, the price of a sandwich in Rookie Harbor.", + getMoneyEmbed($.author), + ); + } else if (now - user.lastReceived >= 79200000) { + user.money += 25; + user.lastReceived = now; + Storage.save(); + $.channel.send( + "Here's your daily 25 credits.", + getMoneyEmbed($.author), + ); + } else + $.channel.send( + `It's too soon to pick up your daily credits. You have about ${( + (user.lastReceived + 79200000 - now) / + 3600000 + ).toFixed(1)} hours to go.`, + ); + }, + }), + send: new Command({ + description: 'Send money to someone.', + usage: ' ', + run: 'Who are you sending this money to?', + user: new Command({ + run: "You need to enter an amount you're sending!", + number: new Command({ + async run($: CommonLibrary): Promise { + const amount = Math.floor($.args[1]); + const author = $.author; + const sender = Storage.getUser(author.id); + const target = $.args[0]; + const receiver = Storage.getUser(target.id); + + if (amount <= 0) + return $.channel.send('You must send at least one credit!'); + else if (sender.money < amount) + return $.channel.send( + "You don't have enough money to do that!", + getMoneyEmbed(author), + ); + else if (target.id === author.id) + return $.channel.send("You can't send money to yourself!"); + else if (target.bot && process.argv[2] !== 'dev') + return $.channel.send("You can't send money to a bot!"); + + sender.money -= amount; + receiver.money += amount; + Storage.save(); + $.channel.send(getSendEmbed(author, target, amount)); + }, + }), + }), + number: new Command({ + run: 'You must use the format `money send `!', + }), + any: new Command({ + async run($: CommonLibrary): Promise { + const last = $.args.pop(); + + if (!/\d+/g.test(last) && $.args.length === 0) + return $.channel.send( + "You need to enter an amount you're sending!", + ); + + const amount = Math.floor(last); + const author = $.author; + const sender = Storage.getUser(author.id); + + if (amount <= 0) + return $.channel.send('You must send at least one credit!'); + else if (sender.money < amount) + return $.channel.send( + "You don't have enough money to do that!", + getMoneyEmbed(author), + ); + else if (!$.guild) + return $.channel.send( + 'You have to use this in a server if you want to send money with a username!', + ); + + const username = $.args.join(' '); + const member = ( + await $.guild.members.fetch({ + query: username, + limit: 1, + }) + ).first(); + + if (!member) + return $.channel.send( + `Couldn't find a user by the name of \`${username}\`! If you want to send money to someone in a different server, you have to use their user ID!`, + ); + else if (member.user.id === author.id) + return $.channel.send("You can't send money to yourself!"); + else if (member.user.bot && process.argv[2] !== 'dev') + return $.channel.send("You can't send money to a bot!"); + + const target = member.user; + + $.prompt( + await $.channel.send( + `Are you sure you want to send ${$(amount).pluralise( + 'credit', + 's', + )} to this person?\n*(This message will automatically be deleted after 10 seconds.)*`, + { + embed: { + color: '#ffff00', + author: { + name: `${target.username}#${target.discriminator}`, + icon_url: target.displayAvatarURL({ + format: 'png', + dynamic: true, + }), + }, + }, + }, + ), + $.author.id, + () => { + const receiver = Storage.getUser(target.id); + sender.money -= amount; + receiver.money += amount; + Storage.save(); + $.channel.send(getSendEmbed(author, target, amount)); + }, + ); + }, + }), + }), + leaderboard: new Command({ + description: + 'See the richest players tracked by this bot (across servers).', + async run($: CommonLibrary): Promise { + const users = Storage.users; + const ids = Object.keys(users); + ids.sort((a, b) => users[b].money - users[a].money); + const fields = []; + + for (let i = 0, limit = Math.min(10, ids.length); i < limit; i++) { + const id = ids[i]; + const user = await $.client.users.fetch(id); + + fields.push({ + name: `#${i + 1}. ${user.username}#${user.discriminator}`, + value: $(users[id].money).pluralise('credit', 's'), + }); + } + + $.channel.send({ + embed: { + title: 'Top 10 Richest Players', + color: '#ffff00', + fields: fields, + }, + }); + }, + }), + }, + user: new Command({ + description: + 'See how much money someone else has by using their user ID or pinging them.', + async run($: CommonLibrary): Promise { + $.channel.send(getMoneyEmbed($.args[0])); + }, + }), + any: new Command({ + description: 'See how much money someone else has by using their username.', + async run($: CommonLibrary): Promise { + $.callMemberByUsername($.message, $.args.join(' '), (member) => { + $.channel.send(getMoneyEmbed(member.user)); + }); + }, + }), +}); diff --git a/src/commands/scanemotes.ts b/src/commands/scanemotes.ts index 7d33969..107bf6f 100644 --- a/src/commands/scanemotes.ts +++ b/src/commands/scanemotes.ts @@ -1,191 +1,191 @@ import Command from '../core/command'; -import {CommonLibrary} from '../core/lib'; -import moment from "moment"; -import {Collection, TextChannel} from 'discord.js'; +import { CommonLibrary } from '../core/lib'; +import moment from 'moment'; +import { Collection, TextChannel } from 'discord.js'; -const lastUsedTimestamps: {[id: string]: number} = {}; +const lastUsedTimestamps: { [id: string]: number } = {}; export default new Command({ - description: "Scans all text channels in the current guild and returns the number of times each emoji specific to the guild has been used. Has a cooldown of 24 hours per guild.", - async run($: CommonLibrary): Promise - { - if(!$.guild) - return $.channel.send(`You must use this command on a server!`); - - // Test if the command is on cooldown. This isn't the strictest cooldown possible, because in the event that the bot crashes, the cooldown will be reset. But for all intends and purposes, it's a good enough cooldown. It's a per-server cooldown. - const startTime = Date.now(); - const cooldown = 86400000; // 24 hours - const lastUsedTimestamp = lastUsedTimestamps[$.guild.id] ?? 0; - const difference = startTime - lastUsedTimestamp; - const howLong = moment(startTime).to(lastUsedTimestamp + cooldown); - - // If it's been less than an hour since the command was last used, prevent it from executing. - if(difference < cooldown) - return $.channel.send(`This command requires a day to cooldown. You'll be able to activate this command ${howLong}.`); - else - lastUsedTimestamps[$.guild.id] = startTime; - - const stats: {[id: string]: { - name: string, - formatted: string, - users: number, - bots: number - }} = {}; - let totalUserEmoteUsage = 0; - // IMPORTANT: You MUST check if the bot actually has access to the channel in the first place. It will get the list of all channels, but that doesn't mean it has access to every channel. Without this, it'll require admin access and throw an annoying unhelpful DiscordAPIError: Missing Access otherwise. - const allTextChannelsInCurrentGuild = $.guild.channels.cache.filter(channel => channel.type === "text" && channel.viewable) as Collection; - let messagesSearched = 0; - let channelsSearched = 0; - let currentChannelName = ""; - const totalChannels = allTextChannelsInCurrentGuild.size; - const statusMessage = await $.channel.send("Gathering emotes..."); - let warnings = 0; - $.channel.startTyping(); - - // Initialize the emote stats object with every emote in the current guild. - // The goal here is to cut the need to access guild.emojis.get() which'll make it faster and easier to work with. - for(let emote of $.guild.emojis.cache.values()) - { - // If you don't include the "a" for animated emotes, it'll not only not show up, but also cause all other emotes in the same message to not show up. The emote name is self-correcting but it's better to keep the right value since it'll be used to calculate message lengths that fit. - stats[emote.id] = { - name: emote.name, - formatted: `<${emote.animated ? "a" : ""}:${emote.name}:${emote.id}>`, - users: 0, - bots: 0 - }; - }; - - const interval = setInterval(() => { - statusMessage.edit(`Searching channel \`${currentChannelName}\`... (${messagesSearched} messages scanned, ${channelsSearched}/${totalChannels} channels scanned)`); - }, 5000); - - for(const channel of allTextChannelsInCurrentGuild.values()) - { - currentChannelName = channel.name; - let selected = channel.lastMessageID ?? $.message.id; - let continueLoop = true; - - while(continueLoop) - { - // Unfortunately, any kind of .fetch call is limited to 100 items at once by Discord's API. - const messages = await channel.messages.fetch({ - limit: 100, - before: selected - }); - - if(messages.size > 0) - { - for(const msg of messages.values()) - { - // It's very important to not capture an array of matches then do \d+ on each item because emote names can have numbers in them, causing the bot to not count them correctly. - const search = //g; - const text = msg.content; - let match: RegExpExecArray|null; - - while(match = search.exec(text)) - { - const emoteID = match[1]; - - if(emoteID in stats) - { - if(msg.author.bot) - stats[emoteID].bots++; - else - { - stats[emoteID].users++; - totalUserEmoteUsage++; - } - } - } - - for(const reaction of msg.reactions.cache.values()) - { - const emoteID = reaction.emoji.id; - let continueReactionLoop = true; - let lastUserID: string|undefined; - let userReactions = 0; - let botReactions = 0; - - // An emote's ID will be null if it's a unicode emote. - if(emoteID && emoteID in stats) - { - // There is a simple count property on a reaction, but that doesn't separate users from bots. - // So instead, I'll use that property to check for inconsistencies. - while(continueReactionLoop) - { - // After logging users, it seems like the order is strictly numerical. As long as that stays consistent, this should work fine. - const users = await reaction.users.fetch({ - limit: 100, - after: lastUserID - }); - - if(users.size > 0) - { - for(const user of users.values()) - { - if(user.bot) - { - stats[emoteID].bots++; - botReactions++; - } - else - { - stats[emoteID].users++; - totalUserEmoteUsage++; - userReactions++; - } - - lastUserID = user.id; - }; - } - else - { - // Then halt the loop and send warnings of any inconsistencies. - continueReactionLoop = false; - - if(reaction.count !== userReactions + botReactions) - { - $.warn(`[Channel: ${channel.id}, Message: ${msg.id}] A reaction count of ${reaction.count} was expected but was given ${userReactions} user reactions and ${botReactions} bot reactions.`); - warnings++; - } - } - } - } - }; - - selected = msg.id; - messagesSearched++; - }; - } - else - { - continueLoop = false; - channelsSearched++; - } - } - } - - // Mark the operation as ended. - const finishTime = Date.now(); - clearInterval(interval); - statusMessage.edit(`Finished operation in ${moment.duration(finishTime - startTime).humanize()} with ${$(warnings).pluralise("inconsistenc", "ies", "y")}.`); - $.log(`Finished operation in ${finishTime - startTime} ms.`); - $.channel.stopTyping(); - - // Display stats on emote usage. - // This can work outside the loop now that it's synchronous, and now it's clearer what code is meant to execute at the end. - let sortedEmoteIDs = Object.keys(stats).sort((a, b) => stats[b].users - stats[a].users); - const lines: string[] = []; - let rank = 1; - - // It's better to send all the lines at once rather than paginate the data because it's quite a memory-intensive task to search all the messages in a server for it, and I wouldn't want to activate the command again just to get to another page. - for(const emoteID of sortedEmoteIDs) - { - const emote = stats[emoteID]; - const botInfo = emote.bots > 0 ? ` (Bots: ${emote.bots})` : ""; - lines.push(`\`#${rank++}\` ${emote.formatted} x ${emote.users} - ${((emote.users / totalUserEmoteUsage * 100) || 0).toFixed(3)}%` + botInfo); - } - - $.channel.send(lines, {split: true}).catch($.handler.bind($)); - } -}); \ No newline at end of file + description: + 'Scans all text channels in the current guild and returns the number of times each emoji specific to the guild has been used. Has a cooldown of 24 hours per guild.', + async run($: CommonLibrary): Promise { + if (!$.guild) + return $.channel.send(`You must use this command on a server!`); + + // Test if the command is on cooldown. This isn't the strictest cooldown possible, because in the event that the bot crashes, the cooldown will be reset. But for all intends and purposes, it's a good enough cooldown. It's a per-server cooldown. + const startTime = Date.now(); + const cooldown = 86400000; // 24 hours + const lastUsedTimestamp = lastUsedTimestamps[$.guild.id] ?? 0; + const difference = startTime - lastUsedTimestamp; + const howLong = moment(startTime).to(lastUsedTimestamp + cooldown); + + // If it's been less than an hour since the command was last used, prevent it from executing. + if (difference < cooldown) + return $.channel.send( + `This command requires a day to cooldown. You'll be able to activate this command ${howLong}.`, + ); + else lastUsedTimestamps[$.guild.id] = startTime; + + const stats: { + [id: string]: { + name: string; + formatted: string; + users: number; + bots: number; + }; + } = {}; + let totalUserEmoteUsage = 0; + // IMPORTANT: You MUST check if the bot actually has access to the channel in the first place. It will get the list of all channels, but that doesn't mean it has access to every channel. Without this, it'll require admin access and throw an annoying unhelpful DiscordAPIError: Missing Access otherwise. + const allTextChannelsInCurrentGuild = $.guild.channels.cache.filter( + (channel) => channel.type === 'text' && channel.viewable, + ) as Collection; + let messagesSearched = 0; + let channelsSearched = 0; + let currentChannelName = ''; + const totalChannels = allTextChannelsInCurrentGuild.size; + const statusMessage = await $.channel.send('Gathering emotes...'); + let warnings = 0; + $.channel.startTyping(); + + // Initialize the emote stats object with every emote in the current guild. + // The goal here is to cut the need to access guild.emojis.get() which'll make it faster and easier to work with. + for (let emote of $.guild.emojis.cache.values()) { + // If you don't include the "a" for animated emotes, it'll not only not show up, but also cause all other emotes in the same message to not show up. The emote name is self-correcting but it's better to keep the right value since it'll be used to calculate message lengths that fit. + stats[emote.id] = { + name: emote.name, + formatted: `<${emote.animated ? 'a' : ''}:${emote.name}:${emote.id}>`, + users: 0, + bots: 0, + }; + } + + const interval = setInterval(() => { + statusMessage.edit( + `Searching channel \`${currentChannelName}\`... (${messagesSearched} messages scanned, ${channelsSearched}/${totalChannels} channels scanned)`, + ); + }, 5000); + + for (const channel of allTextChannelsInCurrentGuild.values()) { + currentChannelName = channel.name; + let selected = channel.lastMessageID ?? $.message.id; + let continueLoop = true; + + while (continueLoop) { + // Unfortunately, any kind of .fetch call is limited to 100 items at once by Discord's API. + const messages = await channel.messages.fetch({ + limit: 100, + before: selected, + }); + + if (messages.size > 0) { + for (const msg of messages.values()) { + // It's very important to not capture an array of matches then do \d+ on each item because emote names can have numbers in them, causing the bot to not count them correctly. + const search = //g; + const text = msg.content; + let match: RegExpExecArray | null; + + while ((match = search.exec(text))) { + const emoteID = match[1]; + + if (emoteID in stats) { + if (msg.author.bot) stats[emoteID].bots++; + else { + stats[emoteID].users++; + totalUserEmoteUsage++; + } + } + } + + for (const reaction of msg.reactions.cache.values()) { + const emoteID = reaction.emoji.id; + let continueReactionLoop = true; + let lastUserID: string | undefined; + let userReactions = 0; + let botReactions = 0; + + // An emote's ID will be null if it's a unicode emote. + if (emoteID && emoteID in stats) { + // There is a simple count property on a reaction, but that doesn't separate users from bots. + // So instead, I'll use that property to check for inconsistencies. + while (continueReactionLoop) { + // After logging users, it seems like the order is strictly numerical. As long as that stays consistent, this should work fine. + const users = await reaction.users.fetch({ + limit: 100, + after: lastUserID, + }); + + if (users.size > 0) { + for (const user of users.values()) { + if (user.bot) { + stats[emoteID].bots++; + botReactions++; + } else { + stats[emoteID].users++; + totalUserEmoteUsage++; + userReactions++; + } + + lastUserID = user.id; + } + } else { + // Then halt the loop and send warnings of any inconsistencies. + continueReactionLoop = false; + + if (reaction.count !== userReactions + botReactions) { + $.warn( + `[Channel: ${channel.id}, Message: ${msg.id}] A reaction count of ${reaction.count} was expected but was given ${userReactions} user reactions and ${botReactions} bot reactions.`, + ); + warnings++; + } + } + } + } + } + + selected = msg.id; + messagesSearched++; + } + } else { + continueLoop = false; + channelsSearched++; + } + } + } + + // Mark the operation as ended. + const finishTime = Date.now(); + clearInterval(interval); + statusMessage.edit( + `Finished operation in ${moment + .duration(finishTime - startTime) + .humanize()} with ${$(warnings).pluralise( + 'inconsistenc', + 'ies', + 'y', + )}.`, + ); + $.log(`Finished operation in ${finishTime - startTime} ms.`); + $.channel.stopTyping(); + + // Display stats on emote usage. + // This can work outside the loop now that it's synchronous, and now it's clearer what code is meant to execute at the end. + let sortedEmoteIDs = Object.keys(stats).sort( + (a, b) => stats[b].users - stats[a].users, + ); + const lines: string[] = []; + let rank = 1; + + // It's better to send all the lines at once rather than paginate the data because it's quite a memory-intensive task to search all the messages in a server for it, and I wouldn't want to activate the command again just to get to another page. + for (const emoteID of sortedEmoteIDs) { + const emote = stats[emoteID]; + const botInfo = emote.bots > 0 ? ` (Bots: ${emote.bots})` : ''; + lines.push( + `\`#${rank++}\` ${emote.formatted} x ${emote.users} - ${( + (emote.users / totalUserEmoteUsage) * 100 || 0 + ).toFixed(3)}%` + botInfo, + ); + } + + $.channel.send(lines, { split: true }).catch($.handler.bind($)); + }, +}); diff --git a/src/commands/util.ts b/src/commands/util.ts index 1070979..a96b761 100644 --- a/src/commands/util.ts +++ b/src/commands/util.ts @@ -1,58 +1,59 @@ -import { MessageEmbed } from "discord.js"; +import { MessageEmbed } from 'discord.js'; import Command from '../core/command'; -import {CommonLibrary} from '../core/lib'; +import { CommonLibrary } from '../core/lib'; export default new Command({ - description: "Various utilities.", - endpoint: false, - usage: '', - async run($: CommonLibrary): Promise { - - }, - subcommands: { - lsemotes: new Command({ - description: "Lists all emotes the bot has in it's registry,", - endpoint: true, - async run($: CommonLibrary): Promise { - const nsfw: string | string[] = []; - const pages = $.client.emojis.cache.filter(x => !nsfw.includes(x.guild.id), this).array(); - const pagesSplit = $(pages).split(20); - $.log(pagesSplit) - var embed = new MessageEmbed() - .setTitle("**Emoji list!**") - .setColor("AQUA") - let desc = "" - for (const emote of pagesSplit[0]) { - desc += `${emote} | ${emote.name}\n` - } - embed.setDescription(desc) - const msg = await $.channel.send({embed}); + description: 'Various utilities.', + endpoint: false, + usage: '', + async run($: CommonLibrary): Promise {}, + subcommands: { + lsemotes: new Command({ + description: "Lists all emotes the bot has in it's registry,", + endpoint: true, + async run($: CommonLibrary): Promise { + const nsfw: string | string[] = []; + const pages = $.client.emojis.cache + .filter((x) => !nsfw.includes(x.guild.id), this) + .array(); + const pagesSplit = $(pages).split(20); + $.log(pagesSplit); + var embed = new MessageEmbed() + .setTitle('**Emoji list!**') + .setColor('AQUA'); + let desc = ''; + for (const emote of pagesSplit[0]) { + desc += `${emote} | ${emote.name}\n`; + } + embed.setDescription(desc); + const msg = await $.channel.send({ embed }); - $.paginate(msg, $.author.id, pages.length, page => { - let desc = "" - for(const emote of pagesSplit[page]) { - desc += `${emote} | ${emote.name}\n` - } - embed.setDescription(desc) - msg.edit(embed); - }); - } - }), - emote: new Command({ - description: "Send the specified emote.", - run: "Please provide a command name.", - any: new Command({ - description: "The emote to send.", - usage: "", - async run($: CommonLibrary): Promise - { - const search = $.args[0].toLowerCase(); - const emote = $.client.emojis.cache.find(emote => emote.name.toLowerCase().includes(search)); - if (!emote) return $.channel.send("That's not a valid emote name!"); - $.message.delete(); - $.channel.send(`${emote}`); - } - }) - }) - } -}); \ No newline at end of file + $.paginate(msg, $.author.id, pages.length, (page) => { + let desc = ''; + for (const emote of pagesSplit[page]) { + desc += `${emote} | ${emote.name}\n`; + } + embed.setDescription(desc); + msg.edit(embed); + }); + }, + }), + emote: new Command({ + description: 'Send the specified emote.', + run: 'Please provide a command name.', + any: new Command({ + description: 'The emote to send.', + usage: '', + async run($: CommonLibrary): Promise { + const search = $.args[0].toLowerCase(); + const emote = $.client.emojis.cache.find((emote) => + emote.name.toLowerCase().includes(search), + ); + if (!emote) return $.channel.send("That's not a valid emote name!"); + $.message.delete(); + $.channel.send(`${emote}`); + }, + }), + }), + }, +}); diff --git a/src/core/command.ts b/src/core/command.ts index b495154..bdf0b3b 100644 --- a/src/core/command.ts +++ b/src/core/command.ts @@ -1,232 +1,259 @@ -import $, {isType, parseVars, CommonLibrary} from "./lib"; -import {Collection} from "discord.js"; -import {generateHandler} from "./storage"; -import {promises as ffs, existsSync, writeFile} from "fs"; -import {PERMISSIONS} from "./permissions"; -import {getPrefix} from "../core/structures"; +import $, { isType, parseVars, CommonLibrary } from './lib'; +import { Collection } from 'discord.js'; +import { generateHandler } from './storage'; +import { promises as ffs, existsSync, writeFile } from 'fs'; +import { PERMISSIONS } from './permissions'; +import { getPrefix } from '../core/structures'; -interface CommandOptions -{ - description?: string; - endpoint?: boolean; - usage?: string; - permission?: PERMISSIONS|null; - aliases?: string[]; - run?: (($: CommonLibrary) => Promise)|string; - subcommands?: {[key: string]: Command}; - user?: Command; - number?: Command; - any?: Command; +interface CommandOptions { + description?: string; + endpoint?: boolean; + usage?: string; + permission?: PERMISSIONS | null; + aliases?: string[]; + run?: (($: CommonLibrary) => Promise) | string; + subcommands?: { [key: string]: Command }; + user?: Command; + number?: Command; + any?: Command; } -export enum TYPES {SUBCOMMAND, USER, NUMBER, ANY, NONE}; - -export default class Command -{ - public readonly description: string; - public readonly endpoint: boolean; - public readonly usage: string; - public readonly permission: PERMISSIONS|null; - public readonly aliases: string[]; // This is to keep the array intact for parent Command instances to use. It'll also be used when loading top-level aliases. - public originalCommandName: string|null; // If the command is an alias, what's the original name? - public run: (($: CommonLibrary) => Promise)|string; - public readonly subcommands: Collection; // This is the final data structure you'll actually use to work with the commands the aliases point to. - public user: Command|null; - public number: Command|null; - public any: Command|null; - public static readonly TYPES = TYPES; - public static readonly PERMISSIONS = PERMISSIONS; - - constructor(options?: CommandOptions) - { - this.description = options?.description || "No description."; - this.endpoint = options?.endpoint || false; - this.usage = options?.usage || ""; - this.permission = options?.permission ?? null; - this.aliases = options?.aliases ?? []; - this.originalCommandName = null; - this.run = options?.run || "No action was set on this command!"; - this.subcommands = new Collection(); // Populate this collection after setting subcommands. - this.user = options?.user || null; - this.number = options?.number || null; - this.any = options?.any || null; - - if(options?.subcommands) - { - const baseSubcommands = Object.keys(options.subcommands); - - // Loop once to set the base subcommands. - for(const name in options.subcommands) - this.subcommands.set(name, options.subcommands[name]); - - // Then loop again to make aliases point to the base subcommands and warn if something's not right. - // This shouldn't be a problem because I'm hoping that JS stores these as references that point to the same object. - for(const name in options.subcommands) - { - const subcmd = options.subcommands[name]; - subcmd.originalCommandName = name; - const aliases = subcmd.aliases; - - for(const alias of aliases) - { - if(baseSubcommands.includes(alias)) - $.warn(`"${alias}" in subcommand "${name}" was attempted to be declared as an alias but it already exists in the base commands! (Look at the next "Loading Command" line to see which command is affected.)`); - else if(this.subcommands.has(alias)) - $.warn(`Duplicate alias "${alias}" at subcommand "${name}"! (Look at the next "Loading Command" line to see which command is affected.)`); - else - this.subcommands.set(alias, subcmd); - } - } - } - - if(this.user && this.user.aliases.length > 0) - $.warn(`There are aliases defined for a "user"-type subcommand, but those aliases won't be used. (Look at the next "Loading Command" line to see which command is affected.)`); - if(this.number && this.number.aliases.length > 0) - $.warn(`There are aliases defined for a "number"-type subcommand, but those aliases won't be used. (Look at the next "Loading Command" line to see which command is affected.)`); - if(this.any && this.any.aliases.length > 0) - $.warn(`There are aliases defined for an "any"-type subcommand, but those aliases won't be used. (Look at the next "Loading Command" line to see which command is affected.)`); - } - - public execute($: CommonLibrary) - { - if(isType(this.run, String)) - { - $.channel.send(parseVars(this.run as string, { - author: $.author.toString(), - prefix: getPrefix($.guild) - }, "???")); - } - else - (this.run as Function)($).catch($.handler.bind($)); - } - - public resolve(param: string): TYPES - { - if(this.subcommands.has(param)) - return TYPES.SUBCOMMAND; - // Any Discord ID format will automatically format to a user ID. - else if(this.user && (/\d{17,19}/.test(param))) - return TYPES.USER; - // Disallow infinity and allow for 0. - else if(this.number && (Number(param) || param === "0") && !param.includes("Infinity")) - return TYPES.NUMBER; - else if(this.any) - return TYPES.ANY; - else - return TYPES.NONE; - } - - public get(param: string): Command - { - const type = this.resolve(param); - let command: Command; - - switch(type) - { - case TYPES.SUBCOMMAND: command = this.subcommands.get(param) as Command; break; - case TYPES.USER: command = this.user as Command; break; - case TYPES.NUMBER: command = this.number as Command; break; - case TYPES.ANY: command = this.any as Command; break; - default: command = this; break; - } - - return command; - } +export enum TYPES { + SUBCOMMAND, + USER, + NUMBER, + ANY, + NONE, } -let commands: Collection|null = null; +export default class Command { + public readonly description: string; + public readonly endpoint: boolean; + public readonly usage: string; + public readonly permission: PERMISSIONS | null; + public readonly aliases: string[]; // This is to keep the array intact for parent Command instances to use. It'll also be used when loading top-level aliases. + public originalCommandName: string | null; // If the command is an alias, what's the original name? + public run: (($: CommonLibrary) => Promise) | string; + public readonly subcommands: Collection; // This is the final data structure you'll actually use to work with the commands the aliases point to. + public user: Command | null; + public number: Command | null; + public any: Command | null; + public static readonly TYPES = TYPES; + public static readonly PERMISSIONS = PERMISSIONS; + + constructor(options?: CommandOptions) { + this.description = options?.description || 'No description.'; + this.endpoint = options?.endpoint || false; + this.usage = options?.usage || ''; + this.permission = options?.permission ?? null; + this.aliases = options?.aliases ?? []; + this.originalCommandName = null; + this.run = options?.run || 'No action was set on this command!'; + this.subcommands = new Collection(); // Populate this collection after setting subcommands. + this.user = options?.user || null; + this.number = options?.number || null; + this.any = options?.any || null; + + if (options?.subcommands) { + const baseSubcommands = Object.keys(options.subcommands); + + // Loop once to set the base subcommands. + for (const name in options.subcommands) + this.subcommands.set(name, options.subcommands[name]); + + // Then loop again to make aliases point to the base subcommands and warn if something's not right. + // This shouldn't be a problem because I'm hoping that JS stores these as references that point to the same object. + for (const name in options.subcommands) { + const subcmd = options.subcommands[name]; + subcmd.originalCommandName = name; + const aliases = subcmd.aliases; + + for (const alias of aliases) { + if (baseSubcommands.includes(alias)) + $.warn( + `"${alias}" in subcommand "${name}" was attempted to be declared as an alias but it already exists in the base commands! (Look at the next "Loading Command" line to see which command is affected.)`, + ); + else if (this.subcommands.has(alias)) + $.warn( + `Duplicate alias "${alias}" at subcommand "${name}"! (Look at the next "Loading Command" line to see which command is affected.)`, + ); + else this.subcommands.set(alias, subcmd); + } + } + } + + if (this.user && this.user.aliases.length > 0) + $.warn( + `There are aliases defined for a "user"-type subcommand, but those aliases won't be used. (Look at the next "Loading Command" line to see which command is affected.)`, + ); + if (this.number && this.number.aliases.length > 0) + $.warn( + `There are aliases defined for a "number"-type subcommand, but those aliases won't be used. (Look at the next "Loading Command" line to see which command is affected.)`, + ); + if (this.any && this.any.aliases.length > 0) + $.warn( + `There are aliases defined for an "any"-type subcommand, but those aliases won't be used. (Look at the next "Loading Command" line to see which command is affected.)`, + ); + } + + public execute($: CommonLibrary) { + if (isType(this.run, String)) { + $.channel.send( + parseVars( + this.run as string, + { + author: $.author.toString(), + prefix: getPrefix($.guild), + }, + '???', + ), + ); + } else (this.run as Function)($).catch($.handler.bind($)); + } + + public resolve(param: string): TYPES { + if (this.subcommands.has(param)) return TYPES.SUBCOMMAND; + // Any Discord ID format will automatically format to a user ID. + else if (this.user && /\d{17,19}/.test(param)) return TYPES.USER; + // Disallow infinity and allow for 0. + else if ( + this.number && + (Number(param) || param === '0') && + !param.includes('Infinity') + ) + return TYPES.NUMBER; + else if (this.any) return TYPES.ANY; + else return TYPES.NONE; + } + + public get(param: string): Command { + const type = this.resolve(param); + let command: Command; + + switch (type) { + case TYPES.SUBCOMMAND: + command = this.subcommands.get(param) as Command; + break; + case TYPES.USER: + command = this.user as Command; + break; + case TYPES.NUMBER: + command = this.number as Command; + break; + case TYPES.ANY: + command = this.any as Command; + break; + default: + command = this; + break; + } + + return command; + } +} + +let commands: Collection | null = null; export const categories: Collection = new Collection(); export const aliases: Collection = new Collection(); // Top-level aliases only. /** Returns the cache of the commands if it exists and searches the directory if not. */ -export async function loadCommands(): Promise> -{ - if(commands) - return commands; - - if(process.argv[2] === "dev" && !existsSync("src/commands/test.ts")) - writeFile("src/commands/test.ts", template, generateHandler('"test.ts" (testing/template command) successfully generated.')); - - commands = new Collection(); - const dir = await ffs.opendir("dist/commands"); - const listMisc: string[] = []; - let selected; - - // There will only be one level of directory searching (per category). - while(selected = await dir.read()) - { - if(selected.isDirectory()) - { - if(selected.name === "subcommands") - continue; - - const subdir = await ffs.opendir(`dist/commands/${selected.name}`); - const category = $(selected.name).toTitleCase(); - const list: string[] = []; - let cmd; - - while(cmd = await subdir.read()) - { - if(cmd.isDirectory()) - { - if(cmd.name === "subcommands") - continue; - else - $.warn(`You can't have multiple levels of directories! From: "dist/commands/${cmd.name}"`); - } - else - loadCommand(cmd.name, list, selected.name); - } - - subdir.close(); - categories.set(category, list); - } - else - loadCommand(selected.name, listMisc); - } - - dir.close(); - categories.set("Miscellaneous", listMisc); - - return commands; +export async function loadCommands(): Promise> { + if (commands) return commands; + + if (process.argv[2] === 'dev' && !existsSync('src/commands/test.ts')) + writeFile( + 'src/commands/test.ts', + template, + generateHandler( + '"test.ts" (testing/template command) successfully generated.', + ), + ); + + commands = new Collection(); + const dir = await ffs.opendir('dist/commands'); + const listMisc: string[] = []; + let selected; + + // There will only be one level of directory searching (per category). + while ((selected = await dir.read())) { + if (selected.isDirectory()) { + if (selected.name === 'subcommands') continue; + + const subdir = await ffs.opendir(`dist/commands/${selected.name}`); + const category = $(selected.name).toTitleCase(); + const list: string[] = []; + let cmd; + + while ((cmd = await subdir.read())) { + if (cmd.isDirectory()) { + if (cmd.name === 'subcommands') continue; + else + $.warn( + `You can't have multiple levels of directories! From: "dist/commands/${cmd.name}"`, + ); + } else loadCommand(cmd.name, list, selected.name); + } + + subdir.close(); + categories.set(category, list); + } else loadCommand(selected.name, listMisc); + } + + dir.close(); + categories.set('Miscellaneous', listMisc); + + return commands; } -async function loadCommand(filename: string, list: string[], category?: string) -{ - if(!commands) - return $.error(`Function "loadCommand" was called without first initializing commands!`); - - const prefix = category ?? ""; - const header = filename.substring(0, filename.indexOf(".js")); - const command = (await import(`../commands/${prefix}/${header}`)).default as Command|undefined; - - if(!command) - return $.warn(`Command "${header}" has no default export which is a Command instance!`); - - command.originalCommandName = header; - list.push(header); - - if(commands.has(header)) - $.warn(`Command "${header}" already exists! Make sure to make each command uniquely identifiable across categories!`); - else - commands.set(header, command); - - for(const alias of command.aliases) - { - if(commands.has(alias)) - $.warn(`Top-level alias "${alias}" from command "${header}" already exists either as a command or alias!`); - else - commands.set(alias, command); - } - - $.log(`Loading Command: ${header} (${category ? $(category).toTitleCase() : "Miscellaneous"})`); +async function loadCommand( + filename: string, + list: string[], + category?: string, +) { + if (!commands) + return $.error( + `Function "loadCommand" was called without first initializing commands!`, + ); + + const prefix = category ?? ''; + const header = filename.substring(0, filename.indexOf('.js')); + const command = (await import(`../commands/${prefix}/${header}`)).default as + | Command + | undefined; + + if (!command) + return $.warn( + `Command "${header}" has no default export which is a Command instance!`, + ); + + command.originalCommandName = header; + list.push(header); + + if (commands.has(header)) + $.warn( + `Command "${header}" already exists! Make sure to make each command uniquely identifiable across categories!`, + ); + else commands.set(header, command); + + for (const alias of command.aliases) { + if (commands.has(alias)) + $.warn( + `Top-level alias "${alias}" from command "${header}" already exists either as a command or alias!`, + ); + else commands.set(alias, command); + } + + $.log( + `Loading Command: ${header} (${ + category ? $(category).toTitleCase() : 'Miscellaneous' + })`, + ); } // The template should be built with a reductionist mentality. // Provide everything the user needs and then let them remove whatever they want. // That way, they aren't focusing on what's missing, but rather what they need for their command. -const template = -`import Command from '../core/command'; +const template = `import Command from '../core/command'; import {CommonLibrary} from '../core/lib'; export default new Command({ @@ -277,4 +304,4 @@ export default new Command({ } }) -});`; \ No newline at end of file +});`; diff --git a/src/core/event.ts b/src/core/event.ts index 4ede652..fac4bc8 100644 --- a/src/core/event.ts +++ b/src/core/event.ts @@ -1,47 +1,41 @@ -import {Client, ClientEvents, Constants} from "discord.js"; -import Storage from "./storage"; -import $ from "./lib"; +import { Client, ClientEvents, Constants } from 'discord.js'; +import Storage from './storage'; +import $ from './lib'; -interface EventOptions -{ - readonly on?: (...args: ClientEvents[K]) => void; - readonly once?: (...args: ClientEvents[K]) => void; +interface EventOptions { + readonly on?: (...args: ClientEvents[K]) => void; + readonly once?: (...args: ClientEvents[K]) => void; } -export default class Event -{ - private readonly on?: (...args: ClientEvents[K]) => void; - private readonly once?: (...args: ClientEvents[K]) => void; - - constructor(options: EventOptions) - { - this.on = options.on; - this.once = options.once; - } - - // For this function, I'm going to assume that the event is used with the correct arguments and that the event tag is checked in "storage.ts". - public attach(client: Client, event: K) - { - if(this.on) - client.on(event, this.on); - if(this.once) - client.once(event, this.once); - } +export default class Event { + private readonly on?: (...args: ClientEvents[K]) => void; + private readonly once?: (...args: ClientEvents[K]) => void; + + constructor(options: EventOptions) { + this.on = options.on; + this.once = options.once; + } + + // For this function, I'm going to assume that the event is used with the correct arguments and that the event tag is checked in "storage.ts". + public attach(client: Client, event: K) { + if (this.on) client.on(event, this.on); + if (this.once) client.once(event, this.once); + } } -export async function loadEvents(client: Client) -{ - for(const file of Storage.open("dist/events", (filename: string) => filename.endsWith(".js"))) - { - const header = file.substring(0, file.indexOf(".js")); - const event = (await import(`../events/${header}`)).default; - - if((Object.values(Constants.Events) as string[]).includes(header)) - { - event.attach(client, header); - $.log(`Loading Event: ${header}`); - } - else - $.warn(`"${header}" is not a valid event type! Did you misspell it? (Note: If you fixed the issue, delete "dist" because the compiler won't automatically delete any extra files.)`); - } -} \ No newline at end of file +export async function loadEvents(client: Client) { + for (const file of Storage.open('dist/events', (filename: string) => + filename.endsWith('.js'), + )) { + const header = file.substring(0, file.indexOf('.js')); + const event = (await import(`../events/${header}`)).default; + + if ((Object.values(Constants.Events) as string[]).includes(header)) { + event.attach(client, header); + $.log(`Loading Event: ${header}`); + } else + $.warn( + `"${header}" is not a valid event type! Did you misspell it? (Note: If you fixed the issue, delete "dist" because the compiler won't automatically delete any extra files.)`, + ); + } +} diff --git a/src/core/lib.ts b/src/core/lib.ts index 78e1ff9..702bd28 100644 --- a/src/core/lib.ts +++ b/src/core/lib.ts @@ -1,249 +1,323 @@ -import {GenericWrapper, NumberWrapper, StringWrapper, ArrayWrapper} from "./wrappers"; -import {Client, Message, TextChannel, DMChannel, NewsChannel, Guild, User, GuildMember, Permissions} from "discord.js"; -import chalk from "chalk"; -import FileManager from "./storage"; -import {eventListeners} from "../events/messageReactionRemove"; -import {client} from "../index"; +import { + GenericWrapper, + NumberWrapper, + StringWrapper, + ArrayWrapper, +} from './wrappers'; +import { + Client, + Message, + TextChannel, + DMChannel, + NewsChannel, + Guild, + User, + GuildMember, + Permissions, +} from 'discord.js'; +import chalk from 'chalk'; +import FileManager from './storage'; +import { eventListeners } from '../events/messageReactionRemove'; +import { client } from '../index'; /** A type that describes what the library module does. */ -export interface CommonLibrary -{ - // Wrapper Object // - /** Wraps the value you enter with an object that provides extra functionality and provides common utility functions. */ - (value: number): NumberWrapper; - (value: string): StringWrapper; - (value: T[]): ArrayWrapper; - (value: T): GenericWrapper; - - // Common Library Functions // - /** .catch($.handler.bind($)) or .catch(error => $.handler(error)) */ - handler: (error: Error) => void; - log: (...args: any[]) => void; - warn: (...args: any[]) => void; - error: (...args: any[]) => void; - debug: (...args: any[]) => void; - ready: (...args: any[]) => void; - paginate: (message: Message, senderID: string, total: number, callback: (page: number) => void, duration?: number) => void; - prompt: (message: Message, senderID: string, onConfirm: () => void, duration?: number) => void; - getMemberByUsername: (guild: Guild, username: string) => Promise; - callMemberByUsername: (message: Message, username: string, onSuccess: (member: GuildMember) => void) => Promise; - - // Dynamic Properties // - args: any[]; - client: Client; - message: Message; - channel: TextChannel|DMChannel|NewsChannel; - guild: Guild|null; - author: User; - member: GuildMember|null; +export interface CommonLibrary { + // Wrapper Object // + /** Wraps the value you enter with an object that provides extra functionality and provides common utility functions. */ + (value: number): NumberWrapper; + (value: string): StringWrapper; + (value: T[]): ArrayWrapper; + (value: T): GenericWrapper; + + // Common Library Functions // + /** .catch($.handler.bind($)) or .catch(error => $.handler(error)) */ + handler: (error: Error) => void; + log: (...args: any[]) => void; + warn: (...args: any[]) => void; + error: (...args: any[]) => void; + debug: (...args: any[]) => void; + ready: (...args: any[]) => void; + paginate: ( + message: Message, + senderID: string, + total: number, + callback: (page: number) => void, + duration?: number, + ) => void; + prompt: ( + message: Message, + senderID: string, + onConfirm: () => void, + duration?: number, + ) => void; + getMemberByUsername: ( + guild: Guild, + username: string, + ) => Promise; + callMemberByUsername: ( + message: Message, + username: string, + onSuccess: (member: GuildMember) => void, + ) => Promise; + + // Dynamic Properties // + args: any[]; + client: Client; + message: Message; + channel: TextChannel | DMChannel | NewsChannel; + guild: Guild | null; + author: User; + member: GuildMember | null; } export default function $(value: number): NumberWrapper; export default function $(value: string): StringWrapper; export default function $(value: T[]): ArrayWrapper; export default function $(value: T): GenericWrapper; -export default function $(value: any) -{ - if(isType(value, Number)) - return new NumberWrapper(value); - else if(isType(value, String)) - return new StringWrapper(value); - else if(isType(value, Array)) - return new ArrayWrapper(value); - else - return new GenericWrapper(value); +export default function $(value: any) { + if (isType(value, Number)) return new NumberWrapper(value); + else if (isType(value, String)) return new StringWrapper(value); + else if (isType(value, Array)) return new ArrayWrapper(value); + else return new GenericWrapper(value); } // If you use promises, use this function to display the error in chat. // Case #1: await $.channel.send(""); --> Automatically caught by Command.execute(). // Case #2: $.channel.send("").catch($.handler.bind($)); --> Manually caught by the user. -$.handler = function(this: CommonLibrary, error: Error) -{ - if(this) - this.channel.send(`There was an error while trying to execute that command!\`\`\`${error.stack ?? error}\`\`\``); - else - $.warn("No context was attached to $.handler! Make sure to use .catch($.handler.bind($)) or .catch(error => $.handler(error)) instead!"); - - $.error(error); +$.handler = function (this: CommonLibrary, error: Error) { + if (this) + this.channel.send( + `There was an error while trying to execute that command!\`\`\`${ + error.stack ?? error + }\`\`\``, + ); + else + $.warn( + 'No context was attached to $.handler! Make sure to use .catch($.handler.bind($)) or .catch(error => $.handler(error)) instead!', + ); + + $.error(error); }; // Logs with different levels of verbosity. -export const logs: {[type: string]: string} = { - error: "", - warn: "", - info: "", - verbose: "" +export const logs: { [type: string]: string } = { + error: '', + warn: '', + info: '', + verbose: '', }; let enabled = true; -export function setConsoleActivated(activated: boolean) {enabled = activated}; +export function setConsoleActivated(activated: boolean) { + enabled = activated; +} // The custom console. In order of verbosity, error, warn, log, and debug. Ready is a variation of log. // General Purpose Logger $.log = (...args: any[]) => { - if(enabled) - console.log(chalk.white.bgGray(formatTimestamp()), chalk.black.bgWhite("INFO"), ...args); - const text = `[${formatUTCTimestamp()}] [INFO] ${args.join(" ")}\n`; - logs.info += text; - logs.verbose += text; + if (enabled) + console.log( + chalk.white.bgGray(formatTimestamp()), + chalk.black.bgWhite('INFO'), + ...args, + ); + const text = `[${formatUTCTimestamp()}] [INFO] ${args.join(' ')}\n`; + logs.info += text; + logs.verbose += text; }; // "It'll still work, but you should really check up on this." $.warn = (...args: any[]) => { - if(enabled) - console.warn(chalk.white.bgGray(formatTimestamp()), chalk.black.bgYellow("WARN"), ...args); - const text = `[${formatUTCTimestamp()}] [WARN] ${args.join(" ")}\n`; - logs.warn += text; - logs.info += text; - logs.verbose += text; + if (enabled) + console.warn( + chalk.white.bgGray(formatTimestamp()), + chalk.black.bgYellow('WARN'), + ...args, + ); + const text = `[${formatUTCTimestamp()}] [WARN] ${args.join(' ')}\n`; + logs.warn += text; + logs.info += text; + logs.verbose += text; }; // Used for anything which prevents the program from actually running. $.error = (...args: any[]) => { - if(enabled) - console.error(chalk.white.bgGray(formatTimestamp()), chalk.white.bgRed("ERROR"), ...args); - const text = `[${formatUTCTimestamp()}] [ERROR] ${args.join(" ")}\n`; - logs.error += text; - logs.warn += text; - logs.info += text; - logs.verbose += text; + if (enabled) + console.error( + chalk.white.bgGray(formatTimestamp()), + chalk.white.bgRed('ERROR'), + ...args, + ); + const text = `[${formatUTCTimestamp()}] [ERROR] ${args.join(' ')}\n`; + logs.error += text; + logs.warn += text; + logs.info += text; + logs.verbose += text; }; // Be as verbose as possible. If anything might help when debugging an error, then include it. This only shows in your console if you run this with "dev", but you can still get it from "logs.verbose". // $.debug(`core/lib::parseArgs("testing \"in progress\"") = ["testing", "in progress"]`) --> /::(.)() = // Would probably be more suited for debugging program logic rather than function logic, which can be checked using unit tests. $.debug = (...args: any[]) => { - if(process.argv[2] === "dev" && enabled) - console.debug(chalk.white.bgGray(formatTimestamp()), chalk.white.bgBlue("DEBUG"), ...args); - const text = `[${formatUTCTimestamp()}] [DEBUG] ${args.join(" ")}\n`; - logs.verbose += text; + if (process.argv[2] === 'dev' && enabled) + console.debug( + chalk.white.bgGray(formatTimestamp()), + chalk.white.bgBlue('DEBUG'), + ...args, + ); + const text = `[${formatUTCTimestamp()}] [DEBUG] ${args.join(' ')}\n`; + logs.verbose += text; }; // Used once at the start of the program when the bot loads. $.ready = (...args: any[]) => { - if(enabled) - console.log(chalk.white.bgGray(formatTimestamp()), chalk.black.bgGreen("READY"), ...args); - const text = `[${formatUTCTimestamp()}] [READY] ${args.join(" ")}\n`; - logs.info += text; - logs.verbose += text; + if (enabled) + console.log( + chalk.white.bgGray(formatTimestamp()), + chalk.black.bgGreen('READY'), + ...args, + ); + const text = `[${formatUTCTimestamp()}] [READY] ${args.join(' ')}\n`; + logs.info += text; + logs.verbose += text; }; -export function formatTimestamp(now = new Date()) -{ - const year = now.getFullYear(); - const month = (now.getMonth() + 1).toString().padStart(2, '0'); - const day = now.getDate().toString().padStart(2, '0'); - const hour = now.getHours().toString().padStart(2, '0'); - const minute = now.getMinutes().toString().padStart(2, '0'); - const second = now.getSeconds().toString().padStart(2, '0'); - return `${year}-${month}-${day} ${hour}:${minute}:${second}`; +export function formatTimestamp(now = new Date()) { + const year = now.getFullYear(); + const month = (now.getMonth() + 1).toString().padStart(2, '0'); + const day = now.getDate().toString().padStart(2, '0'); + const hour = now.getHours().toString().padStart(2, '0'); + const minute = now.getMinutes().toString().padStart(2, '0'); + const second = now.getSeconds().toString().padStart(2, '0'); + return `${year}-${month}-${day} ${hour}:${minute}:${second}`; } -export function formatUTCTimestamp(now = new Date()) -{ - const year = now.getUTCFullYear(); - const month = (now.getUTCMonth() + 1).toString().padStart(2, '0'); - const day = now.getUTCDate().toString().padStart(2, '0'); - const hour = now.getUTCHours().toString().padStart(2, '0'); - const minute = now.getUTCMinutes().toString().padStart(2, '0'); - const second = now.getUTCSeconds().toString().padStart(2, '0'); - return `${year}-${month}-${day} ${hour}:${minute}:${second}`; +export function formatUTCTimestamp(now = new Date()) { + const year = now.getUTCFullYear(); + const month = (now.getUTCMonth() + 1).toString().padStart(2, '0'); + const day = now.getUTCDate().toString().padStart(2, '0'); + const hour = now.getUTCHours().toString().padStart(2, '0'); + const minute = now.getUTCMinutes().toString().padStart(2, '0'); + const second = now.getUTCSeconds().toString().padStart(2, '0'); + return `${year}-${month}-${day} ${hour}:${minute}:${second}`; } -export function botHasPermission(guild: Guild|null, permission: number): boolean -{ - return !!(client.user && guild?.members.resolve(client.user)?.hasPermission(permission)) +export function botHasPermission( + guild: Guild | null, + permission: number, +): boolean { + return !!( + client.user && + guild?.members.resolve(client.user)?.hasPermission(permission) + ); } // Pagination function that allows for customization via a callback. // Define your own pages outside the function because this only manages the actual turning of pages. -$.paginate = async(message: Message, senderID: string, total: number, callback: (page: number) => void, duration = 60000) => { - let page = 0; - const turn = (amount: number) => { - page += amount; - - if(page < 0) - page += total; - else if(page >= total) - page -= total; - - callback(page); - } - const handle = (emote: string, reacterID: string) => { - switch(emote) - { - case '⬅️': turn(-1); break; - case '➡️': turn(1); break; - } - }; +$.paginate = async ( + message: Message, + senderID: string, + total: number, + callback: (page: number) => void, + duration = 60000, +) => { + let page = 0; + const turn = (amount: number) => { + page += amount; - // Listen for reactions and call the handler. - await message.react('⬅️'); - await message.react('➡️'); - eventListeners.set(message.id, handle); - await message.awaitReactions((reaction, user) => { - if(user.id === senderID) - { - // The reason this is inside the call is because it's possible to switch a user's permissions halfway and suddenly throw an error. - // This will dynamically adjust for that, switching modes depending on whether it currently has the "Manage Messages" permission. - const canDeleteEmotes = botHasPermission(message.guild, Permissions.FLAGS.MANAGE_MESSAGES); - handle(reaction.emoji.name, user.id); - - if(canDeleteEmotes) - reaction.users.remove(user); - } - return false; - }, {time: duration}); - // When time's up, remove the bot's own reactions. - eventListeners.delete(message.id); - message.reactions.cache.get('⬅️')?.users.remove(message.author); - message.reactions.cache.get('➡️')?.users.remove(message.author); + if (page < 0) page += total; + else if (page >= total) page -= total; + + callback(page); + }; + const handle = (emote: string, reacterID: string) => { + switch (emote) { + case '⬅️': + turn(-1); + break; + case '➡️': + turn(1); + break; + } + }; + + // Listen for reactions and call the handler. + await message.react('⬅️'); + await message.react('➡️'); + eventListeners.set(message.id, handle); + await message.awaitReactions( + (reaction, user) => { + if (user.id === senderID) { + // The reason this is inside the call is because it's possible to switch a user's permissions halfway and suddenly throw an error. + // This will dynamically adjust for that, switching modes depending on whether it currently has the "Manage Messages" permission. + const canDeleteEmotes = botHasPermission( + message.guild, + Permissions.FLAGS.MANAGE_MESSAGES, + ); + handle(reaction.emoji.name, user.id); + + if (canDeleteEmotes) reaction.users.remove(user); + } + return false; + }, + { time: duration }, + ); + // When time's up, remove the bot's own reactions. + eventListeners.delete(message.id); + message.reactions.cache.get('⬅️')?.users.remove(message.author); + message.reactions.cache.get('➡️')?.users.remove(message.author); }; // Waits for the sender to either confirm an action or let it pass (and delete the message). -$.prompt = async(message: Message, senderID: string, onConfirm: () => void, duration = 10000) => { - let isDeleted = false; - - message.react('✅'); - await message.awaitReactions((reaction, user) => { - if(user.id === senderID) - { - if(reaction.emoji.name === '✅') - onConfirm(); - isDeleted = true; - message.delete(); - } - - // CollectorFilter requires a boolean to be returned. - // My guess is that the return value of awaitReactions can be altered by making a boolean filter. - // However, because that's not my concern with this command, I don't have to worry about it. - // May as well just set it to false because I'm not concerned with collecting any reactions. - return false; - }, {time: duration}); - - if(!isDeleted) - message.delete(); +$.prompt = async ( + message: Message, + senderID: string, + onConfirm: () => void, + duration = 10000, +) => { + let isDeleted = false; + + message.react('✅'); + await message.awaitReactions( + (reaction, user) => { + if (user.id === senderID) { + if (reaction.emoji.name === '✅') onConfirm(); + isDeleted = true; + message.delete(); + } + + // CollectorFilter requires a boolean to be returned. + // My guess is that the return value of awaitReactions can be altered by making a boolean filter. + // However, because that's not my concern with this command, I don't have to worry about it. + // May as well just set it to false because I'm not concerned with collecting any reactions. + return false; + }, + { time: duration }, + ); + + if (!isDeleted) message.delete(); }; -$.getMemberByUsername = async(guild: Guild, username: string) => { - return (await guild.members.fetch({ - query: username, - limit: 1 - })).first(); +$.getMemberByUsername = async (guild: Guild, username: string) => { + return ( + await guild.members.fetch({ + query: username, + limit: 1, + }) + ).first(); }; /** Convenience function to handle false cases automatically. */ -$.callMemberByUsername = async(message: Message, username: string, onSuccess: (member: GuildMember) => void) => { - const guild = message.guild; - const send = message.channel.send; - - if(guild) - { - const member = await $.getMemberByUsername(guild, username); - - if(member) - onSuccess(member); - else - send(`Couldn't find a user by the name of \`${username}\`!`); - } - else - send("You must execute this command in a server!"); +$.callMemberByUsername = async ( + message: Message, + username: string, + onSuccess: (member: GuildMember) => void, +) => { + const guild = message.guild; + const send = message.channel.send; + + if (guild) { + const member = await $.getMemberByUsername(guild, username); + + if (member) onSuccess(member); + else send(`Couldn't find a user by the name of \`${username}\`!`); + } else send('You must execute this command in a server!'); }; /** @@ -251,41 +325,29 @@ $.callMemberByUsername = async(message: Message, username: string, onSuccess: (m * - `\"` = `"` * - `\\` = `\` */ -export function parseArgs(line: string): string[] -{ - let result = []; - let selection = ""; - let inString = false; - let isEscaped = false; - - for(let c of line) - { - if(isEscaped) - { - if(['"', '\\'].includes(c)) - selection += c; - else - selection += '\\' + c; - - isEscaped = false; - } - else if(c === '\\') - isEscaped = true; - else if(c === '"') - inString = !inString; - else if(c === ' ' && !inString) - { - result.push(selection); - selection = ""; - } - else - selection += c; - } - - if(selection.length > 0) - result.push(selection) - - return result; +export function parseArgs(line: string): string[] { + let result = []; + let selection = ''; + let inString = false; + let isEscaped = false; + + for (let c of line) { + if (isEscaped) { + if (['"', '\\'].includes(c)) selection += c; + else selection += '\\' + c; + + isEscaped = false; + } else if (c === '\\') isEscaped = true; + else if (c === '"') inString = !inString; + else if (c === ' ' && !inString) { + result.push(selection); + selection = ''; + } else selection += c; + } + + if (selection.length > 0) result.push(selection); + + return result; } /** @@ -294,52 +356,41 @@ export function parseArgs(line: string): string[] * - `%%` = `%` * - If the invalid token is null/undefined, nothing is changed. */ -export function parseVars(line: string, definitions: {[key: string]: string}, invalid: string|null = ""): string -{ - let result = ""; - let inVariable = false; - let token = ""; - - for(const c of line) - { - if(c === '%') - { - if(inVariable) - { - if(token === "") - result += '%'; - else - { - if(token in definitions) - result += definitions[token]; - else if(invalid === null) - result += `%${token}%`; - else - result += invalid; - - token = ""; - } - } - - inVariable = !inVariable; - } - else if(inVariable) - token += c; - else - result += c; - } - - return result; +export function parseVars( + line: string, + definitions: { [key: string]: string }, + invalid: string | null = '', +): string { + let result = ''; + let inVariable = false; + let token = ''; + + for (const c of line) { + if (c === '%') { + if (inVariable) { + if (token === '') result += '%'; + else { + if (token in definitions) result += definitions[token]; + else if (invalid === null) result += `%${token}%`; + else result += invalid; + + token = ''; + } + } + + inVariable = !inVariable; + } else if (inVariable) token += c; + else result += c; + } + + return result; } -export function isType(value: any, type: any): boolean -{ - if(value === undefined && type === undefined) - return true; - else if(value === null && type === null) - return true; - else - return value !== undefined && value !== null && value.constructor === type; +export function isType(value: any, type: any): boolean { + if (value === undefined && type === undefined) return true; + else if (value === null && type === null) return true; + else + return value !== undefined && value !== null && value.constructor === type; } /** @@ -348,54 +399,48 @@ export function isType(value: any, type: any): boolean * If at any point the value doesn't match the data structure provided, the fallback is returned. * Warning: Type checking is based on the fallback's type. Be sure that the "type" parameter is accurate to this! */ -export function select(value: any, fallback: T, type: Function, isArray = false): T -{ - if(isArray && isType(value, Array)) - { - for(let item of value) - if(!isType(item, type)) - return fallback; - return value; - } - else - { - if(isType(value, type)) - return value; - else - return fallback; - } +export function select( + value: any, + fallback: T, + type: Function, + isArray = false, +): T { + if (isArray && isType(value, Array)) { + for (let item of value) if (!isType(item, type)) return fallback; + return value; + } else { + if (isType(value, type)) return value; + else return fallback; + } } -export interface GenericJSON -{ - [key: string]: any; +export interface GenericJSON { + [key: string]: any; } -export abstract class GenericStructure -{ - private __meta__ = "generic"; - - constructor(tag?: string) - { - this.__meta__ = tag || this.__meta__; - } - - public save(asynchronous = true) - { - const tag = this.__meta__; - /// @ts-ignore - delete this.__meta__; - FileManager.write(tag, this, asynchronous); - this.__meta__ = tag; - } +export abstract class GenericStructure { + private __meta__ = 'generic'; + + constructor(tag?: string) { + this.__meta__ = tag || this.__meta__; + } + + public save(asynchronous = true) { + const tag = this.__meta__; + /// @ts-ignore + delete this.__meta__; + FileManager.write(tag, this, asynchronous); + this.__meta__ = tag; + } } // A 50% chance would be "Math.random() < 0.5" because Math.random() can be [0, 1), so to make two equal ranges, you'd need [0, 0.5)U[0.5, 1). // Similar logic would follow for any other percentage. Math.random() < 1 is always true (100% chance) and Math.random() < 0 is always false (0% chance). export const Random = { - num: (min: number, max: number) => (Math.random() * (max - min)) + min, - int: (min: number, max: number) => Math.floor(Random.num(min, max)), - chance: (decimal: number) => Math.random() < decimal, - sign: (number = 1) => number * (Random.chance(0.5) ? -1 : 1), - deviation: (base: number, deviation: number) => Random.num(base - deviation, base + deviation) -}; \ No newline at end of file + num: (min: number, max: number) => Math.random() * (max - min) + min, + int: (min: number, max: number) => Math.floor(Random.num(min, max)), + chance: (decimal: number) => Math.random() < decimal, + sign: (number = 1) => number * (Random.chance(0.5) ? -1 : 1), + deviation: (base: number, deviation: number) => + Random.num(base - deviation, base + deviation), +}; diff --git a/src/core/permissions.ts b/src/core/permissions.ts index 8935b6c..d190526 100644 --- a/src/core/permissions.ts +++ b/src/core/permissions.ts @@ -1,63 +1,80 @@ -import {GuildMember, Permissions} from "discord.js"; -import {Config} from "./structures"; -import $ from "./lib"; +import { GuildMember, Permissions } from 'discord.js'; +import { Config } from './structures'; +import $ from './lib'; -export enum PERMISSIONS {NONE, MOD, ADMIN, OWNER, BOT_SUPPORT, BOT_ADMIN, BOT_OWNER}; -export const PermissionNames = ["User", "Moderator", "Administrator", "Server Owner", "Bot Support", "Bot Admin", "Bot Owner"]; +export enum PERMISSIONS { + NONE, + MOD, + ADMIN, + OWNER, + BOT_SUPPORT, + BOT_ADMIN, + BOT_OWNER, +} +export const PermissionNames = [ + 'User', + 'Moderator', + 'Administrator', + 'Server Owner', + 'Bot Support', + 'Bot Admin', + 'Bot Owner', +]; // Here is where you enter in the functions that check for permissions. const PermissionChecker: ((member: GuildMember) => boolean)[] = [ - // NONE // - () => true, - - // MOD // - member => - member.hasPermission(Permissions.FLAGS.MANAGE_ROLES) || - member.hasPermission(Permissions.FLAGS.MANAGE_MESSAGES) || - member.hasPermission(Permissions.FLAGS.KICK_MEMBERS) || - member.hasPermission(Permissions.FLAGS.BAN_MEMBERS), - - // ADMIN // - member => member.hasPermission(Permissions.FLAGS.ADMINISTRATOR), - - // OWNER // - member => member.guild.ownerID === member.id, - - // BOT_SUPPORT // - member => Config.support.includes(member.id), - - // BOT_ADMIN // - member => Config.admins.includes(member.id), - - // BOT_OWNER // - member => Config.owner === member.id + // NONE // + () => true, + + // MOD // + (member) => + member.hasPermission(Permissions.FLAGS.MANAGE_ROLES) || + member.hasPermission(Permissions.FLAGS.MANAGE_MESSAGES) || + member.hasPermission(Permissions.FLAGS.KICK_MEMBERS) || + member.hasPermission(Permissions.FLAGS.BAN_MEMBERS), + + // ADMIN // + (member) => member.hasPermission(Permissions.FLAGS.ADMINISTRATOR), + + // OWNER // + (member) => member.guild.ownerID === member.id, + + // BOT_SUPPORT // + (member) => Config.support.includes(member.id), + + // BOT_ADMIN // + (member) => Config.admins.includes(member.id), + + // BOT_OWNER // + (member) => Config.owner === member.id, ]; // After checking the lengths of these three objects, use this as the length for consistency. const length = Object.keys(PERMISSIONS).length / 2; -export function hasPermission(member: GuildMember, permission: PERMISSIONS): boolean -{ - for(let i = length-1; i >= permission; i--) - if(PermissionChecker[i](member)) - return true; - return false; +export function hasPermission( + member: GuildMember, + permission: PERMISSIONS, +): boolean { + for (let i = length - 1; i >= permission; i--) + if (PermissionChecker[i](member)) return true; + return false; } -export function getPermissionLevel(member: GuildMember): number -{ - for(let i = length-1; i >= 0; i--) - if(PermissionChecker[i](member)) - return i; - return 0; +export function getPermissionLevel(member: GuildMember): number { + for (let i = length - 1; i >= 0; i--) + if (PermissionChecker[i](member)) return i; + return 0; } // Length Checking (() => { - const lenNames = PermissionNames.length; - const lenChecker = PermissionChecker.length; - - // By transitive property, lenNames and lenChecker have to be equal to each other as well. - if(length !== lenNames || length !== lenChecker) - $.error(`Permission object lengths aren't equal! Enum Length (${length}), Names Length (${lenNames}), and Functions Length (${lenChecker}). This WILL cause problems!`); -})() \ No newline at end of file + const lenNames = PermissionNames.length; + const lenChecker = PermissionChecker.length; + + // By transitive property, lenNames and lenChecker have to be equal to each other as well. + if (length !== lenNames || length !== lenChecker) + $.error( + `Permission object lengths aren't equal! Enum Length (${length}), Names Length (${lenNames}), and Functions Length (${lenChecker}). This WILL cause problems!`, + ); +})(); diff --git a/src/core/storage.ts b/src/core/storage.ts index 0bf790f..8048993 100644 --- a/src/core/storage.ts +++ b/src/core/storage.ts @@ -1,84 +1,85 @@ -import fs from "fs"; -import $ from "./lib"; +import fs from 'fs'; +import $ from './lib'; const Storage = { - read(header: string): object - { - this.open("data"); - const path = `data/${header}.json`; - let data = {}; - - if(fs.existsSync(path)) - { - const file = fs.readFileSync(path, "utf-8"); - - try - { - data = JSON.parse(file); - } - catch(error) - { - if(process.argv[2] !== "dev") - { - $.warn(`Malformed JSON data (header: ${header}), backing it up.`, file); - fs.writeFile(`${path}.backup`, file, generateHandler(`Backup file of "${header}" successfully written as ${file}.`)); - } - } - } - - return data; - }, - write(header: string, data: object, asynchronous = true) - { - this.open("data"); - const path = `data/${header}.json`; - - if(process.argv[2] === "dev" || header === "config") - { - const result = JSON.stringify(data, null, '\t'); - - if(asynchronous) - fs.writeFile(path, result, generateHandler(`"${header}" sucessfully spaced and written.`)); - else - fs.writeFileSync(path, result); - } - else - { - const result = JSON.stringify(data); - - if(asynchronous) - fs.writeFile(path, result, generateHandler(`"${header}" sucessfully written.`)); - else - fs.writeFileSync(path, result); - } - }, - open(path: string, filter?: (value: string, index: number, array: string[]) => unknown): string[] - { - if(!fs.existsSync(path)) - fs.mkdirSync(path); - - let directory = fs.readdirSync(path); - - if(filter) - directory = directory.filter(filter); - - return directory; - }, - close(path: string) - { - if(fs.existsSync(path) && fs.readdirSync(path).length === 0) - fs.rmdir(path, generateHandler(`"${path}" successfully closed.`)); - } + read(header: string): object { + this.open('data'); + const path = `data/${header}.json`; + let data = {}; + + if (fs.existsSync(path)) { + const file = fs.readFileSync(path, 'utf-8'); + + try { + data = JSON.parse(file); + } catch (error) { + if (process.argv[2] !== 'dev') { + $.warn( + `Malformed JSON data (header: ${header}), backing it up.`, + file, + ); + fs.writeFile( + `${path}.backup`, + file, + generateHandler( + `Backup file of "${header}" successfully written as ${file}.`, + ), + ); + } + } + } + + return data; + }, + write(header: string, data: object, asynchronous = true) { + this.open('data'); + const path = `data/${header}.json`; + + if (process.argv[2] === 'dev' || header === 'config') { + const result = JSON.stringify(data, null, '\t'); + + if (asynchronous) + fs.writeFile( + path, + result, + generateHandler(`"${header}" sucessfully spaced and written.`), + ); + else fs.writeFileSync(path, result); + } else { + const result = JSON.stringify(data); + + if (asynchronous) + fs.writeFile( + path, + result, + generateHandler(`"${header}" sucessfully written.`), + ); + else fs.writeFileSync(path, result); + } + }, + open( + path: string, + filter?: (value: string, index: number, array: string[]) => unknown, + ): string[] { + if (!fs.existsSync(path)) fs.mkdirSync(path); + + let directory = fs.readdirSync(path); + + if (filter) directory = directory.filter(filter); + + return directory; + }, + close(path: string) { + if (fs.existsSync(path) && fs.readdirSync(path).length === 0) + fs.rmdir(path, generateHandler(`"${path}" successfully closed.`)); + }, }; -export function generateHandler(message: string) -{ - return (error: Error|null) => { - if(error) - $.error(error); - else - $.debug(message); - }; -}; +export function generateHandler(message: string) { + return (error: Error | null) => { + if (error) $.error(error); + else $.debug(message); + }; +} -export default Storage; \ No newline at end of file +export default Storage; diff --git a/src/core/structures.ts b/src/core/structures.ts index 4bf75fa..e958714 100644 --- a/src/core/structures.ts +++ b/src/core/structures.ts @@ -1,123 +1,112 @@ -import FileManager from "./storage"; -import $, {select, GenericJSON, GenericStructure} from "./lib"; -import {watch} from "fs"; -import {Guild as DiscordGuild} from "discord.js"; +import FileManager from './storage'; +import $, { select, GenericJSON, GenericStructure } from './lib'; +import { watch } from 'fs'; +import { Guild as DiscordGuild } from 'discord.js'; -class ConfigStructure extends GenericStructure -{ - public token: string; - public prefix: string; - public owner: string; - public admins: string[]; - public support: string[]; - - constructor(data: GenericJSON) - { - super("config"); - this.token = select(data.token, "", String); - this.prefix = select(data.prefix, "$", String); - this.owner = select(data.owner, "", String); - this.admins = select(data.admins, [], String, true); - this.support = select(data.support, [], String, true); - } +class ConfigStructure extends GenericStructure { + public token: string; + public prefix: string; + public owner: string; + public admins: string[]; + public support: string[]; + + constructor(data: GenericJSON) { + super('config'); + this.token = select(data.token, '', String); + this.prefix = select(data.prefix, '$', String); + this.owner = select(data.owner, '', String); + this.admins = select(data.admins, [], String, true); + this.support = select(data.support, [], String, true); + } } -class User -{ - public money: number; - public lastReceived: number; - - constructor(data?: GenericJSON) - { - this.money = select(data?.money, 0, Number); - this.lastReceived = select(data?.lastReceived, -1, Number); - } +class User { + public money: number; + public lastReceived: number; + + constructor(data?: GenericJSON) { + this.money = select(data?.money, 0, Number); + this.lastReceived = select(data?.lastReceived, -1, Number); + } } -class Guild -{ - public prefix: string|null; - - constructor(data?: GenericJSON) - { - this.prefix = select(data?.prefix, null, String); - } +class Guild { + public prefix: string | null; + + constructor(data?: GenericJSON) { + this.prefix = select(data?.prefix, null, String); + } } -class StorageStructure extends GenericStructure -{ - public users: {[id: string]: User}; - public guilds: {[id: string]: Guild}; - - constructor(data: GenericJSON) - { - super("storage"); - this.users = {}; - this.guilds = {}; - - for(let id in data.users) - if(/\d{17,19}/g.test(id)) - this.users[id] = new User(data.users[id]); - - for(let id in data.guilds) - if(/\d{17,19}/g.test(id)) - this.guilds[id] = new Guild(data.guilds[id]); - } - - /** Gets a user's profile if they exist and generate one if not. */ - public getUser(id: string): User - { - if(!/\d{17,19}/g.test(id)) - $.warn(`"${id}" is not a valid user ID! It will be erased when the data loads again.`); - - if(id in this.users) - return this.users[id]; - else - { - const user = new User(); - this.users[id] = user; - return user; - } - } - - /** Gets a guild's settings if they exist and generate one if not. */ - public getGuild(id: string): Guild - { - if(!/\d{17,19}/g.test(id)) - $.warn(`"${id}" is not a valid guild ID! It will be erased when the data loads again.`); - - if(id in this.guilds) - return this.guilds[id]; - else - { - const guild = new Guild(); - this.guilds[id] = guild; - return guild; - } - } +class StorageStructure extends GenericStructure { + public users: { [id: string]: User }; + public guilds: { [id: string]: Guild }; + + constructor(data: GenericJSON) { + super('storage'); + this.users = {}; + this.guilds = {}; + + for (let id in data.users) + if (/\d{17,19}/g.test(id)) this.users[id] = new User(data.users[id]); + + for (let id in data.guilds) + if (/\d{17,19}/g.test(id)) this.guilds[id] = new Guild(data.guilds[id]); + } + + /** Gets a user's profile if they exist and generate one if not. */ + public getUser(id: string): User { + if (!/\d{17,19}/g.test(id)) + $.warn( + `"${id}" is not a valid user ID! It will be erased when the data loads again.`, + ); + + if (id in this.users) return this.users[id]; + else { + const user = new User(); + this.users[id] = user; + return user; + } + } + + /** Gets a guild's settings if they exist and generate one if not. */ + public getGuild(id: string): Guild { + if (!/\d{17,19}/g.test(id)) + $.warn( + `"${id}" is not a valid guild ID! It will be erased when the data loads again.`, + ); + + if (id in this.guilds) return this.guilds[id]; + else { + const guild = new Guild(); + this.guilds[id] = guild; + return guild; + } + } } // Exports instances. Don't worry, importing it from different files will load the same instance. -export let Config = new ConfigStructure(FileManager.read("config")); -export let Storage = new StorageStructure(FileManager.read("storage")); +export let Config = new ConfigStructure(FileManager.read('config')); +export let Storage = new StorageStructure(FileManager.read('storage')); // This part will allow the user to manually edit any JSON files they want while the program is running which'll update the program's cache. // However, fs.watch is a buggy mess that should be avoided in production. While it helps test out stuff for development, it's not a good idea to have it running outside of development as it causes all sorts of issues. -if(process.argv[2] === "dev") -{ - watch("data", (event, filename) => { - $.debug("File Watcher:", event, filename); - const header = filename.substring(0, filename.indexOf(".json")); - - switch(header) - { - case "config": Config = new ConfigStructure(FileManager.read("config")); break; - case "storage": Storage = new StorageStructure(FileManager.read("storage")); break; - } - }); +if (process.argv[2] === 'dev') { + watch('data', (event, filename) => { + $.debug('File Watcher:', event, filename); + const header = filename.substring(0, filename.indexOf('.json')); + + switch (header) { + case 'config': + Config = new ConfigStructure(FileManager.read('config')); + break; + case 'storage': + Storage = new StorageStructure(FileManager.read('storage')); + break; + } + }); } -export function getPrefix(guild: DiscordGuild|null): string -{ - return Storage.getGuild(guild?.id || "N/A").prefix ?? Config.prefix; -} \ No newline at end of file +export function getPrefix(guild: DiscordGuild | null): string { + return Storage.getGuild(guild?.id || 'N/A').prefix ?? Config.prefix; +} diff --git a/src/core/wrappers.ts b/src/core/wrappers.ts index fbda382..96df202 100644 --- a/src/core/wrappers.ts +++ b/src/core/wrappers.ts @@ -1,84 +1,87 @@ -export class GenericWrapper -{ - protected readonly value: T; - - public constructor(value: T) - { - this.value = value; - } +export class GenericWrapper { + protected readonly value: T; + + public constructor(value: T) { + this.value = value; + } } -export class NumberWrapper extends GenericWrapper -{ - /** - * Pluralises a word and chooses a suffix attached to the root provided. - * - pluralise("credit", "s") = credit/credits - * - pluralise("part", "ies", "y") = party/parties - * - pluralise("sheep") = sheep - */ - public pluralise(word: string, plural = "", singular = "", excludeNumber = false): string - { - let result = excludeNumber ? "" : `${this.value} `; - - if(this.value === 1) - result += word + singular; - else - result += word + plural; - - return result; - } - - /** - * Pluralises a word for changes. - * - (-1).pluraliseSigned() = '-1 credits' - * - (0).pluraliseSigned() = '+0 credits' - * - (1).pluraliseSigned() = '+1 credit' - */ - public pluraliseSigned(word: string, plural = "", singular = "", excludeNumber = false): string - { - const sign = this.value >= 0 ? '+' : ''; - return `${sign}${this.pluralise(word, plural, singular, excludeNumber)}`; - } +export class NumberWrapper extends GenericWrapper { + /** + * Pluralises a word and chooses a suffix attached to the root provided. + * - pluralise("credit", "s") = credit/credits + * - pluralise("part", "ies", "y") = party/parties + * - pluralise("sheep") = sheep + */ + public pluralise( + word: string, + plural = '', + singular = '', + excludeNumber = false, + ): string { + let result = excludeNumber ? '' : `${this.value} `; + + if (this.value === 1) result += word + singular; + else result += word + plural; + + return result; + } + + /** + * Pluralises a word for changes. + * - (-1).pluraliseSigned() = '-1 credits' + * - (0).pluraliseSigned() = '+0 credits' + * - (1).pluraliseSigned() = '+1 credit' + */ + public pluraliseSigned( + word: string, + plural = '', + singular = '', + excludeNumber = false, + ): string { + const sign = this.value >= 0 ? '+' : ''; + return `${sign}${this.pluralise(word, plural, singular, excludeNumber)}`; + } } -export class StringWrapper extends GenericWrapper -{ - public replaceAll(before: string, after: string): string - { - let result = this.value; - - while(result.indexOf(before) !== -1) - result = result.replace(before, after); - - return result; - } - - public toTitleCase(): string - { - return this.value.replace(/([^\W_]+[^\s-]*) */g, txt => txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase()); - } +export class StringWrapper extends GenericWrapper { + public replaceAll(before: string, after: string): string { + let result = this.value; + + while (result.indexOf(before) !== -1) + result = result.replace(before, after); + + return result; + } + + public toTitleCase(): string { + return this.value.replace( + /([^\W_]+[^\s-]*) */g, + (txt) => txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase(), + ); + } } -export class ArrayWrapper extends GenericWrapper -{ - /** Returns a random element from this array. */ - public random(): T - { - return this.value[Math.floor(Math.random() * this.value.length)]; - } - - /** - * Splits up this array into a specified length. - * `$([1,2,3,4,5,6,7,8,9,10]).split(3)` = `[[1,2,3],[4,5,6],[7,8,9],[10]]` - */ - public split(lengthOfEachSection: number): T[][] - { - const amountOfSections = Math.ceil(this.value.length / lengthOfEachSection); - const sections: T[][] = new Array(amountOfSections); - - for(let index = 0; index < amountOfSections; index++) - sections[index] = this.value.slice(index * lengthOfEachSection, (index + 1) * lengthOfEachSection); - - return sections; - } -} \ No newline at end of file +export class ArrayWrapper extends GenericWrapper { + /** Returns a random element from this array. */ + public random(): T { + return this.value[Math.floor(Math.random() * this.value.length)]; + } + + /** + * Splits up this array into a specified length. + * `$([1,2,3,4,5,6,7,8,9,10]).split(3)` = `[[1,2,3],[4,5,6],[7,8,9],[10]]` + */ + public split(lengthOfEachSection: number): T[][] { + const amountOfSections = Math.ceil(this.value.length / lengthOfEachSection); + const sections: T[][] = new Array(amountOfSections); + + for (let index = 0; index < amountOfSections; index++) + sections[index] = this.value.slice( + index * lengthOfEachSection, + (index + 1) * lengthOfEachSection, + ); + + return sections; + } +} diff --git a/src/defs/info.ts b/src/defs/info.ts index d560e2c..30b4786 100644 --- a/src/defs/info.ts +++ b/src/defs/info.ts @@ -1,45 +1,45 @@ // Flags a user can have. // They're basically your profile badges. -export const flags: {[index: string]:any} = { - DISCORD_EMPLOYEE: 'Discord Employee', - DISCORD_PARTNER: 'Discord Partner', - BUGHUNTER_LEVEL_1: 'Bug Hunter (Level 1)', - BUGHUNTER_LEVEL_2: 'Bug Hunter (Level 2)', - HYPESQUAD_EVENTS: 'HypeSquad Events', - HOUSE_BRAVERY: 'House of Bravery', - HOUSE_BRILLIANCE: 'House of Brilliance', - HOUSE_BALANCE: 'House of Balance', - EARLY_SUPPORTER: 'Early Supporter', - TEAM_USER: 'Team User', - SYSTEM: 'System', - VERIFIED_BOT: 'Verified Bot', - VERIFIED_DEVELOPER: 'Verified Bot Developer', +export const flags: { [index: string]: any } = { + DISCORD_EMPLOYEE: 'Discord Employee', + DISCORD_PARTNER: 'Discord Partner', + BUGHUNTER_LEVEL_1: 'Bug Hunter (Level 1)', + BUGHUNTER_LEVEL_2: 'Bug Hunter (Level 2)', + HYPESQUAD_EVENTS: 'HypeSquad Events', + HOUSE_BRAVERY: 'House of Bravery', + HOUSE_BRILLIANCE: 'House of Brilliance', + HOUSE_BALANCE: 'House of Balance', + EARLY_SUPPORTER: 'Early Supporter', + TEAM_USER: 'Team User', + SYSTEM: 'System', + VERIFIED_BOT: 'Verified Bot', + VERIFIED_DEVELOPER: 'Verified Bot Developer', }; -export const filterLevels: {[index: string]:any} = { - DISABLED: 'Off', - MEMBERS_WITHOUT_ROLES: 'No Role', - ALL_MEMBERS: 'Everyone', +export const filterLevels: { [index: string]: any } = { + DISABLED: 'Off', + MEMBERS_WITHOUT_ROLES: 'No Role', + ALL_MEMBERS: 'Everyone', }; -export const verificationLevels: {[index: string]:any} = { - NONE: 'None', - LOW: 'Low', - MEDIUM: 'Medium', - HIGH: '(╯°□°)╯︵ ┻━┻', - VERY_HIGH: '┻━┻ ミヽ(ಠ益ಠ)ノ彡┻━┻', +export const verificationLevels: { [index: string]: any } = { + NONE: 'None', + LOW: 'Low', + MEDIUM: 'Medium', + HIGH: '(╯°□°)╯︵ ┻━┻', + VERY_HIGH: '┻━┻ ミヽ(ಠ益ಠ)ノ彡┻━┻', +}; +export const regions: { [index: string]: any } = { + brazil: 'Brazil', + europe: 'Europe', + hongkong: 'Hong Kong', + india: 'India', + japan: 'Japan', + russia: 'Russia', + singapore: 'Singapore', + southafrica: 'South Africa', + sydney: 'Sydney', + 'us-central': 'US Central', + 'us-east': 'US East', + 'us-west': 'US West', + 'us-south': 'US South', }; -export const regions: {[index: string]:any} = { - brazil: 'Brazil', - europe: 'Europe', - hongkong: 'Hong Kong', - india: 'India', - japan: 'Japan', - russia: 'Russia', - singapore: 'Singapore', - southafrica: 'South Africa', - sydney: 'Sydney', - 'us-central': 'US Central', - 'us-east': 'US East', - 'us-west': 'US West', - 'us-south': 'US South', -}; \ No newline at end of file diff --git a/src/events/message.ts b/src/events/message.ts index 4a20047..dab5e7b 100644 --- a/src/events/message.ts +++ b/src/events/message.ts @@ -1,109 +1,137 @@ -import Event from "../core/event"; -import Command, {loadCommands} from "../core/command"; -import {hasPermission, getPermissionLevel, PermissionNames} from "../core/permissions"; -import {Permissions, Collection} from "discord.js"; -import {getPrefix} from "../core/structures"; -import $ from "../core/lib"; +import Event from '../core/event'; +import Command, { loadCommands } from '../core/command'; +import { + hasPermission, + getPermissionLevel, + PermissionNames, +} from '../core/permissions'; +import { Permissions, Collection } from 'discord.js'; +import { getPrefix } from '../core/structures'; +import $ from '../core/lib'; // It's a rather hacky solution, but since there's no top-level await, I just have to make the loading conditional. -let commands: Collection|null = null; +let commands: Collection | null = null; -export default new Event<"message">({ - async on(message) - { - // Load commands if it hasn't already done so. Luckily, it's called once at most. - if(!commands) - commands = await loadCommands(); - - // Message Setup // - if(message.author.bot) - return; - - const prefix = getPrefix(message.guild); - - if(!message.content.startsWith(prefix)) - { - if(message.client.user && message.mentions.has(message.client.user)) - message.channel.send(`${message.author.toString()}, my prefix on this guild is \`${prefix}\`.`); - return; - } - - const [header, ...args] = message.content.substring(prefix.length).split(/ +/); - - if(!commands.has(header)) - return; - - if(message.channel.type === "text" && !message.channel.permissionsFor(message.client.user || "")?.has(Permissions.FLAGS.SEND_MESSAGES)) - { - let status; - - if(message.member?.hasPermission(Permissions.FLAGS.ADMINISTRATOR)) - status = "Because you're a server admin, you have the ability to change that channel's permissions to match if that's what you intended."; - else - status = "Try using a different channel or contacting a server admin to change permissions of that channel if you think something's wrong."; - - return message.author.send(`I don't have permission to send messages in ${message.channel.toString()}. ${status}`); - } - - $.log(`${message.author.username}#${message.author.discriminator} executed the command "${header}" with arguments "${args}".`); - - // Subcommand Recursion // - let command = commands.get(header); - if(!command) return $.warn(`Command "${header}" was called but for some reason it's still undefined!`); - const params: any[] = []; - let isEndpoint = false; - let permLevel = command.permission ?? Command.PERMISSIONS.NONE; - - for(let param of args) - { - if(command.endpoint) - { - if(command.subcommands.size > 0 || command.user || command.number || command.any) - $.warn(`An endpoint cannot have subcommands! Check ${prefix}${header} again.`); - isEndpoint = true; - break; - } - - const type = command.resolve(param); - command = command.get(param); - permLevel = command.permission ?? permLevel; - - if(type === Command.TYPES.USER) - { - const id = param.match(/\d+/g)![0]; - try {params.push(await message.client.users.fetch(id))} - catch(error) {return message.channel.send(`No user found by the ID \`${id}\`!`)} - } - else if(type === Command.TYPES.NUMBER) - params.push(Number(param)); - else if(type !== Command.TYPES.SUBCOMMAND) - params.push(param); - } - - if(!message.member) - return $.warn("This command was likely called from a DM channel meaning the member object is null."); - - if(!hasPermission(message.member, permLevel)) - { - const userPermLevel = getPermissionLevel(message.member); - return message.channel.send(`You don't have access to this command! Your permission level is \`${PermissionNames[userPermLevel]}\` (${userPermLevel}), but this command requires a permission level of \`${PermissionNames[permLevel]}\` (${permLevel}).`); - } - - if(isEndpoint) - return message.channel.send("Too many arguments!"); - - // Execute with dynamic library attached. // - // The purpose of using $.bind($) is to clone the function so as to not modify the original $. - // The cloned function doesn't copy the properties, so Object.assign() is used. - // Object.assign() modifies the first element and returns that, the second element applies its properties and the third element applies its own overriding the second one. - command.execute(Object.assign($.bind($), { - args: params, - author: message.author, - channel: message.channel, - client: message.client, - guild: message.guild, - member: message.member, - message: message - }, $)); - } -}); \ No newline at end of file +export default new Event<'message'>({ + async on(message) { + // Load commands if it hasn't already done so. Luckily, it's called once at most. + if (!commands) commands = await loadCommands(); + + // Message Setup // + if (message.author.bot) return; + + const prefix = getPrefix(message.guild); + + if (!message.content.startsWith(prefix)) { + if (message.client.user && message.mentions.has(message.client.user)) + message.channel.send( + `${message.author.toString()}, my prefix on this guild is \`${prefix}\`.`, + ); + return; + } + + const [header, ...args] = message.content + .substring(prefix.length) + .split(/ +/); + + if (!commands.has(header)) return; + + if ( + message.channel.type === 'text' && + !message.channel + .permissionsFor(message.client.user || '') + ?.has(Permissions.FLAGS.SEND_MESSAGES) + ) { + let status; + + if (message.member?.hasPermission(Permissions.FLAGS.ADMINISTRATOR)) + status = + "Because you're a server admin, you have the ability to change that channel's permissions to match if that's what you intended."; + else + status = + "Try using a different channel or contacting a server admin to change permissions of that channel if you think something's wrong."; + + return message.author.send( + `I don't have permission to send messages in ${message.channel.toString()}. ${status}`, + ); + } + + $.log( + `${message.author.username}#${message.author.discriminator} executed the command "${header}" with arguments "${args}".`, + ); + + // Subcommand Recursion // + let command = commands.get(header); + if (!command) + return $.warn( + `Command "${header}" was called but for some reason it's still undefined!`, + ); + const params: any[] = []; + let isEndpoint = false; + let permLevel = command.permission ?? Command.PERMISSIONS.NONE; + + for (let param of args) { + if (command.endpoint) { + if ( + command.subcommands.size > 0 || + command.user || + command.number || + command.any + ) + $.warn( + `An endpoint cannot have subcommands! Check ${prefix}${header} again.`, + ); + isEndpoint = true; + break; + } + + const type = command.resolve(param); + command = command.get(param); + permLevel = command.permission ?? permLevel; + + if (type === Command.TYPES.USER) { + const id = param.match(/\d+/g)![0]; + try { + params.push(await message.client.users.fetch(id)); + } catch (error) { + return message.channel.send(`No user found by the ID \`${id}\`!`); + } + } else if (type === Command.TYPES.NUMBER) params.push(Number(param)); + else if (type !== Command.TYPES.SUBCOMMAND) params.push(param); + } + + if (!message.member) + return $.warn( + 'This command was likely called from a DM channel meaning the member object is null.', + ); + + if (!hasPermission(message.member, permLevel)) { + const userPermLevel = getPermissionLevel(message.member); + return message.channel.send( + `You don't have access to this command! Your permission level is \`${PermissionNames[userPermLevel]}\` (${userPermLevel}), but this command requires a permission level of \`${PermissionNames[permLevel]}\` (${permLevel}).`, + ); + } + + if (isEndpoint) return message.channel.send('Too many arguments!'); + + // Execute with dynamic library attached. // + // The purpose of using $.bind($) is to clone the function so as to not modify the original $. + // The cloned function doesn't copy the properties, so Object.assign() is used. + // Object.assign() modifies the first element and returns that, the second element applies its properties and the third element applies its own overriding the second one. + command.execute( + Object.assign( + $.bind($), + { + args: params, + author: message.author, + channel: message.channel, + client: message.client, + guild: message.guild, + member: message.member, + message: message, + }, + $, + ), + ); + }, +}); diff --git a/src/events/messageReactionRemove.ts b/src/events/messageReactionRemove.ts index 4463350..8957a24 100644 --- a/src/events/messageReactionRemove.ts +++ b/src/events/messageReactionRemove.ts @@ -1,20 +1,24 @@ -import Event from "../core/event"; -import {Permissions} from "discord.js"; -import {botHasPermission} from "../core/lib"; +import Event from '../core/event'; +import { Permissions } from 'discord.js'; +import { botHasPermission } from '../core/lib'; // A list of message ID and callback pairs. You get the emote name and ID of the user reacting. -export const eventListeners: Map void> = new Map(); +export const eventListeners: Map< + string, + (emote: string, id: string) => void +> = new Map(); // Attached to the client, there can be one event listener attached to a message ID which is executed if present. -export default new Event<"messageReactionRemove">({ - on(reaction, user) - { - const canDeleteEmotes = botHasPermission(reaction.message.guild, Permissions.FLAGS.MANAGE_MESSAGES); - - if(!canDeleteEmotes) - { - const callback = eventListeners.get(reaction.message.id); - callback && callback(reaction.emoji.name, user.id); - } - } -}); \ No newline at end of file +export default new Event<'messageReactionRemove'>({ + on(reaction, user) { + const canDeleteEmotes = botHasPermission( + reaction.message.guild, + Permissions.FLAGS.MANAGE_MESSAGES, + ); + + if (!canDeleteEmotes) { + const callback = eventListeners.get(reaction.message.id); + callback && callback(reaction.emoji.name, user.id); + } + }, +}); diff --git a/src/events/ready.ts b/src/events/ready.ts index a7ff1e8..10ca87a 100644 --- a/src/events/ready.ts +++ b/src/events/ready.ts @@ -1,18 +1,18 @@ -import Event from "../core/event"; -import {client} from "../index"; -import $ from "../core/lib"; -import {Config} from "../core/structures"; +import Event from '../core/event'; +import { client } from '../index'; +import $ from '../core/lib'; +import { Config } from '../core/structures'; -export default new Event<"ready">({ - once() - { - if(client.user) - { - $.ready(`Logged in as ${client.user.username}#${client.user.discriminator}.`); - client.user.setActivity({ - type: "LISTENING", - name: `${Config.prefix}help` - }); - } - } -}); \ No newline at end of file +export default new Event<'ready'>({ + once() { + if (client.user) { + $.ready( + `Logged in as ${client.user.username}#${client.user.discriminator}.`, + ); + client.user.setActivity({ + type: 'LISTENING', + name: `${Config.prefix}help`, + }); + } + }, +}); diff --git a/src/index.ts b/src/index.ts index 0c2718a..4ce813a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,8 +1,8 @@ -import {Client} from "discord.js"; -import setup from "./setup"; -import {Config} from "./core/structures"; -import {loadCommands} from "./core/command"; -import {loadEvents} from "./core/event"; +import { Client } from 'discord.js'; +import setup from './setup'; +import { Config } from './core/structures'; +import { loadCommands } from './core/command'; +import { loadEvents } from './core/event'; // This is here in order to make it much less of a headache to access the client from other files. // This of course won't actually do anything until the setup process is complete and it logs in. @@ -10,7 +10,7 @@ export const client = new Client(); // Begin the command loading here rather than when it's needed like in the message event. setup.init().then(() => { - loadCommands(); - loadEvents(client); - client.login(Config.token).catch(setup.again); -}); \ No newline at end of file + loadCommands(); + loadEvents(client); + client.login(Config.token).catch(setup.again); +}); diff --git a/src/setup.ts b/src/setup.ts index 7f08a93..6559236 100644 --- a/src/setup.ts +++ b/src/setup.ts @@ -1,60 +1,64 @@ -import {existsSync as exists} from "fs"; -import inquirer from "inquirer"; -import Storage from "./core/storage"; -import {Config} from "./core/structures"; -import $, {setConsoleActivated} from "./core/lib"; +import { existsSync as exists } from 'fs'; +import inquirer from 'inquirer'; +import Storage from './core/storage'; +import { Config } from './core/structures'; +import $, { setConsoleActivated } from './core/lib'; // This file is called (or at least should be called) automatically as long as a config file doesn't exist yet. // And that file won't be written until the data is successfully initialized. -const prompts = [{ - type: "password", - name: "token", - message: "What's your bot's token?", - mask: true -}, { - type: "input", - name: "prefix", - message: "What do you want your bot's prefix to be?", - default: "$" -}, { - type: "input", - name: "owner", - message: "Enter the owner's user ID here." -}, { - type: "input", - name: "admins", - message: "Enter a list of bot admins (by their IDs) separated by spaces." -}, { - type: "input", - name: "support", - message: "Enter a list of bot troubleshooters (by their IDs) separated by spaces." -}]; +const prompts = [ + { + type: 'password', + name: 'token', + message: "What's your bot's token?", + mask: true, + }, + { + type: 'input', + name: 'prefix', + message: "What do you want your bot's prefix to be?", + default: '$', + }, + { + type: 'input', + name: 'owner', + message: "Enter the owner's user ID here.", + }, + { + type: 'input', + name: 'admins', + message: 'Enter a list of bot admins (by their IDs) separated by spaces.', + }, + { + type: 'input', + name: 'support', + message: + 'Enter a list of bot troubleshooters (by their IDs) separated by spaces.', + }, +]; export default { - async init() - { - while(!exists("data/config.json")) - { - const answers = await inquirer.prompt(prompts); - Storage.open("data"); - Config.token = answers.token as string; - Config.prefix = answers.prefix as string; - Config.owner = answers.owner as string; - const admins = (answers.admins as string); - Config.admins = admins !== "" ? admins.split(" ") : []; - const support = (answers.support as string); - Config.support = support !== "" ? support.split(" ") : []; - Config.save(false); - } - }, - /** Prompt the user to set their token again. */ - async again() - { - $.error("It seems that the token you provided is invalid."); - setConsoleActivated(false); - const answers = await inquirer.prompt(prompts.slice(0, 1)); - Config.token = answers.token as string; - Config.save(false); - process.exit(); - } -}; \ No newline at end of file + async init() { + while (!exists('data/config.json')) { + const answers = await inquirer.prompt(prompts); + Storage.open('data'); + Config.token = answers.token as string; + Config.prefix = answers.prefix as string; + Config.owner = answers.owner as string; + const admins = answers.admins as string; + Config.admins = admins !== '' ? admins.split(' ') : []; + const support = answers.support as string; + Config.support = support !== '' ? support.split(' ') : []; + Config.save(false); + } + }, + /** Prompt the user to set their token again. */ + async again() { + $.error('It seems that the token you provided is invalid.'); + setConsoleActivated(false); + const answers = await inquirer.prompt(prompts.slice(0, 1)); + Config.token = answers.token as string; + Config.save(false); + process.exit(); + }, +}; diff --git a/test/wrappers.ts b/test/wrappers.ts index e6699d9..9597e77 100644 --- a/test/wrappers.ts +++ b/test/wrappers.ts @@ -1,69 +1,111 @@ -import {strict as assert} from "assert"; -import {NumberWrapper, StringWrapper, ArrayWrapper} from "../src/core/wrappers"; +import { strict as assert } from 'assert'; +import { + NumberWrapper, + StringWrapper, + ArrayWrapper, +} from '../src/core/wrappers'; // I can't figure out a way to run the test suite while running the bot. -describe("Wrappers", () => { - describe("NumberWrapper", () => { - describe("#pluralise()", () => { - it('should return "5 credits"', () => { - assert.strictEqual(new NumberWrapper(5).pluralise("credit", "s"), "5 credits"); - }) - - it('should return "1 credit"', () => { - assert.strictEqual(new NumberWrapper(1).pluralise("credit", "s"), "1 credit"); - }) - - it('should return "-1 credits"', () => { - assert.strictEqual(new NumberWrapper(-1).pluralise("credit", "s"), "-1 credits"); - }) - - it('should be able to work with a plural suffix', () => { - assert.strictEqual(new NumberWrapper(2).pluralise("part", "ies", "y"), "2 parties"); - }) - - it('should be able to work with a singular suffix', () => { - assert.strictEqual(new NumberWrapper(1).pluralise("part", "ies", "y"), "1 party"); - }) - - it('should be able to exclude the number', () => { - assert.strictEqual(new NumberWrapper(1).pluralise("credit", "s", "", true), "credit"); - }) - }) - - describe("#pluraliseSigned()", () => { - it('should return "-1 credits"', () => { - assert.strictEqual(new NumberWrapper(-1).pluraliseSigned("credit", "s"), "-1 credits"); - }) - - it('should return "+0 credits"', () => { - assert.strictEqual(new NumberWrapper(0).pluraliseSigned("credit", "s"), "+0 credits"); - }) - - it('should return "+1 credit"', () => { - assert.strictEqual(new NumberWrapper(1).pluraliseSigned("credit", "s"), "+1 credit"); - }) - }) - }) - - describe("StringWrapper", () => { - describe("#replaceAll()", () => { - it('should convert "test" to "zesz"', () => { - assert.strictEqual(new StringWrapper("test").replaceAll('t', 'z'), "zesz"); - }) - }) - - describe("#toTitleCase()", () => { - it('should capitalize the first letter of each word', () => { - assert.strictEqual(new StringWrapper("yeetus deletus find salvation from jesus").toTitleCase(), "Yeetus Deletus Find Salvation From Jesus"); - }) - }) - }) - - describe("ArrayWrapper", () => { - describe("#split()", () => { - it('should split [1,2,3,4,5,6,7,8,9,10] into [[1,2,3],[4,5,6],[7,8,9],[10]]', () => { - assert.deepStrictEqual(new ArrayWrapper([1,2,3,4,5,6,7,8,9,10]).split(3), [[1,2,3],[4,5,6],[7,8,9],[10]]); - }) - }) - }) -}) \ No newline at end of file +describe('Wrappers', () => { + describe('NumberWrapper', () => { + describe('#pluralise()', () => { + it('should return "5 credits"', () => { + assert.strictEqual( + new NumberWrapper(5).pluralise('credit', 's'), + '5 credits', + ); + }); + + it('should return "1 credit"', () => { + assert.strictEqual( + new NumberWrapper(1).pluralise('credit', 's'), + '1 credit', + ); + }); + + it('should return "-1 credits"', () => { + assert.strictEqual( + new NumberWrapper(-1).pluralise('credit', 's'), + '-1 credits', + ); + }); + + it('should be able to work with a plural suffix', () => { + assert.strictEqual( + new NumberWrapper(2).pluralise('part', 'ies', 'y'), + '2 parties', + ); + }); + + it('should be able to work with a singular suffix', () => { + assert.strictEqual( + new NumberWrapper(1).pluralise('part', 'ies', 'y'), + '1 party', + ); + }); + + it('should be able to exclude the number', () => { + assert.strictEqual( + new NumberWrapper(1).pluralise('credit', 's', '', true), + 'credit', + ); + }); + }); + + describe('#pluraliseSigned()', () => { + it('should return "-1 credits"', () => { + assert.strictEqual( + new NumberWrapper(-1).pluraliseSigned('credit', 's'), + '-1 credits', + ); + }); + + it('should return "+0 credits"', () => { + assert.strictEqual( + new NumberWrapper(0).pluraliseSigned('credit', 's'), + '+0 credits', + ); + }); + + it('should return "+1 credit"', () => { + assert.strictEqual( + new NumberWrapper(1).pluraliseSigned('credit', 's'), + '+1 credit', + ); + }); + }); + }); + + describe('StringWrapper', () => { + describe('#replaceAll()', () => { + it('should convert "test" to "zesz"', () => { + assert.strictEqual( + new StringWrapper('test').replaceAll('t', 'z'), + 'zesz', + ); + }); + }); + + describe('#toTitleCase()', () => { + it('should capitalize the first letter of each word', () => { + assert.strictEqual( + new StringWrapper( + 'yeetus deletus find salvation from jesus', + ).toTitleCase(), + 'Yeetus Deletus Find Salvation From Jesus', + ); + }); + }); + }); + + describe('ArrayWrapper', () => { + describe('#split()', () => { + it('should split [1,2,3,4,5,6,7,8,9,10] into [[1,2,3],[4,5,6],[7,8,9],[10]]', () => { + assert.deepStrictEqual( + new ArrayWrapper([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]).split(3), + [[1, 2, 3], [4, 5, 6], [7, 8, 9], [10]], + ); + }); + }); + }); +}); diff --git a/tsconfig.json b/tsconfig.json index 92f72c4..74603be 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,18 +1,17 @@ { - "compilerOptions": - { - "rootDir": "src", - "outDir": "dist", - "target": "ES6", - "module": "CommonJS", - "moduleResolution": "node", - "esModuleInterop": true, - "noImplicitAny": true, - "noImplicitReturns": true, - "strictNullChecks": true, - "strictFunctionTypes": true, - "strictPropertyInitialization": true, - "removeComments": true - }, - "exclude": ["test"] -} \ No newline at end of file + "compilerOptions": { + "rootDir": "src", + "outDir": "dist", + "target": "ES6", + "module": "CommonJS", + "moduleResolution": "node", + "esModuleInterop": true, + "noImplicitAny": true, + "noImplicitReturns": true, + "strictNullChecks": true, + "strictFunctionTypes": true, + "strictPropertyInitialization": true, + "removeComments": true + }, + "exclude": ["test"] +} From 0c0fc083cfab9c7cb6f20f2705e269ff81133af8 Mon Sep 17 00:00:00 2001 From: Keanu Date: Tue, 20 Oct 2020 12:04:13 +0000 Subject: [PATCH 051/178] Moved emote cmds to utility category. Updated Discord.JS version to 12.4.0. Added channelCreate/remove events. Removed husky. --- .husky/.gitignore | 1 - .husky/pre-commit | 4 -- package-lock.json | 48 ++++++++++-------------- package.json | 4 +- src/commands/admin.ts | 4 ++ src/commands/util.ts | 59 ------------------------------ src/commands/utilities/emote.ts | 21 +++++++++++ src/commands/utilities/lsemotes.ts | 32 ++++++++++++++++ src/events/channelCreate.ts | 16 ++++++++ src/events/channelDelete.ts | 16 ++++++++ 10 files changed, 110 insertions(+), 95 deletions(-) delete mode 100644 .husky/.gitignore delete mode 100755 .husky/pre-commit delete mode 100644 src/commands/util.ts create mode 100644 src/commands/utilities/emote.ts create mode 100644 src/commands/utilities/lsemotes.ts create mode 100644 src/events/channelCreate.ts create mode 100644 src/events/channelDelete.ts diff --git a/.husky/.gitignore b/.husky/.gitignore deleted file mode 100644 index 31354ec..0000000 --- a/.husky/.gitignore +++ /dev/null @@ -1 +0,0 @@ -_ diff --git a/.husky/pre-commit b/.husky/pre-commit deleted file mode 100755 index beaf4ba..0000000 --- a/.husky/pre-commit +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh -. "$(dirname $0)/_/husky.sh" - -npm test && npx prettier --write . \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 5c0cf99..c41153c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,9 +5,9 @@ "requires": true, "dependencies": { "@discordjs/collection": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-0.1.5.tgz", - "integrity": "sha512-CU1q0UXQUpFNzNB7gufgoisDHP7n+T3tkqTsp3MNUkVJ5+hS3BCvME8uCXAUFlz+6T2FbTCu75A+yQ7HMKqRKw==" + "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", @@ -359,30 +359,18 @@ "dev": true }, "discord.js": { - "version": "12.2.0", - "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-12.2.0.tgz", - "integrity": "sha512-Ueb/0SOsxXyqwvwFYFe0msMrGqH1OMqpp2Dpbplnlr4MzcRrFWwsBM9gKNZXPVBHWUKiQkwU8AihXBXIvTTSvg==", + "version": "12.4.0", + "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-12.4.0.tgz", + "integrity": "sha512-Lc+/vKzih1DPEya/0MO0BAp4Ru/4+MfDsOJkfbyzGoyIlFqiWSQ78RdyRk4tqAtabl6d5QR4Ygo0Ub0TGEsXBg==", "requires": { - "@discordjs/collection": "^0.1.5", + "@discordjs/collection": "^0.1.6", "@discordjs/form-data": "^3.0.1", "abort-controller": "^3.0.0", - "node-fetch": "^2.6.0", - "prism-media": "^1.2.0", + "node-fetch": "^2.6.1", + "prism-media": "^1.2.2", "setimmediate": "^1.0.5", "tweetnacl": "^1.0.3", - "ws": "^7.2.1" - }, - "dependencies": { - "prism-media": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/prism-media/-/prism-media-1.2.2.tgz", - "integrity": "sha512-I+nkWY212lJ500jLe4tN9tWO7nRiBAVdMv76P9kffZjYhw20raMlW1HSSvS+MLXC9MmbNZCazMrAr+5jEEgTuw==" - }, - "ws": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.3.0.tgz", - "integrity": "sha512-iFtXzngZVXPGgpTlP1rBqsUK82p9tKqsWRPg5L56egiljujJT3vGAYnHANvFxBieXrTFavhzhxW52jnaWV+w2w==" - } + "ws": "^7.3.1" } }, "duplexer": { @@ -610,12 +598,6 @@ "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", "dev": true }, - "husky": { - "version": "5.0.0-alpha.6", - "resolved": "https://registry.npmjs.org/husky/-/husky-5.0.0-alpha.6.tgz", - "integrity": "sha512-Ofqq0oHLCO0r8hTb/1PQ3FAfsW945adUli8jFTeXdOIK8gCUmxY9A0BI0mG9oGboPf+Y53bvEmX6ljdrz+yV6w==", - "dev": true - }, "iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -1044,6 +1026,11 @@ "integrity": "sha512-16c7K+x4qVlJg9rEbXl7HEGmQyZlG4R9AgP+oHKRMsMsuk8s+ATStlf1NpDqyBI1HpVyfjLOeMhH2LvuNvV5Vg==", "dev": true }, + "prism-media": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/prism-media/-/prism-media-1.2.2.tgz", + "integrity": "sha512-I+nkWY212lJ500jLe4tN9tWO7nRiBAVdMv76P9kffZjYhw20raMlW1HSSvS+MLXC9MmbNZCazMrAr+5jEEgTuw==" + }, "promise.allsettled": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/promise.allsettled/-/promise.allsettled-1.0.2.tgz", @@ -1480,6 +1467,11 @@ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "dev": true }, + "ws": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.3.1.tgz", + "integrity": "sha512-D3RuNkynyHmEJIpD2qrgVkc9DQ23OrN/moAwZX4L8DfvszsJxpjQuUq3LMx6HoYji9fbIOBY18XWBsAux1ZZUA==" + }, "y18n": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", diff --git a/package.json b/package.json index 8c53960..34c3697 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "private": true, "dependencies": { "chalk": "^4.1.0", - "discord.js": "^12.2.0", + "discord.js": "^12.4.0", "inquirer": "^7.3.1", "moment": "^2.27.0" }, @@ -15,7 +15,6 @@ "@types/mocha": "^8.0.3", "@types/node": "^14.0.22", "@types/ws": "^7.2.6", - "husky": "^5.0.0-alpha.6", "mocha": "^8.1.2", "prettier": "2.1.2", "ts-node": "^9.0.0", @@ -23,7 +22,6 @@ "typescript": "^3.9.6" }, "scripts": { - "postinstall": "husky install", "build": "tsc && npm prune --production", "start": "node dist/index.js", "once": "tsc && npm start", diff --git a/src/commands/admin.ts b/src/commands/admin.ts index f60f1c5..907dab5 100644 --- a/src/commands/admin.ts +++ b/src/commands/admin.ts @@ -3,6 +3,7 @@ import { CommonLibrary, logs, botHasPermission } from '../core/lib'; import { Config, Storage } from '../core/structures'; import { PermissionNames, getPermissionLevel } from '../core/permissions'; import { Permissions } from 'discord.js'; +import * as discord from 'discord.js'; function getLogBuffer(type: string) { return { @@ -107,6 +108,9 @@ export default new Command({ description: 'Purges bot messages.', permission: Command.PERMISSIONS.BOT_SUPPORT, async run($: CommonLibrary): Promise { + if ($.message.channel instanceof discord.DMChannel) { + return; + } $.message.delete(); const msgs = await $.channel.messages.fetch({ limit: 100, diff --git a/src/commands/util.ts b/src/commands/util.ts deleted file mode 100644 index a96b761..0000000 --- a/src/commands/util.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { MessageEmbed } from 'discord.js'; -import Command from '../core/command'; -import { CommonLibrary } from '../core/lib'; - -export default new Command({ - description: 'Various utilities.', - endpoint: false, - usage: '', - async run($: CommonLibrary): Promise {}, - subcommands: { - lsemotes: new Command({ - description: "Lists all emotes the bot has in it's registry,", - endpoint: true, - async run($: CommonLibrary): Promise { - const nsfw: string | string[] = []; - const pages = $.client.emojis.cache - .filter((x) => !nsfw.includes(x.guild.id), this) - .array(); - const pagesSplit = $(pages).split(20); - $.log(pagesSplit); - var embed = new MessageEmbed() - .setTitle('**Emoji list!**') - .setColor('AQUA'); - let desc = ''; - for (const emote of pagesSplit[0]) { - desc += `${emote} | ${emote.name}\n`; - } - embed.setDescription(desc); - const msg = await $.channel.send({ embed }); - - $.paginate(msg, $.author.id, pages.length, (page) => { - let desc = ''; - for (const emote of pagesSplit[page]) { - desc += `${emote} | ${emote.name}\n`; - } - embed.setDescription(desc); - msg.edit(embed); - }); - }, - }), - emote: new Command({ - description: 'Send the specified emote.', - run: 'Please provide a command name.', - any: new Command({ - description: 'The emote to send.', - usage: '', - async run($: CommonLibrary): Promise { - const search = $.args[0].toLowerCase(); - const emote = $.client.emojis.cache.find((emote) => - emote.name.toLowerCase().includes(search), - ); - if (!emote) return $.channel.send("That's not a valid emote name!"); - $.message.delete(); - $.channel.send(`${emote}`); - }, - }), - }), - }, -}); diff --git a/src/commands/utilities/emote.ts b/src/commands/utilities/emote.ts new file mode 100644 index 0000000..c01129e --- /dev/null +++ b/src/commands/utilities/emote.ts @@ -0,0 +1,21 @@ +import { MessageEmbed } from 'discord.js'; +import Command from '../../core/command'; +import { CommonLibrary } from '../../core/lib'; + +export default new Command({ + description: 'Send the specified emote.', + run: 'Please provide a command name.', + any: new Command({ + description: 'The emote to send.', + usage: '', + async run($: CommonLibrary): Promise { + const search = $.args[0].toLowerCase(); + const emote = $.client.emojis.cache.find((emote) => + emote.name.toLowerCase().includes(search), + ); + if (!emote) return $.channel.send("That's not a valid emote name!"); + $.message.delete(); + $.channel.send(`${emote}`); + }, + }), +}); diff --git a/src/commands/utilities/lsemotes.ts b/src/commands/utilities/lsemotes.ts new file mode 100644 index 0000000..838d62e --- /dev/null +++ b/src/commands/utilities/lsemotes.ts @@ -0,0 +1,32 @@ +import { MessageEmbed } from 'discord.js'; +import Command from '../../core/command'; +import { CommonLibrary } from '../../core/lib'; + +export default new Command({ + description: "Lists all emotes the bot has in it's registry,", + endpoint: true, + async run($: CommonLibrary): Promise { + const nsfw: string | string[] = []; + const pages = $.client.emojis.cache + .filter((x) => !nsfw.includes(x.guild.id), this) + .array(); + const pagesSplit = $(pages).split(20); + $.log(pagesSplit); + var embed = new MessageEmbed().setTitle('**Emoji list!**').setColor('AQUA'); + let desc = ''; + for (const emote of pagesSplit[0]) { + desc += `${emote} | ${emote.name}\n`; + } + embed.setDescription(desc); + const msg = await $.channel.send({ embed }); + + $.paginate(msg, $.author.id, pages.length, (page) => { + let desc = ''; + for (const emote of pagesSplit[page]) { + desc += `${emote} | ${emote.name}\n`; + } + embed.setDescription(desc); + msg.edit(embed); + }); + }, +}); diff --git a/src/events/channelCreate.ts b/src/events/channelCreate.ts new file mode 100644 index 0000000..a1fbd89 --- /dev/null +++ b/src/events/channelCreate.ts @@ -0,0 +1,16 @@ +import Event from '../core/event'; +import { client } from '../index'; +import $ from '../core/lib'; +import * as discord from 'discord.js'; + +export default new Event<'channelCreate'>({ + async on(channel) { + const botGuilds = client.guilds; + if (channel instanceof discord.GuildChannel) { + const createdGuild = await botGuilds.fetch(channel.guild.id); + $.log( + `Channel created in '${createdGuild.name}' called '#${channel.name}'`, + ); + } + }, +}); diff --git a/src/events/channelDelete.ts b/src/events/channelDelete.ts new file mode 100644 index 0000000..c7836d8 --- /dev/null +++ b/src/events/channelDelete.ts @@ -0,0 +1,16 @@ +import Event from '../core/event'; +import { client } from '../index'; +import $ from '../core/lib'; +import * as discord from 'discord.js'; + +export default new Event<'channelDelete'>({ + async on(channel) { + const botGuilds = client.guilds; + if (channel instanceof discord.GuildChannel) { + const createdGuild = await botGuilds.fetch(channel.guild.id); + $.log( + `Channel deleted in '${createdGuild.name}' called '#${channel.name}'`, + ); + } + }, +}); From 942489630f271c309c8bbaa32489150bf5cafa9c Mon Sep 17 00:00:00 2001 From: Keanu Date: Tue, 20 Oct 2020 21:10:03 +0200 Subject: [PATCH 052/178] Reimplemented music functionality. ...in the most disgusting way possible! --- docs/MusicStructure.md | 130 +++++++++++++++++++++++++++++++++++++++++ package-lock.json | 70 ++++++++++++++++++++++ package.json | 1 + src/index.ts | 22 +++++++ 4 files changed, 223 insertions(+) create mode 100644 docs/MusicStructure.md diff --git a/docs/MusicStructure.md b/docs/MusicStructure.md new file mode 100644 index 0000000..ed8b9de --- /dev/null +++ b/docs/MusicStructure.md @@ -0,0 +1,130 @@ +# The commands + +```lang-none +- "mhelp" - Display a help embed. +- "play" - Adds a song to the queue and plays it. +- "skip" - Skips the currently playing track. +- "queue" - Shows the current queue. +- "stop" - Stops currently playing media and leaves the voice channel. +- "np" - Displays the currently playing track. +- "pause" - Pauses the currently playing track. +- "resume" - Resumes the currently paused track. +- "volume" - Changes the global volume of the bot. +- "loop" - Loops the current queue. +- "seek" - Seeks through the queue. +``` + +--- + +Now that the actual info about the functionality of this thing is out of the way, its storytime! + +## The music structure + +Originally, I, keanucode, aimed to port the music structure of TravBot-v2 to this version. + +This would have been much too difficult of a task for three main reasons: + +1. The original code is written badly. +2. The original code is written by *another person*. +3. The original code is written in JS. + +These three reasons make porting the structure *considerably* harder. + +So, of course, I resorted to different matters. I present: [discord.js-lavalink-musicbot](https://github.com/BluSpring/discord.js-lavalink-musicbot). ([npmjs.org](https://www.npmjs.com/package/discord.js-lavalink-musicbot)) + +This *pre-built* module utilises [Lavalink](https://github.com/Frederikam/Lavalink), which is an audio sending node based on [Lavaplayer](https://github.com/sedmelluq/lavaplayer) and [JDA-Audio](https://github.com/DV8FromTheWorld/JDA-Audio). + +I've previously considered using Lavalink, but it turned out to be more difficult for me to implement than I thought. + +So, I tried again with `discord.js-lavalink-musicbot`. + +*ahem*... + +**The library was written in such a way that it didn't work!** + +--- + +## Fixing the broken library + +First off; in the library's interface `LavaLinkNodeOptions`, option `id` was a *required* option: + +```ts +interface LavalinkNodeOptions { + host: string; + id: string; + /* ... */ +} +``` + +Here's the catch. `id` was referenced *nowhere* in the library code. +It was *literally* useless. + +So, I lazily removed that by adding a `?` to the parameter. (`id?:`) + +Next up: + +```ts +declare function LavalinkMusic(client: Client, options: MusicbotOptions) {} +``` + +First up, the TS compiler reports that: `An implementation cannot be declared in ambient contexts. ts(1183)` + +Secondly, this function, which makes up the entirety of the library, explicitly returns an `any` type. As you can see, the *declared* function returns... no specific type. + +So, that had to be changed to: + +```diff +- declare function LavalinkMusic(client: Client, options: MusicbotOptions) {} ++ declare function LavalinkMusic(client: Client, options: MusicbotOptions): any +``` + +...next up: + +```ts +try { +const res = await axios.get( + /* ... */ + `https://${music.lavalink.restnode.host}:` + /* ... */ +) +``` + +The library tries to fetch the URL of the Lavalink node. With *HTTPS*. + +I think you can see where this is going. An SSL error. + +Changed the `https` to `http`, and all is well. + +I republished the library under the name "[discord.js-lavalink-lib](https://npmjs.org/package/discord.js-lavalink-lib)" so I can easily install the non-broken version. + +--- + +## Implementing the functionality + +There's nothing much to do there, honestly. Only one edit to the original snippet has to be made. + +The original example snippet has the following: + +```ts +const Discord = require('discord.js'); +const client = new Discord.Client(); +client.music = new (require('discord.js-lavalink-musicbot'))(client, { + /* ...config... */ +}); +``` + +As you can see, this is... kind of disgusting. And on top of that, incompatible with TS. + +So, we have to change a few things. First off, since TS is strict, it'll tell you that `music` doesn't exist on `client`. Which is true. The `Client` class has no `music` property. + +So, we make `client.` an `any` type using keyword `as`: + +```ts +const Discord = require('discord.js'); +const client = new Discord.Client(); +(client as any).music = LavalinkMusic(client, { + /* ...config... */ +}); +``` + +And that's about it. Launch up Lavalink, and start the bot. diff --git a/package-lock.json b/package-lock.json index c41153c..ca34de3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,25 @@ "mime-types": "^2.1.12" } }, + "@lavacord/discord.js": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/@lavacord/discord.js/-/discord.js-0.0.5.tgz", + "integrity": "sha512-qc2lw0zB48fq4SrSlpMJOmogUXIeM5YvufQfPg0ubjp7jqm20JnOXF9fliy1MPdcKPnZ8LwIZ222H0+ghGzP/Q==", + "requires": { + "lavacord": "^1.1.9" + }, + "dependencies": { + "lavacord": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/lavacord/-/lavacord-1.1.9.tgz", + "integrity": "sha512-haZghbblO1w3Hodc9q63ZWgV5zA/jB6xFKS17fImK5aIdn0PkKuZ6AsJBxMFpR275v8GNYOxg6cTQBYBQ+batQ==", + "requires": { + "node-fetch": "^2.6.0", + "ws": "^7.3.0" + } + } + } + }, "@types/color-name": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", @@ -142,6 +161,14 @@ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" }, + "axios": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.19.2.tgz", + "integrity": "sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA==", + "requires": { + "follow-redirects": "1.5.10" + } + }, "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", @@ -373,6 +400,17 @@ "ws": "^7.3.1" } }, + "discord.js-lavalink-lib": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/discord.js-lavalink-lib/-/discord.js-lavalink-lib-0.1.5.tgz", + "integrity": "sha512-cgQR1JgNav2BiFkuPBV03fNCETm+t70fXaImSiEL0hqookgTdGb+ZxfbRvzmtvGvch6MqfH2AaAYDVvLo5PR4Q==", + "requires": { + "@lavacord/discord.js": "0.0.5", + "axios": "^0.19.2", + "discord.js": "^12.2.0", + "lavacord": "^1.1.7" + } + }, "duplexer": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", @@ -512,6 +550,29 @@ "is-buffer": "~2.0.3" } }, + "follow-redirects": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz", + "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==", + "requires": { + "debug": "=3.1.0" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + } + } + }, "from": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz", @@ -781,6 +842,15 @@ "esprima": "^4.0.0" } }, + "lavacord": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/lavacord/-/lavacord-1.1.9.tgz", + "integrity": "sha512-haZghbblO1w3Hodc9q63ZWgV5zA/jB6xFKS17fImK5aIdn0PkKuZ6AsJBxMFpR275v8GNYOxg6cTQBYBQ+batQ==", + "requires": { + "node-fetch": "^2.6.0", + "ws": "^7.3.0" + } + }, "locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", diff --git a/package.json b/package.json index 34c3697..cd76328 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "dependencies": { "chalk": "^4.1.0", "discord.js": "^12.4.0", + "discord.js-lavalink-lib": "^0.1.5", "inquirer": "^7.3.1", "moment": "^2.27.0" }, diff --git a/src/index.ts b/src/index.ts index 4ce813a..207bb4b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,11 +3,33 @@ import setup from './setup'; import { Config } from './core/structures'; import { loadCommands } from './core/command'; import { loadEvents } from './core/event'; +import 'discord.js-lavalink-lib'; +import LavalinkMusic from 'discord.js-lavalink-lib'; // This is here in order to make it much less of a headache to access the client from other files. // This of course won't actually do anything until the setup process is complete and it logs in. export const client = new Client(); +(client as any).music = LavalinkMusic(client, { + lavalink: { + restnode: { + host: 'localhost', + port: 2333, + password: 'youshallnotpass', + }, + nodes: [ + { + host: 'localhost', + port: 2333, + password: 'youshallnotpass', + }, + ], + }, + prefix: '!!', + helpCmd: 'mhelp', + admins: ['717352467280691331'], +}); + // Begin the command loading here rather than when it's needed like in the message event. setup.init().then(() => { loadCommands(); From 03e1a835bd6770c90c7d3f58cfb89b8f0b3cff11 Mon Sep 17 00:00:00 2001 From: Keanu Date: Tue, 20 Oct 2020 22:02:10 +0200 Subject: [PATCH 053/178] Upgraded Lavalink lib. --- package-lock.json | 6 +++--- package.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index ca34de3..d9ec690 100644 --- a/package-lock.json +++ b/package-lock.json @@ -401,9 +401,9 @@ } }, "discord.js-lavalink-lib": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/discord.js-lavalink-lib/-/discord.js-lavalink-lib-0.1.5.tgz", - "integrity": "sha512-cgQR1JgNav2BiFkuPBV03fNCETm+t70fXaImSiEL0hqookgTdGb+ZxfbRvzmtvGvch6MqfH2AaAYDVvLo5PR4Q==", + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/discord.js-lavalink-lib/-/discord.js-lavalink-lib-0.1.6.tgz", + "integrity": "sha512-oZgb0tCgW2ZwAzY9h8kzWHUzvkZLAeRKil+c7YdZADh4lRdQa+BVo8ZJ30x9LVBcK9OBsscOFkgUuY7HVIMdlA==", "requires": { "@lavacord/discord.js": "0.0.5", "axios": "^0.19.2", diff --git a/package.json b/package.json index cd76328..440c18d 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "dependencies": { "chalk": "^4.1.0", "discord.js": "^12.4.0", - "discord.js-lavalink-lib": "^0.1.5", + "discord.js-lavalink-lib": "^0.1.6", "inquirer": "^7.3.1", "moment": "^2.27.0" }, From 74c42a8fa67683f7cf662b43242e661fb43a649f Mon Sep 17 00:00:00 2001 From: Keanu Date: Wed, 21 Oct 2020 19:53:16 +0000 Subject: [PATCH 054/178] Upgraded Lavalink lib again. --- package-lock.json | 6 +++--- package.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index d9ec690..61f7b16 100644 --- a/package-lock.json +++ b/package-lock.json @@ -401,9 +401,9 @@ } }, "discord.js-lavalink-lib": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/discord.js-lavalink-lib/-/discord.js-lavalink-lib-0.1.6.tgz", - "integrity": "sha512-oZgb0tCgW2ZwAzY9h8kzWHUzvkZLAeRKil+c7YdZADh4lRdQa+BVo8ZJ30x9LVBcK9OBsscOFkgUuY7HVIMdlA==", + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/discord.js-lavalink-lib/-/discord.js-lavalink-lib-0.1.7.tgz", + "integrity": "sha512-czjsdRcbB+sdfTXsko76JYAAPJzoRhTiaphZJXqFpsAYdv0hjkNVLHxuuG2d4hVuXjt/UU7HDYyWEb2e/3g2Ew==", "requires": { "@lavacord/discord.js": "0.0.5", "axios": "^0.19.2", diff --git a/package.json b/package.json index 440c18d..450bcf2 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "dependencies": { "chalk": "^4.1.0", "discord.js": "^12.4.0", - "discord.js-lavalink-lib": "^0.1.6", + "discord.js-lavalink-lib": "^0.1.7", "inquirer": "^7.3.1", "moment": "^2.27.0" }, From 05012718687f90ad88a15ceb320ad09154f21058 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Wed, 21 Oct 2020 20:11:27 +0000 Subject: [PATCH 055/178] Create Dependabot config file --- .github/dependabot.yml | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..9d5155c --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,9 @@ +version: 2 +updates: +- package-ecosystem: npm + directory: "/" + schedule: + interval: daily + time: "14:00" + timezone: Europe/Amsterdam + open-pull-requests-limit: 99 From c959121c030a7ece81cff322cc45b43426c574b1 Mon Sep 17 00:00:00 2001 From: Keanu Date: Wed, 21 Oct 2020 22:19:05 +0200 Subject: [PATCH 056/178] Delete dependabot.yml --- .github/dependabot.yml | 9 --------- 1 file changed, 9 deletions(-) delete mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index 9d5155c..0000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,9 +0,0 @@ -version: 2 -updates: -- package-ecosystem: npm - directory: "/" - schedule: - interval: daily - time: "14:00" - timezone: Europe/Amsterdam - open-pull-requests-limit: 99 From dbebb656b690a8a3683843e5bb991094fa99202f Mon Sep 17 00:00:00 2001 From: Keanu Date: Thu, 22 Oct 2020 13:38:57 +0000 Subject: [PATCH 057/178] Added shorten command. --- src/commands/utilities/shorten.ts | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 src/commands/utilities/shorten.ts diff --git a/src/commands/utilities/shorten.ts b/src/commands/utilities/shorten.ts new file mode 100644 index 0000000..e58b528 --- /dev/null +++ b/src/commands/utilities/shorten.ts @@ -0,0 +1,25 @@ +import Command from '../../core/command'; +import { CommonLibrary } from '../../core/lib'; +import * as https from 'https'; + +export default new Command({ + description: 'Shortens a given URL.', + run: 'Please provide a URL.', + any: new Command({ + async run($: CommonLibrary): Promise { + https.get( + 'https://is.gd/create.php?format=simple&url=' + + encodeURIComponent($.args[0]), + function (res) { + var body = ''; + res.on('data', function (chunk) { + body += chunk; + }); + res.on('end', function () { + $.channel.send(`<${body}>`); + }); + }, + ); + }, + }), +}); From cdf2c47f0c5c21d75ff89649183c4fa4fea2a6a9 Mon Sep 17 00:00:00 2001 From: Keanu Date: Thu, 22 Oct 2020 13:39:33 +0000 Subject: [PATCH 058/178] Fixed up info guild. --- src/commands/info.ts | 403 ++++++++++++++++++++++--------------------- 1 file changed, 206 insertions(+), 197 deletions(-) diff --git a/src/commands/info.ts b/src/commands/info.ts index 6ea85f9..b3b1e44 100644 --- a/src/commands/info.ts +++ b/src/commands/info.ts @@ -1,197 +1,206 @@ -import { Guild, MessageEmbed } from 'discord.js'; -import moment from 'moment'; -import Command from '../core/command'; -import { CommonLibrary } from '../core/lib'; -import { verificationLevels, filterLevels, regions, flags } from '../defs/info'; - -export default new Command({ - description: - 'Command to provide all sorts of info about the current server, a user, etc.', - run: 'Please provide an argument.\nFor help, run `%prefix%help info`.', - subcommands: { - avatar: new Command({ - description: "Shows your own, or another user's avatar.", - usage: '()', - async run($: CommonLibrary): Promise { - $.channel.send( - $.author.displayAvatarURL({ dynamic: true, size: 2048 }), - ); - }, - user: new Command({ - description: "Shows your own, or another user's avatar.", - async run($: CommonLibrary): Promise { - $.channel.send( - $.args[0].displayAvatarURL({ dynamic: true, size: 2048 }), - ); - }, - }), - }), - - guild: new Command({ - description: 'Displays info about the current guild.', - async run($: CommonLibrary): Promise { - if ($.guild) { - const roles = $.guild.roles.cache - .sort((a, b) => b.position - a.position) - .map((role) => role.toString()); - const members = $.guild.members.cache; - const channels = $.guild.channels.cache; - const emojis = $.guild.emojis.cache; - - const iconURL = $.guild.iconURL({ dynamic: true }); - const embed = new MessageEmbed() - .setDescription(`**Guild information for __${$.guild.name}__**`) - .setColor('BLUE'); - if (iconURL) - embed - .setThumbnail(iconURL) - .addField('General', [ - `**❯ Name:** ${$.guild.name}`, - `**❯ ID:** ${$.guild.id}`, - `**❯ Owner:** ${$.guild.owner?.user.tag} (${$.guild.ownerID})`, - `**❯ Region:** ${regions[$.guild.region]}`, - `**❯ Boost Tier:** ${ - $.guild.premiumTier ? `Tier ${$.guild.premiumTier}` : 'None' - }`, - `**❯ Explicit Filter:** ${ - filterLevels[$.guild.explicitContentFilter] - }`, - `**❯ Verification Level:** ${ - verificationLevels[$.guild.verificationLevel] - }`, - `**❯ Time Created:** ${moment($.guild.createdTimestamp).format( - 'LT', - )} ${moment($.guild.createdTimestamp).format('LL')} ${moment( - $.guild.createdTimestamp, - ).fromNow()})`, - '\u200b', - ]) - .addField('Statistics', [ - `**❯ Role Count:** ${roles.length}`, - `**❯ Emoji Count:** ${emojis.size}`, - `**❯ Regular Emoji Count:** ${ - emojis.filter((emoji) => !emoji.animated).size - }`, - `**❯ Animated Emoji Count:** ${ - emojis.filter((emoji) => emoji.animated).size - }`, - `**❯ Member Count:** ${$.guild.memberCount}`, - `**❯ Humans:** ${ - members.filter((member) => !member.user.bot).size - }`, - `**❯ Bots:** ${ - members.filter((member) => member.user.bot).size - }`, - `**❯ Text Channels:** ${ - channels.filter((channel) => channel.type === 'text').size - }`, - `**❯ Voice Channels:** ${ - channels.filter((channel) => channel.type === 'voice').size - }`, - `**❯ Boost Count:** ${$.guild.premiumSubscriptionCount || '0'}`, - `\u200b`, - ]) - .addField('Presence', [ - `**❯ Online:** ${ - members.filter( - (member) => member.presence.status === 'online', - ).size - }`, - `**❯ Idle:** ${ - members.filter((member) => member.presence.status === 'idle') - .size - }`, - `**❯ Do Not Disturb:** ${ - members.filter((member) => member.presence.status === 'dnd') - .size - }`, - `**❯ Offline:** ${ - members.filter( - (member) => member.presence.status === 'offline', - ).size - }`, - '\u200b', - ]) - .addField( - `Roles [${roles.length - 1}]`, - roles.length < 10 - ? roles.join(', ') - : roles.length > 10 - ? this.client.utils.trimArray(roles) - : 'None', - ) - .setTimestamp(); - - $.channel.send(embed); - } else { - $.channel.send('Please execute this command in a guild.'); - } - }, - }), - }, - user: new Command({ - description: 'Displays info about mentioned user.', - async run($: CommonLibrary): Promise { - // Transforms the User object into a GuildMember object of the current guild. - const member = $.guild?.members.resolve($.args[0]); - - if (!member) - return $.channel.send( - 'No member object was found by that user! Are you sure you used this command in a server?', - ); - - const roles = member.roles.cache - .sort( - (a: { position: number }, b: { position: number }) => - b.position - a.position, - ) - .map((role: { toString: () => any }) => role.toString()) - .slice(0, -1); - // @ts-ignore - Discord.js' typings seem to be outdated here. According to their v12 docs, it's User.fetchFlags() instead of User.flags. - const userFlags = ((await member.user.fetchFlags()) as UserFlags).toArray(); - - const embed = new MessageEmbed() - .setThumbnail( - member.user.displayAvatarURL({ dynamic: true, size: 512 }), - ) - .setColor(member.displayHexColor || 'BLUE') - .addField('User', [ - `**❯ Username:** ${member.user.username}`, - `**❯ Discriminator:** ${member.user.discriminator}`, - `**❯ ID:** ${member.id}`, - `**❯ Flags:** ${userFlags.length ? userFlags.join(', ') : 'None'}`, - `**❯ Avatar:** [Link to avatar](${member.user.displayAvatarURL({ - dynamic: true, - })})`, - `**❯ Time Created:** ${moment(member.user.createdTimestamp).format( - 'LT', - )} ${moment(member.user.createdTimestamp).format('LL')} ${moment( - member.user.createdTimestamp, - ).fromNow()}`, - `**❯ Status:** ${member.user.presence.status}`, - `**❯ Game:** ${ - member.user.presence.activities || 'Not playing a game.' - }`, - ]) - .addField('Member', [ - `**❯ Highest Role:** ${ - member.roles.highest.id === $.guild?.id - ? 'None' - : member.roles.highest.name - }`, - `**❯ Server Join Date:** ${moment(member.joinedAt).format('LL LTS')}`, - `**❯ Hoist Role:** ${ - member.roles.hoist ? member.roles.hoist.name : 'None' - }`, - `**❯ Roles:** [${roles.length}]: ${ - roles.length < 10 - ? roles.join(', ') - : roles.length > 10 - ? this.client.utils.trimArray(roles) - : 'None' - }`, - ]); - $.channel.send(embed); - }, - }), -}); +import { Guild, MessageEmbed } from 'discord.js'; +import moment from 'moment'; +import Command from '../core/command'; +import { CommonLibrary } from '../core/lib'; +import { verificationLevels, filterLevels, regions, flags } from '../defs/info'; + +function trimArray(arr: any, maxLen = 10) { + if (arr.length > maxLen) { + const len = arr.length - maxLen; + arr = arr.slice(0, maxLen); + arr.push(`${len} more...`); + } + return arr; +} + +export default new Command({ + description: + 'Command to provide all sorts of info about the current server, a user, etc.', + run: 'Please provide an argument.\nFor help, run `%prefix%help info`.', + subcommands: { + avatar: new Command({ + description: "Shows your own, or another user's avatar.", + usage: '()', + async run($: CommonLibrary): Promise { + $.channel.send( + $.author.displayAvatarURL({ dynamic: true, size: 2048 }), + ); + }, + user: new Command({ + description: "Shows your own, or another user's avatar.", + async run($: CommonLibrary): Promise { + $.channel.send( + $.args[0].displayAvatarURL({ dynamic: true, size: 2048 }), + ); + }, + }), + }), + + guild: new Command({ + description: 'Displays info about the current guild.', + async run($: CommonLibrary): Promise { + if ($.guild) { + const roles = $.guild.roles.cache + .sort((a, b) => b.position - a.position) + .map((role) => role.toString()); + const members = $.guild.members.cache; + const channels = $.guild.channels.cache; + const emojis = $.guild.emojis.cache; + + const iconURL = $.guild.iconURL({ dynamic: true }); + const embed = new MessageEmbed() + .setDescription(`**Guild information for __${$.guild.name}__**`) + .setColor('BLUE'); + if (iconURL) + embed + .setThumbnail(iconURL) + .addField('General', [ + `**❯ Name:** ${$.guild.name}`, + `**❯ ID:** ${$.guild.id}`, + `**❯ Owner:** ${$.guild.owner?.user.tag} (${$.guild.ownerID})`, + `**❯ Region:** ${regions[$.guild.region]}`, + `**❯ Boost Tier:** ${ + $.guild.premiumTier ? `Tier ${$.guild.premiumTier}` : 'None' + }`, + `**❯ Explicit Filter:** ${ + filterLevels[$.guild.explicitContentFilter] + }`, + `**❯ Verification Level:** ${ + verificationLevels[$.guild.verificationLevel] + }`, + `**❯ Time Created:** ${moment($.guild.createdTimestamp).format( + 'LT', + )} ${moment($.guild.createdTimestamp).format('LL')} ${moment( + $.guild.createdTimestamp, + ).fromNow()})`, + '\u200b', + ]) + .addField('Statistics', [ + `**❯ Role Count:** ${roles.length}`, + `**❯ Emoji Count:** ${emojis.size}`, + `**❯ Regular Emoji Count:** ${ + emojis.filter((emoji) => !emoji.animated).size + }`, + `**❯ Animated Emoji Count:** ${ + emojis.filter((emoji) => emoji.animated).size + }`, + `**❯ Member Count:** ${$.guild.memberCount}`, + `**❯ Humans:** ${ + members.filter((member) => !member.user.bot).size + }`, + `**❯ Bots:** ${ + members.filter((member) => member.user.bot).size + }`, + `**❯ Text Channels:** ${ + channels.filter((channel) => channel.type === 'text').size + }`, + `**❯ Voice Channels:** ${ + channels.filter((channel) => channel.type === 'voice').size + }`, + `**❯ Boost Count:** ${$.guild.premiumSubscriptionCount || '0'}`, + `\u200b`, + ]) + .addField('Presence', [ + `**❯ Online:** ${ + members.filter( + (member) => member.presence.status === 'online', + ).size + }`, + `**❯ Idle:** ${ + members.filter((member) => member.presence.status === 'idle') + .size + }`, + `**❯ Do Not Disturb:** ${ + members.filter((member) => member.presence.status === 'dnd') + .size + }`, + `**❯ Offline:** ${ + members.filter( + (member) => member.presence.status === 'offline', + ).size + }`, + '\u200b', + ]) + .addField( + `Roles [${roles.length - 1}]`, + roles.length < 10 + ? roles.join(', ') + : roles.length > 10 + ? trimArray(roles) + : 'None', + ) + .setTimestamp(); + + $.channel.send(embed); + } else { + $.channel.send('Please execute this command in a guild.'); + } + }, + }), + }, + user: new Command({ + description: 'Displays info about mentioned user.', + async run($: CommonLibrary): Promise { + // Transforms the User object into a GuildMember object of the current guild. + const member = $.guild?.members.resolve($.args[0]); + + if (!member) + return $.channel.send( + 'No member object was found by that user! Are you sure you used this command in a server?', + ); + + const roles = member.roles.cache + .sort( + (a: { position: number }, b: { position: number }) => + b.position - a.position, + ) + .map((role: { toString: () => any }) => role.toString()) + .slice(0, -1); + // @ts-ignore - Discord.js' typings seem to be outdated here. According to their v12 docs, it's User.fetchFlags() instead of User.flags. + const userFlags = ((await member.user.fetchFlags()) as UserFlags).toArray(); + + const embed = new MessageEmbed() + .setThumbnail( + member.user.displayAvatarURL({ dynamic: true, size: 512 }), + ) + .setColor(member.displayHexColor || 'BLUE') + .addField('User', [ + `**❯ Username:** ${member.user.username}`, + `**❯ Discriminator:** ${member.user.discriminator}`, + `**❯ ID:** ${member.id}`, + `**❯ Flags:** ${userFlags.length ? userFlags.join(', ') : 'None'}`, + `**❯ Avatar:** [Link to avatar](${member.user.displayAvatarURL({ + dynamic: true, + })})`, + `**❯ Time Created:** ${moment(member.user.createdTimestamp).format( + 'LT', + )} ${moment(member.user.createdTimestamp).format('LL')} ${moment( + member.user.createdTimestamp, + ).fromNow()}`, + `**❯ Status:** ${member.user.presence.status}`, + `**❯ Game:** ${ + member.user.presence.activities || 'Not playing a game.' + }`, + ]) + .addField('Member', [ + `**❯ Highest Role:** ${ + member.roles.highest.id === $.guild?.id + ? 'None' + : member.roles.highest.name + }`, + `**❯ Server Join Date:** ${moment(member.joinedAt).format('LL LTS')}`, + `**❯ Hoist Role:** ${ + member.roles.hoist ? member.roles.hoist.name : 'None' + }`, + `**❯ Roles:** [${roles.length}]: ${ + roles.length < 10 + ? roles.join(', ') + : roles.length > 10 + ? this.client.utils.trimArray(roles) + : 'None' + }`, + ]); + $.channel.send(embed); + }, + }), +}); From beab6cb1fcbaf9a7fb4d45016b33e2caae616ea2 Mon Sep 17 00:00:00 2001 From: Keanu Date: Thu, 22 Oct 2020 13:41:02 +0000 Subject: [PATCH 059/178] Did a number of things. - Upgraded dependencies - Added eval command (admin) - Added bot info command (info) --- package-lock.json | 3215 +++++++++++++++++++---------------------- package.json | 78 +- src/commands/admin.ts | 403 +++--- src/commands/info.ts | 61 +- src/core/lib.ts | 916 ++++++------ 5 files changed, 2262 insertions(+), 2411 deletions(-) diff --git a/package-lock.json b/package-lock.json index c41153c..ec5c34f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,1724 +1,1491 @@ -{ - "name": "d.js-v12-bot", - "version": "0.0.1", - "lockfileVersion": 1, - "requires": true, - "dependencies": { - "@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" - } - }, - "@types/color-name": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", - "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==" - }, - "@types/inquirer": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/@types/inquirer/-/inquirer-6.5.0.tgz", - "integrity": "sha512-rjaYQ9b9y/VFGOpqBEXRavc3jh0a+e6evAbI31tMda8VlPaSy0AZJfXsvmIe3wklc7W6C3zCSfleuMXR7NOyXw==", - "dev": true, - "requires": { - "@types/through": "*", - "rxjs": "^6.4.0" - } - }, - "@types/mocha": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-8.0.3.tgz", - "integrity": "sha512-vyxR57nv8NfcU0GZu8EUXZLTbCMupIUwy95LJ6lllN+JRPG25CwMHoB1q5xKh8YKhQnHYRAn4yW2yuHbf/5xgg==", - "dev": true - }, - "@types/node": { - "version": "14.0.22", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.0.22.tgz", - "integrity": "sha512-emeGcJvdiZ4Z3ohbmw93E/64jRzUHAItSHt8nF7M4TGgQTiWqFVGB8KNpLGFmUHmHLvjvBgFwVlqNcq+VuGv9g==", - "dev": true - }, - "@types/through": { - "version": "0.0.30", - "resolved": "https://registry.npmjs.org/@types/through/-/through-0.0.30.tgz", - "integrity": "sha512-FvnCJljyxhPM3gkRgWmxmDZyAQSiBQQWLI0A0VFL0K7W1oRUrPJSqNO0NvTnLkBcotdlp3lKvaT0JrnyRDkzOg==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/ws": { - "version": "7.2.6", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-7.2.6.tgz", - "integrity": "sha512-Q07IrQUSNpr+cXU4E4LtkSIBPie5GLZyyMC1QtQYRLWz701+XcoVygGUZgvLqElq1nU4ICldMYPnexlBsg3dqQ==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "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" - } - }, - "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-escapes": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.1.tgz", - "integrity": "sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA==", - "requires": { - "type-fest": "^0.11.0" - } - }, - "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" - }, - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "anymatch": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", - "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", - "dev": true, - "requires": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - } - }, - "arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true - }, - "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" - } - }, - "array.prototype.map": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array.prototype.map/-/array.prototype.map-1.0.2.tgz", - "integrity": "sha512-Az3OYxgsa1g7xDYp86l0nnN4bcmuEITGe1rbdEBVkrqkzMgDcbdQ2R7r41pNzti+4NMces3H8gMmuioZUilLgw==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1", - "es-array-method-boxes-properly": "^1.0.0", - "is-string": "^1.0.4" - } - }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" - }, - "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true - }, - "binary-extensions": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz", - "integrity": "sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==", - "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" - } - }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "requires": { - "fill-range": "^7.0.1" - } - }, - "browser-stdout": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", - "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", - "dev": true - }, - "buffer-from": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", - "dev": true - }, - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true - }, - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "chardet": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", - "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==" - }, - "chokidar": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.2.tgz", - "integrity": "sha512-IZHaDeBeI+sZJRX7lGcXsdzgvZqKv6sECqsbErJA4mHWfpRrD8B97kSFN4cQz6nGBGiuFia1MKR4d6c1o8Cv7A==", - "dev": true, - "requires": { - "anymatch": "~3.1.1", - "braces": "~3.0.2", - "fsevents": "~2.1.2", - "glob-parent": "~5.1.0", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.4.0" - } - }, - "cli-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", - "requires": { - "restore-cursor": "^3.1.0" - } - }, - "cli-width": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", - "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==" - }, - "cliui": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", - "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", - "dev": true, - "requires": { - "string-width": "^3.1.0", - "strip-ansi": "^5.2.0", - "wrap-ansi": "^5.1.0" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - }, - "emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - } - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.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==", - "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==" - }, - "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.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", - "dev": true - }, - "define-properties": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", - "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", - "dev": true, - "requires": { - "object-keys": "^1.0.12" - } - }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" - }, - "diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true - }, - "discord.js": { - "version": "12.4.0", - "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-12.4.0.tgz", - "integrity": "sha512-Lc+/vKzih1DPEya/0MO0BAp4Ru/4+MfDsOJkfbyzGoyIlFqiWSQ78RdyRk4tqAtabl6d5QR4Ygo0Ub0TGEsXBg==", - "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.2", - "setimmediate": "^1.0.5", - "tweetnacl": "^1.0.3", - "ws": "^7.3.1" - } - }, - "duplexer": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", - "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=", - "dev": true - }, - "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==" - }, - "es-abstract": { - "version": "1.17.6", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz", - "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==", - "dev": true, - "requires": { - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1", - "is-callable": "^1.2.0", - "is-regex": "^1.1.0", - "object-inspect": "^1.7.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.0", - "string.prototype.trimend": "^1.0.1", - "string.prototype.trimstart": "^1.0.1" - } - }, - "es-array-method-boxes-properly": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz", - "integrity": "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==", - "dev": true - }, - "es-get-iterator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.0.tgz", - "integrity": "sha512-UfrmHuWQlNMTs35e1ypnvikg6jCz3SK8v8ImvmDsh36fCVUR1MqoFDiyn0/k52C8NqO3YsO8Oe0azeesNuqSsQ==", - "dev": true, - "requires": { - "es-abstract": "^1.17.4", - "has-symbols": "^1.0.1", - "is-arguments": "^1.0.4", - "is-map": "^2.0.1", - "is-set": "^2.0.1", - "is-string": "^1.0.5", - "isarray": "^2.0.5" - } - }, - "es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, - "requires": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - } - }, - "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=" - }, - "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 - }, - "event-stream": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz", - "integrity": "sha1-SrTJoPWlTbkzi0w02Gv86PSzVXE=", - "dev": true, - "requires": { - "duplexer": "~0.1.1", - "from": "~0", - "map-stream": "~0.1.0", - "pause-stream": "0.0.11", - "split": "0.3", - "stream-combiner": "~0.0.4", - "through": "~2.3.1" - } - }, - "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==" - }, - "external-editor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", - "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", - "requires": { - "chardet": "^0.7.0", - "iconv-lite": "^0.4.24", - "tmp": "^0.0.33" - } - }, - "figures": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", - "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", - "requires": { - "escape-string-regexp": "^1.0.5" - } - }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "requires": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - } - }, - "flat": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/flat/-/flat-4.1.0.tgz", - "integrity": "sha512-Px/TiLIznH7gEDlPXcUD4KnBusa6kR6ayRUVcnEAbreRIuhkqow/mun59BuRXwoYk7ZQOLW1ZM05ilIvK38hFw==", - "dev": true, - "requires": { - "is-buffer": "~2.0.3" - } - }, - "from": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz", - "integrity": "sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4=", - "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 - }, - "fsevents": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", - "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", - "dev": true, - "optional": true - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, - "get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true - }, - "glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "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.1", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", - "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", - "dev": true, - "requires": { - "is-glob": "^4.0.1" - } - }, - "growl": { - "version": "1.10.5", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", - "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", - "dev": true - }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "requires": { - "function-bind": "^1.1.1" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "has-symbols": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", - "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", - "dev": true - }, - "he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "dev": true - }, - "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "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 - }, - "inquirer": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.3.1.tgz", - "integrity": "sha512-/+vOpHQHhoh90Znev8BXiuw1TDQ7IDxWsQnFafUEoK5+4uN5Eoz1p+3GqOj/NtzEi9VzWKQcV9Bm+i8moxedsA==", - "requires": { - "ansi-escapes": "^4.2.1", - "chalk": "^4.1.0", - "cli-cursor": "^3.1.0", - "cli-width": "^3.0.0", - "external-editor": "^3.0.3", - "figures": "^3.0.0", - "lodash": "^4.17.16", - "mute-stream": "0.0.8", - "run-async": "^2.4.0", - "rxjs": "^6.6.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0", - "through": "^2.3.6" - } - }, - "is-arguments": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.0.4.tgz", - "integrity": "sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA==", - "dev": true - }, - "is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "requires": { - "binary-extensions": "^2.0.0" - } - }, - "is-buffer": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz", - "integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A==", - "dev": true - }, - "is-callable": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.0.tgz", - "integrity": "sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw==", - "dev": true - }, - "is-date-object": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", - "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==", - "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==" - }, - "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" - } - }, - "is-map": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.1.tgz", - "integrity": "sha512-T/S49scO8plUiAOA2DBTBG3JHpn1yiw0kRp6dgiZ0v2/6twi5eiB0rHtHFH9ZIrvlWc6+4O+m4zg5+Z833aXgw==", - "dev": true - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true - }, - "is-plain-obj": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", - "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=", - "dev": true - }, - "is-regex": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", - "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", - "dev": true, - "requires": { - "has-symbols": "^1.0.1" - } - }, - "is-set": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.1.tgz", - "integrity": "sha512-eJEzOtVyenDs1TMzSQ3kU3K+E0GUS9sno+F0OBT97xsgcJsF9nXMBtkT9/kut5JEpM7oL7X/0qxR17K3mcwIAA==", - "dev": true - }, - "is-string": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.5.tgz", - "integrity": "sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==", - "dev": true - }, - "is-symbol": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", - "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", - "dev": true, - "requires": { - "has-symbols": "^1.0.1" - } - }, - "isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "dev": true - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true - }, - "iterate-iterator": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/iterate-iterator/-/iterate-iterator-1.0.1.tgz", - "integrity": "sha512-3Q6tudGN05kbkDQDI4CqjaBf4qf85w6W6GnuZDtUVYwKgtC1q8yxYX7CZed7N+tLzQqS6roujWvszf13T+n9aw==", - "dev": true - }, - "iterate-value": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/iterate-value/-/iterate-value-1.0.2.tgz", - "integrity": "sha512-A6fMAio4D2ot2r/TYzr4yUWrmwNdsN5xL7+HUiyACE4DXm+q8HtPcnFTp+NnW3k4N05tZ7FVYFFb2CR13NxyHQ==", - "dev": true, - "requires": { - "es-get-iterator": "^1.0.2", - "iterate-iterator": "^1.0.1" - } - }, - "js-yaml": { - "version": "3.14.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz", - "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==", - "dev": true, - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - } - }, - "locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "requires": { - "p-locate": "^5.0.0" - } - }, - "lodash": { - "version": "4.17.19", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz", - "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==" - }, - "log-symbols": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.0.0.tgz", - "integrity": "sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA==", - "dev": true, - "requires": { - "chalk": "^4.0.0" - } - }, - "make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true - }, - "map-stream": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz", - "integrity": "sha1-5WqpTEyAVaFkBKBnS3jyFffI4ZQ=", - "dev": true - }, - "mime-db": { - "version": "1.44.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", - "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==" - }, - "mime-types": { - "version": "2.1.27", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", - "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", - "requires": { - "mime-db": "1.44.0" - } - }, - "mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==" - }, - "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" - } - }, - "mocha": { - "version": "8.1.2", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-8.1.2.tgz", - "integrity": "sha512-I8FRAcuACNMLQn3lS4qeWLxXqLvGf6r2CaLstDpZmMUUSmvW6Cnm1AuHxgbc7ctZVRcfwspCRbDHymPsi3dkJw==", - "dev": true, - "requires": { - "ansi-colors": "4.1.1", - "browser-stdout": "1.3.1", - "chokidar": "3.4.2", - "debug": "4.1.1", - "diff": "4.0.2", - "escape-string-regexp": "4.0.0", - "find-up": "5.0.0", - "glob": "7.1.6", - "growl": "1.10.5", - "he": "1.2.0", - "js-yaml": "3.14.0", - "log-symbols": "4.0.0", - "minimatch": "3.0.4", - "ms": "2.1.2", - "object.assign": "4.1.0", - "promise.allsettled": "1.0.2", - "serialize-javascript": "4.0.0", - "strip-json-comments": "3.0.1", - "supports-color": "7.1.0", - "which": "2.0.2", - "wide-align": "1.1.3", - "workerpool": "6.0.0", - "yargs": "13.3.2", - "yargs-parser": "13.1.2", - "yargs-unparser": "1.6.1" - }, - "dependencies": { - "escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true - } - } - }, - "moment": { - "version": "2.27.0", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.27.0.tgz", - "integrity": "sha512-al0MUK7cpIcglMv3YF13qSgdAIqxHTO7brRtaz3DlSULbqfazqkc5kEjNrLDOM7fsjshoFIihnU8snrP7zUvhQ==" - }, - "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 - }, - "mute-stream": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", - "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==" - }, - "node-cleanup": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/node-cleanup/-/node-cleanup-2.1.2.tgz", - "integrity": "sha1-esGavSl+Caf3KnFUXZUbUX5N3iw=", - "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==" - }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true - }, - "object-inspect": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz", - "integrity": "sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==", - "dev": true - }, - "object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true - }, - "object.assign": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", - "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", - "dev": true, - "requires": { - "define-properties": "^1.1.2", - "function-bind": "^1.1.1", - "has-symbols": "^1.0.0", - "object-keys": "^1.0.11" - } - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, - "requires": { - "wrappy": "1" - } - }, - "onetime": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.0.tgz", - "integrity": "sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q==", - "requires": { - "mimic-fn": "^2.1.0" - } - }, - "os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" - }, - "p-limit": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.0.2.tgz", - "integrity": "sha512-iwqZSOoWIW+Ew4kAGUlN16J4M7OB3ysMLSZtnhmqx7njIHFPlxWBX8xo3lVTyFVq6mI/lL9qt2IsN1sHwaxJkg==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "requires": { - "p-limit": "^3.0.2" - } - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true - }, - "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 - }, - "pause-stream": { - "version": "0.0.11", - "resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", - "integrity": "sha1-/lo0sMvOErWqaitAPuLnO2AvFEU=", - "dev": true, - "requires": { - "through": "~2.3" - } - }, - "picomatch": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", - "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", - "dev": true - }, - "prettier": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.1.2.tgz", - "integrity": "sha512-16c7K+x4qVlJg9rEbXl7HEGmQyZlG4R9AgP+oHKRMsMsuk8s+ATStlf1NpDqyBI1HpVyfjLOeMhH2LvuNvV5Vg==", - "dev": true - }, - "prism-media": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/prism-media/-/prism-media-1.2.2.tgz", - "integrity": "sha512-I+nkWY212lJ500jLe4tN9tWO7nRiBAVdMv76P9kffZjYhw20raMlW1HSSvS+MLXC9MmbNZCazMrAr+5jEEgTuw==" - }, - "promise.allsettled": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/promise.allsettled/-/promise.allsettled-1.0.2.tgz", - "integrity": "sha512-UpcYW5S1RaNKT6pd+s9jp9K9rlQge1UXKskec0j6Mmuq7UJCvlS2J2/s/yuPN8ehftf9HXMxWlKiPbGGUzpoRg==", - "dev": true, - "requires": { - "array.prototype.map": "^1.0.1", - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1", - "function-bind": "^1.1.1", - "iterate-value": "^1.0.0" - } - }, - "ps-tree": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/ps-tree/-/ps-tree-1.2.0.tgz", - "integrity": "sha512-0VnamPPYHl4uaU/nSFeZZpR21QAWRz+sRv4iW9+v/GS/J5U5iZB5BNN6J0RMoOvdx2gWM2+ZFMIm58q24e4UYA==", - "dev": true, - "requires": { - "event-stream": "=3.3.4" - } - }, - "randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "requires": { - "safe-buffer": "^5.1.0" - } - }, - "readdirp": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.4.0.tgz", - "integrity": "sha512-0xe001vZBnJEK+uKcj8qOhyAKPzIT+gStxWr3LCB0DwcXR5NZJ3IaC+yGnHCYzB/S7ov3m3EEbZI2zeNvX+hGQ==", - "dev": true, - "requires": { - "picomatch": "^2.2.1" - } - }, - "require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", - "dev": true - }, - "require-main-filename": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", - "dev": true - }, - "restore-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", - "requires": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" - } - }, - "run-async": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", - "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==" - }, - "rxjs": { - "version": "6.6.0", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.0.tgz", - "integrity": "sha512-3HMA8z/Oz61DUHe+SdOiQyzIf4tOx5oQHmMir7IZEu6TMqCLHT4LRcmNaUS0NwOz8VLvmmBduMsoaUvMaIiqzg==", - "requires": { - "tslib": "^1.9.0" - } - }, - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "serialize-javascript": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", - "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==", - "dev": true, - "requires": { - "randombytes": "^2.1.0" - } - }, - "set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", - "dev": true - }, - "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 - }, - "signal-exit": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", - "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==" - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "source-map-support": { - "version": "0.5.19", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", - "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "split": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/split/-/split-0.3.3.tgz", - "integrity": "sha1-zQ7qXmOiEd//frDwkcQTPi0N0o8=", - "dev": true, - "requires": { - "through": "2" - } - }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true - }, - "stream-combiner": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz", - "integrity": "sha1-TV5DPBhSYd3mI8o/RMWGvPXErRQ=", - "dev": true, - "requires": { - "duplexer": "~0.1.1" - } - }, - "string-argv": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.1.2.tgz", - "integrity": "sha512-mBqPGEOMNJKXRo7z0keX0wlAhbBAjilUdPW13nN0PecVryZxdHIeM7TqbsSUA7VYuS00HGC6mojP7DlQzfa9ZA==", - "dev": true - }, - "string-width": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", - "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - } - }, - "string.prototype.trimend": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz", - "integrity": "sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5" - } - }, - "string.prototype.trimstart": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz", - "integrity": "sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5" - } - }, - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "requires": { - "ansi-regex": "^5.0.0" - } - }, - "strip-json-comments": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.0.1.tgz", - "integrity": "sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw==", - "dev": true - }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "requires": { - "has-flag": "^4.0.0" - } - }, - "through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" - }, - "tmp": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", - "requires": { - "os-tmpdir": "~1.0.2" - } - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "requires": { - "is-number": "^7.0.0" - } - }, - "ts-node": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-9.0.0.tgz", - "integrity": "sha512-/TqB4SnererCDR/vb4S/QvSZvzQMJN8daAslg7MeaiHvD8rDZsSfXmNeNumyZZzMned72Xoq/isQljYSt8Ynfg==", - "dev": true, - "requires": { - "arg": "^4.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "source-map-support": "^0.5.17", - "yn": "3.1.1" - } - }, - "tsc-watch": { - "version": "4.2.9", - "resolved": "https://registry.npmjs.org/tsc-watch/-/tsc-watch-4.2.9.tgz", - "integrity": "sha512-DlTaoDs74+KUpyWr7dCGhuscAUKCz6CiFduBN7R9RbLJSSN1moWdwoCLASE7+zLgGvV5AwXfYDiEMAsPGaO+Vw==", - "dev": true, - "requires": { - "cross-spawn": "^7.0.3", - "node-cleanup": "^2.1.2", - "ps-tree": "^1.2.0", - "string-argv": "^0.1.1", - "strip-ansi": "^6.0.0" - } - }, - "tslib": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz", - "integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==" - }, - "tweetnacl": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", - "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==" - }, - "type-fest": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.11.0.tgz", - "integrity": "sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==" - }, - "typescript": { - "version": "3.9.6", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.6.tgz", - "integrity": "sha512-Pspx3oKAPJtjNwE92YS05HQoY7z2SFyOpHo9MqJor3BXAGNaPUs83CuVp9VISFkSjyRfiTpmKuAYGJB7S7hOxw==", - "dev": true - }, - "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" - } - }, - "which-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", - "dev": true - }, - "wide-align": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", - "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", - "dev": true, - "requires": { - "string-width": "^1.0.2 || 2" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, - "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - } - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0" - } - } - } - }, - "workerpool": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.0.0.tgz", - "integrity": "sha512-fU2OcNA/GVAJLLyKUoHkAgIhKb0JoCpSjLC/G2vYKxUjVmQwGbRVeoPJ1a8U4pnVofz4AQV5Y/NEw8oKqxEBtA==", - "dev": true - }, - "wrap-ansi": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", - "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.0", - "string-width": "^3.0.0", - "strip-ansi": "^5.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - }, - "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" - } - }, - "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 - }, - "emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - } - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - } - } - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true - }, - "ws": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.3.1.tgz", - "integrity": "sha512-D3RuNkynyHmEJIpD2qrgVkc9DQ23OrN/moAwZX4L8DfvszsJxpjQuUq3LMx6HoYji9fbIOBY18XWBsAux1ZZUA==" - }, - "y18n": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", - "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", - "dev": true - }, - "yargs": { - "version": "13.3.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", - "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", - "dev": true, - "requires": { - "cliui": "^5.0.0", - "find-up": "^3.0.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^3.0.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^13.1.2" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - }, - "emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", - "dev": true - }, - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "requires": { - "locate-path": "^3.0.0" - } - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, - "requires": { - "p-limit": "^2.0.0" - } - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true - }, - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - } - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - } - } - } - }, - "yargs-parser": { - "version": "13.1.2", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", - "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", - "dev": true, - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } - }, - "yargs-unparser": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.6.1.tgz", - "integrity": "sha512-qZV14lK9MWsGCmcr7u5oXGH0dbGqZAIxTDrWXZDo5zUr6b6iUmelNKO6x6R1dQT24AH3LgRxJpr8meWy2unolA==", - "dev": true, - "requires": { - "camelcase": "^5.3.1", - "decamelize": "^1.2.0", - "flat": "^4.1.0", - "is-plain-obj": "^1.1.0", - "yargs": "^14.2.3" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - }, - "emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", - "dev": true - }, - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "requires": { - "locate-path": "^3.0.0" - } - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, - "requires": { - "p-limit": "^2.0.0" - } - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true - }, - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - } - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - } - }, - "yargs": { - "version": "14.2.3", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-14.2.3.tgz", - "integrity": "sha512-ZbotRWhF+lkjijC/VhmOT9wSgyBQ7+zr13+YLkhfsSiTriYsMzkTUFP18pFhWwBeMa5gUc1MzbhrO6/VB7c9Xg==", - "dev": true, - "requires": { - "cliui": "^5.0.0", - "decamelize": "^1.2.0", - "find-up": "^3.0.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^3.0.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^15.0.1" - } - }, - "yargs-parser": { - "version": "15.0.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-15.0.1.tgz", - "integrity": "sha512-0OAMV2mAZQrs3FkNpDQcBk1x5HXb8X4twADss4S0Iuk+2dGnLOE/fRHrsYm542GduMveyA77OF4wrNJuanRCWw==", - "dev": true, - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } - } - } - }, - "yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true - } - } -} +{ + "name": "d.js-v12-bot", + "version": "0.0.1", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@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" + } + }, + "@lavacord/discord.js": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/@lavacord/discord.js/-/discord.js-0.0.5.tgz", + "integrity": "sha512-qc2lw0zB48fq4SrSlpMJOmogUXIeM5YvufQfPg0ubjp7jqm20JnOXF9fliy1MPdcKPnZ8LwIZ222H0+ghGzP/Q==", + "requires": { + "lavacord": "^1.1.9" + }, + "dependencies": { + "lavacord": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/lavacord/-/lavacord-1.1.9.tgz", + "integrity": "sha512-haZghbblO1w3Hodc9q63ZWgV5zA/jB6xFKS17fImK5aIdn0PkKuZ6AsJBxMFpR275v8GNYOxg6cTQBYBQ+batQ==", + "requires": { + "node-fetch": "^2.6.0", + "ws": "^7.3.0" + } + } + } + }, + "@types/color-name": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", + "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==" + }, + "@types/inquirer": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@types/inquirer/-/inquirer-6.5.0.tgz", + "integrity": "sha512-rjaYQ9b9y/VFGOpqBEXRavc3jh0a+e6evAbI31tMda8VlPaSy0AZJfXsvmIe3wklc7W6C3zCSfleuMXR7NOyXw==", + "dev": true, + "requires": { + "@types/through": "*", + "rxjs": "^6.4.0" + } + }, + "@types/mocha": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-8.0.3.tgz", + "integrity": "sha512-vyxR57nv8NfcU0GZu8EUXZLTbCMupIUwy95LJ6lllN+JRPG25CwMHoB1q5xKh8YKhQnHYRAn4yW2yuHbf/5xgg==", + "dev": true + }, + "@types/ms": { + "version": "0.7.31", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz", + "integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==", + "dev": true + }, + "@types/node": { + "version": "14.14.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.2.tgz", + "integrity": "sha512-jeYJU2kl7hL9U5xuI/BhKPZ4vqGM/OmK6whiFAXVhlstzZhVamWhDSmHyGLIp+RVyuF9/d0dqr2P85aFj4BvJg==", + "dev": true + }, + "@types/through": { + "version": "0.0.30", + "resolved": "https://registry.npmjs.org/@types/through/-/through-0.0.30.tgz", + "integrity": "sha512-FvnCJljyxhPM3gkRgWmxmDZyAQSiBQQWLI0A0VFL0K7W1oRUrPJSqNO0NvTnLkBcotdlp3lKvaT0JrnyRDkzOg==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/ws": { + "version": "7.2.7", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-7.2.7.tgz", + "integrity": "sha512-UUFC/xxqFLP17hTva8/lVT0SybLUrfSD9c+iapKb0fEiC8uoDbA+xuZ3pAN603eW+bY8ebSMLm9jXdIPnD0ZgA==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@ungap/promise-all-settled": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", + "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", + "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" + } + }, + "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-escapes": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.1.tgz", + "integrity": "sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA==", + "requires": { + "type-fest": "^0.11.0" + } + }, + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" + }, + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "anymatch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", + "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "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" + } + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, + "axios": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.19.2.tgz", + "integrity": "sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA==", + "requires": { + "follow-redirects": "1.5.10" + } + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "binary-extensions": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz", + "integrity": "sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==", + "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" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==" + }, + "chokidar": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.3.tgz", + "integrity": "sha512-DtM3g7juCXQxFVSNPNByEC2+NImtBuxQQvWlHunpJIS5Ocr0lG306cC7FCi7cEA0fzmybPUIl4txBIobk1gGOQ==", + "dev": true, + "requires": { + "anymatch": "~3.1.1", + "braces": "~3.0.2", + "fsevents": "~2.1.2", + "glob-parent": "~5.1.0", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.5.0" + } + }, + "cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "requires": { + "restore-cursor": "^3.1.0" + } + }, + "cli-width": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", + "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==" + }, + "cliui": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "dev": true, + "requires": { + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.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==", + "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==" + }, + "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.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", + "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "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=" + }, + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true + }, + "discord.js": { + "version": "12.4.0", + "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-12.4.0.tgz", + "integrity": "sha512-Lc+/vKzih1DPEya/0MO0BAp4Ru/4+MfDsOJkfbyzGoyIlFqiWSQ78RdyRk4tqAtabl6d5QR4Ygo0Ub0TGEsXBg==", + "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.2", + "setimmediate": "^1.0.5", + "tweetnacl": "^1.0.3", + "ws": "^7.3.1" + } + }, + "discord.js-lavalink-lib": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/discord.js-lavalink-lib/-/discord.js-lavalink-lib-0.1.7.tgz", + "integrity": "sha512-czjsdRcbB+sdfTXsko76JYAAPJzoRhTiaphZJXqFpsAYdv0hjkNVLHxuuG2d4hVuXjt/UU7HDYyWEb2e/3g2Ew==", + "requires": { + "@lavacord/discord.js": "0.0.5", + "axios": "^0.19.2", + "discord.js": "^12.2.0", + "lavacord": "^1.1.7" + } + }, + "duplexer": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", + "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=", + "dev": true + }, + "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==" + }, + "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=" + }, + "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 + }, + "event-stream": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz", + "integrity": "sha1-SrTJoPWlTbkzi0w02Gv86PSzVXE=", + "dev": true, + "requires": { + "duplexer": "~0.1.1", + "from": "~0", + "map-stream": "~0.1.0", + "pause-stream": "0.0.11", + "split": "0.3", + "stream-combiner": "~0.0.4", + "through": "~2.3.1" + } + }, + "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==" + }, + "external-editor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", + "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", + "requires": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + } + }, + "figures": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "requires": { + "escape-string-regexp": "^1.0.5" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, + "flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true + }, + "follow-redirects": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz", + "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==", + "requires": { + "debug": "=3.1.0" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + } + } + }, + "from": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz", + "integrity": "sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4=", + "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 + }, + "fsevents": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", + "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", + "dev": true, + "optional": true + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true + }, + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "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.1", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", + "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "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 + }, + "inquirer": { + "version": "7.3.3", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.3.3.tgz", + "integrity": "sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==", + "requires": { + "ansi-escapes": "^4.2.1", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-width": "^3.0.0", + "external-editor": "^3.0.3", + "figures": "^3.0.0", + "lodash": "^4.17.19", + "mute-stream": "0.0.8", + "run-async": "^2.4.0", + "rxjs": "^6.6.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0", + "through": "^2.3.6" + } + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "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==" + }, + "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" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "js-yaml": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz", + "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "lavacord": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/lavacord/-/lavacord-1.1.9.tgz", + "integrity": "sha512-haZghbblO1w3Hodc9q63ZWgV5zA/jB6xFKS17fImK5aIdn0PkKuZ6AsJBxMFpR275v8GNYOxg6cTQBYBQ+batQ==", + "requires": { + "node-fetch": "^2.6.0", + "ws": "^7.3.0" + } + }, + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "requires": { + "p-locate": "^5.0.0" + } + }, + "lodash": { + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==" + }, + "log-symbols": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.0.0.tgz", + "integrity": "sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA==", + "dev": true, + "requires": { + "chalk": "^4.0.0" + } + }, + "make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "map-stream": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz", + "integrity": "sha1-5WqpTEyAVaFkBKBnS3jyFffI4ZQ=", + "dev": true + }, + "mime-db": { + "version": "1.44.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", + "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==" + }, + "mime-types": { + "version": "2.1.27", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", + "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", + "requires": { + "mime-db": "1.44.0" + } + }, + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==" + }, + "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" + } + }, + "mocha": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-8.2.0.tgz", + "integrity": "sha512-lEWEMq2LMfNJMKeuEwb5UELi+OgFDollXaytR5ggQcHpzG3NP/R7rvixAvF+9/lLsTWhWG+4yD2M70GsM06nxw==", + "dev": true, + "requires": { + "@ungap/promise-all-settled": "1.1.2", + "ansi-colors": "4.1.1", + "browser-stdout": "1.3.1", + "chokidar": "3.4.3", + "debug": "4.2.0", + "diff": "4.0.2", + "escape-string-regexp": "4.0.0", + "find-up": "5.0.0", + "glob": "7.1.6", + "growl": "1.10.5", + "he": "1.2.0", + "js-yaml": "3.14.0", + "log-symbols": "4.0.0", + "minimatch": "3.0.4", + "ms": "2.1.2", + "nanoid": "3.1.12", + "serialize-javascript": "5.0.1", + "strip-json-comments": "3.1.1", + "supports-color": "7.2.0", + "which": "2.0.2", + "wide-align": "1.1.3", + "workerpool": "6.0.2", + "yargs": "13.3.2", + "yargs-parser": "13.1.2", + "yargs-unparser": "2.0.0" + }, + "dependencies": { + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + }, + "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" + } + } + } + }, + "moment": { + "version": "2.29.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz", + "integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==" + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "mute-stream": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==" + }, + "nanoid": { + "version": "3.1.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.12.tgz", + "integrity": "sha512-1qstj9z5+x491jfiC4Nelk+f8XBad7LN20PmyWINJEMRSf3wcAjAWysw1qaA8z6NSKe2sjq1hRSDpBH5paCb6A==", + "dev": true + }, + "node-cleanup": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/node-cleanup/-/node-cleanup-2.1.2.tgz", + "integrity": "sha1-esGavSl+Caf3KnFUXZUbUX5N3iw=", + "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==" + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "requires": { + "mimic-fn": "^2.1.0" + } + }, + "os": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/os/-/os-0.1.1.tgz", + "integrity": "sha1-IIhF6J4ZOtTZcUdLk5R3NqVtE/M=" + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" + }, + "p-limit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.0.2.tgz", + "integrity": "sha512-iwqZSOoWIW+Ew4kAGUlN16J4M7OB3ysMLSZtnhmqx7njIHFPlxWBX8xo3lVTyFVq6mI/lL9qt2IsN1sHwaxJkg==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "requires": { + "p-limit": "^3.0.2" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "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 + }, + "pause-stream": { + "version": "0.0.11", + "resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", + "integrity": "sha1-/lo0sMvOErWqaitAPuLnO2AvFEU=", + "dev": true, + "requires": { + "through": "~2.3" + } + }, + "picomatch": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", + "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", + "dev": true + }, + "prettier": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.1.2.tgz", + "integrity": "sha512-16c7K+x4qVlJg9rEbXl7HEGmQyZlG4R9AgP+oHKRMsMsuk8s+ATStlf1NpDqyBI1HpVyfjLOeMhH2LvuNvV5Vg==", + "dev": true + }, + "prism-media": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/prism-media/-/prism-media-1.2.2.tgz", + "integrity": "sha512-I+nkWY212lJ500jLe4tN9tWO7nRiBAVdMv76P9kffZjYhw20raMlW1HSSvS+MLXC9MmbNZCazMrAr+5jEEgTuw==" + }, + "ps-tree": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/ps-tree/-/ps-tree-1.2.0.tgz", + "integrity": "sha512-0VnamPPYHl4uaU/nSFeZZpR21QAWRz+sRv4iW9+v/GS/J5U5iZB5BNN6J0RMoOvdx2gWM2+ZFMIm58q24e4UYA==", + "dev": true, + "requires": { + "event-stream": "=3.3.4" + } + }, + "randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.0" + } + }, + "readdirp": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", + "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", + "dev": true, + "requires": { + "picomatch": "^2.2.1" + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true + }, + "require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true + }, + "restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "requires": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + } + }, + "run-async": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", + "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==" + }, + "rxjs": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.0.tgz", + "integrity": "sha512-3HMA8z/Oz61DUHe+SdOiQyzIf4tOx5oQHmMir7IZEu6TMqCLHT4LRcmNaUS0NwOz8VLvmmBduMsoaUvMaIiqzg==", + "requires": { + "tslib": "^1.9.0" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "serialize-javascript": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-5.0.1.tgz", + "integrity": "sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==", + "dev": true, + "requires": { + "randombytes": "^2.1.0" + } + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true + }, + "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 + }, + "signal-exit": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", + "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==" + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "source-map-support": { + "version": "0.5.19", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", + "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "split": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/split/-/split-0.3.3.tgz", + "integrity": "sha1-zQ7qXmOiEd//frDwkcQTPi0N0o8=", + "dev": true, + "requires": { + "through": "2" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "stream-combiner": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz", + "integrity": "sha1-TV5DPBhSYd3mI8o/RMWGvPXErRQ=", + "dev": true, + "requires": { + "duplexer": "~0.1.1" + } + }, + "string-argv": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.1.2.tgz", + "integrity": "sha512-mBqPGEOMNJKXRo7z0keX0wlAhbBAjilUdPW13nN0PecVryZxdHIeM7TqbsSUA7VYuS00HGC6mojP7DlQzfa9ZA==", + "dev": true + }, + "string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "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.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "requires": { + "has-flag": "^4.0.0" + } + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" + }, + "tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "requires": { + "os-tmpdir": "~1.0.2" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "ts-node": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-9.0.0.tgz", + "integrity": "sha512-/TqB4SnererCDR/vb4S/QvSZvzQMJN8daAslg7MeaiHvD8rDZsSfXmNeNumyZZzMned72Xoq/isQljYSt8Ynfg==", + "dev": true, + "requires": { + "arg": "^4.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "source-map-support": "^0.5.17", + "yn": "3.1.1" + } + }, + "tsc-watch": { + "version": "4.2.9", + "resolved": "https://registry.npmjs.org/tsc-watch/-/tsc-watch-4.2.9.tgz", + "integrity": "sha512-DlTaoDs74+KUpyWr7dCGhuscAUKCz6CiFduBN7R9RbLJSSN1moWdwoCLASE7+zLgGvV5AwXfYDiEMAsPGaO+Vw==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.3", + "node-cleanup": "^2.1.2", + "ps-tree": "^1.2.0", + "string-argv": "^0.1.1", + "strip-ansi": "^6.0.0" + } + }, + "tslib": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz", + "integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==" + }, + "tweetnacl": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", + "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==" + }, + "type-fest": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.11.0.tgz", + "integrity": "sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==" + }, + "typescript": { + "version": "3.9.7", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.7.tgz", + "integrity": "sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw==", + "dev": true + }, + "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" + } + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true + }, + "wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "dev": true, + "requires": { + "string-width": "^1.0.2 || 2" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "workerpool": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.0.2.tgz", + "integrity": "sha512-DSNyvOpFKrNusaaUwk+ej6cBj1bmhLcBfj80elGk+ZIo5JSkq+unB1dLKEOcNfJDZgjGICfhQ0Q5TbP0PvF4+Q==", + "dev": true + }, + "wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "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" + } + }, + "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 + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "ws": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.3.1.tgz", + "integrity": "sha512-D3RuNkynyHmEJIpD2qrgVkc9DQ23OrN/moAwZX4L8DfvszsJxpjQuUq3LMx6HoYji9fbIOBY18XWBsAux1ZZUA==" + }, + "y18n": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", + "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", + "dev": true + }, + "yargs": { + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", + "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", + "dev": true, + "requires": { + "cliui": "^5.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.1.2" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "yargs-parser": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", + "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + }, + "yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "dev": true, + "requires": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + }, + "dependencies": { + "camelcase": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.1.0.tgz", + "integrity": "sha512-WCMml9ivU60+8rEJgELlFp1gxFcEGxwYleE3bziHEDeqsqAWGHdimB7beBFGjLzVNgPGyDsfgXLQEYMpmIFnVQ==", + "dev": true + }, + "decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true + } + } + }, + "yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true + } + } +} diff --git a/package.json b/package.json index 34c3697..bfc3fac 100644 --- a/package.json +++ b/package.json @@ -1,37 +1,41 @@ -{ - "name": "d.js-v12-bot", - "version": "0.0.1", - "description": "A Discord bot built on Discord.JS v12", - "main": "dist/index.js", - "private": true, - "dependencies": { - "chalk": "^4.1.0", - "discord.js": "^12.4.0", - "inquirer": "^7.3.1", - "moment": "^2.27.0" - }, - "devDependencies": { - "@types/inquirer": "^6.5.0", - "@types/mocha": "^8.0.3", - "@types/node": "^14.0.22", - "@types/ws": "^7.2.6", - "mocha": "^8.1.2", - "prettier": "2.1.2", - "ts-node": "^9.0.0", - "tsc-watch": "^4.2.9", - "typescript": "^3.9.6" - }, - "scripts": { - "build": "tsc && npm prune --production", - "start": "node dist/index.js", - "once": "tsc && npm start", - "dev": "tsc-watch --onSuccess \"node dist/index.js dev\"", - "test": "mocha --require ts-node/register --extension ts --recursive" - }, - "keywords": [ - "discord.js", - "bot" - ], - "author": "Keanu Timmermans", - "license": "MIT" -} +{ + "name": "d.js-v12-bot", + "version": "0.0.1", + "description": "A Discord bot built on Discord.JS v12", + "main": "dist/index.js", + "private": true, + "dependencies": { + "chalk": "^4.1.0", + "discord.js": "^12.4.0", + "discord.js-lavalink-lib": "^0.1.7", + "inquirer": "^7.3.3", + "moment": "^2.29.1", + "ms": "^2.1.2", + "os": "^0.1.1" + }, + "devDependencies": { + "@types/inquirer": "^6.5.0", + "@types/mocha": "^8.0.3", + "@types/ms": "^0.7.31", + "@types/node": "^14.14.2", + "@types/ws": "^7.2.7", + "mocha": "^8.2.0", + "prettier": "2.1.2", + "ts-node": "^9.0.0", + "tsc-watch": "^4.2.9", + "typescript": "^3.9.7" + }, + "scripts": { + "build": "tsc && npm prune --production", + "start": "node dist/index.js", + "once": "tsc && npm start", + "dev": "tsc-watch --onSuccess \"node dist/index.js dev\"", + "test": "mocha --require ts-node/register --extension ts --recursive" + }, + "keywords": [ + "discord.js", + "bot" + ], + "author": "Keanu Timmermans", + "license": "MIT" +} diff --git a/src/commands/admin.ts b/src/commands/admin.ts index 907dab5..a8b9f56 100644 --- a/src/commands/admin.ts +++ b/src/commands/admin.ts @@ -1,192 +1,211 @@ -import Command from '../core/command'; -import { CommonLibrary, logs, botHasPermission } from '../core/lib'; -import { Config, Storage } from '../core/structures'; -import { PermissionNames, getPermissionLevel } from '../core/permissions'; -import { Permissions } from 'discord.js'; -import * as discord from 'discord.js'; - -function getLogBuffer(type: string) { - return { - files: [ - { - attachment: Buffer.alloc(logs[type].length, logs[type]), - name: `${Date.now()}.${type}.log`, - }, - ], - }; -} - -const activities = ['playing', 'listening', 'streaming', 'watching']; - -export default new Command({ - description: - "An all-in-one command to do admin stuff. You need to be either an admin of the server or one of the bot's mechanics to use this command.", - async run($: CommonLibrary): Promise { - if (!$.member) - return $.channel.send( - "Couldn't find a member object for you! Did you make sure you used this in a server?", - ); - const permLevel = getPermissionLevel($.member); - $.channel.send( - `${$.author.toString()}, your permission level is \`${ - PermissionNames[permLevel] - }\` (${permLevel}).`, - ); - }, - subcommands: { - set: new Command({ - description: 'Set different per-guild settings for the bot.', - run: 'You have to specify the option you want to set.', - permission: Command.PERMISSIONS.ADMIN, - subcommands: { - prefix: new Command({ - description: - 'Set a custom prefix for your guild. Removes your custom prefix if none is provided.', - usage: '()', - async run($: CommonLibrary): Promise { - Storage.getGuild($.guild?.id || 'N/A').prefix = null; - Storage.save(); - $.channel.send( - `The custom prefix for this guild has been removed. My prefix is now back to \`${Config.prefix}\`.`, - ); - }, - any: new Command({ - async run($: CommonLibrary): Promise { - Storage.getGuild($.guild?.id || 'N/A').prefix = $.args[0]; - Storage.save(); - $.channel.send( - `The custom prefix for this guild is now \`${$.args[0]}\`.`, - ); - }, - }), - }), - }, - }), - diag: new Command({ - description: 'Requests a debug log with the "info" verbosity level.', - permission: Command.PERMISSIONS.BOT_SUPPORT, - async run($: CommonLibrary): Promise { - $.channel.send(getLogBuffer('info')); - }, - any: new Command({ - description: `Select a verbosity to listen to. Available levels: \`[${Object.keys( - logs, - ).join(', ')}]\``, - async run($: CommonLibrary): Promise { - const type = $.args[0]; - - if (type in logs) $.channel.send(getLogBuffer(type)); - else - $.channel.send( - `Couldn't find a verbosity level named \`${type}\`! The available types are \`[${Object.keys( - logs, - ).join(', ')}]\`.`, - ); - }, - }), - }), - status: new Command({ - description: "Changes the bot's status.", - permission: Command.PERMISSIONS.BOT_SUPPORT, - async run($: CommonLibrary): Promise { - $.channel.send('Setting status to `online`...'); - }, - any: new Command({ - description: `Select a status to set to. Available statuses: \`online\`, \`idle\`, \`dnd\`, \`invisible\``, - async run($: CommonLibrary): Promise { - let statuses = ['online', 'idle', 'dnd', 'invisible']; - if (!statuses.includes($.args[0])) - return $.channel.send("That status doesn't exist!"); - else { - $.client.user?.setStatus($.args[0]); - $.channel.send(`Setting status to \`${$.args[0]}\`...`); - } - }, - }), - }), - purge: new Command({ - description: 'Purges bot messages.', - permission: Command.PERMISSIONS.BOT_SUPPORT, - async run($: CommonLibrary): Promise { - if ($.message.channel instanceof discord.DMChannel) { - return; - } - $.message.delete(); - const msgs = await $.channel.messages.fetch({ - limit: 100, - }); - const travMessages = msgs.filter( - (m) => m.author.id === $.client.user?.id, - ); - - await $.message.channel - .send(`Found ${travMessages.size} messages to delete.`) - .then((m) => - m.delete({ - timeout: 5000, - }), - ); - await $.message.channel.bulkDelete(travMessages); - }, - }), - nick: new Command({ - description: "Change the bot's nickname.", - permission: Command.PERMISSIONS.BOT_SUPPORT, - async run($: CommonLibrary): Promise { - const nickName = $.args.join(' '); - const trav = $.guild?.members.cache.find( - (member) => member.id === $.client.user?.id, - ); - await trav?.setNickname(nickName); - if (botHasPermission($.guild, Permissions.FLAGS.MANAGE_MESSAGES)) - $.message.delete({ timeout: 5000 }).catch($.handler.bind($)); - $.channel - .send(`Nickname set to \`${nickName}\``) - .then((m) => m.delete({ timeout: 5000 })); - }, - }), - guilds: new Command({ - description: 'Shows a list of all guilds the bot is a member of.', - permission: Command.PERMISSIONS.BOT_SUPPORT, - async run($: CommonLibrary): Promise { - const guildList = $.client.guilds.cache.array().map((e) => e.name); - $.channel.send(guildList); - }, - }), - activity: new Command({ - description: 'Set the activity of the bot.', - permission: Command.PERMISSIONS.BOT_SUPPORT, - usage: ' ', - async run($: CommonLibrary): Promise { - $.client.user?.setActivity('.help', { - type: 'LISTENING', - }); - $.channel.send('Activity set to default.'); - }, - any: new Command({ - description: `Select an activity type to set. Available levels: \`[${activities.join( - ', ', - )}]\``, - async run($: CommonLibrary): Promise { - const type = $.args[0]; - - if (activities.includes(type)) { - $.client.user?.setActivity($.args.slice(1).join(' '), { - type: $.args[0].toUpperCase(), - }); - $.channel.send( - `Set activity to \`${$.args[0].toUpperCase()}\` \`${$.args - .slice(1) - .join(' ')}\`.`, - ); - } else - $.channel.send( - `Couldn't find an activity type named \`${type}\`! The available types are \`[${activities.join( - ', ', - )}]\`.`, - ); - }, - }), - }), - }, -}); +import Command from '../core/command'; +import { CommonLibrary, logs, botHasPermission, clean } from '../core/lib'; +import { Config, Storage } from '../core/structures'; +import { PermissionNames, getPermissionLevel } from '../core/permissions'; +import { Permissions } from 'discord.js'; +import * as discord from 'discord.js'; + +function getLogBuffer(type: string) { + return { + files: [ + { + attachment: Buffer.alloc(logs[type].length, logs[type]), + name: `${Date.now()}.${type}.log`, + }, + ], + }; +} + +const activities = ['playing', 'listening', 'streaming', 'watching']; +const statuses = ['online', 'idle', 'dnd', 'invisible']; + +export default new Command({ + description: + "An all-in-one command to do admin stuff. You need to be either an admin of the server or one of the bot's mechanics to use this command.", + async run($: CommonLibrary): Promise { + if (!$.member) + return $.channel.send( + "Couldn't find a member object for you! Did you make sure you used this in a server?", + ); + const permLevel = getPermissionLevel($.member); + $.channel.send( + `${$.author.toString()}, your permission level is \`${ + PermissionNames[permLevel] + }\` (${permLevel}).`, + ); + }, + subcommands: { + set: new Command({ + description: 'Set different per-guild settings for the bot.', + run: 'You have to specify the option you want to set.', + permission: Command.PERMISSIONS.ADMIN, + subcommands: { + prefix: new Command({ + description: + 'Set a custom prefix for your guild. Removes your custom prefix if none is provided.', + usage: '()', + async run($: CommonLibrary): Promise { + Storage.getGuild($.guild?.id || 'N/A').prefix = null; + Storage.save(); + $.channel.send( + `The custom prefix for this guild has been removed. My prefix is now back to \`${Config.prefix}\`.`, + ); + }, + any: new Command({ + async run($: CommonLibrary): Promise { + Storage.getGuild($.guild?.id || 'N/A').prefix = $.args[0]; + Storage.save(); + $.channel.send( + `The custom prefix for this guild is now \`${$.args[0]}\`.`, + ); + }, + }), + }), + }, + }), + diag: new Command({ + description: 'Requests a debug log with the "info" verbosity level.', + permission: Command.PERMISSIONS.BOT_SUPPORT, + async run($: CommonLibrary): Promise { + $.channel.send(getLogBuffer('info')); + }, + any: new Command({ + description: `Select a verbosity to listen to. Available levels: \`[${Object.keys( + logs, + ).join(', ')}]\``, + async run($: CommonLibrary): Promise { + const type = $.args[0]; + + if (type in logs) $.channel.send(getLogBuffer(type)); + else + $.channel.send( + `Couldn't find a verbosity level named \`${type}\`! The available types are \`[${Object.keys( + logs, + ).join(', ')}]\`.`, + ); + }, + }), + }), + status: new Command({ + description: "Changes the bot's status.", + permission: Command.PERMISSIONS.BOT_SUPPORT, + async run($: CommonLibrary): Promise { + $.channel.send('Setting status to `online`...'); + }, + any: new Command({ + description: `Select a status to set to. Available statuses: \`[${statuses.join( + ', ', + )}]\`.`, + async run($: CommonLibrary): Promise { + if (!statuses.includes($.args[0])) + return $.channel.send("That status doesn't exist!"); + else { + $.client.user?.setStatus($.args[0]); + $.channel.send(`Setting status to \`${$.args[0]}\`...`); + } + }, + }), + }), + purge: new Command({ + description: 'Purges bot messages.', + permission: Command.PERMISSIONS.BOT_SUPPORT, + async run($: CommonLibrary): Promise { + if ($.message.channel instanceof discord.DMChannel) { + return; + } + $.message.delete(); + const msgs = await $.channel.messages.fetch({ + limit: 100, + }); + const travMessages = msgs.filter( + (m) => m.author.id === $.client.user?.id, + ); + + await $.message.channel + .send(`Found ${travMessages.size} messages to delete.`) + .then((m) => + m.delete({ + timeout: 5000, + }), + ); + await $.message.channel.bulkDelete(travMessages); + }, + }), + eval: new Command({ + description: 'Evaluate code.', + usage: '', + permission: Command.PERMISSIONS.BOT_OWNER, + async run($: CommonLibrary): Promise { + try { + const code = $.args.join(' '); + let evaled = eval(code); + + if (typeof evaled !== 'string') + evaled = require('util').inspect(evaled); + $.channel.send(clean(evaled), { code: 'x1' }); + } catch (err) { + $.channel.send(`\`ERROR\` \`\`\`x1\n${clean(err)}\n\`\`\``); + } + }, + }), + nick: new Command({ + description: "Change the bot's nickname.", + permission: Command.PERMISSIONS.BOT_SUPPORT, + async run($: CommonLibrary): Promise { + const nickName = $.args.join(' '); + const trav = $.guild?.members.cache.find( + (member) => member.id === $.client.user?.id, + ); + await trav?.setNickname(nickName); + if (botHasPermission($.guild, Permissions.FLAGS.MANAGE_MESSAGES)) + $.message.delete({ timeout: 5000 }).catch($.handler.bind($)); + $.channel + .send(`Nickname set to \`${nickName}\``) + .then((m) => m.delete({ timeout: 5000 })); + }, + }), + guilds: new Command({ + description: 'Shows a list of all guilds the bot is a member of.', + permission: Command.PERMISSIONS.BOT_SUPPORT, + async run($: CommonLibrary): Promise { + const guildList = $.client.guilds.cache.array().map((e) => e.name); + $.channel.send(guildList); + }, + }), + activity: new Command({ + description: 'Set the activity of the bot.', + permission: Command.PERMISSIONS.BOT_SUPPORT, + usage: ' ', + async run($: CommonLibrary): Promise { + $.client.user?.setActivity('.help', { + type: 'LISTENING', + }); + $.channel.send('Activity set to default.'); + }, + any: new Command({ + description: `Select an activity type to set. Available levels: \`[${activities.join( + ', ', + )}]\``, + async run($: CommonLibrary): Promise { + const type = $.args[0]; + + if (activities.includes(type)) { + $.client.user?.setActivity($.args.slice(1).join(' '), { + type: $.args[0].toUpperCase(), + }); + $.channel.send( + `Set activity to \`${$.args[0].toUpperCase()}\` \`${$.args + .slice(1) + .join(' ')}\`.`, + ); + } else + $.channel.send( + `Couldn't find an activity type named \`${type}\`! The available types are \`[${activities.join( + ', ', + )}]\`.`, + ); + }, + }), + }), + }, +}); diff --git a/src/commands/info.ts b/src/commands/info.ts index b3b1e44..18d4a50 100644 --- a/src/commands/info.ts +++ b/src/commands/info.ts @@ -1,17 +1,13 @@ -import { Guild, MessageEmbed } from 'discord.js'; -import moment from 'moment'; +import { MessageEmbed, version as djsversion } from 'discord.js'; +/// @ts-ignore +import { version } from '../../package.json'; +import ms from 'ms'; +import os from 'os'; import Command from '../core/command'; -import { CommonLibrary } from '../core/lib'; +import { CommonLibrary, formatBytes, trimArray } from '../core/lib'; import { verificationLevels, filterLevels, regions, flags } from '../defs/info'; - -function trimArray(arr: any, maxLen = 10) { - if (arr.length > maxLen) { - const len = arr.length - maxLen; - arr = arr.slice(0, maxLen); - arr.push(`${len} more...`); - } - return arr; -} +import moment from 'moment'; +import utc from 'moment'; export default new Command({ description: @@ -36,6 +32,47 @@ export default new Command({ }), }), + bot: new Command({ + description: 'Displays info about the bot.', + async run($: CommonLibrary): Promise { + const core = os.cpus()[0]; + const embed = new MessageEmbed() + .setThumbnail( + /// @ts-ignore + $.client.user?.displayAvatarURL({ dynamic: true, size: 2048 }), + ) + .setColor($.guild?.me?.displayHexColor || 'BLUE') + .addField('General', [ + `**❯ Client:** ${$.client.user?.tag} (${$.client.user?.id})`, + `**❯ Servers:** ${$.client.guilds.cache.size.toLocaleString()}`, + `**❯ Users:** ${$.client.guilds.cache + .reduce((a: any, b: { memberCount: any }) => a + b.memberCount, 0) + .toLocaleString()}`, + `**❯ Channels:** ${$.client.channels.cache.size.toLocaleString()}`, + `**❯ Creation Date:** ${utc($.client.user?.createdTimestamp).format( + 'Do MMMM YYYY HH:mm:ss', + )}`, + `**❯ Node.JS:** ${process.version}`, + `**❯ Version:** v${version}`, + `**❯ Discord.JS:** ${djsversion}`, + '\u200b', + ]) + .addField('System', [ + `**❯ Platform:** ${process.platform}`, + `**❯ Uptime:** ${ms(os.uptime() * 1000, { long: true })}`, + `**❯ CPU:**`, + `\u3000 • Cores: ${os.cpus().length}`, + `\u3000 • Model: ${core.model}`, + `\u3000 • Speed: ${core.speed}MHz`, + `**❯ Memory:**`, + `\u3000 • Total: ${formatBytes(process.memoryUsage().heapTotal)}`, + `\u3000 • Used: ${formatBytes(process.memoryUsage().heapTotal)}`, + ]) + .setTimestamp(); + $.channel.send(embed); + }, + }), + guild: new Command({ description: 'Displays info about the current guild.', async run($: CommonLibrary): Promise { diff --git a/src/core/lib.ts b/src/core/lib.ts index 702bd28..5b61439 100644 --- a/src/core/lib.ts +++ b/src/core/lib.ts @@ -1,446 +1,470 @@ -import { - GenericWrapper, - NumberWrapper, - StringWrapper, - ArrayWrapper, -} from './wrappers'; -import { - Client, - Message, - TextChannel, - DMChannel, - NewsChannel, - Guild, - User, - GuildMember, - Permissions, -} from 'discord.js'; -import chalk from 'chalk'; -import FileManager from './storage'; -import { eventListeners } from '../events/messageReactionRemove'; -import { client } from '../index'; - -/** A type that describes what the library module does. */ -export interface CommonLibrary { - // Wrapper Object // - /** Wraps the value you enter with an object that provides extra functionality and provides common utility functions. */ - (value: number): NumberWrapper; - (value: string): StringWrapper; - (value: T[]): ArrayWrapper; - (value: T): GenericWrapper; - - // Common Library Functions // - /** .catch($.handler.bind($)) or .catch(error => $.handler(error)) */ - handler: (error: Error) => void; - log: (...args: any[]) => void; - warn: (...args: any[]) => void; - error: (...args: any[]) => void; - debug: (...args: any[]) => void; - ready: (...args: any[]) => void; - paginate: ( - message: Message, - senderID: string, - total: number, - callback: (page: number) => void, - duration?: number, - ) => void; - prompt: ( - message: Message, - senderID: string, - onConfirm: () => void, - duration?: number, - ) => void; - getMemberByUsername: ( - guild: Guild, - username: string, - ) => Promise; - callMemberByUsername: ( - message: Message, - username: string, - onSuccess: (member: GuildMember) => void, - ) => Promise; - - // Dynamic Properties // - args: any[]; - client: Client; - message: Message; - channel: TextChannel | DMChannel | NewsChannel; - guild: Guild | null; - author: User; - member: GuildMember | null; -} - -export default function $(value: number): NumberWrapper; -export default function $(value: string): StringWrapper; -export default function $(value: T[]): ArrayWrapper; -export default function $(value: T): GenericWrapper; -export default function $(value: any) { - if (isType(value, Number)) return new NumberWrapper(value); - else if (isType(value, String)) return new StringWrapper(value); - else if (isType(value, Array)) return new ArrayWrapper(value); - else return new GenericWrapper(value); -} - -// If you use promises, use this function to display the error in chat. -// Case #1: await $.channel.send(""); --> Automatically caught by Command.execute(). -// Case #2: $.channel.send("").catch($.handler.bind($)); --> Manually caught by the user. -$.handler = function (this: CommonLibrary, error: Error) { - if (this) - this.channel.send( - `There was an error while trying to execute that command!\`\`\`${ - error.stack ?? error - }\`\`\``, - ); - else - $.warn( - 'No context was attached to $.handler! Make sure to use .catch($.handler.bind($)) or .catch(error => $.handler(error)) instead!', - ); - - $.error(error); -}; - -// Logs with different levels of verbosity. -export const logs: { [type: string]: string } = { - error: '', - warn: '', - info: '', - verbose: '', -}; - -let enabled = true; -export function setConsoleActivated(activated: boolean) { - enabled = activated; -} - -// The custom console. In order of verbosity, error, warn, log, and debug. Ready is a variation of log. -// General Purpose Logger -$.log = (...args: any[]) => { - if (enabled) - console.log( - chalk.white.bgGray(formatTimestamp()), - chalk.black.bgWhite('INFO'), - ...args, - ); - const text = `[${formatUTCTimestamp()}] [INFO] ${args.join(' ')}\n`; - logs.info += text; - logs.verbose += text; -}; -// "It'll still work, but you should really check up on this." -$.warn = (...args: any[]) => { - if (enabled) - console.warn( - chalk.white.bgGray(formatTimestamp()), - chalk.black.bgYellow('WARN'), - ...args, - ); - const text = `[${formatUTCTimestamp()}] [WARN] ${args.join(' ')}\n`; - logs.warn += text; - logs.info += text; - logs.verbose += text; -}; -// Used for anything which prevents the program from actually running. -$.error = (...args: any[]) => { - if (enabled) - console.error( - chalk.white.bgGray(formatTimestamp()), - chalk.white.bgRed('ERROR'), - ...args, - ); - const text = `[${formatUTCTimestamp()}] [ERROR] ${args.join(' ')}\n`; - logs.error += text; - logs.warn += text; - logs.info += text; - logs.verbose += text; -}; -// Be as verbose as possible. If anything might help when debugging an error, then include it. This only shows in your console if you run this with "dev", but you can still get it from "logs.verbose". -// $.debug(`core/lib::parseArgs("testing \"in progress\"") = ["testing", "in progress"]`) --> /::(.)() = -// Would probably be more suited for debugging program logic rather than function logic, which can be checked using unit tests. -$.debug = (...args: any[]) => { - if (process.argv[2] === 'dev' && enabled) - console.debug( - chalk.white.bgGray(formatTimestamp()), - chalk.white.bgBlue('DEBUG'), - ...args, - ); - const text = `[${formatUTCTimestamp()}] [DEBUG] ${args.join(' ')}\n`; - logs.verbose += text; -}; -// Used once at the start of the program when the bot loads. -$.ready = (...args: any[]) => { - if (enabled) - console.log( - chalk.white.bgGray(formatTimestamp()), - chalk.black.bgGreen('READY'), - ...args, - ); - const text = `[${formatUTCTimestamp()}] [READY] ${args.join(' ')}\n`; - logs.info += text; - logs.verbose += text; -}; - -export function formatTimestamp(now = new Date()) { - const year = now.getFullYear(); - const month = (now.getMonth() + 1).toString().padStart(2, '0'); - const day = now.getDate().toString().padStart(2, '0'); - const hour = now.getHours().toString().padStart(2, '0'); - const minute = now.getMinutes().toString().padStart(2, '0'); - const second = now.getSeconds().toString().padStart(2, '0'); - return `${year}-${month}-${day} ${hour}:${minute}:${second}`; -} - -export function formatUTCTimestamp(now = new Date()) { - const year = now.getUTCFullYear(); - const month = (now.getUTCMonth() + 1).toString().padStart(2, '0'); - const day = now.getUTCDate().toString().padStart(2, '0'); - const hour = now.getUTCHours().toString().padStart(2, '0'); - const minute = now.getUTCMinutes().toString().padStart(2, '0'); - const second = now.getUTCSeconds().toString().padStart(2, '0'); - return `${year}-${month}-${day} ${hour}:${minute}:${second}`; -} - -export function botHasPermission( - guild: Guild | null, - permission: number, -): boolean { - return !!( - client.user && - guild?.members.resolve(client.user)?.hasPermission(permission) - ); -} - -// Pagination function that allows for customization via a callback. -// Define your own pages outside the function because this only manages the actual turning of pages. -$.paginate = async ( - message: Message, - senderID: string, - total: number, - callback: (page: number) => void, - duration = 60000, -) => { - let page = 0; - const turn = (amount: number) => { - page += amount; - - if (page < 0) page += total; - else if (page >= total) page -= total; - - callback(page); - }; - const handle = (emote: string, reacterID: string) => { - switch (emote) { - case '⬅️': - turn(-1); - break; - case '➡️': - turn(1); - break; - } - }; - - // Listen for reactions and call the handler. - await message.react('⬅️'); - await message.react('➡️'); - eventListeners.set(message.id, handle); - await message.awaitReactions( - (reaction, user) => { - if (user.id === senderID) { - // The reason this is inside the call is because it's possible to switch a user's permissions halfway and suddenly throw an error. - // This will dynamically adjust for that, switching modes depending on whether it currently has the "Manage Messages" permission. - const canDeleteEmotes = botHasPermission( - message.guild, - Permissions.FLAGS.MANAGE_MESSAGES, - ); - handle(reaction.emoji.name, user.id); - - if (canDeleteEmotes) reaction.users.remove(user); - } - return false; - }, - { time: duration }, - ); - // When time's up, remove the bot's own reactions. - eventListeners.delete(message.id); - message.reactions.cache.get('⬅️')?.users.remove(message.author); - message.reactions.cache.get('➡️')?.users.remove(message.author); -}; - -// Waits for the sender to either confirm an action or let it pass (and delete the message). -$.prompt = async ( - message: Message, - senderID: string, - onConfirm: () => void, - duration = 10000, -) => { - let isDeleted = false; - - message.react('✅'); - await message.awaitReactions( - (reaction, user) => { - if (user.id === senderID) { - if (reaction.emoji.name === '✅') onConfirm(); - isDeleted = true; - message.delete(); - } - - // CollectorFilter requires a boolean to be returned. - // My guess is that the return value of awaitReactions can be altered by making a boolean filter. - // However, because that's not my concern with this command, I don't have to worry about it. - // May as well just set it to false because I'm not concerned with collecting any reactions. - return false; - }, - { time: duration }, - ); - - if (!isDeleted) message.delete(); -}; - -$.getMemberByUsername = async (guild: Guild, username: string) => { - return ( - await guild.members.fetch({ - query: username, - limit: 1, - }) - ).first(); -}; - -/** Convenience function to handle false cases automatically. */ -$.callMemberByUsername = async ( - message: Message, - username: string, - onSuccess: (member: GuildMember) => void, -) => { - const guild = message.guild; - const send = message.channel.send; - - if (guild) { - const member = await $.getMemberByUsername(guild, username); - - if (member) onSuccess(member); - else send(`Couldn't find a user by the name of \`${username}\`!`); - } else send('You must execute this command in a server!'); -}; - -/** - * Splits a command by spaces while accounting for quotes which capture string arguments. - * - `\"` = `"` - * - `\\` = `\` - */ -export function parseArgs(line: string): string[] { - let result = []; - let selection = ''; - let inString = false; - let isEscaped = false; - - for (let c of line) { - if (isEscaped) { - if (['"', '\\'].includes(c)) selection += c; - else selection += '\\' + c; - - isEscaped = false; - } else if (c === '\\') isEscaped = true; - else if (c === '"') inString = !inString; - else if (c === ' ' && !inString) { - result.push(selection); - selection = ''; - } else selection += c; - } - - if (selection.length > 0) result.push(selection); - - return result; -} - -/** - * Allows you to store a template string with variable markers and parse it later. - * - Use `%name%` for variables - * - `%%` = `%` - * - If the invalid token is null/undefined, nothing is changed. - */ -export function parseVars( - line: string, - definitions: { [key: string]: string }, - invalid: string | null = '', -): string { - let result = ''; - let inVariable = false; - let token = ''; - - for (const c of line) { - if (c === '%') { - if (inVariable) { - if (token === '') result += '%'; - else { - if (token in definitions) result += definitions[token]; - else if (invalid === null) result += `%${token}%`; - else result += invalid; - - token = ''; - } - } - - inVariable = !inVariable; - } else if (inVariable) token += c; - else result += c; - } - - return result; -} - -export function isType(value: any, type: any): boolean { - if (value === undefined && type === undefined) return true; - else if (value === null && type === null) return true; - else - return value !== undefined && value !== null && value.constructor === type; -} - -/** - * Checks a value to see if it matches the fallback's type, otherwise returns the fallback. - * For the purposes of the templates system, this function will only check array types, objects should be checked under their own type (as you'd do anyway with something like a User object). - * If at any point the value doesn't match the data structure provided, the fallback is returned. - * Warning: Type checking is based on the fallback's type. Be sure that the "type" parameter is accurate to this! - */ -export function select( - value: any, - fallback: T, - type: Function, - isArray = false, -): T { - if (isArray && isType(value, Array)) { - for (let item of value) if (!isType(item, type)) return fallback; - return value; - } else { - if (isType(value, type)) return value; - else return fallback; - } -} - -export interface GenericJSON { - [key: string]: any; -} - -export abstract class GenericStructure { - private __meta__ = 'generic'; - - constructor(tag?: string) { - this.__meta__ = tag || this.__meta__; - } - - public save(asynchronous = true) { - const tag = this.__meta__; - /// @ts-ignore - delete this.__meta__; - FileManager.write(tag, this, asynchronous); - this.__meta__ = tag; - } -} - -// A 50% chance would be "Math.random() < 0.5" because Math.random() can be [0, 1), so to make two equal ranges, you'd need [0, 0.5)U[0.5, 1). -// Similar logic would follow for any other percentage. Math.random() < 1 is always true (100% chance) and Math.random() < 0 is always false (0% chance). -export const Random = { - num: (min: number, max: number) => Math.random() * (max - min) + min, - int: (min: number, max: number) => Math.floor(Random.num(min, max)), - chance: (decimal: number) => Math.random() < decimal, - sign: (number = 1) => number * (Random.chance(0.5) ? -1 : 1), - deviation: (base: number, deviation: number) => - Random.num(base - deviation, base + deviation), -}; +import { + GenericWrapper, + NumberWrapper, + StringWrapper, + ArrayWrapper, +} from './wrappers'; +import { + Client, + Message, + TextChannel, + DMChannel, + NewsChannel, + Guild, + User, + GuildMember, + Permissions, +} from 'discord.js'; +import chalk from 'chalk'; +import FileManager from './storage'; +import { eventListeners } from '../events/messageReactionRemove'; +import { client } from '../index'; + +/** A type that describes what the library module does. */ +export interface CommonLibrary { + // Wrapper Object // + /** Wraps the value you enter with an object that provides extra functionality and provides common utility functions. */ + (value: number): NumberWrapper; + (value: string): StringWrapper; + (value: T[]): ArrayWrapper; + (value: T): GenericWrapper; + + // Common Library Functions // + /** .catch($.handler.bind($)) or .catch(error => $.handler(error)) */ + handler: (error: Error) => void; + log: (...args: any[]) => void; + warn: (...args: any[]) => void; + error: (...args: any[]) => void; + debug: (...args: any[]) => void; + ready: (...args: any[]) => void; + paginate: ( + message: Message, + senderID: string, + total: number, + callback: (page: number) => void, + duration?: number, + ) => void; + prompt: ( + message: Message, + senderID: string, + onConfirm: () => void, + duration?: number, + ) => void; + getMemberByUsername: ( + guild: Guild, + username: string, + ) => Promise; + callMemberByUsername: ( + message: Message, + username: string, + onSuccess: (member: GuildMember) => void, + ) => Promise; + + // Dynamic Properties // + args: any[]; + client: Client; + message: Message; + channel: TextChannel | DMChannel | NewsChannel; + guild: Guild | null; + author: User; + member: GuildMember | null; +} + +export default function $(value: number): NumberWrapper; +export default function $(value: string): StringWrapper; +export default function $(value: T[]): ArrayWrapper; +export default function $(value: T): GenericWrapper; +export default function $(value: any) { + if (isType(value, Number)) return new NumberWrapper(value); + else if (isType(value, String)) return new StringWrapper(value); + else if (isType(value, Array)) return new ArrayWrapper(value); + else return new GenericWrapper(value); +} + +// If you use promises, use this function to display the error in chat. +// Case #1: await $.channel.send(""); --> Automatically caught by Command.execute(). +// Case #2: $.channel.send("").catch($.handler.bind($)); --> Manually caught by the user. +$.handler = function (this: CommonLibrary, error: Error) { + if (this) + this.channel.send( + `There was an error while trying to execute that command!\`\`\`${ + error.stack ?? error + }\`\`\``, + ); + else + $.warn( + 'No context was attached to $.handler! Make sure to use .catch($.handler.bind($)) or .catch(error => $.handler(error)) instead!', + ); + + $.error(error); +}; + +// Logs with different levels of verbosity. +export const logs: { [type: string]: string } = { + error: '', + warn: '', + info: '', + verbose: '', +}; + +let enabled = true; +export function setConsoleActivated(activated: boolean) { + enabled = activated; +} + +// The custom console. In order of verbosity, error, warn, log, and debug. Ready is a variation of log. +// General Purpose Logger +$.log = (...args: any[]) => { + if (enabled) + console.log( + chalk.white.bgGray(formatTimestamp()), + chalk.black.bgWhite('INFO'), + ...args, + ); + const text = `[${formatUTCTimestamp()}] [INFO] ${args.join(' ')}\n`; + logs.info += text; + logs.verbose += text; +}; +// "It'll still work, but you should really check up on this." +$.warn = (...args: any[]) => { + if (enabled) + console.warn( + chalk.white.bgGray(formatTimestamp()), + chalk.black.bgYellow('WARN'), + ...args, + ); + const text = `[${formatUTCTimestamp()}] [WARN] ${args.join(' ')}\n`; + logs.warn += text; + logs.info += text; + logs.verbose += text; +}; +// Used for anything which prevents the program from actually running. +$.error = (...args: any[]) => { + if (enabled) + console.error( + chalk.white.bgGray(formatTimestamp()), + chalk.white.bgRed('ERROR'), + ...args, + ); + const text = `[${formatUTCTimestamp()}] [ERROR] ${args.join(' ')}\n`; + logs.error += text; + logs.warn += text; + logs.info += text; + logs.verbose += text; +}; +// Be as verbose as possible. If anything might help when debugging an error, then include it. This only shows in your console if you run this with "dev", but you can still get it from "logs.verbose". +// $.debug(`core/lib::parseArgs("testing \"in progress\"") = ["testing", "in progress"]`) --> /::(.)() = +// Would probably be more suited for debugging program logic rather than function logic, which can be checked using unit tests. +$.debug = (...args: any[]) => { + if (process.argv[2] === 'dev' && enabled) + console.debug( + chalk.white.bgGray(formatTimestamp()), + chalk.white.bgBlue('DEBUG'), + ...args, + ); + const text = `[${formatUTCTimestamp()}] [DEBUG] ${args.join(' ')}\n`; + logs.verbose += text; +}; +// Used once at the start of the program when the bot loads. +$.ready = (...args: any[]) => { + if (enabled) + console.log( + chalk.white.bgGray(formatTimestamp()), + chalk.black.bgGreen('READY'), + ...args, + ); + const text = `[${formatUTCTimestamp()}] [READY] ${args.join(' ')}\n`; + logs.info += text; + logs.verbose += text; +}; + +export function formatTimestamp(now = new Date()) { + const year = now.getFullYear(); + const month = (now.getMonth() + 1).toString().padStart(2, '0'); + const day = now.getDate().toString().padStart(2, '0'); + const hour = now.getHours().toString().padStart(2, '0'); + const minute = now.getMinutes().toString().padStart(2, '0'); + const second = now.getSeconds().toString().padStart(2, '0'); + return `${year}-${month}-${day} ${hour}:${minute}:${second}`; +} + +export function formatUTCTimestamp(now = new Date()) { + const year = now.getUTCFullYear(); + const month = (now.getUTCMonth() + 1).toString().padStart(2, '0'); + const day = now.getUTCDate().toString().padStart(2, '0'); + const hour = now.getUTCHours().toString().padStart(2, '0'); + const minute = now.getUTCMinutes().toString().padStart(2, '0'); + const second = now.getUTCSeconds().toString().padStart(2, '0'); + return `${year}-${month}-${day} ${hour}:${minute}:${second}`; +} + +export function botHasPermission( + guild: Guild | null, + permission: number, +): boolean { + return !!( + client.user && + guild?.members.resolve(client.user)?.hasPermission(permission) + ); +} + +// Pagination function that allows for customization via a callback. +// Define your own pages outside the function because this only manages the actual turning of pages. +$.paginate = async ( + message: Message, + senderID: string, + total: number, + callback: (page: number) => void, + duration = 60000, +) => { + let page = 0; + const turn = (amount: number) => { + page += amount; + + if (page < 0) page += total; + else if (page >= total) page -= total; + + callback(page); + }; + const handle = (emote: string, reacterID: string) => { + switch (emote) { + case '⬅️': + turn(-1); + break; + case '➡️': + turn(1); + break; + } + }; + + // Listen for reactions and call the handler. + await message.react('⬅️'); + await message.react('➡️'); + eventListeners.set(message.id, handle); + await message.awaitReactions( + (reaction, user) => { + if (user.id === senderID) { + // The reason this is inside the call is because it's possible to switch a user's permissions halfway and suddenly throw an error. + // This will dynamically adjust for that, switching modes depending on whether it currently has the "Manage Messages" permission. + const canDeleteEmotes = botHasPermission( + message.guild, + Permissions.FLAGS.MANAGE_MESSAGES, + ); + handle(reaction.emoji.name, user.id); + + if (canDeleteEmotes) reaction.users.remove(user); + } + return false; + }, + { time: duration }, + ); + // When time's up, remove the bot's own reactions. + eventListeners.delete(message.id); + message.reactions.cache.get('⬅️')?.users.remove(message.author); + message.reactions.cache.get('➡️')?.users.remove(message.author); +}; + +// Waits for the sender to either confirm an action or let it pass (and delete the message). +$.prompt = async ( + message: Message, + senderID: string, + onConfirm: () => void, + duration = 10000, +) => { + let isDeleted = false; + + message.react('✅'); + await message.awaitReactions( + (reaction, user) => { + if (user.id === senderID) { + if (reaction.emoji.name === '✅') onConfirm(); + isDeleted = true; + message.delete(); + } + + // CollectorFilter requires a boolean to be returned. + // My guess is that the return value of awaitReactions can be altered by making a boolean filter. + // However, because that's not my concern with this command, I don't have to worry about it. + // May as well just set it to false because I'm not concerned with collecting any reactions. + return false; + }, + { time: duration }, + ); + + if (!isDeleted) message.delete(); +}; + +$.getMemberByUsername = async (guild: Guild, username: string) => { + return ( + await guild.members.fetch({ + query: username, + limit: 1, + }) + ).first(); +}; + +/** Convenience function to handle false cases automatically. */ +$.callMemberByUsername = async ( + message: Message, + username: string, + onSuccess: (member: GuildMember) => void, +) => { + const guild = message.guild; + const send = message.channel.send; + + if (guild) { + const member = await $.getMemberByUsername(guild, username); + + if (member) onSuccess(member); + else send(`Couldn't find a user by the name of \`${username}\`!`); + } else send('You must execute this command in a server!'); +}; + +/** + * Splits a command by spaces while accounting for quotes which capture string arguments. + * - `\"` = `"` + * - `\\` = `\` + */ +export function parseArgs(line: string): string[] { + let result = []; + let selection = ''; + let inString = false; + let isEscaped = false; + + for (let c of line) { + if (isEscaped) { + if (['"', '\\'].includes(c)) selection += c; + else selection += '\\' + c; + + isEscaped = false; + } else if (c === '\\') isEscaped = true; + else if (c === '"') inString = !inString; + else if (c === ' ' && !inString) { + result.push(selection); + selection = ''; + } else selection += c; + } + + if (selection.length > 0) result.push(selection); + + return result; +} + +/** + * Allows you to store a template string with variable markers and parse it later. + * - Use `%name%` for variables + * - `%%` = `%` + * - If the invalid token is null/undefined, nothing is changed. + */ +export function parseVars( + line: string, + definitions: { [key: string]: string }, + invalid: string | null = '', +): string { + let result = ''; + let inVariable = false; + let token = ''; + + for (const c of line) { + if (c === '%') { + if (inVariable) { + if (token === '') result += '%'; + else { + if (token in definitions) result += definitions[token]; + else if (invalid === null) result += `%${token}%`; + else result += invalid; + + token = ''; + } + } + + inVariable = !inVariable; + } else if (inVariable) token += c; + else result += c; + } + + return result; +} + +export function isType(value: any, type: any): boolean { + if (value === undefined && type === undefined) return true; + else if (value === null && type === null) return true; + else + return value !== undefined && value !== null && value.constructor === type; +} + +/** + * Checks a value to see if it matches the fallback's type, otherwise returns the fallback. + * For the purposes of the templates system, this function will only check array types, objects should be checked under their own type (as you'd do anyway with something like a User object). + * If at any point the value doesn't match the data structure provided, the fallback is returned. + * Warning: Type checking is based on the fallback's type. Be sure that the "type" parameter is accurate to this! + */ +export function select( + value: any, + fallback: T, + type: Function, + isArray = false, +): T { + if (isArray && isType(value, Array)) { + for (let item of value) if (!isType(item, type)) return fallback; + return value; + } else { + if (isType(value, type)) return value; + else return fallback; + } +} + +export function clean(text: any) { + if (typeof text === 'string') + return text + .replace(/`/g, '`' + String.fromCharCode(8203)) + .replace(/@/g, '@' + String.fromCharCode(8203)); + else return text; +} + +export function trimArray(arr: any, maxLen = 10) { + if (arr.length > maxLen) { + const len = arr.length - maxLen; + arr = arr.slice(0, maxLen); + arr.push(`${len} more...`); + } + return arr; +} + +export function formatBytes(bytes: any) { + if (bytes === 0) return '0 Bytes'; + const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; + const i = Math.floor(Math.log(bytes) / Math.log(1024)); + return `${parseFloat((bytes / Math.pow(1024, i)).toFixed(2))} ${sizes[i]}`; +} + +export interface GenericJSON { + [key: string]: any; +} + +export abstract class GenericStructure { + private __meta__ = 'generic'; + + constructor(tag?: string) { + this.__meta__ = tag || this.__meta__; + } + + public save(asynchronous = true) { + const tag = this.__meta__; + /// @ts-ignore + delete this.__meta__; + FileManager.write(tag, this, asynchronous); + this.__meta__ = tag; + } +} + +// A 50% chance would be "Math.random() < 0.5" because Math.random() can be [0, 1), so to make two equal ranges, you'd need [0, 0.5)U[0.5, 1). +// Similar logic would follow for any other percentage. Math.random() < 1 is always true (100% chance) and Math.random() < 0 is always false (0% chance). +export const Random = { + num: (min: number, max: number) => Math.random() * (max - min) + min, + int: (min: number, max: number) => Math.floor(Random.num(min, max)), + chance: (decimal: number) => Math.random() < decimal, + sign: (number = 1) => number * (Random.chance(0.5) ? -1 : 1), + deviation: (base: number, deviation: number) => + Random.num(base - deviation, base + deviation), +}; From 1abab93362816fe9f8da1503f9cbfbd736116b55 Mon Sep 17 00:00:00 2001 From: Keanu Date: Thu, 22 Oct 2020 14:24:58 +0000 Subject: [PATCH 060/178] Ported all legacy commands! (except eco) --- .gitignore | 3 +- data/endpoints.json | 31 +++++++++++++++ src/commands/admin.ts | 17 +++++++++ src/commands/fun/cookie.ts | 50 ++++++++++++++++++++++++ src/commands/fun/neko.ts | 26 +++++++++++++ src/commands/fun/ok.ts | 68 +++++++++++++++++++++++++++++++++ src/commands/fun/owoify.ts | 12 ++++++ src/commands/utilities/desc.ts | 29 ++++++++++++++ src/commands/utilities/react.ts | 68 +++++++++++++++++++++++++++++++++ src/commands/utilities/say.ts | 14 +++++++ src/core/lib.ts | 36 +++++++++++++++++ 11 files changed, 353 insertions(+), 1 deletion(-) create mode 100644 data/endpoints.json create mode 100644 src/commands/fun/cookie.ts create mode 100644 src/commands/fun/neko.ts create mode 100644 src/commands/fun/ok.ts create mode 100644 src/commands/fun/owoify.ts create mode 100644 src/commands/utilities/desc.ts create mode 100644 src/commands/utilities/react.ts create mode 100644 src/commands/utilities/say.ts diff --git a/.gitignore b/.gitignore index 2f75f8b..7f1e29d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ # Specific to this repository dist/ -data/ +data/* +!data/endpoints.json tmp/ test* !test/ diff --git a/data/endpoints.json b/data/endpoints.json new file mode 100644 index 0000000..69f15ac --- /dev/null +++ b/data/endpoints.json @@ -0,0 +1,31 @@ +{ + "sfw": { + "tickle": "/img/tickle", + "slap": "/img/slap", + "poke": "/img/poke", + "pat": "/img/pat", + "neko": "/img/neko", + "meow": "/img/meow", + "lizard": "/img/lizard", + "kiss": "/img/kiss", + "hug": "/img/hug", + "foxGirl": "/img/fox_girl", + "feed": "/img/feed", + "cuddle": "/img/cuddle", + "why": "/why", + "catText": "/cat", + "fact": "/fact", + "nekoGif": "/img/ngif", + "kemonomimi": "/img/kemonomimi", + "holo": "/img/holo", + "smug": "/img/smug", + "baka": "/img/baka", + "woof": "/img/woof", + "spoiler": "/spoiler", + "wallpaper": "/img/wallpaper", + "goose": "/img/goose", + "gecg": "/img/gecg", + "avatar": "/img/avatar", + "waifu": "/img/waifu" + } +} \ No newline at end of file diff --git a/src/commands/admin.ts b/src/commands/admin.ts index a8b9f56..ad34bc4 100644 --- a/src/commands/admin.ts +++ b/src/commands/admin.ts @@ -131,6 +131,23 @@ export default new Command({ await $.message.channel.bulkDelete(travMessages); }, }), + clear: new Command({ + description: "Clears a given amount of messages.", + usage: "", + run: "A number was not provided.", + number: new Command({ + description: "Amount of messages to delete.", + async run($: CommonLibrary): Promise { + $.message.delete(); + const fetched = await $.channel.messages.fetch({ + limit: $.args[0], + }); + /// @ts-ignore + $.channel.bulkDelete(fetched) + .catch((error: any) => $.channel.send(`Error: ${error}`)); + } + }) + }), eval: new Command({ description: 'Evaluate code.', usage: '', diff --git a/src/commands/fun/cookie.ts b/src/commands/fun/cookie.ts new file mode 100644 index 0000000..15f75e8 --- /dev/null +++ b/src/commands/fun/cookie.ts @@ -0,0 +1,50 @@ +import Command from '../../core/command'; +import { CommonLibrary } from '../../core/lib'; + +export default new Command({ + description: "Gives specified user a cookie.", + usage: "['all'/@user]", + run: ":cookie: Here's a cookie!", + any: new Command({ + async run($: CommonLibrary): Promise { + if ($.args[0] == "all") return $.channel.send(`${$.author} gave everybody a cookie!`) + } + }), + user: new Command({ + description: "User to give cookie to.", + async run($: CommonLibrary): Promise { + const sender = $.author; + const mention = $.message.mentions.users.first(); + if (!mention) return; + const cookies = [ + `has given <@${mention.id}> a chocolate chip cookie!`, + `has given <@${mention.id}> a soft homemade oatmeal cookie!`, + `has given <@${mention.id}> a plain, dry, old cookie. It was the last one in the bag. Gross.`, + `gives <@${mention.id}> a sugar cookie. What, no frosting and sprinkles? 0/10 would not touch.`, + `gives <@${mention.id}> a chocolate chip cookie. Oh wait, those are raisins. Bleck!`, + `gives <@${mention.id}> an enormous cookie. Poking it gives you more cookies. Weird.`, + `gives <@${mention.id}> a fortune cookie. It reads "Why aren't you working on any projects?"`, + `gives <@${mention.id}> a fortune cookie. It reads "Give that special someone a compliment"`, + `gives <@${mention.id}> a fortune cookie. It reads "Take a risk!"`, + `gives <@${mention.id}> a fortune cookie. It reads "Go outside."`, + `gives <@${mention.id}> a fortune cookie. It reads "Don't forget to eat your veggies!"`, + `gives <@${mention.id}> a fortune cookie. It reads "Do you even lift?"`, + `gives <@${mention.id}> a fortune cookie. It reads "m808 pls"`, + `gives <@${mention.id}> a fortune cookie. It reads "If you move your hips, you'll get all the ladies."`, + `gives <@${mention.id}> a fortune cookie. It reads "I love you."`, + `gives <@${mention.id}> a Golden Cookie. You can't eat it because it is made of gold. Dammit.`, + `gives <@${mention.id}> an Oreo cookie with a glass of milk!`, + `gives <@${mention.id}> a rainbow cookie made with love :heart:`, + `gives <@${mention.id}> an old cookie that was left out in the rain, it's moldy.`, + `bakes <@${mention.id}> fresh cookies, it smells amazing.`, + ]; + if (mention.id == sender.id) + return $.channel.send("You can't give yourself cookies!"); + $.channel.send( + `:cookie: <@${sender.id}> ` + + cookies[Math.floor(Math.random() * cookies.length)], + ); + + } + }) +}) \ No newline at end of file diff --git a/src/commands/fun/neko.ts b/src/commands/fun/neko.ts new file mode 100644 index 0000000..1fdcf05 --- /dev/null +++ b/src/commands/fun/neko.ts @@ -0,0 +1,26 @@ +/// @ts-nocheck +import { URL } from 'url' +import FileManager from '../../core/storage'; +import Command from '../../core/command'; +import { CommonLibrary, getContent } from '../../core/lib'; + +const endpoints = FileManager.read('endpoints'); + +export default new Command({ + description: 'Provides you with a random image with the selected argument.', + async run($: CommonLibrary): Promise { + console.log(endpoints.sfw) + $.channel.send(`Please provide an image type. Available arguments:\n\`[${Object.keys(endpoints.sfw).join(', ')}]\`.`) + }, + any: new Command({ + description: "Image type to send.", + async run($: CommonLibrary): Promise { + if (!$.args[0] in endpoints.sfw) + return $.channel.send("Couldn't find that endpoint!"); + let baseURL = 'https://nekos.life/api/v2'; + let url = new URL(`${baseURL}${endpoints.sfw[$.args[0]]}`); + const content = await getContent(url.toString()) + $.channel.send(content.url) + }, + }) +}); diff --git a/src/commands/fun/ok.ts b/src/commands/fun/ok.ts new file mode 100644 index 0000000..8fa0263 --- /dev/null +++ b/src/commands/fun/ok.ts @@ -0,0 +1,68 @@ +import Command from '../../core/command'; +import { CommonLibrary } from '../../core/lib'; + +export default new Command({ + description: "Sends random ok message.", + async run($: CommonLibrary): Promise { + const responses = [ + 'boomer', + 'zoomer', + 'the last generationer', + 'the last airbender', + 'fire nation', + 'fire lord', + 'guy fieri', + 'guy from final fight', + 'haggar', + 'Max Thunder from Streets of Rage 2', + 'police guy who fires bazookas', + 'Mr. X', + 'Leon Its Wrong If Its Not Ada Wong S. Kennedy.', + 'Jill', + 'JFK', + 'george bush', + 'obama', + 'the world', + 'copy of scott pilgrim vs the world', + 'ok', + 'ko', + 'Hot Daddy Venomous', + 'big daddy', + 'John Cena', + 'BubbleSpurJarJarBinks', + 'T-Series', + 'pewdiepie', + 'markiplier', + 'jacksepticeye', + 'vanossgaming', + 'miniladd', + 'Traves', + 'Wilbur Soot', + 'sootrhianna', + 'person with tiny ears', + 'anti-rabbit', + 'homo sapiens', + 'homo', + 'cute kitty', + 'ugly kitty', + 'sadness', + 'doomer', + 'gloomer', + 'bloomer', + 'edgelord', + 'weeb', + "m'lady", + 'Mr. Crabs', + 'hand', + 'lahoma', + 'big man', + 'fox', + 'pear', + 'cat', + 'large man', + ]; + $.channel.send( + 'ok ' + responses[Math.floor(Math.random() * responses.length)], + ); + } +}) \ No newline at end of file diff --git a/src/commands/fun/owoify.ts b/src/commands/fun/owoify.ts new file mode 100644 index 0000000..33be968 --- /dev/null +++ b/src/commands/fun/owoify.ts @@ -0,0 +1,12 @@ +/// @ts-nocheck +import Command from '../../core/command'; +import { CommonLibrary, getContent } from '../../core/lib'; + +export default new Command({ + description: 'OwO-ifies the input.', + async run($: CommonLibrary): Promise { + let url = new URL(`https://nekos.life/api/v2/owoify?text=${$.args.join(' ')}`); + const content = await getContent(url.toString()); + $.channel.send(content.owo); + }, +}); diff --git a/src/commands/utilities/desc.ts b/src/commands/utilities/desc.ts new file mode 100644 index 0000000..5b96306 --- /dev/null +++ b/src/commands/utilities/desc.ts @@ -0,0 +1,29 @@ +import Command from '../../core/command'; +import { CommonLibrary } from '../../core/lib'; + +export default new Command({ + description: "Renames current voice channel.", + usage: "", + async run($: CommonLibrary): Promise { + const voiceChannel = $.message.member?.voice.channel; + if (!voiceChannel) + return $.channel.send('You are not in a voice channel.'); + if (!$.guild?.me?.hasPermission('MANAGE_CHANNELS')) + return $.channel.send( + 'I am lacking the required permissions to perform this action.', + ); + if ($.args.length === 0) + return $.channel.send( + 'Please provide a new voice channel name.', + ); + const changeVC = $.guild.channels.resolve(voiceChannel.id); + $.channel + .send( + `Changed channel name from "${voiceChannel}" to "${$.args.join( + ' ', + )}".`, + ) + /// @ts-ignore + .then(changeVC?.setName($.args.join(' '))); + } +}) \ No newline at end of file diff --git a/src/commands/utilities/react.ts b/src/commands/utilities/react.ts new file mode 100644 index 0000000..d7135d4 --- /dev/null +++ b/src/commands/utilities/react.ts @@ -0,0 +1,68 @@ +import Command from '../../core/command'; +import { CommonLibrary } from '../../core/lib'; + +export default new Command({ + description: + 'Reacts to the a previous message in your place. You have to react with the same emote before the bot removes that reaction.', + usage: 'react ()', + async run($: CommonLibrary): Promise { + let target; + let distance = 1; + + if ($.args.length >= 2) { + const last = $.args[$.args.length - 1]; + + if (/\d{17,19}/g.test(last)) { + try { + target = await $.channel.messages.fetch(last); + } catch { + return $.channel.send( + `No valid message found by the ID \`${last}\`!`, + ); + } + $.args.pop(); + } + // The entire string has to be a number for this to match. Prevents leaCheeseAmerican1 from triggering this. + else if (/^\d+$/g.test(last)) { + distance = parseInt(last); + + if (distance >= 0 && distance <= 99) $.args.pop(); + else return $.channel.send('Your distance must be between 0 and 99!'); + } + } + + if (!target) { + // Messages are ordered from latest to earliest. + // You also have to add 1 as well because fetchMessages includes your own message. + target = ( + await $.message.channel.messages.fetch({ + limit: distance + 1, + }) + ).last(); + } + + let anyEmoteIsValid = false; + + for (const search of $.args) { + const emoji = $.client.emojis.cache.find( + (emoji) => emoji.name === search, + ); + + if (emoji) { + // Call the delete function only once to avoid unnecessary errors. + if (!anyEmoteIsValid && distance !== 0) $.message.delete(); + anyEmoteIsValid = true; + const reaction = await target?.react(emoji); + + // This part is called with a promise because you don't want to wait 5 seconds between each reaction. + + setTimeout(() => { + /// @ts-ignore + reaction.users.remove($.client.user); + }, 5000); + } + } + + if (!anyEmoteIsValid && !$.message.deleted) $.message.react('❓'); + }, +}); diff --git a/src/commands/utilities/say.ts b/src/commands/utilities/say.ts new file mode 100644 index 0000000..05695eb --- /dev/null +++ b/src/commands/utilities/say.ts @@ -0,0 +1,14 @@ +import Command from '../../core/command'; +import { CommonLibrary } from '../../core/lib'; + +export default new Command({ + description: "Repeats your message.", + usage: "", + run: "Please provide a message for me to say!", + any: new Command({ + description: "Message to repeat.", + async run($: CommonLibrary): Promise { + $.channel.send(`*${$.author} says:*\n${$.args.join(' ')}`); + } + }) +}) \ No newline at end of file diff --git a/src/core/lib.ts b/src/core/lib.ts index 5b61439..b30bcb5 100644 --- a/src/core/lib.ts +++ b/src/core/lib.ts @@ -16,6 +16,7 @@ import { Permissions, } from 'discord.js'; import chalk from 'chalk'; +import { get } from 'https'; import FileManager from './storage'; import { eventListeners } from '../events/messageReactionRemove'; import { client } from '../index'; @@ -438,6 +439,41 @@ export function formatBytes(bytes: any) { return `${parseFloat((bytes / Math.pow(1024, i)).toFixed(2))} ${sizes[i]}`; } +export function getContent(url: any) { + return new Promise((resolve, reject) => { + get( + url, + (res: { + resume?: any; + setEncoding?: any; + on?: any; + statusCode?: any; + }) => { + const { statusCode } = res; + if (statusCode !== 200) { + res.resume(); + reject(`Request failed. Status code: ${statusCode}`); + } + res.setEncoding('utf8'); + let rawData = ''; + res.on('data', (chunk: string) => { + rawData += chunk; + }); + res.on('end', () => { + try { + const parsedData = JSON.parse(rawData); + resolve(parsedData); + } catch (e) { + reject(`Error: ${e.message}`); + } + }); + }, + ).on('error', (err: { message: any }) => { + reject(`Error: ${err.message}`); + }); + }); +} + export interface GenericJSON { [key: string]: any; } From 8417a1ba57c408c874d61e7d1306481b5e1005e1 Mon Sep 17 00:00:00 2001 From: WatDuhHekBro <44940783+WatDuhHekBro@users.noreply.github.com> Date: Wed, 4 Nov 2020 02:04:07 -0600 Subject: [PATCH 061/178] Split up the money command into several parts --- package.json | 3 +- prettier.config.js | 1 + src/commands/fun/eco.ts | 35 +++ src/commands/fun/subcommands/eco-core.ts | 180 ++++++++++++ .../fun/subcommands/eco-shop-items.ts | 0 src/commands/fun/subcommands/eco-shop.ts | 20 ++ src/commands/fun/subcommands/eco-utils.ts | 80 ++++++ src/commands/money.ts | 260 ------------------ 8 files changed, 318 insertions(+), 261 deletions(-) create mode 100644 src/commands/fun/eco.ts create mode 100644 src/commands/fun/subcommands/eco-core.ts create mode 100644 src/commands/fun/subcommands/eco-shop-items.ts create mode 100644 src/commands/fun/subcommands/eco-shop.ts create mode 100644 src/commands/fun/subcommands/eco-utils.ts delete mode 100644 src/commands/money.ts diff --git a/package.json b/package.json index bfc3fac..16d6760 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,8 @@ "start": "node dist/index.js", "once": "tsc && npm start", "dev": "tsc-watch --onSuccess \"node dist/index.js dev\"", - "test": "mocha --require ts-node/register --extension ts --recursive" + "test": "mocha --require ts-node/register --extension ts --recursive", + "format": "prettier --write **/*" }, "keywords": [ "discord.js", diff --git a/prettier.config.js b/prettier.config.js index 7584823..4cbaf1d 100644 --- a/prettier.config.js +++ b/prettier.config.js @@ -10,4 +10,5 @@ module.exports = { bracketSpacing: true, jsxBracketSameLine: true, arrowParens: 'always', + endOfLine: 'auto', }; diff --git a/src/commands/fun/eco.ts b/src/commands/fun/eco.ts new file mode 100644 index 0000000..ceab224 --- /dev/null +++ b/src/commands/fun/eco.ts @@ -0,0 +1,35 @@ +import Command from '../../core/command'; +import { isAuthorized, getMoneyEmbed } from './subcommands/eco-utils'; +import { DailyCommand, PayCommand, GuildCommand } from './subcommands/eco-core'; +import { BuyCommand, ShopCommand } from './subcommands/eco-shop'; + +export default new Command({ + description: 'Economy command for Monika.', + + async run({ guild, channel, author }) { + if (isAuthorized(guild, channel)) channel.send(getMoneyEmbed(author)); + }, + subcommands: { + daily: DailyCommand, + pay: PayCommand, + guild: GuildCommand, + buy: BuyCommand, + shop: ShopCommand, + }, + user: new Command({ + description: + 'See how much money someone else has by using their user ID or pinging them.', + async run({ guild, channel, args }) { + if (isAuthorized(guild, channel)) channel.send(getMoneyEmbed(args[0])); + }, + }), + any: new Command({ + description: 'See how much money someone else has by using their username.', + async run({ guild, channel, args, callMemberByUsername, message }) { + if (isAuthorized(guild, channel)) + callMemberByUsername(message, args.join(' '), (member) => { + channel.send(getMoneyEmbed(member.user)); + }); + }, + }), +}); diff --git a/src/commands/fun/subcommands/eco-core.ts b/src/commands/fun/subcommands/eco-core.ts new file mode 100644 index 0000000..e2c8c5c --- /dev/null +++ b/src/commands/fun/subcommands/eco-core.ts @@ -0,0 +1,180 @@ +import Command from '../../../core/command'; +import $ from '../../../core/lib'; +import { Storage } from '../../../core/structures'; +import { isAuthorized, getMoneyEmbed, getSendEmbed } from './eco-utils'; + +export const DailyCommand = new Command({ + description: + 'Pick up your daily Mons. The cooldown is per user and every 22 hours to allow for some leeway.', + async run({ author, channel, guild }) { + if (isAuthorized(guild, channel)) { + const user = Storage.getUser(author.id); + const now = Date.now(); + + if (now - user.lastReceived >= 79200000) { + user.money++; + user.lastReceived = now; + Storage.save(); + channel.send({ + embed: { + title: 'Daily Reward', + description: 'You received 1 Mon!', + color: 0xf1c40f, + }, + }); + } else + channel.send({ + embed: { + title: 'Daily Reward', + description: `It's too soon to pick up your daily credits. You have about ${( + (user.lastReceived + 79200000 - now) / + 3600000 + ).toFixed(1)} hours to go.`, + color: 0xf1c40f, + }, + }); + } + }, +}); + +export const GuildCommand = new Command({ + description: 'See the richest players.', + async run({ guild, channel, client }) { + if (isAuthorized(guild, channel)) { + const users = Storage.users; + const ids = Object.keys(users); + ids.sort((a, b) => users[b].money - users[a].money); + const fields = []; + + for (let i = 0, limit = Math.min(10, ids.length); i < limit; i++) { + const id = ids[i]; + const user = await client.users.fetch(id); + + fields.push({ + name: `#${i + 1}. ${user.username}#${user.discriminator}`, + value: $(users[id].money).pluralise('credit', 's'), + }); + } + + channel.send({ + embed: { + title: 'Top 10 Richest Players', + color: '#ffff00', + fields: fields, + }, + }); + } + }, +}); + +export const PayCommand = new Command({ + description: 'Send money to someone.', + usage: ' ', + run: 'Who are you sending this money to?', + user: new Command({ + run: "You need to enter an amount you're sending!", + number: new Command({ + async run({ args, author, channel, guild }): Promise { + if (isAuthorized(guild, channel)) { + const amount = Math.floor(args[1]); + const sender = Storage.getUser(author.id); + const target = args[0]; + const receiver = Storage.getUser(target.id); + + if (amount <= 0) + return channel.send('You must send at least one Mon!'); + else if (sender.money < amount) + return channel.send( + "You don't have enough Mons for that.", + getMoneyEmbed(author), + ); + else if (target.id === author.id) + return channel.send("You can't send Mons to yourself!"); + else if (target.bot && process.argv[2] !== 'dev') + return channel.send("You can't send Mons to a bot!"); + + sender.money -= amount; + receiver.money += amount; + Storage.save(); + return channel.send(getSendEmbed(author, target, amount)); + } + }, + }), + }), + number: new Command({ + run: 'You must use the format `money send `!', + }), + any: new Command({ + async run({ args, author, channel, guild, prompt }) { + if (isAuthorized(guild, channel)) { + const last = args.pop(); + + if (!/\d+/g.test(last) && args.length === 0) + return channel.send("You need to enter an amount you're sending!"); + + const amount = Math.floor(last); + const sender = Storage.getUser(author.id); + + if (amount <= 0) + return channel.send('You must send at least one credit!'); + else if (sender.money < amount) + return channel.send( + "You don't have enough money to do that!", + getMoneyEmbed(author), + ); + else if (!guild) + return channel.send( + 'You have to use this in a server if you want to send money with a username!', + ); + + const username = args.join(' '); + const member = ( + await guild.members.fetch({ + query: username, + limit: 1, + }) + ).first(); + + if (!member) + return channel.send( + `Couldn't find a user by the name of \`${username}\`! If you want to send money to someone in a different server, you have to use their user ID!`, + ); + else if (member.user.id === author.id) + return channel.send("You can't send money to yourself!"); + else if (member.user.bot && process.argv[2] !== 'dev') + return channel.send("You can't send money to a bot!"); + + const target = member.user; + + return prompt( + await channel.send( + `Are you sure you want to send ${$(amount).pluralise( + 'credit', + 's', + )} to this person?\n*(This message will automatically be deleted after 10 seconds.)*`, + { + embed: { + color: '#ffff00', + author: { + name: `${target.username}#${target.discriminator}`, + icon_url: target.displayAvatarURL({ + format: 'png', + dynamic: true, + }), + }, + }, + }, + ), + author.id, + () => { + const receiver = Storage.getUser(target.id); + sender.money -= amount; + receiver.money += amount; + Storage.save(); + channel.send(getSendEmbed(author, target, amount)); + }, + ); + } + }, + }), +}); diff --git a/src/commands/fun/subcommands/eco-shop-items.ts b/src/commands/fun/subcommands/eco-shop-items.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/commands/fun/subcommands/eco-shop.ts b/src/commands/fun/subcommands/eco-shop.ts new file mode 100644 index 0000000..7bd4bd0 --- /dev/null +++ b/src/commands/fun/subcommands/eco-shop.ts @@ -0,0 +1,20 @@ +import Command from '../../../core/command'; +import $ from '../../../core/lib'; +import { Storage } from '../../../core/structures'; +import { isAuthorized, getMoneyEmbed, getSendEmbed } from './eco-utils'; + +export const BuyCommand = new Command({ + description: '', + async run({ guild, channel }) { + if (isAuthorized(guild, channel)) { + } + }, +}); + +export const ShopCommand = new Command({ + description: '', + async run({ guild, channel }) { + if (isAuthorized(guild, channel)) { + } + }, +}); diff --git a/src/commands/fun/subcommands/eco-utils.ts b/src/commands/fun/subcommands/eco-utils.ts new file mode 100644 index 0000000..7b5bafd --- /dev/null +++ b/src/commands/fun/subcommands/eco-utils.ts @@ -0,0 +1,80 @@ +import $ from '../../../core/lib'; +import { Storage } from '../../../core/structures'; +import { User, Guild, TextChannel, DMChannel, NewsChannel } from 'discord.js'; + +export function getMoneyEmbed(user: User): object { + const profile = Storage.getUser(user.id); + + return { + embed: { + color: 0xffff00, + author: { + name: user.username, + icon_url: user.displayAvatarURL({ + format: 'png', + dynamic: true, + }), + }, + fields: [ + { + name: 'Balance', + value: $(profile.money).pluralise('credit', 's'), + }, + ], + }, + }; +} + +export function getSendEmbed( + sender: User, + receiver: User, + amount: number, +): object { + return { + embed: { + color: 0xffff00, + author: { + name: sender.username, + icon_url: sender.displayAvatarURL({ + format: 'png', + dynamic: true, + }), + }, + title: 'Transaction', + description: `${sender.toString()} has sent ${$(amount).pluralise( + 'credit', + 's', + )} to ${receiver.toString()}!`, + fields: [ + { + name: `Sender: ${sender.username}#${sender.discriminator}`, + value: $(Storage.getUser(sender.id).money).pluralise('credit', 's'), + }, + { + name: `Receiver: ${receiver.username}#${receiver.discriminator}`, + value: $(Storage.getUser(receiver.id).money).pluralise('credit', 's'), + }, + ], + footer: { + text: receiver.username, + icon_url: receiver.displayAvatarURL({ + format: 'png', + dynamic: true, + }), + }, + }, + }; +} + +export function isAuthorized( + guild: Guild | null, + channel: TextChannel | DMChannel | NewsChannel, +): boolean { + if (guild?.id !== '637512823676600330') return true; + else { + channel.send( + "Sorry, this command can only be used in Monika's emote server.", + ); + return false; + } +} diff --git a/src/commands/money.ts b/src/commands/money.ts deleted file mode 100644 index 7abffb9..0000000 --- a/src/commands/money.ts +++ /dev/null @@ -1,260 +0,0 @@ -import Command from '../core/command'; -import $, { CommonLibrary } from '../core/lib'; -import { Storage } from '../core/structures'; -import { User } from 'discord.js'; - -export function getMoneyEmbed(user: User): object { - const profile = Storage.getUser(user.id); - - return { - embed: { - color: 0xffff00, - author: { - name: user.username, - icon_url: user.displayAvatarURL({ - format: 'png', - dynamic: true, - }), - }, - fields: [ - { - name: 'Balance', - value: $(profile.money).pluralise('credit', 's'), - }, - ], - }, - }; -} - -function getSendEmbed(sender: User, receiver: User, amount: number): object { - return { - embed: { - color: 0xffff00, - author: { - name: sender.username, - icon_url: sender.displayAvatarURL({ - format: 'png', - dynamic: true, - }), - }, - title: 'Transaction', - description: `${sender.toString()} has sent ${$(amount).pluralise( - 'credit', - 's', - )} to ${receiver.toString()}!`, - fields: [ - { - name: `Sender: ${sender.username}#${sender.discriminator}`, - value: $(Storage.getUser(sender.id).money).pluralise('credit', 's'), - }, - { - name: `Receiver: ${receiver.username}#${receiver.discriminator}`, - value: $(Storage.getUser(receiver.id).money).pluralise('credit', 's'), - }, - ], - footer: { - text: receiver.username, - icon_url: receiver.displayAvatarURL({ - format: 'png', - dynamic: true, - }), - }, - }, - }; -} - -export default new Command({ - description: - 'See how much money you have. Also provides other commands related to money.', - async run($: CommonLibrary): Promise { - $.channel.send(getMoneyEmbed($.author)); - }, - subcommands: { - get: new Command({ - description: - 'Pick up your daily credits. The cooldown is per user and every 22 hours to allow for some leeway.', - async run($: CommonLibrary): Promise { - const user = Storage.getUser($.author.id); - const now = Date.now(); - - if (user.lastReceived === -1) { - user.money = 100; - user.lastReceived = now; - Storage.save(); - $.channel.send( - "Here's 100 credits to get started, the price of a sandwich in Rookie Harbor.", - getMoneyEmbed($.author), - ); - } else if (now - user.lastReceived >= 79200000) { - user.money += 25; - user.lastReceived = now; - Storage.save(); - $.channel.send( - "Here's your daily 25 credits.", - getMoneyEmbed($.author), - ); - } else - $.channel.send( - `It's too soon to pick up your daily credits. You have about ${( - (user.lastReceived + 79200000 - now) / - 3600000 - ).toFixed(1)} hours to go.`, - ); - }, - }), - send: new Command({ - description: 'Send money to someone.', - usage: ' ', - run: 'Who are you sending this money to?', - user: new Command({ - run: "You need to enter an amount you're sending!", - number: new Command({ - async run($: CommonLibrary): Promise { - const amount = Math.floor($.args[1]); - const author = $.author; - const sender = Storage.getUser(author.id); - const target = $.args[0]; - const receiver = Storage.getUser(target.id); - - if (amount <= 0) - return $.channel.send('You must send at least one credit!'); - else if (sender.money < amount) - return $.channel.send( - "You don't have enough money to do that!", - getMoneyEmbed(author), - ); - else if (target.id === author.id) - return $.channel.send("You can't send money to yourself!"); - else if (target.bot && process.argv[2] !== 'dev') - return $.channel.send("You can't send money to a bot!"); - - sender.money -= amount; - receiver.money += amount; - Storage.save(); - $.channel.send(getSendEmbed(author, target, amount)); - }, - }), - }), - number: new Command({ - run: 'You must use the format `money send `!', - }), - any: new Command({ - async run($: CommonLibrary): Promise { - const last = $.args.pop(); - - if (!/\d+/g.test(last) && $.args.length === 0) - return $.channel.send( - "You need to enter an amount you're sending!", - ); - - const amount = Math.floor(last); - const author = $.author; - const sender = Storage.getUser(author.id); - - if (amount <= 0) - return $.channel.send('You must send at least one credit!'); - else if (sender.money < amount) - return $.channel.send( - "You don't have enough money to do that!", - getMoneyEmbed(author), - ); - else if (!$.guild) - return $.channel.send( - 'You have to use this in a server if you want to send money with a username!', - ); - - const username = $.args.join(' '); - const member = ( - await $.guild.members.fetch({ - query: username, - limit: 1, - }) - ).first(); - - if (!member) - return $.channel.send( - `Couldn't find a user by the name of \`${username}\`! If you want to send money to someone in a different server, you have to use their user ID!`, - ); - else if (member.user.id === author.id) - return $.channel.send("You can't send money to yourself!"); - else if (member.user.bot && process.argv[2] !== 'dev') - return $.channel.send("You can't send money to a bot!"); - - const target = member.user; - - $.prompt( - await $.channel.send( - `Are you sure you want to send ${$(amount).pluralise( - 'credit', - 's', - )} to this person?\n*(This message will automatically be deleted after 10 seconds.)*`, - { - embed: { - color: '#ffff00', - author: { - name: `${target.username}#${target.discriminator}`, - icon_url: target.displayAvatarURL({ - format: 'png', - dynamic: true, - }), - }, - }, - }, - ), - $.author.id, - () => { - const receiver = Storage.getUser(target.id); - sender.money -= amount; - receiver.money += amount; - Storage.save(); - $.channel.send(getSendEmbed(author, target, amount)); - }, - ); - }, - }), - }), - leaderboard: new Command({ - description: - 'See the richest players tracked by this bot (across servers).', - async run($: CommonLibrary): Promise { - const users = Storage.users; - const ids = Object.keys(users); - ids.sort((a, b) => users[b].money - users[a].money); - const fields = []; - - for (let i = 0, limit = Math.min(10, ids.length); i < limit; i++) { - const id = ids[i]; - const user = await $.client.users.fetch(id); - - fields.push({ - name: `#${i + 1}. ${user.username}#${user.discriminator}`, - value: $(users[id].money).pluralise('credit', 's'), - }); - } - - $.channel.send({ - embed: { - title: 'Top 10 Richest Players', - color: '#ffff00', - fields: fields, - }, - }); - }, - }), - }, - user: new Command({ - description: - 'See how much money someone else has by using their user ID or pinging them.', - async run($: CommonLibrary): Promise { - $.channel.send(getMoneyEmbed($.args[0])); - }, - }), - any: new Command({ - description: 'See how much money someone else has by using their username.', - async run($: CommonLibrary): Promise { - $.callMemberByUsername($.message, $.args.join(' '), (member) => { - $.channel.send(getMoneyEmbed(member.user)); - }); - }, - }), -}); From 4f443d3e4fadd9be1f9832aa4c473b9c552427d1 Mon Sep 17 00:00:00 2001 From: WatDuhHekBro <44940783+WatDuhHekBro@users.noreply.github.com> Date: Mon, 14 Dec 2020 01:13:16 -0600 Subject: [PATCH 062/178] Ported over "eco shop" and "eco buy" --- .../fun/subcommands/eco-shop-items.ts | 73 +++++++++++++ src/commands/fun/subcommands/eco-shop.ts | 101 ++++++++++++++++-- src/commands/fun/subcommands/eco-utils.ts | 3 +- 3 files changed, 165 insertions(+), 12 deletions(-) diff --git a/src/commands/fun/subcommands/eco-shop-items.ts b/src/commands/fun/subcommands/eco-shop-items.ts index e69de29..b7531cd 100644 --- a/src/commands/fun/subcommands/eco-shop-items.ts +++ b/src/commands/fun/subcommands/eco-shop-items.ts @@ -0,0 +1,73 @@ +import { Message } from 'discord.js'; +import $ from '../../../core/lib'; + +export interface ShopItem { + cost: number; + title: string; + description: string; + usage: string; + run(message: Message, cost: number, amount: number): void; +} + +export const ShopItems: ShopItem[] = [ + { + cost: 1, + title: 'Hug', + description: 'Hug Monika.', + usage: 'hug', + run(message, cost) { + message.channel.send( + `Transaction of ${cost} Mon completed successfully. <@394808963356688394>`, + ); + }, + }, + { + cost: 2, + title: 'Handholding', + description: "Hold Monika's hand.", + usage: 'handhold', + run(message, cost) { + message.channel.send( + `Transaction of ${cost} Mons completed successfully. <@394808963356688394>`, + ); + }, + }, + { + cost: 1, + title: 'Cute', + description: 'Calls Monika cute.', + usage: 'cute', + run(message) { + message.channel.send('<:MoniCheeseBlushRed:637513137083383826>'); + }, + }, + { + cost: 3, + title: 'Laser Bridge', + description: 'Buys what is technically a laser bridge.', + usage: 'laser bridge', + run(message) { + message.channel.send($(lines).random(), { + files: [ + { + attachment: + 'https://raw.githubusercontent.com/keanuplayz/TravBot/dev/assets/TheUltimateLaser.gif', + }, + ], + }); + }, + }, +]; + +const lines = [ + "It's technically a laser bridge. No refunds.", + 'You want a laser bridge? You got one!', + "Now what'd they say about building bridges... Oh wait, looks like I nuked the planet again. Whoops!", + 'I saw this redhead the other day who was so excited to buy what I was selling. Needless to say, she was not very happy with me afterward.', + "Sorry, but you'll have to wait until the Laser Bridge Builder leaves early access.", + 'Thank you for your purchase! For you see, this is the legendary laser of obliteration that has been defended and preserved for countless generations!', + 'They say that a certain troll dwells under this laser bridge, waiting for an unlucky person to fall for th- I mean- Thank you for your purchase!', + "Buy?! Hah! How about our new rental service for just under $9.99 a month? But wait, there's more! For just $99.99, you can rent this laser bridge for an entire year and save 16.67% as opposed to renting it monthly!", + 'Good choice. Owning a laser bridge is the penultimate experience that all true seekers strive for!', + 'I can already imagine the reviews...\n"9/10 needs more lasers"', +]; diff --git a/src/commands/fun/subcommands/eco-shop.ts b/src/commands/fun/subcommands/eco-shop.ts index 7bd4bd0..8aa2c21 100644 --- a/src/commands/fun/subcommands/eco-shop.ts +++ b/src/commands/fun/subcommands/eco-shop.ts @@ -1,20 +1,99 @@ import Command from '../../../core/command'; import $ from '../../../core/lib'; -import { Storage } from '../../../core/structures'; +import { Storage, getPrefix } from '../../../core/structures'; import { isAuthorized, getMoneyEmbed, getSendEmbed } from './eco-utils'; - -export const BuyCommand = new Command({ - description: '', - async run({ guild, channel }) { - if (isAuthorized(guild, channel)) { - } - }, -}); +import { ShopItems, ShopItem } from './eco-shop-items'; +import { EmbedField } from 'discord.js'; export const ShopCommand = new Command({ - description: '', - async run({ guild, channel }) { + description: 'Displays the list of items you can buy in the shop.', + async run({ guild, channel, author }) { if (isAuthorized(guild, channel)) { + function getShopEmbed(selection: ShopItem[], title = 'Shop') { + const fields: EmbedField[] = []; + + for (const item of selection) + fields.push({ + name: `**${item.title}** (${getPrefix(guild)}eco buy ${ + item.usage + })`, + value: `${item.description} Costs ${$(item.cost).pluralise( + 'Mon', + 's', + )}.`, + inline: false, + }); + + return { + embed: { + color: 0xf1c40f, + title: title, + fields: fields, + footer: { + text: 'Mon Shop | TravBot Services', + }, + }, + }; + } + + // In case there's just one page, omit unnecessary details. + if (ShopItems.length <= 5) channel.send(getShopEmbed(ShopItems)); + else { + const shopPages = $(ShopItems).split(5); + const pageAmount = shopPages.length; + const msg = await channel.send( + getShopEmbed(shopPages[0], `Shop (Page 1 of ${pageAmount})`), + ); + + $.paginate(msg, author.id, pageAmount, (page) => { + msg.edit( + getShopEmbed( + shopPages[page], + `Shop (Page ${page + 1} of ${pageAmount})`, + ), + ); + }); + } + } + }, +}); + +export const BuyCommand = new Command({ + description: 'Buys an item from the shop.', + usage: '', + async run({ guild, channel, args, message, author }) { + if (isAuthorized(guild, channel)) { + let found = false; + + let amount = 1; // The amount the user is buying. + + // For now, no shop items support being bought multiple times. Uncomment these 2 lines when it's supported/needed. + //if (/\d+/g.test(args[args.length - 1])) + //amount = parseInt(args.pop()); + + let requested = args.join(' '); // The item the user is buying. + + for (let item of ShopItems) { + if (item.usage === requested) { + const user = Storage.getUser(author.id); + const cost = item.cost * amount; + + if (cost > user.money) { + channel.send('Not enough Mons!'); + } else { + user.money -= cost; + item.run(message, cost, amount); + } + + found = true; + break; + } + } + + if (!found) + channel.send( + `There's no item in the shop that goes by \`${requested}\`!`, + ); } }, }); diff --git a/src/commands/fun/subcommands/eco-utils.ts b/src/commands/fun/subcommands/eco-utils.ts index 7b5bafd..3af6971 100644 --- a/src/commands/fun/subcommands/eco-utils.ts +++ b/src/commands/fun/subcommands/eco-utils.ts @@ -70,7 +70,8 @@ export function isAuthorized( guild: Guild | null, channel: TextChannel | DMChannel | NewsChannel, ): boolean { - if (guild?.id !== '637512823676600330') return true; + if (guild?.id === '637512823676600330' || process.argv[2] === 'dev') + return true; else { channel.send( "Sorry, this command can only be used in Monika's emote server.", From 927e306e9ab9532ae647a11c900c9581229744a8 Mon Sep 17 00:00:00 2001 From: WatDuhHekBro <44940783+WatDuhHekBro@users.noreply.github.com> Date: Mon, 14 Dec 2020 01:54:46 -0600 Subject: [PATCH 063/178] Hotfix: Buying an item now saves data properly --- src/commands/fun/subcommands/eco-shop.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/commands/fun/subcommands/eco-shop.ts b/src/commands/fun/subcommands/eco-shop.ts index 8aa2c21..9c3a5f8 100644 --- a/src/commands/fun/subcommands/eco-shop.ts +++ b/src/commands/fun/subcommands/eco-shop.ts @@ -82,6 +82,7 @@ export const BuyCommand = new Command({ channel.send('Not enough Mons!'); } else { user.money -= cost; + Storage.save(); item.run(message, cost, amount); } From 98e47e379664d8e5c6a055cae093f0c033dd9d04 Mon Sep 17 00:00:00 2001 From: WatDuhHekBro <44940783+WatDuhHekBro@users.noreply.github.com> Date: Mon, 14 Dec 2020 02:13:31 -0600 Subject: [PATCH 064/178] Fixed the error prevention in the neko command --- src/commands/fun/neko.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands/fun/neko.ts b/src/commands/fun/neko.ts index 1fdcf05..e079581 100644 --- a/src/commands/fun/neko.ts +++ b/src/commands/fun/neko.ts @@ -15,7 +15,7 @@ export default new Command({ any: new Command({ description: "Image type to send.", async run($: CommonLibrary): Promise { - if (!$.args[0] in endpoints.sfw) + if (!($.args[0] in endpoints.sfw)) return $.channel.send("Couldn't find that endpoint!"); let baseURL = 'https://nekos.life/api/v2'; let url = new URL(`${baseURL}${endpoints.sfw[$.args[0]]}`); From 39f89a9f63fb857c9834c28a070d9ca27672d1d6 Mon Sep 17 00:00:00 2001 From: WatDuhHekBro <44940783+WatDuhHekBro@users.noreply.github.com> Date: Mon, 14 Dec 2020 19:44:28 -0600 Subject: [PATCH 065/178] Formatting Preview Alpha --- .github/codeql/codeql-config.yml | 6 +- .github/workflows/codeql-analysis.yml | 62 +- .github/workflows/image.yml | 42 +- .prettierignore | 7 + README.md | 6 +- docs/GettingStarted.md | 1 + package-lock.json | 2948 ++++++++--------- package.json | 84 +- prettier.config.js | 24 +- src/commands/admin.ts | 465 +-- src/commands/fun/8ball.ts | 67 +- src/commands/fun/cookie.ts | 104 +- src/commands/fun/eco.ts | 61 +- src/commands/fun/neko.ts | 57 +- src/commands/fun/ok.ts | 137 +- src/commands/fun/owoify.ts | 26 +- src/commands/fun/poll.ts | 50 +- src/commands/fun/subcommands/eco-core.ts | 312 +- .../fun/subcommands/eco-shop-items.ts | 122 +- src/commands/fun/subcommands/eco-shop.ts | 161 +- src/commands/fun/subcommands/eco-utils.ts | 144 +- src/commands/help.ts | 276 +- src/commands/info.ts | 531 +-- src/commands/scanemotes.ts | 367 +- src/commands/utilities/desc.ts | 38 +- src/commands/utilities/emote.ts | 36 +- src/commands/utilities/lsemotes.ts | 60 +- src/commands/utilities/react.ts | 140 +- src/commands/utilities/say.ts | 28 +- src/commands/utilities/shorten.ts | 50 +- src/core/command.ts | 423 +-- src/core/event.ts | 58 +- src/core/lib.ts | 1021 +++--- src/core/permissions.ts | 101 +- src/core/storage.ts | 148 +- src/core/structures.ts | 162 +- src/core/wrappers.ts | 139 +- src/defs/info.ts | 78 +- src/events/channelCreate.ts | 26 +- src/events/channelDelete.ts | 26 +- src/events/message.ts | 258 +- src/events/messageReactionRemove.ts | 30 +- src/events/ready.ts | 30 +- src/index.ts | 52 +- src/setup.ts | 111 +- test/wrappers.ts | 190 +- tsconfig.json | 30 +- 47 files changed, 4714 insertions(+), 4581 deletions(-) diff --git a/.github/codeql/codeql-config.yml b/.github/codeql/codeql-config.yml index 08ced75..ef82e9f 100644 --- a/.github/codeql/codeql-config.yml +++ b/.github/codeql/codeql-config.yml @@ -1,6 +1,6 @@ -name: 'CodeQL Config' +name: "CodeQL Config" queries: - - uses: security-and-quality + - uses: security-and-quality paths: - - dist + - dist diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index f28a185..200682a 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -1,41 +1,41 @@ -name: 'CodeQL' +name: "CodeQL" on: - push: - branches: [typescript] - pull_request: - branches: [typescript] - schedule: - - cron: '0 5 * * 1' + push: + branches: [typescript] + pull_request: + branches: [typescript] + schedule: + - cron: "0 5 * * 1" jobs: - analyze: - name: Analyze - runs-on: ubuntu-latest + analyze: + name: Analyze + runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@v2 - with: - fetch-depth: 2 + steps: + - name: Checkout repository + uses: actions/checkout@v2 + with: + fetch-depth: 2 - - run: git checkout HEAD^2 - if: ${{ github.event_name == 'pull_request' }} + - run: git checkout HEAD^2 + if: ${{ github.event_name == 'pull_request' }} - - name: Setup Node.JS - uses: actions/setup-node@v2-beta - with: - node-version: '12' - - run: npm ci + - name: Setup Node.JS + uses: actions/setup-node@v2-beta + with: + node-version: "12" + - run: npm ci - - name: Build codebase - run: npm run build + - name: Build codebase + run: npm run build - - name: Initialize CodeQL - uses: github/codeql-action/init@v1 - with: - config-file: ./.github/codeql/codeql-config.yml - languages: javascript + - name: Initialize CodeQL + uses: github/codeql-action/init@v1 + with: + config-file: ./.github/codeql/codeql-config.yml + languages: javascript - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v1 + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v1 diff --git a/.github/workflows/image.yml b/.github/workflows/image.yml index d6650c9..dac2e7a 100644 --- a/.github/workflows/image.yml +++ b/.github/workflows/image.yml @@ -1,25 +1,25 @@ name: Build Docker Image + Push on: - push: - branches: - - typescript - - docker + push: + branches: + - typescript + - docker jobs: - build: - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v2 - - name: Install Docker BuildX - id: buildx - uses: crazy-max/ghaction-docker-buildx@v1 - with: - buildx-version: latest - - name: Login to Docker Hub - run: echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u "${{ secrets.DOCKER_USERNAME }}" --password-stdin - - name: Build the image - run: | - docker buildx build \ - --tag keanucode/travbot-v3:latest \ - --platform linux/amd64,linux/arm/v7,linux/arm64 --push . + build: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v2 + - name: Install Docker BuildX + id: buildx + uses: crazy-max/ghaction-docker-buildx@v1 + with: + buildx-version: latest + - name: Login to Docker Hub + run: echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u "${{ secrets.DOCKER_USERNAME }}" --password-stdin + - name: Build the image + run: | + docker buildx build \ + --tag keanucode/travbot-v3:latest \ + --platform linux/amd64,linux/arm/v7,linux/arm64 --push . diff --git a/.prettierignore b/.prettierignore index 3b0f1ed..960cb07 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,3 +1,10 @@ +# Specific to prettier (so it doesn't throw a bunch of errors when running "npm run format") +.dockerignore +.gitignore +.prettierignore +Dockerfile +LICENSE + # Specific to this repository dist/ data/ diff --git a/README.md b/README.md index 292e3f9..b099c29 100644 --- a/README.md +++ b/README.md @@ -12,9 +12,9 @@ Thank you for coming on this journey with me, but it is time to put the big chan Special thanks to: -- Lexi Sother (TravBot v2, structural overhaul. Reviewing PRs.) -- WatDuhHekBro (a _lot_ of contributions to TravBot v2) -- Zeehondie (Ideas for various commands.) +- Lexi Sother (TravBot v2, structural overhaul. Reviewing PRs.) +- WatDuhHekBro (a _lot_ of contributions to TravBot v2) +- Zeehondie (Ideas for various commands.) ### License diff --git a/docs/GettingStarted.md b/docs/GettingStarted.md index 398cc33..a003a81 100644 --- a/docs/GettingStarted.md +++ b/docs/GettingStarted.md @@ -17,3 +17,4 @@ - ...update the [changelog](CHANGELOG.md) and any other necessary docs. - ...update the version numbers in `package.json` and `package-lock.json`. - ...make sure the test suite passes by running `npm test`. +- ...format the code by running `npm run format`. diff --git a/package-lock.json b/package-lock.json index 78eba10..c7e8e8f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,1491 +1,1491 @@ { - "name": "d.js-v12-bot", - "version": "0.0.1", - "lockfileVersion": 1, - "requires": true, - "dependencies": { - "@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" - } - }, - "@lavacord/discord.js": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/@lavacord/discord.js/-/discord.js-0.0.5.tgz", - "integrity": "sha512-qc2lw0zB48fq4SrSlpMJOmogUXIeM5YvufQfPg0ubjp7jqm20JnOXF9fliy1MPdcKPnZ8LwIZ222H0+ghGzP/Q==", - "requires": { - "lavacord": "^1.1.9" - }, - "dependencies": { - "lavacord": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/lavacord/-/lavacord-1.1.9.tgz", - "integrity": "sha512-haZghbblO1w3Hodc9q63ZWgV5zA/jB6xFKS17fImK5aIdn0PkKuZ6AsJBxMFpR275v8GNYOxg6cTQBYBQ+batQ==", - "requires": { - "node-fetch": "^2.6.0", - "ws": "^7.3.0" - } - } - } - }, - "@types/color-name": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", - "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==" - }, - "@types/inquirer": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/@types/inquirer/-/inquirer-6.5.0.tgz", - "integrity": "sha512-rjaYQ9b9y/VFGOpqBEXRavc3jh0a+e6evAbI31tMda8VlPaSy0AZJfXsvmIe3wklc7W6C3zCSfleuMXR7NOyXw==", - "dev": true, - "requires": { - "@types/through": "*", - "rxjs": "^6.4.0" - } - }, - "@types/mocha": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-8.0.3.tgz", - "integrity": "sha512-vyxR57nv8NfcU0GZu8EUXZLTbCMupIUwy95LJ6lllN+JRPG25CwMHoB1q5xKh8YKhQnHYRAn4yW2yuHbf/5xgg==", - "dev": true - }, - "@types/ms": { - "version": "0.7.31", - "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz", - "integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==", - "dev": true - }, - "@types/node": { - "version": "14.14.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.2.tgz", - "integrity": "sha512-jeYJU2kl7hL9U5xuI/BhKPZ4vqGM/OmK6whiFAXVhlstzZhVamWhDSmHyGLIp+RVyuF9/d0dqr2P85aFj4BvJg==", - "dev": true - }, - "@types/through": { - "version": "0.0.30", - "resolved": "https://registry.npmjs.org/@types/through/-/through-0.0.30.tgz", - "integrity": "sha512-FvnCJljyxhPM3gkRgWmxmDZyAQSiBQQWLI0A0VFL0K7W1oRUrPJSqNO0NvTnLkBcotdlp3lKvaT0JrnyRDkzOg==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/ws": { - "version": "7.2.7", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-7.2.7.tgz", - "integrity": "sha512-UUFC/xxqFLP17hTva8/lVT0SybLUrfSD9c+iapKb0fEiC8uoDbA+xuZ3pAN603eW+bY8ebSMLm9jXdIPnD0ZgA==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@ungap/promise-all-settled": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", - "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", - "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" - } - }, - "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-escapes": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.1.tgz", - "integrity": "sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA==", - "requires": { - "type-fest": "^0.11.0" - } - }, - "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" - }, - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "anymatch": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", - "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", - "dev": true, - "requires": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - } - }, - "arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true - }, - "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" - } - }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" - }, - "axios": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.19.2.tgz", - "integrity": "sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA==", - "requires": { - "follow-redirects": "1.5.10" - } - }, - "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true - }, - "binary-extensions": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz", - "integrity": "sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==", - "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" - } - }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "requires": { - "fill-range": "^7.0.1" - } - }, - "browser-stdout": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", - "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", - "dev": true - }, - "buffer-from": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", - "dev": true - }, - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true - }, - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "chardet": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", - "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==" - }, - "chokidar": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.3.tgz", - "integrity": "sha512-DtM3g7juCXQxFVSNPNByEC2+NImtBuxQQvWlHunpJIS5Ocr0lG306cC7FCi7cEA0fzmybPUIl4txBIobk1gGOQ==", - "dev": true, - "requires": { - "anymatch": "~3.1.1", - "braces": "~3.0.2", - "fsevents": "~2.1.2", - "glob-parent": "~5.1.0", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.5.0" - } - }, - "cli-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", - "requires": { - "restore-cursor": "^3.1.0" - } - }, - "cli-width": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", - "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==" - }, - "cliui": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", - "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", - "dev": true, - "requires": { - "string-width": "^3.1.0", - "strip-ansi": "^5.2.0", - "wrap-ansi": "^5.1.0" - }, - "dependencies": { + "name": "d.js-v12-bot", + "version": "0.0.1", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@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" + } + }, + "@lavacord/discord.js": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/@lavacord/discord.js/-/discord.js-0.0.5.tgz", + "integrity": "sha512-qc2lw0zB48fq4SrSlpMJOmogUXIeM5YvufQfPg0ubjp7jqm20JnOXF9fliy1MPdcKPnZ8LwIZ222H0+ghGzP/Q==", + "requires": { + "lavacord": "^1.1.9" + }, + "dependencies": { + "lavacord": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/lavacord/-/lavacord-1.1.9.tgz", + "integrity": "sha512-haZghbblO1w3Hodc9q63ZWgV5zA/jB6xFKS17fImK5aIdn0PkKuZ6AsJBxMFpR275v8GNYOxg6cTQBYBQ+batQ==", + "requires": { + "node-fetch": "^2.6.0", + "ws": "^7.3.0" + } + } + } + }, + "@types/color-name": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", + "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==" + }, + "@types/inquirer": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@types/inquirer/-/inquirer-6.5.0.tgz", + "integrity": "sha512-rjaYQ9b9y/VFGOpqBEXRavc3jh0a+e6evAbI31tMda8VlPaSy0AZJfXsvmIe3wklc7W6C3zCSfleuMXR7NOyXw==", + "dev": true, + "requires": { + "@types/through": "*", + "rxjs": "^6.4.0" + } + }, + "@types/mocha": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-8.0.3.tgz", + "integrity": "sha512-vyxR57nv8NfcU0GZu8EUXZLTbCMupIUwy95LJ6lllN+JRPG25CwMHoB1q5xKh8YKhQnHYRAn4yW2yuHbf/5xgg==", + "dev": true + }, + "@types/ms": { + "version": "0.7.31", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz", + "integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==", + "dev": true + }, + "@types/node": { + "version": "14.14.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.2.tgz", + "integrity": "sha512-jeYJU2kl7hL9U5xuI/BhKPZ4vqGM/OmK6whiFAXVhlstzZhVamWhDSmHyGLIp+RVyuF9/d0dqr2P85aFj4BvJg==", + "dev": true + }, + "@types/through": { + "version": "0.0.30", + "resolved": "https://registry.npmjs.org/@types/through/-/through-0.0.30.tgz", + "integrity": "sha512-FvnCJljyxhPM3gkRgWmxmDZyAQSiBQQWLI0A0VFL0K7W1oRUrPJSqNO0NvTnLkBcotdlp3lKvaT0JrnyRDkzOg==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/ws": { + "version": "7.2.7", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-7.2.7.tgz", + "integrity": "sha512-UUFC/xxqFLP17hTva8/lVT0SybLUrfSD9c+iapKb0fEiC8uoDbA+xuZ3pAN603eW+bY8ebSMLm9jXdIPnD0ZgA==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@ungap/promise-all-settled": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", + "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", + "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" + } + }, + "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-escapes": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.1.tgz", + "integrity": "sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA==", + "requires": { + "type-fest": "^0.11.0" + } + }, "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - }, - "emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - } - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.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==", - "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==" - }, - "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.2.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", - "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", - "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=" - }, - "diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true - }, - "discord.js": { - "version": "12.4.0", - "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-12.4.0.tgz", - "integrity": "sha512-Lc+/vKzih1DPEya/0MO0BAp4Ru/4+MfDsOJkfbyzGoyIlFqiWSQ78RdyRk4tqAtabl6d5QR4Ygo0Ub0TGEsXBg==", - "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.2", - "setimmediate": "^1.0.5", - "tweetnacl": "^1.0.3", - "ws": "^7.3.1" - } - }, - "discord.js-lavalink-lib": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/discord.js-lavalink-lib/-/discord.js-lavalink-lib-0.1.7.tgz", - "integrity": "sha512-czjsdRcbB+sdfTXsko76JYAAPJzoRhTiaphZJXqFpsAYdv0hjkNVLHxuuG2d4hVuXjt/UU7HDYyWEb2e/3g2Ew==", - "requires": { - "@lavacord/discord.js": "0.0.5", - "axios": "^0.19.2", - "discord.js": "^12.2.0", - "lavacord": "^1.1.7" - } - }, - "duplexer": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", - "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=", - "dev": true - }, - "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==" - }, - "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=" - }, - "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 - }, - "event-stream": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz", - "integrity": "sha1-SrTJoPWlTbkzi0w02Gv86PSzVXE=", - "dev": true, - "requires": { - "duplexer": "~0.1.1", - "from": "~0", - "map-stream": "~0.1.0", - "pause-stream": "0.0.11", - "split": "0.3", - "stream-combiner": "~0.0.4", - "through": "~2.3.1" - } - }, - "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==" - }, - "external-editor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", - "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", - "requires": { - "chardet": "^0.7.0", - "iconv-lite": "^0.4.24", - "tmp": "^0.0.33" - } - }, - "figures": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", - "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", - "requires": { - "escape-string-regexp": "^1.0.5" - } - }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "requires": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - } - }, - "flat": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", - "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", - "dev": true - }, - "follow-redirects": { - "version": "1.5.10", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz", - "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==", - "requires": { - "debug": "=3.1.0" - }, - "dependencies": { - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - } - } - }, - "from": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz", - "integrity": "sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4=", - "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 - }, - "fsevents": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", - "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", - "dev": true, - "optional": true - }, - "get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true - }, - "glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "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.1", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", - "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", - "dev": true, - "requires": { - "is-glob": "^4.0.1" - } - }, - "growl": { - "version": "1.10.5", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", - "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "dev": true - }, - "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "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 - }, - "inquirer": { - "version": "7.3.3", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.3.3.tgz", - "integrity": "sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==", - "requires": { - "ansi-escapes": "^4.2.1", - "chalk": "^4.1.0", - "cli-cursor": "^3.1.0", - "cli-width": "^3.0.0", - "external-editor": "^3.0.3", - "figures": "^3.0.0", - "lodash": "^4.17.19", - "mute-stream": "0.0.8", - "run-async": "^2.4.0", - "rxjs": "^6.6.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0", - "through": "^2.3.6" - } - }, - "is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "requires": { - "binary-extensions": "^2.0.0" - } - }, - "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==" - }, - "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" - } - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true - }, - "is-plain-obj": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", - "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", - "dev": true - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true - }, - "js-yaml": { - "version": "3.14.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz", - "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==", - "dev": true, - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - } - }, - "lavacord": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/lavacord/-/lavacord-1.1.9.tgz", - "integrity": "sha512-haZghbblO1w3Hodc9q63ZWgV5zA/jB6xFKS17fImK5aIdn0PkKuZ6AsJBxMFpR275v8GNYOxg6cTQBYBQ+batQ==", - "requires": { - "node-fetch": "^2.6.0", - "ws": "^7.3.0" - } - }, - "locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "requires": { - "p-locate": "^5.0.0" - } - }, - "lodash": { - "version": "4.17.20", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", - "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==" - }, - "log-symbols": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.0.0.tgz", - "integrity": "sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA==", - "dev": true, - "requires": { - "chalk": "^4.0.0" - } - }, - "make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true - }, - "map-stream": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz", - "integrity": "sha1-5WqpTEyAVaFkBKBnS3jyFffI4ZQ=", - "dev": true - }, - "mime-db": { - "version": "1.44.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", - "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==" - }, - "mime-types": { - "version": "2.1.27", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", - "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", - "requires": { - "mime-db": "1.44.0" - } - }, - "mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==" - }, - "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" - } - }, - "mocha": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-8.2.0.tgz", - "integrity": "sha512-lEWEMq2LMfNJMKeuEwb5UELi+OgFDollXaytR5ggQcHpzG3NP/R7rvixAvF+9/lLsTWhWG+4yD2M70GsM06nxw==", - "dev": true, - "requires": { - "@ungap/promise-all-settled": "1.1.2", - "ansi-colors": "4.1.1", - "browser-stdout": "1.3.1", - "chokidar": "3.4.3", - "debug": "4.2.0", - "diff": "4.0.2", - "escape-string-regexp": "4.0.0", - "find-up": "5.0.0", - "glob": "7.1.6", - "growl": "1.10.5", - "he": "1.2.0", - "js-yaml": "3.14.0", - "log-symbols": "4.0.0", - "minimatch": "3.0.4", - "ms": "2.1.2", - "nanoid": "3.1.12", - "serialize-javascript": "5.0.1", - "strip-json-comments": "3.1.1", - "supports-color": "7.2.0", - "which": "2.0.2", - "wide-align": "1.1.3", - "workerpool": "6.0.2", - "yargs": "13.3.2", - "yargs-parser": "13.1.2", - "yargs-unparser": "2.0.0" - }, - "dependencies": { - "escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true - }, - "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" - } - } - } - }, - "moment": { - "version": "2.29.1", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz", - "integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==" - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "mute-stream": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", - "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==" - }, - "nanoid": { - "version": "3.1.12", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.12.tgz", - "integrity": "sha512-1qstj9z5+x491jfiC4Nelk+f8XBad7LN20PmyWINJEMRSf3wcAjAWysw1qaA8z6NSKe2sjq1hRSDpBH5paCb6A==", - "dev": true - }, - "node-cleanup": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/node-cleanup/-/node-cleanup-2.1.2.tgz", - "integrity": "sha1-esGavSl+Caf3KnFUXZUbUX5N3iw=", - "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==" - }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, - "requires": { - "wrappy": "1" - } - }, - "onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "requires": { - "mimic-fn": "^2.1.0" - } - }, - "os": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/os/-/os-0.1.1.tgz", - "integrity": "sha1-IIhF6J4ZOtTZcUdLk5R3NqVtE/M=" - }, - "os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" - }, - "p-limit": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.0.2.tgz", - "integrity": "sha512-iwqZSOoWIW+Ew4kAGUlN16J4M7OB3ysMLSZtnhmqx7njIHFPlxWBX8xo3lVTyFVq6mI/lL9qt2IsN1sHwaxJkg==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "requires": { - "p-limit": "^3.0.2" - } - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true - }, - "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 - }, - "pause-stream": { - "version": "0.0.11", - "resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", - "integrity": "sha1-/lo0sMvOErWqaitAPuLnO2AvFEU=", - "dev": true, - "requires": { - "through": "~2.3" - } - }, - "picomatch": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", - "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", - "dev": true - }, - "prettier": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.1.2.tgz", - "integrity": "sha512-16c7K+x4qVlJg9rEbXl7HEGmQyZlG4R9AgP+oHKRMsMsuk8s+ATStlf1NpDqyBI1HpVyfjLOeMhH2LvuNvV5Vg==", - "dev": true - }, - "prism-media": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/prism-media/-/prism-media-1.2.2.tgz", - "integrity": "sha512-I+nkWY212lJ500jLe4tN9tWO7nRiBAVdMv76P9kffZjYhw20raMlW1HSSvS+MLXC9MmbNZCazMrAr+5jEEgTuw==" - }, - "ps-tree": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/ps-tree/-/ps-tree-1.2.0.tgz", - "integrity": "sha512-0VnamPPYHl4uaU/nSFeZZpR21QAWRz+sRv4iW9+v/GS/J5U5iZB5BNN6J0RMoOvdx2gWM2+ZFMIm58q24e4UYA==", - "dev": true, - "requires": { - "event-stream": "=3.3.4" - } - }, - "randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "requires": { - "safe-buffer": "^5.1.0" - } - }, - "readdirp": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", - "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", - "dev": true, - "requires": { - "picomatch": "^2.2.1" - } - }, - "require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", - "dev": true - }, - "require-main-filename": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", - "dev": true - }, - "restore-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", - "requires": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" - } - }, - "run-async": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", - "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==" - }, - "rxjs": { - "version": "6.6.0", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.0.tgz", - "integrity": "sha512-3HMA8z/Oz61DUHe+SdOiQyzIf4tOx5oQHmMir7IZEu6TMqCLHT4LRcmNaUS0NwOz8VLvmmBduMsoaUvMaIiqzg==", - "requires": { - "tslib": "^1.9.0" - } - }, - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "serialize-javascript": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-5.0.1.tgz", - "integrity": "sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==", - "dev": true, - "requires": { - "randombytes": "^2.1.0" - } - }, - "set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", - "dev": true - }, - "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 - }, - "signal-exit": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", - "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==" - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "source-map-support": { - "version": "0.5.19", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", - "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "split": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/split/-/split-0.3.3.tgz", - "integrity": "sha1-zQ7qXmOiEd//frDwkcQTPi0N0o8=", - "dev": true, - "requires": { - "through": "2" - } - }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true - }, - "stream-combiner": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz", - "integrity": "sha1-TV5DPBhSYd3mI8o/RMWGvPXErRQ=", - "dev": true, - "requires": { - "duplexer": "~0.1.1" - } - }, - "string-argv": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.1.2.tgz", - "integrity": "sha512-mBqPGEOMNJKXRo7z0keX0wlAhbBAjilUdPW13nN0PecVryZxdHIeM7TqbsSUA7VYuS00HGC6mojP7DlQzfa9ZA==", - "dev": true - }, - "string-width": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", - "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - } - }, - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "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.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "requires": { - "has-flag": "^4.0.0" - } - }, - "through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" - }, - "tmp": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", - "requires": { - "os-tmpdir": "~1.0.2" - } - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "requires": { - "is-number": "^7.0.0" - } - }, - "ts-node": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-9.0.0.tgz", - "integrity": "sha512-/TqB4SnererCDR/vb4S/QvSZvzQMJN8daAslg7MeaiHvD8rDZsSfXmNeNumyZZzMned72Xoq/isQljYSt8Ynfg==", - "dev": true, - "requires": { - "arg": "^4.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "source-map-support": "^0.5.17", - "yn": "3.1.1" - } - }, - "tsc-watch": { - "version": "4.2.9", - "resolved": "https://registry.npmjs.org/tsc-watch/-/tsc-watch-4.2.9.tgz", - "integrity": "sha512-DlTaoDs74+KUpyWr7dCGhuscAUKCz6CiFduBN7R9RbLJSSN1moWdwoCLASE7+zLgGvV5AwXfYDiEMAsPGaO+Vw==", - "dev": true, - "requires": { - "cross-spawn": "^7.0.3", - "node-cleanup": "^2.1.2", - "ps-tree": "^1.2.0", - "string-argv": "^0.1.1", - "strip-ansi": "^6.0.0" - } - }, - "tslib": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz", - "integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==" - }, - "tweetnacl": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", - "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==" - }, - "type-fest": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.11.0.tgz", - "integrity": "sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==" - }, - "typescript": { - "version": "3.9.7", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.7.tgz", - "integrity": "sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw==", - "dev": true - }, - "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" - } - }, - "which-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", - "dev": true - }, - "wide-align": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", - "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", - "dev": true, - "requires": { - "string-width": "^1.0.2 || 2" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, - "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - } - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0" - } - } - } - }, - "workerpool": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.0.2.tgz", - "integrity": "sha512-DSNyvOpFKrNusaaUwk+ej6cBj1bmhLcBfj80elGk+ZIo5JSkq+unB1dLKEOcNfJDZgjGICfhQ0Q5TbP0PvF4+Q==", - "dev": true - }, - "wrap-ansi": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", - "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.0", - "string-width": "^3.0.0", - "strip-ansi": "^5.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" }, "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" - } + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "anymatch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", + "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "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" + } + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, + "axios": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.19.2.tgz", + "integrity": "sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA==", + "requires": { + "follow-redirects": "1.5.10" + } + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "binary-extensions": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz", + "integrity": "sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==", + "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" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==" + }, + "chokidar": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.3.tgz", + "integrity": "sha512-DtM3g7juCXQxFVSNPNByEC2+NImtBuxQQvWlHunpJIS5Ocr0lG306cC7FCi7cEA0fzmybPUIl4txBIobk1gGOQ==", + "dev": true, + "requires": { + "anymatch": "~3.1.1", + "braces": "~3.0.2", + "fsevents": "~2.1.2", + "glob-parent": "~5.1.0", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.5.0" + } + }, + "cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "requires": { + "restore-cursor": "^3.1.0" + } + }, + "cli-width": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", + "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==" + }, + "cliui": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "dev": true, + "requires": { + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.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" - } + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } }, "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, - "emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", - "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" + } }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true + "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 }, - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - } + "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" + } }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - } - } - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true - }, - "ws": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.3.1.tgz", - "integrity": "sha512-D3RuNkynyHmEJIpD2qrgVkc9DQ23OrN/moAwZX4L8DfvszsJxpjQuUq3LMx6HoYji9fbIOBY18XWBsAux1ZZUA==" - }, - "y18n": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", - "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", - "dev": true - }, - "yargs": { - "version": "13.3.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", - "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", - "dev": true, - "requires": { - "cliui": "^5.0.0", - "find-up": "^3.0.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^3.0.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^13.1.2" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - }, - "emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", - "dev": true - }, - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "requires": { - "locate-path": "^3.0.0" - } - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, - "requires": { - "p-limit": "^2.0.0" - } - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true - }, - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - } - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - } - } - } - }, - "yargs-parser": { - "version": "13.1.2", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", - "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", - "dev": true, - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } - }, - "yargs-unparser": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", - "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", - "dev": true, - "requires": { - "camelcase": "^6.0.0", - "decamelize": "^4.0.0", - "flat": "^5.0.2", - "is-plain-obj": "^2.1.0" - }, - "dependencies": { - "camelcase": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.1.0.tgz", - "integrity": "sha512-WCMml9ivU60+8rEJgELlFp1gxFcEGxwYleE3bziHEDeqsqAWGHdimB7beBFGjLzVNgPGyDsfgXLQEYMpmIFnVQ==", - "dev": true + "debug": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", + "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", + "dev": true, + "requires": { + "ms": "2.1.2" + } }, "decamelize": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", - "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", - "dev": true + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "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=" + }, + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true + }, + "discord.js": { + "version": "12.4.0", + "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-12.4.0.tgz", + "integrity": "sha512-Lc+/vKzih1DPEya/0MO0BAp4Ru/4+MfDsOJkfbyzGoyIlFqiWSQ78RdyRk4tqAtabl6d5QR4Ygo0Ub0TGEsXBg==", + "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.2", + "setimmediate": "^1.0.5", + "tweetnacl": "^1.0.3", + "ws": "^7.3.1" + } + }, + "discord.js-lavalink-lib": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/discord.js-lavalink-lib/-/discord.js-lavalink-lib-0.1.7.tgz", + "integrity": "sha512-czjsdRcbB+sdfTXsko76JYAAPJzoRhTiaphZJXqFpsAYdv0hjkNVLHxuuG2d4hVuXjt/UU7HDYyWEb2e/3g2Ew==", + "requires": { + "@lavacord/discord.js": "0.0.5", + "axios": "^0.19.2", + "discord.js": "^12.2.0", + "lavacord": "^1.1.7" + } + }, + "duplexer": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", + "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=", + "dev": true + }, + "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==" + }, + "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=" + }, + "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 + }, + "event-stream": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz", + "integrity": "sha1-SrTJoPWlTbkzi0w02Gv86PSzVXE=", + "dev": true, + "requires": { + "duplexer": "~0.1.1", + "from": "~0", + "map-stream": "~0.1.0", + "pause-stream": "0.0.11", + "split": "0.3", + "stream-combiner": "~0.0.4", + "through": "~2.3.1" + } + }, + "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==" + }, + "external-editor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", + "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", + "requires": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + } + }, + "figures": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "requires": { + "escape-string-regexp": "^1.0.5" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, + "flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true + }, + "follow-redirects": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz", + "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==", + "requires": { + "debug": "=3.1.0" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + } + } + }, + "from": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz", + "integrity": "sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4=", + "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 + }, + "fsevents": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", + "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", + "dev": true, + "optional": true + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true + }, + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "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.1", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", + "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "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 + }, + "inquirer": { + "version": "7.3.3", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.3.3.tgz", + "integrity": "sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==", + "requires": { + "ansi-escapes": "^4.2.1", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-width": "^3.0.0", + "external-editor": "^3.0.3", + "figures": "^3.0.0", + "lodash": "^4.17.19", + "mute-stream": "0.0.8", + "run-async": "^2.4.0", + "rxjs": "^6.6.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0", + "through": "^2.3.6" + } + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "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==" + }, + "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" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "js-yaml": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz", + "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "lavacord": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/lavacord/-/lavacord-1.1.9.tgz", + "integrity": "sha512-haZghbblO1w3Hodc9q63ZWgV5zA/jB6xFKS17fImK5aIdn0PkKuZ6AsJBxMFpR275v8GNYOxg6cTQBYBQ+batQ==", + "requires": { + "node-fetch": "^2.6.0", + "ws": "^7.3.0" + } + }, + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "requires": { + "p-locate": "^5.0.0" + } + }, + "lodash": { + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==" + }, + "log-symbols": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.0.0.tgz", + "integrity": "sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA==", + "dev": true, + "requires": { + "chalk": "^4.0.0" + } + }, + "make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "map-stream": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz", + "integrity": "sha1-5WqpTEyAVaFkBKBnS3jyFffI4ZQ=", + "dev": true + }, + "mime-db": { + "version": "1.44.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", + "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==" + }, + "mime-types": { + "version": "2.1.27", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", + "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", + "requires": { + "mime-db": "1.44.0" + } + }, + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==" + }, + "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" + } + }, + "mocha": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-8.2.0.tgz", + "integrity": "sha512-lEWEMq2LMfNJMKeuEwb5UELi+OgFDollXaytR5ggQcHpzG3NP/R7rvixAvF+9/lLsTWhWG+4yD2M70GsM06nxw==", + "dev": true, + "requires": { + "@ungap/promise-all-settled": "1.1.2", + "ansi-colors": "4.1.1", + "browser-stdout": "1.3.1", + "chokidar": "3.4.3", + "debug": "4.2.0", + "diff": "4.0.2", + "escape-string-regexp": "4.0.0", + "find-up": "5.0.0", + "glob": "7.1.6", + "growl": "1.10.5", + "he": "1.2.0", + "js-yaml": "3.14.0", + "log-symbols": "4.0.0", + "minimatch": "3.0.4", + "ms": "2.1.2", + "nanoid": "3.1.12", + "serialize-javascript": "5.0.1", + "strip-json-comments": "3.1.1", + "supports-color": "7.2.0", + "which": "2.0.2", + "wide-align": "1.1.3", + "workerpool": "6.0.2", + "yargs": "13.3.2", + "yargs-parser": "13.1.2", + "yargs-unparser": "2.0.0" + }, + "dependencies": { + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + }, + "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" + } + } + } + }, + "moment": { + "version": "2.29.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz", + "integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==" + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "mute-stream": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==" + }, + "nanoid": { + "version": "3.1.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.12.tgz", + "integrity": "sha512-1qstj9z5+x491jfiC4Nelk+f8XBad7LN20PmyWINJEMRSf3wcAjAWysw1qaA8z6NSKe2sjq1hRSDpBH5paCb6A==", + "dev": true + }, + "node-cleanup": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/node-cleanup/-/node-cleanup-2.1.2.tgz", + "integrity": "sha1-esGavSl+Caf3KnFUXZUbUX5N3iw=", + "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==" + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "requires": { + "mimic-fn": "^2.1.0" + } + }, + "os": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/os/-/os-0.1.1.tgz", + "integrity": "sha1-IIhF6J4ZOtTZcUdLk5R3NqVtE/M=" + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" + }, + "p-limit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.0.2.tgz", + "integrity": "sha512-iwqZSOoWIW+Ew4kAGUlN16J4M7OB3ysMLSZtnhmqx7njIHFPlxWBX8xo3lVTyFVq6mI/lL9qt2IsN1sHwaxJkg==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "requires": { + "p-limit": "^3.0.2" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "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 + }, + "pause-stream": { + "version": "0.0.11", + "resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", + "integrity": "sha1-/lo0sMvOErWqaitAPuLnO2AvFEU=", + "dev": true, + "requires": { + "through": "~2.3" + } + }, + "picomatch": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", + "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", + "dev": true + }, + "prettier": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.1.2.tgz", + "integrity": "sha512-16c7K+x4qVlJg9rEbXl7HEGmQyZlG4R9AgP+oHKRMsMsuk8s+ATStlf1NpDqyBI1HpVyfjLOeMhH2LvuNvV5Vg==", + "dev": true + }, + "prism-media": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/prism-media/-/prism-media-1.2.2.tgz", + "integrity": "sha512-I+nkWY212lJ500jLe4tN9tWO7nRiBAVdMv76P9kffZjYhw20raMlW1HSSvS+MLXC9MmbNZCazMrAr+5jEEgTuw==" + }, + "ps-tree": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/ps-tree/-/ps-tree-1.2.0.tgz", + "integrity": "sha512-0VnamPPYHl4uaU/nSFeZZpR21QAWRz+sRv4iW9+v/GS/J5U5iZB5BNN6J0RMoOvdx2gWM2+ZFMIm58q24e4UYA==", + "dev": true, + "requires": { + "event-stream": "=3.3.4" + } + }, + "randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.0" + } + }, + "readdirp": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", + "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", + "dev": true, + "requires": { + "picomatch": "^2.2.1" + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true + }, + "require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true + }, + "restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "requires": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + } + }, + "run-async": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", + "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==" + }, + "rxjs": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.0.tgz", + "integrity": "sha512-3HMA8z/Oz61DUHe+SdOiQyzIf4tOx5oQHmMir7IZEu6TMqCLHT4LRcmNaUS0NwOz8VLvmmBduMsoaUvMaIiqzg==", + "requires": { + "tslib": "^1.9.0" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "serialize-javascript": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-5.0.1.tgz", + "integrity": "sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==", + "dev": true, + "requires": { + "randombytes": "^2.1.0" + } + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true + }, + "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 + }, + "signal-exit": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", + "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==" + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "source-map-support": { + "version": "0.5.19", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", + "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "split": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/split/-/split-0.3.3.tgz", + "integrity": "sha1-zQ7qXmOiEd//frDwkcQTPi0N0o8=", + "dev": true, + "requires": { + "through": "2" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "stream-combiner": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz", + "integrity": "sha1-TV5DPBhSYd3mI8o/RMWGvPXErRQ=", + "dev": true, + "requires": { + "duplexer": "~0.1.1" + } + }, + "string-argv": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.1.2.tgz", + "integrity": "sha512-mBqPGEOMNJKXRo7z0keX0wlAhbBAjilUdPW13nN0PecVryZxdHIeM7TqbsSUA7VYuS00HGC6mojP7DlQzfa9ZA==", + "dev": true + }, + "string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "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.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "requires": { + "has-flag": "^4.0.0" + } + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" + }, + "tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "requires": { + "os-tmpdir": "~1.0.2" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "ts-node": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-9.0.0.tgz", + "integrity": "sha512-/TqB4SnererCDR/vb4S/QvSZvzQMJN8daAslg7MeaiHvD8rDZsSfXmNeNumyZZzMned72Xoq/isQljYSt8Ynfg==", + "dev": true, + "requires": { + "arg": "^4.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "source-map-support": "^0.5.17", + "yn": "3.1.1" + } + }, + "tsc-watch": { + "version": "4.2.9", + "resolved": "https://registry.npmjs.org/tsc-watch/-/tsc-watch-4.2.9.tgz", + "integrity": "sha512-DlTaoDs74+KUpyWr7dCGhuscAUKCz6CiFduBN7R9RbLJSSN1moWdwoCLASE7+zLgGvV5AwXfYDiEMAsPGaO+Vw==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.3", + "node-cleanup": "^2.1.2", + "ps-tree": "^1.2.0", + "string-argv": "^0.1.1", + "strip-ansi": "^6.0.0" + } + }, + "tslib": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz", + "integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==" + }, + "tweetnacl": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", + "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==" + }, + "type-fest": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.11.0.tgz", + "integrity": "sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==" + }, + "typescript": { + "version": "3.9.7", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.7.tgz", + "integrity": "sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw==", + "dev": true + }, + "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" + } + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true + }, + "wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "dev": true, + "requires": { + "string-width": "^1.0.2 || 2" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "workerpool": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.0.2.tgz", + "integrity": "sha512-DSNyvOpFKrNusaaUwk+ej6cBj1bmhLcBfj80elGk+ZIo5JSkq+unB1dLKEOcNfJDZgjGICfhQ0Q5TbP0PvF4+Q==", + "dev": true + }, + "wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "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" + } + }, + "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 + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "ws": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.3.1.tgz", + "integrity": "sha512-D3RuNkynyHmEJIpD2qrgVkc9DQ23OrN/moAwZX4L8DfvszsJxpjQuUq3LMx6HoYji9fbIOBY18XWBsAux1ZZUA==" + }, + "y18n": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", + "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", + "dev": true + }, + "yargs": { + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", + "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", + "dev": true, + "requires": { + "cliui": "^5.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.1.2" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "yargs-parser": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", + "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + }, + "yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "dev": true, + "requires": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + }, + "dependencies": { + "camelcase": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.1.0.tgz", + "integrity": "sha512-WCMml9ivU60+8rEJgELlFp1gxFcEGxwYleE3bziHEDeqsqAWGHdimB7beBFGjLzVNgPGyDsfgXLQEYMpmIFnVQ==", + "dev": true + }, + "decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true + } + } + }, + "yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true } - } - }, - "yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true } - } } diff --git a/package.json b/package.json index 208ddfc..27612ab 100644 --- a/package.json +++ b/package.json @@ -1,42 +1,42 @@ -{ - "name": "d.js-v12-bot", - "version": "0.0.1", - "description": "A Discord bot built on Discord.JS v12", - "main": "dist/index.js", - "private": true, - "dependencies": { - "chalk": "^4.1.0", - "discord.js": "^12.4.0", - "discord.js-lavalink-lib": "^0.1.7", - "inquirer": "^7.3.3", - "moment": "^2.29.1", - "ms": "^2.1.2", - "os": "^0.1.1" - }, - "devDependencies": { - "@types/inquirer": "^6.5.0", - "@types/mocha": "^8.0.3", - "@types/ms": "^0.7.31", - "@types/node": "^14.14.2", - "@types/ws": "^7.2.7", - "mocha": "^8.2.0", - "prettier": "2.1.2", - "ts-node": "^9.0.0", - "tsc-watch": "^4.2.9", - "typescript": "^3.9.7" - }, - "scripts": { - "build": "tsc && npm prune --production", - "start": "node dist/index.js", - "once": "tsc && npm start", - "dev": "tsc-watch --onSuccess \"node dist/index.js dev\"", - "test": "mocha --require ts-node/register --extension ts --recursive", - "format": "prettier --write **/*" - }, - "keywords": [ - "discord.js", - "bot" - ], - "author": "Keanu Timmermans", - "license": "MIT" -} \ No newline at end of file +{ + "name": "d.js-v12-bot", + "version": "0.0.1", + "description": "A Discord bot built on Discord.JS v12", + "main": "dist/index.js", + "private": true, + "dependencies": { + "chalk": "^4.1.0", + "discord.js": "^12.4.0", + "discord.js-lavalink-lib": "^0.1.7", + "inquirer": "^7.3.3", + "moment": "^2.29.1", + "ms": "^2.1.2", + "os": "^0.1.1" + }, + "devDependencies": { + "@types/inquirer": "^6.5.0", + "@types/mocha": "^8.0.3", + "@types/ms": "^0.7.31", + "@types/node": "^14.14.2", + "@types/ws": "^7.2.7", + "mocha": "^8.2.0", + "prettier": "2.1.2", + "ts-node": "^9.0.0", + "tsc-watch": "^4.2.9", + "typescript": "^3.9.7" + }, + "scripts": { + "build": "tsc && npm prune --production", + "start": "node dist/index.js", + "once": "tsc && npm start", + "dev": "tsc-watch --onSuccess \"node dist/index.js dev\"", + "test": "mocha --require ts-node/register --extension ts --recursive", + "format": "prettier --write **/*" + }, + "keywords": [ + "discord.js", + "bot" + ], + "author": "Keanu Timmermans", + "license": "MIT" +} diff --git a/prettier.config.js b/prettier.config.js index 4cbaf1d..f5d98bc 100644 --- a/prettier.config.js +++ b/prettier.config.js @@ -1,14 +1,14 @@ module.exports = { - printWidth: 80, - tabWidth: 2, - useTabs: false, - semi: true, - singleQuote: true, - quoteProps: 'as-needed', - jsxSingleQuote: false, - trailingComma: 'all', - bracketSpacing: true, - jsxBracketSameLine: true, - arrowParens: 'always', - endOfLine: 'auto', + printWidth: 80, + tabWidth: 4, + useTabs: false, + semi: true, + singleQuote: false, + quoteProps: "as-needed", + jsxSingleQuote: false, + trailingComma: "none", + bracketSpacing: false, + jsxBracketSameLine: false, + arrowParens: "always", + endOfLine: "lf" }; diff --git a/src/commands/admin.ts b/src/commands/admin.ts index ad34bc4..48320c0 100644 --- a/src/commands/admin.ts +++ b/src/commands/admin.ts @@ -1,228 +1,237 @@ -import Command from '../core/command'; -import { CommonLibrary, logs, botHasPermission, clean } from '../core/lib'; -import { Config, Storage } from '../core/structures'; -import { PermissionNames, getPermissionLevel } from '../core/permissions'; -import { Permissions } from 'discord.js'; -import * as discord from 'discord.js'; - -function getLogBuffer(type: string) { - return { - files: [ - { - attachment: Buffer.alloc(logs[type].length, logs[type]), - name: `${Date.now()}.${type}.log`, - }, - ], - }; -} - -const activities = ['playing', 'listening', 'streaming', 'watching']; -const statuses = ['online', 'idle', 'dnd', 'invisible']; - -export default new Command({ - description: - "An all-in-one command to do admin stuff. You need to be either an admin of the server or one of the bot's mechanics to use this command.", - async run($: CommonLibrary): Promise { - if (!$.member) - return $.channel.send( - "Couldn't find a member object for you! Did you make sure you used this in a server?", - ); - const permLevel = getPermissionLevel($.member); - $.channel.send( - `${$.author.toString()}, your permission level is \`${ - PermissionNames[permLevel] - }\` (${permLevel}).`, - ); - }, - subcommands: { - set: new Command({ - description: 'Set different per-guild settings for the bot.', - run: 'You have to specify the option you want to set.', - permission: Command.PERMISSIONS.ADMIN, - subcommands: { - prefix: new Command({ - description: - 'Set a custom prefix for your guild. Removes your custom prefix if none is provided.', - usage: '()', - async run($: CommonLibrary): Promise { - Storage.getGuild($.guild?.id || 'N/A').prefix = null; - Storage.save(); - $.channel.send( - `The custom prefix for this guild has been removed. My prefix is now back to \`${Config.prefix}\`.`, - ); - }, - any: new Command({ - async run($: CommonLibrary): Promise { - Storage.getGuild($.guild?.id || 'N/A').prefix = $.args[0]; - Storage.save(); - $.channel.send( - `The custom prefix for this guild is now \`${$.args[0]}\`.`, - ); - }, - }), - }), - }, - }), - diag: new Command({ - description: 'Requests a debug log with the "info" verbosity level.', - permission: Command.PERMISSIONS.BOT_SUPPORT, - async run($: CommonLibrary): Promise { - $.channel.send(getLogBuffer('info')); - }, - any: new Command({ - description: `Select a verbosity to listen to. Available levels: \`[${Object.keys( - logs, - ).join(', ')}]\``, - async run($: CommonLibrary): Promise { - const type = $.args[0]; - - if (type in logs) $.channel.send(getLogBuffer(type)); - else - $.channel.send( - `Couldn't find a verbosity level named \`${type}\`! The available types are \`[${Object.keys( - logs, - ).join(', ')}]\`.`, - ); - }, - }), - }), - status: new Command({ - description: "Changes the bot's status.", - permission: Command.PERMISSIONS.BOT_SUPPORT, - async run($: CommonLibrary): Promise { - $.channel.send('Setting status to `online`...'); - }, - any: new Command({ - description: `Select a status to set to. Available statuses: \`[${statuses.join( - ', ', - )}]\`.`, - async run($: CommonLibrary): Promise { - if (!statuses.includes($.args[0])) - return $.channel.send("That status doesn't exist!"); - else { - $.client.user?.setStatus($.args[0]); - $.channel.send(`Setting status to \`${$.args[0]}\`...`); - } - }, - }), - }), - purge: new Command({ - description: 'Purges bot messages.', - permission: Command.PERMISSIONS.BOT_SUPPORT, - async run($: CommonLibrary): Promise { - if ($.message.channel instanceof discord.DMChannel) { - return; - } - $.message.delete(); - const msgs = await $.channel.messages.fetch({ - limit: 100, - }); - const travMessages = msgs.filter( - (m) => m.author.id === $.client.user?.id, - ); - - await $.message.channel - .send(`Found ${travMessages.size} messages to delete.`) - .then((m) => - m.delete({ - timeout: 5000, - }), - ); - await $.message.channel.bulkDelete(travMessages); - }, - }), - clear: new Command({ - description: "Clears a given amount of messages.", - usage: "", - run: "A number was not provided.", - number: new Command({ - description: "Amount of messages to delete.", - async run($: CommonLibrary): Promise { - $.message.delete(); - const fetched = await $.channel.messages.fetch({ - limit: $.args[0], - }); - /// @ts-ignore - $.channel.bulkDelete(fetched) - .catch((error: any) => $.channel.send(`Error: ${error}`)); - } - }) - }), - eval: new Command({ - description: 'Evaluate code.', - usage: '', - permission: Command.PERMISSIONS.BOT_OWNER, - async run($: CommonLibrary): Promise { - try { - const code = $.args.join(' '); - let evaled = eval(code); - - if (typeof evaled !== 'string') - evaled = require('util').inspect(evaled); - $.channel.send(clean(evaled), { code: 'x1' }); - } catch (err) { - $.channel.send(`\`ERROR\` \`\`\`x1\n${clean(err)}\n\`\`\``); - } - }, - }), - nick: new Command({ - description: "Change the bot's nickname.", - permission: Command.PERMISSIONS.BOT_SUPPORT, - async run($: CommonLibrary): Promise { - const nickName = $.args.join(' '); - const trav = $.guild?.members.cache.find( - (member) => member.id === $.client.user?.id, - ); - await trav?.setNickname(nickName); - if (botHasPermission($.guild, Permissions.FLAGS.MANAGE_MESSAGES)) - $.message.delete({ timeout: 5000 }).catch($.handler.bind($)); - $.channel - .send(`Nickname set to \`${nickName}\``) - .then((m) => m.delete({ timeout: 5000 })); - }, - }), - guilds: new Command({ - description: 'Shows a list of all guilds the bot is a member of.', - permission: Command.PERMISSIONS.BOT_SUPPORT, - async run($: CommonLibrary): Promise { - const guildList = $.client.guilds.cache.array().map((e) => e.name); - $.channel.send(guildList); - }, - }), - activity: new Command({ - description: 'Set the activity of the bot.', - permission: Command.PERMISSIONS.BOT_SUPPORT, - usage: ' ', - async run($: CommonLibrary): Promise { - $.client.user?.setActivity('.help', { - type: 'LISTENING', - }); - $.channel.send('Activity set to default.'); - }, - any: new Command({ - description: `Select an activity type to set. Available levels: \`[${activities.join( - ', ', - )}]\``, - async run($: CommonLibrary): Promise { - const type = $.args[0]; - - if (activities.includes(type)) { - $.client.user?.setActivity($.args.slice(1).join(' '), { - type: $.args[0].toUpperCase(), - }); - $.channel.send( - `Set activity to \`${$.args[0].toUpperCase()}\` \`${$.args - .slice(1) - .join(' ')}\`.`, - ); - } else - $.channel.send( - `Couldn't find an activity type named \`${type}\`! The available types are \`[${activities.join( - ', ', - )}]\`.`, - ); - }, - }), - }), - }, -}); +import Command from "../core/command"; +import {CommonLibrary, logs, botHasPermission, clean} from "../core/lib"; +import {Config, Storage} from "../core/structures"; +import {PermissionNames, getPermissionLevel} from "../core/permissions"; +import {Permissions} from "discord.js"; +import * as discord from "discord.js"; + +function getLogBuffer(type: string) { + return { + files: [ + { + attachment: Buffer.alloc(logs[type].length, logs[type]), + name: `${Date.now()}.${type}.log` + } + ] + }; +} + +const activities = ["playing", "listening", "streaming", "watching"]; +const statuses = ["online", "idle", "dnd", "invisible"]; + +export default new Command({ + description: + "An all-in-one command to do admin stuff. You need to be either an admin of the server or one of the bot's mechanics to use this command.", + async run($: CommonLibrary): Promise { + if (!$.member) + return $.channel.send( + "Couldn't find a member object for you! Did you make sure you used this in a server?" + ); + const permLevel = getPermissionLevel($.member); + $.channel.send( + `${$.author.toString()}, your permission level is \`${ + PermissionNames[permLevel] + }\` (${permLevel}).` + ); + }, + subcommands: { + set: new Command({ + description: "Set different per-guild settings for the bot.", + run: "You have to specify the option you want to set.", + permission: Command.PERMISSIONS.ADMIN, + subcommands: { + prefix: new Command({ + description: + "Set a custom prefix for your guild. Removes your custom prefix if none is provided.", + usage: "()", + async run($: CommonLibrary): Promise { + Storage.getGuild($.guild?.id || "N/A").prefix = null; + Storage.save(); + $.channel.send( + `The custom prefix for this guild has been removed. My prefix is now back to \`${Config.prefix}\`.` + ); + }, + any: new Command({ + async run($: CommonLibrary): Promise { + Storage.getGuild($.guild?.id || "N/A").prefix = + $.args[0]; + Storage.save(); + $.channel.send( + `The custom prefix for this guild is now \`${$.args[0]}\`.` + ); + } + }) + }) + } + }), + diag: new Command({ + description: + 'Requests a debug log with the "info" verbosity level.', + permission: Command.PERMISSIONS.BOT_SUPPORT, + async run($: CommonLibrary): Promise { + $.channel.send(getLogBuffer("info")); + }, + any: new Command({ + description: `Select a verbosity to listen to. Available levels: \`[${Object.keys( + logs + ).join(", ")}]\``, + async run($: CommonLibrary): Promise { + const type = $.args[0]; + + if (type in logs) $.channel.send(getLogBuffer(type)); + else + $.channel.send( + `Couldn't find a verbosity level named \`${type}\`! The available types are \`[${Object.keys( + logs + ).join(", ")}]\`.` + ); + } + }) + }), + status: new Command({ + description: "Changes the bot's status.", + permission: Command.PERMISSIONS.BOT_SUPPORT, + async run($: CommonLibrary): Promise { + $.channel.send("Setting status to `online`..."); + }, + any: new Command({ + description: `Select a status to set to. Available statuses: \`[${statuses.join( + ", " + )}]\`.`, + async run($: CommonLibrary): Promise { + if (!statuses.includes($.args[0])) + return $.channel.send("That status doesn't exist!"); + else { + $.client.user?.setStatus($.args[0]); + $.channel.send(`Setting status to \`${$.args[0]}\`...`); + } + } + }) + }), + purge: new Command({ + description: "Purges bot messages.", + permission: Command.PERMISSIONS.BOT_SUPPORT, + async run($: CommonLibrary): Promise { + if ($.message.channel instanceof discord.DMChannel) { + return; + } + $.message.delete(); + const msgs = await $.channel.messages.fetch({ + limit: 100 + }); + const travMessages = msgs.filter( + (m) => m.author.id === $.client.user?.id + ); + + await $.message.channel + .send(`Found ${travMessages.size} messages to delete.`) + .then((m) => + m.delete({ + timeout: 5000 + }) + ); + await $.message.channel.bulkDelete(travMessages); + } + }), + clear: new Command({ + description: "Clears a given amount of messages.", + usage: "", + run: "A number was not provided.", + number: new Command({ + description: "Amount of messages to delete.", + async run($: CommonLibrary): Promise { + $.message.delete(); + const fetched = await $.channel.messages.fetch({ + limit: $.args[0] + }); + $.channel + /// @ts-ignore + .bulkDelete(fetched) + .catch((error: any) => + $.channel.send(`Error: ${error}`) + ); + } + }) + }), + eval: new Command({ + description: "Evaluate code.", + usage: "", + permission: Command.PERMISSIONS.BOT_OWNER, + async run($: CommonLibrary): Promise { + try { + const code = $.args.join(" "); + let evaled = eval(code); + + if (typeof evaled !== "string") + evaled = require("util").inspect(evaled); + $.channel.send(clean(evaled), {code: "x1"}); + } catch (err) { + $.channel.send(`\`ERROR\` \`\`\`x1\n${clean(err)}\n\`\`\``); + } + } + }), + nick: new Command({ + description: "Change the bot's nickname.", + permission: Command.PERMISSIONS.BOT_SUPPORT, + async run($: CommonLibrary): Promise { + const nickName = $.args.join(" "); + const trav = $.guild?.members.cache.find( + (member) => member.id === $.client.user?.id + ); + await trav?.setNickname(nickName); + if ( + botHasPermission($.guild, Permissions.FLAGS.MANAGE_MESSAGES) + ) + $.message.delete({timeout: 5000}).catch($.handler.bind($)); + $.channel + .send(`Nickname set to \`${nickName}\``) + .then((m) => m.delete({timeout: 5000})); + } + }), + guilds: new Command({ + description: "Shows a list of all guilds the bot is a member of.", + permission: Command.PERMISSIONS.BOT_SUPPORT, + async run($: CommonLibrary): Promise { + const guildList = $.client.guilds.cache + .array() + .map((e) => e.name); + $.channel.send(guildList); + } + }), + activity: new Command({ + description: "Set the activity of the bot.", + permission: Command.PERMISSIONS.BOT_SUPPORT, + usage: " ", + async run($: CommonLibrary): Promise { + $.client.user?.setActivity(".help", { + type: "LISTENING" + }); + $.channel.send("Activity set to default."); + }, + any: new Command({ + description: `Select an activity type to set. Available levels: \`[${activities.join( + ", " + )}]\``, + async run($: CommonLibrary): Promise { + const type = $.args[0]; + + if (activities.includes(type)) { + $.client.user?.setActivity($.args.slice(1).join(" "), { + type: $.args[0].toUpperCase() + }); + $.channel.send( + `Set activity to \`${$.args[0].toUpperCase()}\` \`${$.args + .slice(1) + .join(" ")}\`.` + ); + } else + $.channel.send( + `Couldn't find an activity type named \`${type}\`! The available types are \`[${activities.join( + ", " + )}]\`.` + ); + } + }) + }) + } +}); diff --git a/src/commands/fun/8ball.ts b/src/commands/fun/8ball.ts index ebd2a35..c43ac31 100644 --- a/src/commands/fun/8ball.ts +++ b/src/commands/fun/8ball.ts @@ -1,38 +1,39 @@ -import Command from '../../core/command'; -import { CommonLibrary } from '../../core/lib'; +import Command from "../../core/command"; +import {CommonLibrary} from "../../core/lib"; + const responses = [ - 'Most likely,', - 'It is certain,', - 'It is decidedly so,', - 'Without a doubt,', - 'Definitely,', - 'You may rely on it,', - 'As I see it, yes,', - 'Outlook good,', - 'Yes,', - 'Signs point to yes,', - 'Reply hazy, try again,', - 'Ask again later,', - 'Better not tell you now,', - 'Cannot predict now,', - 'Concentrate and ask again,', - "Don't count on it,", - 'My reply is no,', - 'My sources say no,', - 'Outlook not so good,', - 'Very doubtful,', + "Most likely,", + "It is certain,", + "It is decidedly so,", + "Without a doubt,", + "Definitely,", + "You may rely on it,", + "As I see it, yes,", + "Outlook good,", + "Yes,", + "Signs point to yes,", + "Reply hazy, try again,", + "Ask again later,", + "Better not tell you now,", + "Cannot predict now,", + "Concentrate and ask again,", + "Don't count on it,", + "My reply is no,", + "My sources say no,", + "Outlook not so good,", + "Very doubtful," ]; export default new Command({ - description: 'Answers your question in an 8-ball manner.', - endpoint: false, - usage: '', - run: 'Please provide a question.', - any: new Command({ - description: 'Question to ask the 8-ball.', - async run($: CommonLibrary): Promise { - const sender = $.message.author; - $.channel.send($(responses).random() + ` <@${sender.id}>`); - }, - }), + description: "Answers your question in an 8-ball manner.", + endpoint: false, + usage: "", + run: "Please provide a question.", + any: new Command({ + description: "Question to ask the 8-ball.", + async run($: CommonLibrary): Promise { + const sender = $.message.author; + $.channel.send($(responses).random() + ` <@${sender.id}>`); + } + }) }); diff --git a/src/commands/fun/cookie.ts b/src/commands/fun/cookie.ts index 15f75e8..f9be54f 100644 --- a/src/commands/fun/cookie.ts +++ b/src/commands/fun/cookie.ts @@ -1,50 +1,54 @@ -import Command from '../../core/command'; -import { CommonLibrary } from '../../core/lib'; - -export default new Command({ - description: "Gives specified user a cookie.", - usage: "['all'/@user]", - run: ":cookie: Here's a cookie!", - any: new Command({ - async run($: CommonLibrary): Promise { - if ($.args[0] == "all") return $.channel.send(`${$.author} gave everybody a cookie!`) - } - }), - user: new Command({ - description: "User to give cookie to.", - async run($: CommonLibrary): Promise { - const sender = $.author; - const mention = $.message.mentions.users.first(); - if (!mention) return; - const cookies = [ - `has given <@${mention.id}> a chocolate chip cookie!`, - `has given <@${mention.id}> a soft homemade oatmeal cookie!`, - `has given <@${mention.id}> a plain, dry, old cookie. It was the last one in the bag. Gross.`, - `gives <@${mention.id}> a sugar cookie. What, no frosting and sprinkles? 0/10 would not touch.`, - `gives <@${mention.id}> a chocolate chip cookie. Oh wait, those are raisins. Bleck!`, - `gives <@${mention.id}> an enormous cookie. Poking it gives you more cookies. Weird.`, - `gives <@${mention.id}> a fortune cookie. It reads "Why aren't you working on any projects?"`, - `gives <@${mention.id}> a fortune cookie. It reads "Give that special someone a compliment"`, - `gives <@${mention.id}> a fortune cookie. It reads "Take a risk!"`, - `gives <@${mention.id}> a fortune cookie. It reads "Go outside."`, - `gives <@${mention.id}> a fortune cookie. It reads "Don't forget to eat your veggies!"`, - `gives <@${mention.id}> a fortune cookie. It reads "Do you even lift?"`, - `gives <@${mention.id}> a fortune cookie. It reads "m808 pls"`, - `gives <@${mention.id}> a fortune cookie. It reads "If you move your hips, you'll get all the ladies."`, - `gives <@${mention.id}> a fortune cookie. It reads "I love you."`, - `gives <@${mention.id}> a Golden Cookie. You can't eat it because it is made of gold. Dammit.`, - `gives <@${mention.id}> an Oreo cookie with a glass of milk!`, - `gives <@${mention.id}> a rainbow cookie made with love :heart:`, - `gives <@${mention.id}> an old cookie that was left out in the rain, it's moldy.`, - `bakes <@${mention.id}> fresh cookies, it smells amazing.`, - ]; - if (mention.id == sender.id) - return $.channel.send("You can't give yourself cookies!"); - $.channel.send( - `:cookie: <@${sender.id}> ` + - cookies[Math.floor(Math.random() * cookies.length)], - ); - - } - }) -}) \ No newline at end of file +import Command from "../../core/command"; +import {CommonLibrary} from "../../core/lib"; + +export default new Command({ + description: "Gives specified user a cookie.", + usage: "['all'/@user]", + run: ":cookie: Here's a cookie!", + any: new Command({ + async run($: CommonLibrary): Promise { + if ($.args[0] == "all") + return $.channel.send(`${$.author} gave everybody a cookie!`); + } + }), + user: new Command({ + description: "User to give cookie to.", + async run($: CommonLibrary): Promise { + const sender = $.author; + const mention = $.message.mentions.users.first(); + + if (!mention) return; + + const cookies = [ + `has given <@${mention.id}> a chocolate chip cookie!`, + `has given <@${mention.id}> a soft homemade oatmeal cookie!`, + `has given <@${mention.id}> a plain, dry, old cookie. It was the last one in the bag. Gross.`, + `gives <@${mention.id}> a sugar cookie. What, no frosting and sprinkles? 0/10 would not touch.`, + `gives <@${mention.id}> a chocolate chip cookie. Oh wait, those are raisins. Bleck!`, + `gives <@${mention.id}> an enormous cookie. Poking it gives you more cookies. Weird.`, + `gives <@${mention.id}> a fortune cookie. It reads "Why aren't you working on any projects?"`, + `gives <@${mention.id}> a fortune cookie. It reads "Give that special someone a compliment"`, + `gives <@${mention.id}> a fortune cookie. It reads "Take a risk!"`, + `gives <@${mention.id}> a fortune cookie. It reads "Go outside."`, + `gives <@${mention.id}> a fortune cookie. It reads "Don't forget to eat your veggies!"`, + `gives <@${mention.id}> a fortune cookie. It reads "Do you even lift?"`, + `gives <@${mention.id}> a fortune cookie. It reads "m808 pls"`, + `gives <@${mention.id}> a fortune cookie. It reads "If you move your hips, you'll get all the ladies."`, + `gives <@${mention.id}> a fortune cookie. It reads "I love you."`, + `gives <@${mention.id}> a Golden Cookie. You can't eat it because it is made of gold. Dammit.`, + `gives <@${mention.id}> an Oreo cookie with a glass of milk!`, + `gives <@${mention.id}> a rainbow cookie made with love :heart:`, + `gives <@${mention.id}> an old cookie that was left out in the rain, it's moldy.`, + `bakes <@${mention.id}> fresh cookies, it smells amazing.` + ]; + + if (mention.id == sender.id) + return $.channel.send("You can't give yourself cookies!"); + + $.channel.send( + `:cookie: <@${sender.id}> ` + + cookies[Math.floor(Math.random() * cookies.length)] + ); + } + }) +}); diff --git a/src/commands/fun/eco.ts b/src/commands/fun/eco.ts index ceab224..9e5978c 100644 --- a/src/commands/fun/eco.ts +++ b/src/commands/fun/eco.ts @@ -1,35 +1,36 @@ -import Command from '../../core/command'; -import { isAuthorized, getMoneyEmbed } from './subcommands/eco-utils'; -import { DailyCommand, PayCommand, GuildCommand } from './subcommands/eco-core'; -import { BuyCommand, ShopCommand } from './subcommands/eco-shop'; +import Command from "../../core/command"; +import {isAuthorized, getMoneyEmbed} from "./subcommands/eco-utils"; +import {DailyCommand, PayCommand, GuildCommand} from "./subcommands/eco-core"; +import {BuyCommand, ShopCommand} from "./subcommands/eco-shop"; export default new Command({ - description: 'Economy command for Monika.', - - async run({ guild, channel, author }) { - if (isAuthorized(guild, channel)) channel.send(getMoneyEmbed(author)); - }, - subcommands: { - daily: DailyCommand, - pay: PayCommand, - guild: GuildCommand, - buy: BuyCommand, - shop: ShopCommand, - }, - user: new Command({ - description: - 'See how much money someone else has by using their user ID or pinging them.', - async run({ guild, channel, args }) { - if (isAuthorized(guild, channel)) channel.send(getMoneyEmbed(args[0])); + description: "Economy command for Monika.", + async run({guild, channel, author}) { + if (isAuthorized(guild, channel)) channel.send(getMoneyEmbed(author)); }, - }), - any: new Command({ - description: 'See how much money someone else has by using their username.', - async run({ guild, channel, args, callMemberByUsername, message }) { - if (isAuthorized(guild, channel)) - callMemberByUsername(message, args.join(' '), (member) => { - channel.send(getMoneyEmbed(member.user)); - }); + subcommands: { + daily: DailyCommand, + pay: PayCommand, + guild: GuildCommand, + buy: BuyCommand, + shop: ShopCommand }, - }), + user: new Command({ + description: + "See how much money someone else has by using their user ID or pinging them.", + async run({guild, channel, args}) { + if (isAuthorized(guild, channel)) + channel.send(getMoneyEmbed(args[0])); + } + }), + any: new Command({ + description: + "See how much money someone else has by using their username.", + async run({guild, channel, args, callMemberByUsername, message}) { + if (isAuthorized(guild, channel)) + callMemberByUsername(message, args.join(" "), (member) => { + channel.send(getMoneyEmbed(member.user)); + }); + } + }) }); diff --git a/src/commands/fun/neko.ts b/src/commands/fun/neko.ts index e079581..73901fa 100644 --- a/src/commands/fun/neko.ts +++ b/src/commands/fun/neko.ts @@ -1,26 +1,31 @@ -/// @ts-nocheck -import { URL } from 'url' -import FileManager from '../../core/storage'; -import Command from '../../core/command'; -import { CommonLibrary, getContent } from '../../core/lib'; - -const endpoints = FileManager.read('endpoints'); - -export default new Command({ - description: 'Provides you with a random image with the selected argument.', - async run($: CommonLibrary): Promise { - console.log(endpoints.sfw) - $.channel.send(`Please provide an image type. Available arguments:\n\`[${Object.keys(endpoints.sfw).join(', ')}]\`.`) - }, - any: new Command({ - description: "Image type to send.", - async run($: CommonLibrary): Promise { - if (!($.args[0] in endpoints.sfw)) - return $.channel.send("Couldn't find that endpoint!"); - let baseURL = 'https://nekos.life/api/v2'; - let url = new URL(`${baseURL}${endpoints.sfw[$.args[0]]}`); - const content = await getContent(url.toString()) - $.channel.send(content.url) - }, - }) -}); +/// @ts-nocheck +import {URL} from "url"; +import FileManager from "../../core/storage"; +import Command from "../../core/command"; +import {CommonLibrary, getContent} from "../../core/lib"; + +const endpoints = FileManager.read("endpoints"); + +export default new Command({ + description: "Provides you with a random image with the selected argument.", + async run($: CommonLibrary): Promise { + console.log(endpoints.sfw); + $.channel.send( + `Please provide an image type. Available arguments:\n\`[${Object.keys( + endpoints.sfw + ).join(", ")}]\`.` + ); + }, + any: new Command({ + description: "Image type to send.", + async run($: CommonLibrary): Promise { + if (!($.args[0] in endpoints.sfw)) + return $.channel.send("Couldn't find that endpoint!"); + + let baseURL = "https://nekos.life/api/v2"; + let url = new URL(`${baseURL}${endpoints.sfw[$.args[0]]}`); + const content = await getContent(url.toString()); + $.channel.send(content.url); + } + }) +}); diff --git a/src/commands/fun/ok.ts b/src/commands/fun/ok.ts index 8fa0263..882021c 100644 --- a/src/commands/fun/ok.ts +++ b/src/commands/fun/ok.ts @@ -1,68 +1,69 @@ -import Command from '../../core/command'; -import { CommonLibrary } from '../../core/lib'; - -export default new Command({ - description: "Sends random ok message.", - async run($: CommonLibrary): Promise { - const responses = [ - 'boomer', - 'zoomer', - 'the last generationer', - 'the last airbender', - 'fire nation', - 'fire lord', - 'guy fieri', - 'guy from final fight', - 'haggar', - 'Max Thunder from Streets of Rage 2', - 'police guy who fires bazookas', - 'Mr. X', - 'Leon Its Wrong If Its Not Ada Wong S. Kennedy.', - 'Jill', - 'JFK', - 'george bush', - 'obama', - 'the world', - 'copy of scott pilgrim vs the world', - 'ok', - 'ko', - 'Hot Daddy Venomous', - 'big daddy', - 'John Cena', - 'BubbleSpurJarJarBinks', - 'T-Series', - 'pewdiepie', - 'markiplier', - 'jacksepticeye', - 'vanossgaming', - 'miniladd', - 'Traves', - 'Wilbur Soot', - 'sootrhianna', - 'person with tiny ears', - 'anti-rabbit', - 'homo sapiens', - 'homo', - 'cute kitty', - 'ugly kitty', - 'sadness', - 'doomer', - 'gloomer', - 'bloomer', - 'edgelord', - 'weeb', - "m'lady", - 'Mr. Crabs', - 'hand', - 'lahoma', - 'big man', - 'fox', - 'pear', - 'cat', - 'large man', - ]; - $.channel.send( - 'ok ' + responses[Math.floor(Math.random() * responses.length)], - ); - } -}) \ No newline at end of file +import Command from "../../core/command"; +import {CommonLibrary} from "../../core/lib"; + +export default new Command({ + description: "Sends random ok message.", + async run($: CommonLibrary): Promise { + const responses = [ + "boomer", + "zoomer", + "the last generationer", + "the last airbender", + "fire nation", + "fire lord", + "guy fieri", + "guy from final fight", + "haggar", + "Max Thunder from Streets of Rage 2", + "police guy who fires bazookas", + "Mr. X", + "Leon Its Wrong If Its Not Ada Wong S. Kennedy.", + "Jill", + "JFK", + "george bush", + "obama", + "the world", + "copy of scott pilgrim vs the world", + "ok", + "ko", + "Hot Daddy Venomous", + "big daddy", + "John Cena", + "BubbleSpurJarJarBinks", + "T-Series", + "pewdiepie", + "markiplier", + "jacksepticeye", + "vanossgaming", + "miniladd", + "Traves", + "Wilbur Soot", + "sootrhianna", + "person with tiny ears", + "anti-rabbit", + "homo sapiens", + "homo", + "cute kitty", + "ugly kitty", + "sadness", + "doomer", + "gloomer", + "bloomer", + "edgelord", + "weeb", + "m'lady", + "Mr. Crabs", + "hand", + "lahoma", + "big man", + "fox", + "pear", + "cat", + "large man" + ]; + + $.channel.send( + "ok " + responses[Math.floor(Math.random() * responses.length)] + ); + } +}); diff --git a/src/commands/fun/owoify.ts b/src/commands/fun/owoify.ts index 33be968..6c02084 100644 --- a/src/commands/fun/owoify.ts +++ b/src/commands/fun/owoify.ts @@ -1,12 +1,14 @@ -/// @ts-nocheck -import Command from '../../core/command'; -import { CommonLibrary, getContent } from '../../core/lib'; - -export default new Command({ - description: 'OwO-ifies the input.', - async run($: CommonLibrary): Promise { - let url = new URL(`https://nekos.life/api/v2/owoify?text=${$.args.join(' ')}`); - const content = await getContent(url.toString()); - $.channel.send(content.owo); - }, -}); +/// @ts-nocheck +import Command from "../../core/command"; +import {CommonLibrary, getContent} from "../../core/lib"; + +export default new Command({ + description: "OwO-ifies the input.", + async run($: CommonLibrary): Promise { + let url = new URL( + `https://nekos.life/api/v2/owoify?text=${$.args.join(" ")}` + ); + const content = await getContent(url.toString()); + $.channel.send(content.owo); + } +}); diff --git a/src/commands/fun/poll.ts b/src/commands/fun/poll.ts index 0e46f90..e104798 100644 --- a/src/commands/fun/poll.ts +++ b/src/commands/fun/poll.ts @@ -1,28 +1,28 @@ -import { MessageEmbed } from 'discord.js'; -import Command from '../../core/command'; -import { CommonLibrary } from '../../core/lib'; +import {MessageEmbed} from "discord.js"; +import Command from "../../core/command"; +import {CommonLibrary} from "../../core/lib"; export default new Command({ - description: 'Create a poll.', - usage: '', - run: 'Please provide a question.', - any: new Command({ - description: 'Question for the poll.', - async run($: CommonLibrary): Promise { - const embed = new MessageEmbed() - .setAuthor( - `Poll created by ${$.message.author.username}`, - $.message.guild?.iconURL({ dynamic: true }) ?? undefined, - ) - .setColor(0xffffff) - .setFooter('React to vote.') - .setDescription($.args.join(' ')); - const msg = await $.channel.send(embed); - await msg.react('✅'); - await msg.react('⛔'); - $.message.delete({ - timeout: 1000, - }); - }, - }), + description: "Create a poll.", + usage: "", + run: "Please provide a question.", + any: new Command({ + description: "Question for the poll.", + async run($: CommonLibrary): Promise { + const embed = new MessageEmbed() + .setAuthor( + `Poll created by ${$.message.author.username}`, + $.message.guild?.iconURL({dynamic: true}) ?? undefined + ) + .setColor(0xffffff) + .setFooter("React to vote.") + .setDescription($.args.join(" ")); + const msg = await $.channel.send(embed); + await msg.react("✅"); + await msg.react("⛔"); + $.message.delete({ + timeout: 1000 + }); + } + }) }); diff --git a/src/commands/fun/subcommands/eco-core.ts b/src/commands/fun/subcommands/eco-core.ts index e2c8c5c..5bf937a 100644 --- a/src/commands/fun/subcommands/eco-core.ts +++ b/src/commands/fun/subcommands/eco-core.ts @@ -1,180 +1,182 @@ -import Command from '../../../core/command'; -import $ from '../../../core/lib'; -import { Storage } from '../../../core/structures'; -import { isAuthorized, getMoneyEmbed, getSendEmbed } from './eco-utils'; +import Command from "../../../core/command"; +import $ from "../../../core/lib"; +import {Storage} from "../../../core/structures"; +import {isAuthorized, getMoneyEmbed, getSendEmbed} from "./eco-utils"; export const DailyCommand = new Command({ - description: - 'Pick up your daily Mons. The cooldown is per user and every 22 hours to allow for some leeway.', - async run({ author, channel, guild }) { - if (isAuthorized(guild, channel)) { - const user = Storage.getUser(author.id); - const now = Date.now(); + description: + "Pick up your daily Mons. The cooldown is per user and every 22 hours to allow for some leeway.", + async run({author, channel, guild}) { + if (isAuthorized(guild, channel)) { + const user = Storage.getUser(author.id); + const now = Date.now(); - if (now - user.lastReceived >= 79200000) { - user.money++; - user.lastReceived = now; - Storage.save(); - channel.send({ - embed: { - title: 'Daily Reward', - description: 'You received 1 Mon!', - color: 0xf1c40f, - }, - }); - } else - channel.send({ - embed: { - title: 'Daily Reward', - description: `It's too soon to pick up your daily credits. You have about ${( - (user.lastReceived + 79200000 - now) / - 3600000 - ).toFixed(1)} hours to go.`, - color: 0xf1c40f, - }, - }); + if (now - user.lastReceived >= 79200000) { + user.money++; + user.lastReceived = now; + Storage.save(); + channel.send({ + embed: { + title: "Daily Reward", + description: "You received 1 Mon!", + color: 0xf1c40f + } + }); + } else + channel.send({ + embed: { + title: "Daily Reward", + description: `It's too soon to pick up your daily credits. You have about ${( + (user.lastReceived + 79200000 - now) / + 3600000 + ).toFixed(1)} hours to go.`, + color: 0xf1c40f + } + }); + } } - }, }); export const GuildCommand = new Command({ - description: 'See the richest players.', - async run({ guild, channel, client }) { - if (isAuthorized(guild, channel)) { - const users = Storage.users; - const ids = Object.keys(users); - ids.sort((a, b) => users[b].money - users[a].money); - const fields = []; + description: "See the richest players.", + async run({guild, channel, client}) { + if (isAuthorized(guild, channel)) { + const users = Storage.users; + const ids = Object.keys(users); + ids.sort((a, b) => users[b].money - users[a].money); + const fields = []; - for (let i = 0, limit = Math.min(10, ids.length); i < limit; i++) { - const id = ids[i]; - const user = await client.users.fetch(id); + for (let i = 0, limit = Math.min(10, ids.length); i < limit; i++) { + const id = ids[i]; + const user = await client.users.fetch(id); - fields.push({ - name: `#${i + 1}. ${user.username}#${user.discriminator}`, - value: $(users[id].money).pluralise('credit', 's'), - }); - } + fields.push({ + name: `#${i + 1}. ${user.username}#${user.discriminator}`, + value: $(users[id].money).pluralise("credit", "s") + }); + } - channel.send({ - embed: { - title: 'Top 10 Richest Players', - color: '#ffff00', - fields: fields, - }, - }); + channel.send({ + embed: { + title: "Top 10 Richest Players", + color: "#ffff00", + fields: fields + } + }); + } } - }, }); export const PayCommand = new Command({ - description: 'Send money to someone.', - usage: ' ', - run: 'Who are you sending this money to?', - user: new Command({ - run: "You need to enter an amount you're sending!", - number: new Command({ - async run({ args, author, channel, guild }): Promise { - if (isAuthorized(guild, channel)) { - const amount = Math.floor(args[1]); - const sender = Storage.getUser(author.id); - const target = args[0]; - const receiver = Storage.getUser(target.id); + description: "Send money to someone.", + usage: " ", + run: "Who are you sending this money to?", + user: new Command({ + run: "You need to enter an amount you're sending!", + number: new Command({ + async run({args, author, channel, guild}): Promise { + if (isAuthorized(guild, channel)) { + const amount = Math.floor(args[1]); + const sender = Storage.getUser(author.id); + const target = args[0]; + const receiver = Storage.getUser(target.id); - if (amount <= 0) - return channel.send('You must send at least one Mon!'); - else if (sender.money < amount) - return channel.send( - "You don't have enough Mons for that.", - getMoneyEmbed(author), - ); - else if (target.id === author.id) - return channel.send("You can't send Mons to yourself!"); - else if (target.bot && process.argv[2] !== 'dev') - return channel.send("You can't send Mons to a bot!"); + if (amount <= 0) + return channel.send("You must send at least one Mon!"); + else if (sender.money < amount) + return channel.send( + "You don't have enough Mons for that.", + getMoneyEmbed(author) + ); + else if (target.id === author.id) + return channel.send("You can't send Mons to yourself!"); + else if (target.bot && process.argv[2] !== "dev") + return channel.send("You can't send Mons to a bot!"); - sender.money -= amount; - receiver.money += amount; - Storage.save(); - return channel.send(getSendEmbed(author, target, amount)); - } - }, + sender.money -= amount; + receiver.money += amount; + Storage.save(); + return channel.send(getSendEmbed(author, target, amount)); + } + } + }) }), - }), - number: new Command({ - run: 'You must use the format `money send `!', - }), - any: new Command({ - async run({ args, author, channel, guild, prompt }) { - if (isAuthorized(guild, channel)) { - const last = args.pop(); + number: new Command({ + run: "You must use the format `money send `!" + }), + any: new Command({ + async run({args, author, channel, guild, prompt}) { + if (isAuthorized(guild, channel)) { + const last = args.pop(); - if (!/\d+/g.test(last) && args.length === 0) - return channel.send("You need to enter an amount you're sending!"); + if (!/\d+/g.test(last) && args.length === 0) + return channel.send( + "You need to enter an amount you're sending!" + ); - const amount = Math.floor(last); - const sender = Storage.getUser(author.id); + const amount = Math.floor(last); + const sender = Storage.getUser(author.id); - if (amount <= 0) - return channel.send('You must send at least one credit!'); - else if (sender.money < amount) - return channel.send( - "You don't have enough money to do that!", - getMoneyEmbed(author), - ); - else if (!guild) - return channel.send( - 'You have to use this in a server if you want to send money with a username!', - ); + if (amount <= 0) + return channel.send("You must send at least one credit!"); + else if (sender.money < amount) + return channel.send( + "You don't have enough money to do that!", + getMoneyEmbed(author) + ); + else if (!guild) + return channel.send( + "You have to use this in a server if you want to send money with a username!" + ); - const username = args.join(' '); - const member = ( - await guild.members.fetch({ - query: username, - limit: 1, - }) - ).first(); + const username = args.join(" "); + const member = ( + await guild.members.fetch({ + query: username, + limit: 1 + }) + ).first(); - if (!member) - return channel.send( - `Couldn't find a user by the name of \`${username}\`! If you want to send money to someone in a different server, you have to use their user ID!`, - ); - else if (member.user.id === author.id) - return channel.send("You can't send money to yourself!"); - else if (member.user.bot && process.argv[2] !== 'dev') - return channel.send("You can't send money to a bot!"); + if (!member) + return channel.send( + `Couldn't find a user by the name of \`${username}\`! If you want to send money to someone in a different server, you have to use their user ID!` + ); + else if (member.user.id === author.id) + return channel.send("You can't send money to yourself!"); + else if (member.user.bot && process.argv[2] !== "dev") + return channel.send("You can't send money to a bot!"); - const target = member.user; + const target = member.user; - return prompt( - await channel.send( - `Are you sure you want to send ${$(amount).pluralise( - 'credit', - 's', - )} to this person?\n*(This message will automatically be deleted after 10 seconds.)*`, - { - embed: { - color: '#ffff00', - author: { - name: `${target.username}#${target.discriminator}`, - icon_url: target.displayAvatarURL({ - format: 'png', - dynamic: true, - }), - }, - }, - }, - ), - author.id, - () => { - const receiver = Storage.getUser(target.id); - sender.money -= amount; - receiver.money += amount; - Storage.save(); - channel.send(getSendEmbed(author, target, amount)); - }, - ); - } - }, - }), + return prompt( + await channel.send( + `Are you sure you want to send ${$(amount).pluralise( + "credit", + "s" + )} to this person?\n*(This message will automatically be deleted after 10 seconds.)*`, + { + embed: { + color: "#ffff00", + author: { + name: `${target.username}#${target.discriminator}`, + icon_url: target.displayAvatarURL({ + format: "png", + dynamic: true + }) + } + } + } + ), + author.id, + () => { + const receiver = Storage.getUser(target.id); + sender.money -= amount; + receiver.money += amount; + Storage.save(); + channel.send(getSendEmbed(author, target, amount)); + } + ); + } + } + }) }); diff --git a/src/commands/fun/subcommands/eco-shop-items.ts b/src/commands/fun/subcommands/eco-shop-items.ts index b7531cd..107959d 100644 --- a/src/commands/fun/subcommands/eco-shop-items.ts +++ b/src/commands/fun/subcommands/eco-shop-items.ts @@ -1,73 +1,73 @@ -import { Message } from 'discord.js'; -import $ from '../../../core/lib'; +import {Message} from "discord.js"; +import $ from "../../../core/lib"; export interface ShopItem { - cost: number; - title: string; - description: string; - usage: string; - run(message: Message, cost: number, amount: number): void; + cost: number; + title: string; + description: string; + usage: string; + run(message: Message, cost: number, amount: number): void; } export const ShopItems: ShopItem[] = [ - { - cost: 1, - title: 'Hug', - description: 'Hug Monika.', - usage: 'hug', - run(message, cost) { - message.channel.send( - `Transaction of ${cost} Mon completed successfully. <@394808963356688394>`, - ); + { + cost: 1, + title: "Hug", + description: "Hug Monika.", + usage: "hug", + run(message, cost) { + message.channel.send( + `Transaction of ${cost} Mon completed successfully. <@394808963356688394>` + ); + } }, - }, - { - cost: 2, - title: 'Handholding', - description: "Hold Monika's hand.", - usage: 'handhold', - run(message, cost) { - message.channel.send( - `Transaction of ${cost} Mons completed successfully. <@394808963356688394>`, - ); + { + cost: 2, + title: "Handholding", + description: "Hold Monika's hand.", + usage: "handhold", + run(message, cost) { + message.channel.send( + `Transaction of ${cost} Mons completed successfully. <@394808963356688394>` + ); + } }, - }, - { - cost: 1, - title: 'Cute', - description: 'Calls Monika cute.', - usage: 'cute', - run(message) { - message.channel.send('<:MoniCheeseBlushRed:637513137083383826>'); + { + cost: 1, + title: "Cute", + description: "Calls Monika cute.", + usage: "cute", + run(message) { + message.channel.send("<:MoniCheeseBlushRed:637513137083383826>"); + } }, - }, - { - cost: 3, - title: 'Laser Bridge', - description: 'Buys what is technically a laser bridge.', - usage: 'laser bridge', - run(message) { - message.channel.send($(lines).random(), { - files: [ - { - attachment: - 'https://raw.githubusercontent.com/keanuplayz/TravBot/dev/assets/TheUltimateLaser.gif', - }, - ], - }); - }, - }, + { + cost: 3, + title: "Laser Bridge", + description: "Buys what is technically a laser bridge.", + usage: "laser bridge", + run(message) { + message.channel.send($(lines).random(), { + files: [ + { + attachment: + "https://raw.githubusercontent.com/keanuplayz/TravBot/dev/assets/TheUltimateLaser.gif" + } + ] + }); + } + } ]; const lines = [ - "It's technically a laser bridge. No refunds.", - 'You want a laser bridge? You got one!', - "Now what'd they say about building bridges... Oh wait, looks like I nuked the planet again. Whoops!", - 'I saw this redhead the other day who was so excited to buy what I was selling. Needless to say, she was not very happy with me afterward.', - "Sorry, but you'll have to wait until the Laser Bridge Builder leaves early access.", - 'Thank you for your purchase! For you see, this is the legendary laser of obliteration that has been defended and preserved for countless generations!', - 'They say that a certain troll dwells under this laser bridge, waiting for an unlucky person to fall for th- I mean- Thank you for your purchase!', - "Buy?! Hah! How about our new rental service for just under $9.99 a month? But wait, there's more! For just $99.99, you can rent this laser bridge for an entire year and save 16.67% as opposed to renting it monthly!", - 'Good choice. Owning a laser bridge is the penultimate experience that all true seekers strive for!', - 'I can already imagine the reviews...\n"9/10 needs more lasers"', + "It's technically a laser bridge. No refunds.", + "You want a laser bridge? You got one!", + "Now what'd they say about building bridges... Oh wait, looks like I nuked the planet again. Whoops!", + "I saw this redhead the other day who was so excited to buy what I was selling. Needless to say, she was not very happy with me afterward.", + "Sorry, but you'll have to wait until the Laser Bridge Builder leaves early access.", + "Thank you for your purchase! For you see, this is the legendary laser of obliteration that has been defended and preserved for countless generations!", + "They say that a certain troll dwells under this laser bridge, waiting for an unlucky person to fall for th- I mean- Thank you for your purchase!", + "Buy?! Hah! How about our new rental service for just under $9.99 a month? But wait, there's more! For just $99.99, you can rent this laser bridge for an entire year and save 16.67% as opposed to renting it monthly!", + "Good choice. Owning a laser bridge is the penultimate experience that all true seekers strive for!", + 'I can already imagine the reviews...\n"9/10 needs more lasers"' ]; diff --git a/src/commands/fun/subcommands/eco-shop.ts b/src/commands/fun/subcommands/eco-shop.ts index 9c3a5f8..48422a5 100644 --- a/src/commands/fun/subcommands/eco-shop.ts +++ b/src/commands/fun/subcommands/eco-shop.ts @@ -1,100 +1,99 @@ -import Command from '../../../core/command'; -import $ from '../../../core/lib'; -import { Storage, getPrefix } from '../../../core/structures'; -import { isAuthorized, getMoneyEmbed, getSendEmbed } from './eco-utils'; -import { ShopItems, ShopItem } from './eco-shop-items'; -import { EmbedField } from 'discord.js'; +import Command from "../../../core/command"; +import $ from "../../../core/lib"; +import {Storage, getPrefix} from "../../../core/structures"; +import {isAuthorized} from "./eco-utils"; +import {ShopItems, ShopItem} from "./eco-shop-items"; +import {EmbedField} from "discord.js"; export const ShopCommand = new Command({ - description: 'Displays the list of items you can buy in the shop.', - async run({ guild, channel, author }) { - if (isAuthorized(guild, channel)) { - function getShopEmbed(selection: ShopItem[], title = 'Shop') { - const fields: EmbedField[] = []; + description: "Displays the list of items you can buy in the shop.", + async run({guild, channel, author}) { + if (isAuthorized(guild, channel)) { + function getShopEmbed(selection: ShopItem[], title = "Shop") { + const fields: EmbedField[] = []; - for (const item of selection) - fields.push({ - name: `**${item.title}** (${getPrefix(guild)}eco buy ${ - item.usage - })`, - value: `${item.description} Costs ${$(item.cost).pluralise( - 'Mon', - 's', - )}.`, - inline: false, - }); + for (const item of selection) + fields.push({ + name: `**${item.title}** (${getPrefix(guild)}eco buy ${ + item.usage + })`, + value: `${item.description} Costs ${$( + item.cost + ).pluralise("Mon", "s")}.`, + inline: false + }); - return { - embed: { - color: 0xf1c40f, - title: title, - fields: fields, - footer: { - text: 'Mon Shop | TravBot Services', - }, - }, - }; - } + return { + embed: { + color: 0xf1c40f, + title: title, + fields: fields, + footer: { + text: "Mon Shop | TravBot Services" + } + } + }; + } - // In case there's just one page, omit unnecessary details. - if (ShopItems.length <= 5) channel.send(getShopEmbed(ShopItems)); - else { - const shopPages = $(ShopItems).split(5); - const pageAmount = shopPages.length; - const msg = await channel.send( - getShopEmbed(shopPages[0], `Shop (Page 1 of ${pageAmount})`), - ); + // In case there's just one page, omit unnecessary details. + if (ShopItems.length <= 5) channel.send(getShopEmbed(ShopItems)); + else { + const shopPages = $(ShopItems).split(5); + const pageAmount = shopPages.length; + const msg = await channel.send( + getShopEmbed(shopPages[0], `Shop (Page 1 of ${pageAmount})`) + ); - $.paginate(msg, author.id, pageAmount, (page) => { - msg.edit( - getShopEmbed( - shopPages[page], - `Shop (Page ${page + 1} of ${pageAmount})`, - ), - ); - }); - } + $.paginate(msg, author.id, pageAmount, (page) => { + msg.edit( + getShopEmbed( + shopPages[page], + `Shop (Page ${page + 1} of ${pageAmount})` + ) + ); + }); + } + } } - }, }); export const BuyCommand = new Command({ - description: 'Buys an item from the shop.', - usage: '', - async run({ guild, channel, args, message, author }) { - if (isAuthorized(guild, channel)) { - let found = false; + description: "Buys an item from the shop.", + usage: "", + async run({guild, channel, args, message, author}) { + if (isAuthorized(guild, channel)) { + let found = false; - let amount = 1; // The amount the user is buying. + let amount = 1; // The amount the user is buying. - // For now, no shop items support being bought multiple times. Uncomment these 2 lines when it's supported/needed. - //if (/\d+/g.test(args[args.length - 1])) - //amount = parseInt(args.pop()); + // For now, no shop items support being bought multiple times. Uncomment these 2 lines when it's supported/needed. + //if (/\d+/g.test(args[args.length - 1])) + //amount = parseInt(args.pop()); - let requested = args.join(' '); // The item the user is buying. + let requested = args.join(" "); // The item the user is buying. - for (let item of ShopItems) { - if (item.usage === requested) { - const user = Storage.getUser(author.id); - const cost = item.cost * amount; + for (let item of ShopItems) { + if (item.usage === requested) { + const user = Storage.getUser(author.id); + const cost = item.cost * amount; - if (cost > user.money) { - channel.send('Not enough Mons!'); - } else { - user.money -= cost; - Storage.save(); - item.run(message, cost, amount); - } + if (cost > user.money) { + channel.send("Not enough Mons!"); + } else { + user.money -= cost; + Storage.save(); + item.run(message, cost, amount); + } - found = true; - break; + found = true; + break; + } + } + + if (!found) + channel.send( + `There's no item in the shop that goes by \`${requested}\`!` + ); } - } - - if (!found) - channel.send( - `There's no item in the shop that goes by \`${requested}\`!`, - ); } - }, }); diff --git a/src/commands/fun/subcommands/eco-utils.ts b/src/commands/fun/subcommands/eco-utils.ts index 3af6971..fb58435 100644 --- a/src/commands/fun/subcommands/eco-utils.ts +++ b/src/commands/fun/subcommands/eco-utils.ts @@ -1,81 +1,87 @@ -import $ from '../../../core/lib'; -import { Storage } from '../../../core/structures'; -import { User, Guild, TextChannel, DMChannel, NewsChannel } from 'discord.js'; +import $ from "../../../core/lib"; +import {Storage} from "../../../core/structures"; +import {User, Guild, TextChannel, DMChannel, NewsChannel} from "discord.js"; export function getMoneyEmbed(user: User): object { - const profile = Storage.getUser(user.id); + const profile = Storage.getUser(user.id); - return { - embed: { - color: 0xffff00, - author: { - name: user.username, - icon_url: user.displayAvatarURL({ - format: 'png', - dynamic: true, - }), - }, - fields: [ - { - name: 'Balance', - value: $(profile.money).pluralise('credit', 's'), - }, - ], - }, - }; + return { + embed: { + color: 0xffff00, + author: { + name: user.username, + icon_url: user.displayAvatarURL({ + format: "png", + dynamic: true + }) + }, + fields: [ + { + name: "Balance", + value: $(profile.money).pluralise("credit", "s") + } + ] + } + }; } export function getSendEmbed( - sender: User, - receiver: User, - amount: number, + sender: User, + receiver: User, + amount: number ): object { - return { - embed: { - color: 0xffff00, - author: { - name: sender.username, - icon_url: sender.displayAvatarURL({ - format: 'png', - dynamic: true, - }), - }, - title: 'Transaction', - description: `${sender.toString()} has sent ${$(amount).pluralise( - 'credit', - 's', - )} to ${receiver.toString()}!`, - fields: [ - { - name: `Sender: ${sender.username}#${sender.discriminator}`, - value: $(Storage.getUser(sender.id).money).pluralise('credit', 's'), - }, - { - name: `Receiver: ${receiver.username}#${receiver.discriminator}`, - value: $(Storage.getUser(receiver.id).money).pluralise('credit', 's'), - }, - ], - footer: { - text: receiver.username, - icon_url: receiver.displayAvatarURL({ - format: 'png', - dynamic: true, - }), - }, - }, - }; + return { + embed: { + color: 0xffff00, + author: { + name: sender.username, + icon_url: sender.displayAvatarURL({ + format: "png", + dynamic: true + }) + }, + title: "Transaction", + description: `${sender.toString()} has sent ${$(amount).pluralise( + "credit", + "s" + )} to ${receiver.toString()}!`, + fields: [ + { + name: `Sender: ${sender.username}#${sender.discriminator}`, + value: $(Storage.getUser(sender.id).money).pluralise( + "credit", + "s" + ) + }, + { + name: `Receiver: ${receiver.username}#${receiver.discriminator}`, + value: $(Storage.getUser(receiver.id).money).pluralise( + "credit", + "s" + ) + } + ], + footer: { + text: receiver.username, + icon_url: receiver.displayAvatarURL({ + format: "png", + dynamic: true + }) + } + } + }; } export function isAuthorized( - guild: Guild | null, - channel: TextChannel | DMChannel | NewsChannel, + guild: Guild | null, + channel: TextChannel | DMChannel | NewsChannel ): boolean { - if (guild?.id === '637512823676600330' || process.argv[2] === 'dev') - return true; - else { - channel.send( - "Sorry, this command can only be used in Monika's emote server.", - ); - return false; - } + if (guild?.id === "637512823676600330" || process.argv[2] === "dev") + return true; + else { + channel.send( + "Sorry, this command can only be used in Monika's emote server." + ); + return false; + } } diff --git a/src/commands/help.ts b/src/commands/help.ts index 3150df7..0424b10 100644 --- a/src/commands/help.ts +++ b/src/commands/help.ts @@ -1,145 +1,153 @@ -import Command from '../core/command'; -import { CommonLibrary } from '../core/lib'; -import { loadCommands, categories } from '../core/command'; -import { PermissionNames } from '../core/permissions'; +import Command from "../core/command"; +import {CommonLibrary} from "../core/lib"; +import {loadCommands, categories} from "../core/command"; +import {PermissionNames} from "../core/permissions"; export default new Command({ - description: - 'Lists all commands. If a command is specified, their arguments are listed as well.', - usage: '([command, [subcommand/type], ...])', - aliases: ['h'], - async run($: CommonLibrary): Promise { - const commands = await loadCommands(); - let output = `Legend: \`\`, \`[list/of/stuff]\`, \`(optional)\`, \`()\`, \`([optional/list/...])\``; - - for (const [category, headers] of categories) { - output += `\n\n===[ ${category} ]===`; - - for (const header of headers) { - if (header !== 'test') { - const command = commands.get(header); - - if (!command) - return $.warn( - `Command "${header}" of category "${category}" unexpectedly doesn't exist!`, - ); - - output += `\n- \`${header}\`: ${command.description}`; - } - } - } - - $.channel.send(output, { split: true }); - }, - any: new Command({ + description: + "Lists all commands. If a command is specified, their arguments are listed as well.", + usage: "([command, [subcommand/type], ...])", + aliases: ["h"], async run($: CommonLibrary): Promise { - const commands = await loadCommands(); - let header = $.args.shift() as string; - let command = commands.get(header); + const commands = await loadCommands(); + let output = `Legend: \`\`, \`[list/of/stuff]\`, \`(optional)\`, \`()\`, \`([optional/list/...])\``; - if (!command || header === 'test') - return $.channel.send(`No command found by the name \`${header}\`!`); + for (const [category, headers] of categories) { + output += `\n\n===[ ${category} ]===`; - if (command.originalCommandName) header = command.originalCommandName; - else $.warn(`originalCommandName isn't defined for ${header}?!`); + for (const header of headers) { + if (header !== "test") { + const command = commands.get(header); - let permLevel = command.permission ?? Command.PERMISSIONS.NONE; - let usage = command.usage; - let invalid = false; + if (!command) + return $.warn( + `Command "${header}" of category "${category}" unexpectedly doesn't exist!` + ); - let selectedCategory = 'Unknown'; - - for (const [category, headers] of categories) { - if (headers.includes(header)) { - if (selectedCategory !== 'Unknown') - $.warn( - `Command "${header}" is somehow in multiple categories. This means that the command loading stage probably failed in properly adding categories.`, - ); - else selectedCategory = category; - } - } - - for (const param of $.args) { - const type = command.resolve(param); - command = command.get(param); - permLevel = command.permission ?? permLevel; - - switch (type) { - case Command.TYPES.SUBCOMMAND: - header += ` ${command.originalCommandName}`; - break; - case Command.TYPES.USER: - header += ' '; - break; - case Command.TYPES.NUMBER: - header += ' '; - break; - case Command.TYPES.ANY: - header += ' '; - break; - default: - header += ` ${param}`; - break; + output += `\n- \`${header}\`: ${command.description}`; + } + } } - if (type === Command.TYPES.NONE) { - invalid = true; - break; - } - } - - if (invalid) - return $.channel.send(`No command found by the name \`${header}\`!`); - - let append = ''; - - if (usage === '') { - const list: string[] = []; - - command.subcommands.forEach((subcmd, subtag) => { - // Don't capture duplicates generated from aliases. - if (subcmd.originalCommandName === subtag) { - const customUsage = subcmd.usage ? ` ${subcmd.usage}` : ''; - list.push( - `- \`${header} ${subtag}${customUsage}\` - ${subcmd.description}`, - ); - } - }); - - const addDynamicType = (cmd: Command | null, type: string) => { - if (cmd) { - const customUsage = cmd.usage ? ` ${cmd.usage}` : ''; - list.push( - `- \`${header} <${type}>${customUsage}\` - ${cmd.description}`, - ); - } - }; - - addDynamicType(command.user, 'user'); - addDynamicType(command.number, 'number'); - addDynamicType(command.any, 'any'); - - append = - 'Usages:' + (list.length > 0 ? `\n${list.join('\n')}` : ' None.'); - } else append = `Usage: \`${header} ${usage}\``; - - let aliases = 'None'; - - if (command.aliases.length > 0) { - aliases = ''; - - for (let i = 0; i < command.aliases.length; i++) { - const alias = command.aliases[i]; - aliases += `\`${alias}\``; - - if (i !== command.aliases.length - 1) aliases += ', '; - } - } - - $.channel.send( - `Command: \`${header}\`\nAliases: ${aliases}\nCategory: \`${selectedCategory}\`\nPermission Required: \`${PermissionNames[permLevel]}\` (${permLevel})\nDescription: ${command.description}\n${append}`, - { split: true }, - ); + $.channel.send(output, {split: true}); }, - }), + any: new Command({ + async run($: CommonLibrary): Promise { + const commands = await loadCommands(); + let header = $.args.shift() as string; + let command = commands.get(header); + + if (!command || header === "test") + return $.channel.send( + `No command found by the name \`${header}\`!` + ); + + if (command.originalCommandName) + header = command.originalCommandName; + else $.warn(`originalCommandName isn't defined for ${header}?!`); + + let permLevel = command.permission ?? Command.PERMISSIONS.NONE; + let usage = command.usage; + let invalid = false; + + let selectedCategory = "Unknown"; + + for (const [category, headers] of categories) { + if (headers.includes(header)) { + if (selectedCategory !== "Unknown") + $.warn( + `Command "${header}" is somehow in multiple categories. This means that the command loading stage probably failed in properly adding categories.` + ); + else selectedCategory = category; + } + } + + for (const param of $.args) { + const type = command.resolve(param); + command = command.get(param); + permLevel = command.permission ?? permLevel; + + switch (type) { + case Command.TYPES.SUBCOMMAND: + header += ` ${command.originalCommandName}`; + break; + case Command.TYPES.USER: + header += " "; + break; + case Command.TYPES.NUMBER: + header += " "; + break; + case Command.TYPES.ANY: + header += " "; + break; + default: + header += ` ${param}`; + break; + } + + if (type === Command.TYPES.NONE) { + invalid = true; + break; + } + } + + if (invalid) + return $.channel.send( + `No command found by the name \`${header}\`!` + ); + + let append = ""; + + if (usage === "") { + const list: string[] = []; + + command.subcommands.forEach((subcmd, subtag) => { + // Don't capture duplicates generated from aliases. + if (subcmd.originalCommandName === subtag) { + const customUsage = subcmd.usage + ? ` ${subcmd.usage}` + : ""; + list.push( + `- \`${header} ${subtag}${customUsage}\` - ${subcmd.description}` + ); + } + }); + + const addDynamicType = (cmd: Command | null, type: string) => { + if (cmd) { + const customUsage = cmd.usage ? ` ${cmd.usage}` : ""; + list.push( + `- \`${header} <${type}>${customUsage}\` - ${cmd.description}` + ); + } + }; + + addDynamicType(command.user, "user"); + addDynamicType(command.number, "number"); + addDynamicType(command.any, "any"); + + append = + "Usages:" + + (list.length > 0 ? `\n${list.join("\n")}` : " None."); + } else append = `Usage: \`${header} ${usage}\``; + + let aliases = "None"; + + if (command.aliases.length > 0) { + aliases = ""; + + for (let i = 0; i < command.aliases.length; i++) { + const alias = command.aliases[i]; + aliases += `\`${alias}\``; + + if (i !== command.aliases.length - 1) aliases += ", "; + } + } + + $.channel.send( + `Command: \`${header}\`\nAliases: ${aliases}\nCategory: \`${selectedCategory}\`\nPermission Required: \`${PermissionNames[permLevel]}\` (${permLevel})\nDescription: ${command.description}\n${append}`, + {split: true} + ); + } + }) }); diff --git a/src/commands/info.ts b/src/commands/info.ts index 18d4a50..c9153a1 100644 --- a/src/commands/info.ts +++ b/src/commands/info.ts @@ -1,243 +1,288 @@ -import { MessageEmbed, version as djsversion } from 'discord.js'; -/// @ts-ignore -import { version } from '../../package.json'; -import ms from 'ms'; -import os from 'os'; -import Command from '../core/command'; -import { CommonLibrary, formatBytes, trimArray } from '../core/lib'; -import { verificationLevels, filterLevels, regions, flags } from '../defs/info'; -import moment from 'moment'; -import utc from 'moment'; - -export default new Command({ - description: - 'Command to provide all sorts of info about the current server, a user, etc.', - run: 'Please provide an argument.\nFor help, run `%prefix%help info`.', - subcommands: { - avatar: new Command({ - description: "Shows your own, or another user's avatar.", - usage: '()', - async run($: CommonLibrary): Promise { - $.channel.send( - $.author.displayAvatarURL({ dynamic: true, size: 2048 }), - ); - }, - user: new Command({ - description: "Shows your own, or another user's avatar.", - async run($: CommonLibrary): Promise { - $.channel.send( - $.args[0].displayAvatarURL({ dynamic: true, size: 2048 }), - ); - }, - }), - }), - - bot: new Command({ - description: 'Displays info about the bot.', - async run($: CommonLibrary): Promise { - const core = os.cpus()[0]; - const embed = new MessageEmbed() - .setThumbnail( - /// @ts-ignore - $.client.user?.displayAvatarURL({ dynamic: true, size: 2048 }), - ) - .setColor($.guild?.me?.displayHexColor || 'BLUE') - .addField('General', [ - `**❯ Client:** ${$.client.user?.tag} (${$.client.user?.id})`, - `**❯ Servers:** ${$.client.guilds.cache.size.toLocaleString()}`, - `**❯ Users:** ${$.client.guilds.cache - .reduce((a: any, b: { memberCount: any }) => a + b.memberCount, 0) - .toLocaleString()}`, - `**❯ Channels:** ${$.client.channels.cache.size.toLocaleString()}`, - `**❯ Creation Date:** ${utc($.client.user?.createdTimestamp).format( - 'Do MMMM YYYY HH:mm:ss', - )}`, - `**❯ Node.JS:** ${process.version}`, - `**❯ Version:** v${version}`, - `**❯ Discord.JS:** ${djsversion}`, - '\u200b', - ]) - .addField('System', [ - `**❯ Platform:** ${process.platform}`, - `**❯ Uptime:** ${ms(os.uptime() * 1000, { long: true })}`, - `**❯ CPU:**`, - `\u3000 • Cores: ${os.cpus().length}`, - `\u3000 • Model: ${core.model}`, - `\u3000 • Speed: ${core.speed}MHz`, - `**❯ Memory:**`, - `\u3000 • Total: ${formatBytes(process.memoryUsage().heapTotal)}`, - `\u3000 • Used: ${formatBytes(process.memoryUsage().heapTotal)}`, - ]) - .setTimestamp(); - $.channel.send(embed); - }, - }), - - guild: new Command({ - description: 'Displays info about the current guild.', - async run($: CommonLibrary): Promise { - if ($.guild) { - const roles = $.guild.roles.cache - .sort((a, b) => b.position - a.position) - .map((role) => role.toString()); - const members = $.guild.members.cache; - const channels = $.guild.channels.cache; - const emojis = $.guild.emojis.cache; - - const iconURL = $.guild.iconURL({ dynamic: true }); - const embed = new MessageEmbed() - .setDescription(`**Guild information for __${$.guild.name}__**`) - .setColor('BLUE'); - if (iconURL) - embed - .setThumbnail(iconURL) - .addField('General', [ - `**❯ Name:** ${$.guild.name}`, - `**❯ ID:** ${$.guild.id}`, - `**❯ Owner:** ${$.guild.owner?.user.tag} (${$.guild.ownerID})`, - `**❯ Region:** ${regions[$.guild.region]}`, - `**❯ Boost Tier:** ${ - $.guild.premiumTier ? `Tier ${$.guild.premiumTier}` : 'None' - }`, - `**❯ Explicit Filter:** ${ - filterLevels[$.guild.explicitContentFilter] - }`, - `**❯ Verification Level:** ${ - verificationLevels[$.guild.verificationLevel] - }`, - `**❯ Time Created:** ${moment($.guild.createdTimestamp).format( - 'LT', - )} ${moment($.guild.createdTimestamp).format('LL')} ${moment( - $.guild.createdTimestamp, - ).fromNow()})`, - '\u200b', - ]) - .addField('Statistics', [ - `**❯ Role Count:** ${roles.length}`, - `**❯ Emoji Count:** ${emojis.size}`, - `**❯ Regular Emoji Count:** ${ - emojis.filter((emoji) => !emoji.animated).size - }`, - `**❯ Animated Emoji Count:** ${ - emojis.filter((emoji) => emoji.animated).size - }`, - `**❯ Member Count:** ${$.guild.memberCount}`, - `**❯ Humans:** ${ - members.filter((member) => !member.user.bot).size - }`, - `**❯ Bots:** ${ - members.filter((member) => member.user.bot).size - }`, - `**❯ Text Channels:** ${ - channels.filter((channel) => channel.type === 'text').size - }`, - `**❯ Voice Channels:** ${ - channels.filter((channel) => channel.type === 'voice').size - }`, - `**❯ Boost Count:** ${$.guild.premiumSubscriptionCount || '0'}`, - `\u200b`, - ]) - .addField('Presence', [ - `**❯ Online:** ${ - members.filter( - (member) => member.presence.status === 'online', - ).size - }`, - `**❯ Idle:** ${ - members.filter((member) => member.presence.status === 'idle') - .size - }`, - `**❯ Do Not Disturb:** ${ - members.filter((member) => member.presence.status === 'dnd') - .size - }`, - `**❯ Offline:** ${ - members.filter( - (member) => member.presence.status === 'offline', - ).size - }`, - '\u200b', - ]) - .addField( - `Roles [${roles.length - 1}]`, - roles.length < 10 - ? roles.join(', ') - : roles.length > 10 - ? trimArray(roles) - : 'None', - ) - .setTimestamp(); - - $.channel.send(embed); - } else { - $.channel.send('Please execute this command in a guild.'); - } - }, - }), - }, - user: new Command({ - description: 'Displays info about mentioned user.', - async run($: CommonLibrary): Promise { - // Transforms the User object into a GuildMember object of the current guild. - const member = $.guild?.members.resolve($.args[0]); - - if (!member) - return $.channel.send( - 'No member object was found by that user! Are you sure you used this command in a server?', - ); - - const roles = member.roles.cache - .sort( - (a: { position: number }, b: { position: number }) => - b.position - a.position, - ) - .map((role: { toString: () => any }) => role.toString()) - .slice(0, -1); - // @ts-ignore - Discord.js' typings seem to be outdated here. According to their v12 docs, it's User.fetchFlags() instead of User.flags. - const userFlags = ((await member.user.fetchFlags()) as UserFlags).toArray(); - - const embed = new MessageEmbed() - .setThumbnail( - member.user.displayAvatarURL({ dynamic: true, size: 512 }), - ) - .setColor(member.displayHexColor || 'BLUE') - .addField('User', [ - `**❯ Username:** ${member.user.username}`, - `**❯ Discriminator:** ${member.user.discriminator}`, - `**❯ ID:** ${member.id}`, - `**❯ Flags:** ${userFlags.length ? userFlags.join(', ') : 'None'}`, - `**❯ Avatar:** [Link to avatar](${member.user.displayAvatarURL({ - dynamic: true, - })})`, - `**❯ Time Created:** ${moment(member.user.createdTimestamp).format( - 'LT', - )} ${moment(member.user.createdTimestamp).format('LL')} ${moment( - member.user.createdTimestamp, - ).fromNow()}`, - `**❯ Status:** ${member.user.presence.status}`, - `**❯ Game:** ${ - member.user.presence.activities || 'Not playing a game.' - }`, - ]) - .addField('Member', [ - `**❯ Highest Role:** ${ - member.roles.highest.id === $.guild?.id - ? 'None' - : member.roles.highest.name - }`, - `**❯ Server Join Date:** ${moment(member.joinedAt).format('LL LTS')}`, - `**❯ Hoist Role:** ${ - member.roles.hoist ? member.roles.hoist.name : 'None' - }`, - `**❯ Roles:** [${roles.length}]: ${ - roles.length < 10 - ? roles.join(', ') - : roles.length > 10 - ? this.client.utils.trimArray(roles) - : 'None' - }`, - ]); - $.channel.send(embed); - }, - }), -}); +import {MessageEmbed, version as djsversion} from "discord.js"; +/// @ts-ignore +import {version} from "../../package.json"; +import ms from "ms"; +import os from "os"; +import Command from "../core/command"; +import {CommonLibrary, formatBytes, trimArray} from "../core/lib"; +import {verificationLevels, filterLevels, regions, flags} from "../defs/info"; +import moment from "moment"; +import utc from "moment"; + +export default new Command({ + description: + "Command to provide all sorts of info about the current server, a user, etc.", + run: "Please provide an argument.\nFor help, run `%prefix%help info`.", + subcommands: { + avatar: new Command({ + description: "Shows your own, or another user's avatar.", + usage: "()", + async run($: CommonLibrary): Promise { + $.channel.send( + $.author.displayAvatarURL({dynamic: true, size: 2048}) + ); + }, + user: new Command({ + description: "Shows your own, or another user's avatar.", + async run($: CommonLibrary): Promise { + $.channel.send( + $.args[0].displayAvatarURL({ + dynamic: true, + size: 2048 + }) + ); + } + }) + }), + bot: new Command({ + description: "Displays info about the bot.", + async run($: CommonLibrary): Promise { + const core = os.cpus()[0]; + const embed = new MessageEmbed() + .setThumbnail( + /// @ts-ignore + $.client.user?.displayAvatarURL({ + dynamic: true, + size: 2048 + }) + ) + .setColor($.guild?.me?.displayHexColor || "BLUE") + .addField("General", [ + `**❯ Client:** ${$.client.user?.tag} (${$.client.user?.id})`, + `**❯ Servers:** ${$.client.guilds.cache.size.toLocaleString()}`, + `**❯ Users:** ${$.client.guilds.cache + .reduce( + (a: any, b: {memberCount: any}) => + a + b.memberCount, + 0 + ) + .toLocaleString()}`, + `**❯ Channels:** ${$.client.channels.cache.size.toLocaleString()}`, + `**❯ Creation Date:** ${utc( + $.client.user?.createdTimestamp + ).format("Do MMMM YYYY HH:mm:ss")}`, + `**❯ Node.JS:** ${process.version}`, + `**❯ Version:** v${version}`, + `**❯ Discord.JS:** ${djsversion}`, + "\u200b" + ]) + .addField("System", [ + `**❯ Platform:** ${process.platform}`, + `**❯ Uptime:** ${ms(os.uptime() * 1000, { + long: true + })}`, + `**❯ CPU:**`, + `\u3000 • Cores: ${os.cpus().length}`, + `\u3000 • Model: ${core.model}`, + `\u3000 • Speed: ${core.speed}MHz`, + `**❯ Memory:**`, + `\u3000 • Total: ${formatBytes( + process.memoryUsage().heapTotal + )}`, + `\u3000 • Used: ${formatBytes( + process.memoryUsage().heapTotal + )}` + ]) + .setTimestamp(); + $.channel.send(embed); + } + }), + guild: new Command({ + description: "Displays info about the current guild.", + async run($: CommonLibrary): Promise { + if ($.guild) { + const roles = $.guild.roles.cache + .sort((a, b) => b.position - a.position) + .map((role) => role.toString()); + const members = $.guild.members.cache; + const channels = $.guild.channels.cache; + const emojis = $.guild.emojis.cache; + const iconURL = $.guild.iconURL({dynamic: true}); + const embed = new MessageEmbed() + .setDescription( + `**Guild information for __${$.guild.name}__**` + ) + .setColor("BLUE"); + if (iconURL) + embed + .setThumbnail(iconURL) + .addField("General", [ + `**❯ Name:** ${$.guild.name}`, + `**❯ ID:** ${$.guild.id}`, + `**❯ Owner:** ${$.guild.owner?.user.tag} (${$.guild.ownerID})`, + `**❯ Region:** ${regions[$.guild.region]}`, + `**❯ Boost Tier:** ${ + $.guild.premiumTier + ? `Tier ${$.guild.premiumTier}` + : "None" + }`, + `**❯ Explicit Filter:** ${ + filterLevels[$.guild.explicitContentFilter] + }`, + `**❯ Verification Level:** ${ + verificationLevels[ + $.guild.verificationLevel + ] + }`, + `**❯ Time Created:** ${moment( + $.guild.createdTimestamp + ).format("LT")} ${moment( + $.guild.createdTimestamp + ).format("LL")} ${moment( + $.guild.createdTimestamp + ).fromNow()})`, + "\u200b" + ]) + .addField("Statistics", [ + `**❯ Role Count:** ${roles.length}`, + `**❯ Emoji Count:** ${emojis.size}`, + `**❯ Regular Emoji Count:** ${ + emojis.filter((emoji) => !emoji.animated) + .size + }`, + `**❯ Animated Emoji Count:** ${ + emojis.filter((emoji) => emoji.animated) + .size + }`, + `**❯ Member Count:** ${$.guild.memberCount}`, + `**❯ Humans:** ${ + members.filter((member) => !member.user.bot) + .size + }`, + `**❯ Bots:** ${ + members.filter((member) => member.user.bot) + .size + }`, + `**❯ Text Channels:** ${ + channels.filter( + (channel) => channel.type === "text" + ).size + }`, + `**❯ Voice Channels:** ${ + channels.filter( + (channel) => channel.type === "voice" + ).size + }`, + `**❯ Boost Count:** ${ + $.guild.premiumSubscriptionCount || "0" + }`, + `\u200b` + ]) + .addField("Presence", [ + `**❯ Online:** ${ + members.filter( + (member) => + member.presence.status === "online" + ).size + }`, + `**❯ Idle:** ${ + members.filter( + (member) => + member.presence.status === "idle" + ).size + }`, + `**❯ Do Not Disturb:** ${ + members.filter( + (member) => + member.presence.status === "dnd" + ).size + }`, + `**❯ Offline:** ${ + members.filter( + (member) => + member.presence.status === "offline" + ).size + }`, + "\u200b" + ]) + .addField( + `Roles [${roles.length - 1}]`, + roles.length < 10 + ? roles.join(", ") + : roles.length > 10 + ? trimArray(roles) + : "None" + ) + .setTimestamp(); + + $.channel.send(embed); + } else { + $.channel.send("Please execute this command in a guild."); + } + } + }) + }, + user: new Command({ + description: "Displays info about mentioned user.", + async run($: CommonLibrary): Promise { + // Transforms the User object into a GuildMember object of the current guild. + const member = $.guild?.members.resolve($.args[0]); + + if (!member) + return $.channel.send( + "No member object was found by that user! Are you sure you used this command in a server?" + ); + + const roles = member.roles.cache + .sort( + (a: {position: number}, b: {position: number}) => + b.position - a.position + ) + .map((role: {toString: () => any}) => role.toString()) + .slice(0, -1); + // @ts-ignore - Discord.js' typings seem to be outdated here. According to their v12 docs, it's User.fetchFlags() instead of User.flags. + const userFlags = ((await member.user.fetchFlags()) as UserFlags).toArray(); + + const embed = new MessageEmbed() + .setThumbnail( + member.user.displayAvatarURL({dynamic: true, size: 512}) + ) + .setColor(member.displayHexColor || "BLUE") + .addField("User", [ + `**❯ Username:** ${member.user.username}`, + `**❯ Discriminator:** ${member.user.discriminator}`, + `**❯ ID:** ${member.id}`, + `**❯ Flags:** ${ + userFlags.length ? userFlags.join(", ") : "None" + }`, + `**❯ Avatar:** [Link to avatar](${member.user.displayAvatarURL( + { + dynamic: true + } + )})`, + `**❯ Time Created:** ${moment( + member.user.createdTimestamp + ).format("LT")} ${moment( + member.user.createdTimestamp + ).format("LL")} ${moment( + member.user.createdTimestamp + ).fromNow()}`, + `**❯ Status:** ${member.user.presence.status}`, + `**❯ Game:** ${ + member.user.presence.activities || "Not playing a game." + }` + ]) + .addField("Member", [ + `**❯ Highest Role:** ${ + member.roles.highest.id === $.guild?.id + ? "None" + : member.roles.highest.name + }`, + `**❯ Server Join Date:** ${moment(member.joinedAt).format( + "LL LTS" + )}`, + `**❯ Hoist Role:** ${ + member.roles.hoist ? member.roles.hoist.name : "None" + }`, + `**❯ Roles:** [${roles.length}]: ${ + roles.length < 10 + ? roles.join(", ") + : roles.length > 10 + ? this.client.utils.trimArray(roles) + : "None" + }` + ]); + $.channel.send(embed); + } + }) +}); diff --git a/src/commands/scanemotes.ts b/src/commands/scanemotes.ts index 107bf6f..ed93a3f 100644 --- a/src/commands/scanemotes.ts +++ b/src/commands/scanemotes.ts @@ -1,191 +1,196 @@ -import Command from '../core/command'; -import { CommonLibrary } from '../core/lib'; -import moment from 'moment'; -import { Collection, TextChannel } from 'discord.js'; +import Command from "../core/command"; +import {CommonLibrary} from "../core/lib"; +import moment from "moment"; +import {Collection, TextChannel} from "discord.js"; -const lastUsedTimestamps: { [id: string]: number } = {}; +const lastUsedTimestamps: {[id: string]: number} = {}; export default new Command({ - description: - 'Scans all text channels in the current guild and returns the number of times each emoji specific to the guild has been used. Has a cooldown of 24 hours per guild.', - async run($: CommonLibrary): Promise { - if (!$.guild) - return $.channel.send(`You must use this command on a server!`); + description: + "Scans all text channels in the current guild and returns the number of times each emoji specific to the guild has been used. Has a cooldown of 24 hours per guild.", + async run($: CommonLibrary): Promise { + if (!$.guild) + return $.channel.send(`You must use this command on a server!`); - // Test if the command is on cooldown. This isn't the strictest cooldown possible, because in the event that the bot crashes, the cooldown will be reset. But for all intends and purposes, it's a good enough cooldown. It's a per-server cooldown. - const startTime = Date.now(); - const cooldown = 86400000; // 24 hours - const lastUsedTimestamp = lastUsedTimestamps[$.guild.id] ?? 0; - const difference = startTime - lastUsedTimestamp; - const howLong = moment(startTime).to(lastUsedTimestamp + cooldown); + // Test if the command is on cooldown. This isn't the strictest cooldown possible, because in the event that the bot crashes, the cooldown will be reset. But for all intends and purposes, it's a good enough cooldown. It's a per-server cooldown. + const startTime = Date.now(); + const cooldown = 86400000; // 24 hours + const lastUsedTimestamp = lastUsedTimestamps[$.guild.id] ?? 0; + const difference = startTime - lastUsedTimestamp; + const howLong = moment(startTime).to(lastUsedTimestamp + cooldown); - // If it's been less than an hour since the command was last used, prevent it from executing. - if (difference < cooldown) - return $.channel.send( - `This command requires a day to cooldown. You'll be able to activate this command ${howLong}.`, - ); - else lastUsedTimestamps[$.guild.id] = startTime; + // If it's been less than an hour since the command was last used, prevent it from executing. + if (difference < cooldown) + return $.channel.send( + `This command requires a day to cooldown. You'll be able to activate this command ${howLong}.` + ); + else lastUsedTimestamps[$.guild.id] = startTime; - const stats: { - [id: string]: { - name: string; - formatted: string; - users: number; - bots: number; - }; - } = {}; - let totalUserEmoteUsage = 0; - // IMPORTANT: You MUST check if the bot actually has access to the channel in the first place. It will get the list of all channels, but that doesn't mean it has access to every channel. Without this, it'll require admin access and throw an annoying unhelpful DiscordAPIError: Missing Access otherwise. - const allTextChannelsInCurrentGuild = $.guild.channels.cache.filter( - (channel) => channel.type === 'text' && channel.viewable, - ) as Collection; - let messagesSearched = 0; - let channelsSearched = 0; - let currentChannelName = ''; - const totalChannels = allTextChannelsInCurrentGuild.size; - const statusMessage = await $.channel.send('Gathering emotes...'); - let warnings = 0; - $.channel.startTyping(); + const stats: { + [id: string]: { + name: string; + formatted: string; + users: number; + bots: number; + }; + } = {}; + let totalUserEmoteUsage = 0; + // IMPORTANT: You MUST check if the bot actually has access to the channel in the first place. It will get the list of all channels, but that doesn't mean it has access to every channel. Without this, it'll require admin access and throw an annoying unhelpful DiscordAPIError: Missing Access otherwise. + const allTextChannelsInCurrentGuild = $.guild.channels.cache.filter( + (channel) => channel.type === "text" && channel.viewable + ) as Collection; + let messagesSearched = 0; + let channelsSearched = 0; + let currentChannelName = ""; + const totalChannels = allTextChannelsInCurrentGuild.size; + const statusMessage = await $.channel.send("Gathering emotes..."); + let warnings = 0; + $.channel.startTyping(); - // Initialize the emote stats object with every emote in the current guild. - // The goal here is to cut the need to access guild.emojis.get() which'll make it faster and easier to work with. - for (let emote of $.guild.emojis.cache.values()) { - // If you don't include the "a" for animated emotes, it'll not only not show up, but also cause all other emotes in the same message to not show up. The emote name is self-correcting but it's better to keep the right value since it'll be used to calculate message lengths that fit. - stats[emote.id] = { - name: emote.name, - formatted: `<${emote.animated ? 'a' : ''}:${emote.name}:${emote.id}>`, - users: 0, - bots: 0, - }; - } - - const interval = setInterval(() => { - statusMessage.edit( - `Searching channel \`${currentChannelName}\`... (${messagesSearched} messages scanned, ${channelsSearched}/${totalChannels} channels scanned)`, - ); - }, 5000); - - for (const channel of allTextChannelsInCurrentGuild.values()) { - currentChannelName = channel.name; - let selected = channel.lastMessageID ?? $.message.id; - let continueLoop = true; - - while (continueLoop) { - // Unfortunately, any kind of .fetch call is limited to 100 items at once by Discord's API. - const messages = await channel.messages.fetch({ - limit: 100, - before: selected, - }); - - if (messages.size > 0) { - for (const msg of messages.values()) { - // It's very important to not capture an array of matches then do \d+ on each item because emote names can have numbers in them, causing the bot to not count them correctly. - const search = //g; - const text = msg.content; - let match: RegExpExecArray | null; - - while ((match = search.exec(text))) { - const emoteID = match[1]; - - if (emoteID in stats) { - if (msg.author.bot) stats[emoteID].bots++; - else { - stats[emoteID].users++; - totalUserEmoteUsage++; - } - } - } - - for (const reaction of msg.reactions.cache.values()) { - const emoteID = reaction.emoji.id; - let continueReactionLoop = true; - let lastUserID: string | undefined; - let userReactions = 0; - let botReactions = 0; - - // An emote's ID will be null if it's a unicode emote. - if (emoteID && emoteID in stats) { - // There is a simple count property on a reaction, but that doesn't separate users from bots. - // So instead, I'll use that property to check for inconsistencies. - while (continueReactionLoop) { - // After logging users, it seems like the order is strictly numerical. As long as that stays consistent, this should work fine. - const users = await reaction.users.fetch({ - limit: 100, - after: lastUserID, - }); - - if (users.size > 0) { - for (const user of users.values()) { - if (user.bot) { - stats[emoteID].bots++; - botReactions++; - } else { - stats[emoteID].users++; - totalUserEmoteUsage++; - userReactions++; - } - - lastUserID = user.id; - } - } else { - // Then halt the loop and send warnings of any inconsistencies. - continueReactionLoop = false; - - if (reaction.count !== userReactions + botReactions) { - $.warn( - `[Channel: ${channel.id}, Message: ${msg.id}] A reaction count of ${reaction.count} was expected but was given ${userReactions} user reactions and ${botReactions} bot reactions.`, - ); - warnings++; - } - } - } - } - } - - selected = msg.id; - messagesSearched++; - } - } else { - continueLoop = false; - channelsSearched++; + // Initialize the emote stats object with every emote in the current guild. + // The goal here is to cut the need to access guild.emojis.get() which'll make it faster and easier to work with. + for (let emote of $.guild.emojis.cache.values()) { + // If you don't include the "a" for animated emotes, it'll not only not show up, but also cause all other emotes in the same message to not show up. The emote name is self-correcting but it's better to keep the right value since it'll be used to calculate message lengths that fit. + stats[emote.id] = { + name: emote.name, + formatted: `<${emote.animated ? "a" : ""}:${emote.name}:${ + emote.id + }>`, + users: 0, + bots: 0 + }; } - } + + const interval = setInterval(() => { + statusMessage.edit( + `Searching channel \`${currentChannelName}\`... (${messagesSearched} messages scanned, ${channelsSearched}/${totalChannels} channels scanned)` + ); + }, 5000); + + for (const channel of allTextChannelsInCurrentGuild.values()) { + currentChannelName = channel.name; + let selected = channel.lastMessageID ?? $.message.id; + let continueLoop = true; + + while (continueLoop) { + // Unfortunately, any kind of .fetch call is limited to 100 items at once by Discord's API. + const messages = await channel.messages.fetch({ + limit: 100, + before: selected + }); + + if (messages.size > 0) { + for (const msg of messages.values()) { + // It's very important to not capture an array of matches then do \d+ on each item because emote names can have numbers in them, causing the bot to not count them correctly. + const search = //g; + const text = msg.content; + let match: RegExpExecArray | null; + + while ((match = search.exec(text))) { + const emoteID = match[1]; + + if (emoteID in stats) { + if (msg.author.bot) stats[emoteID].bots++; + else { + stats[emoteID].users++; + totalUserEmoteUsage++; + } + } + } + + for (const reaction of msg.reactions.cache.values()) { + const emoteID = reaction.emoji.id; + let continueReactionLoop = true; + let lastUserID: string | undefined; + let userReactions = 0; + let botReactions = 0; + + // An emote's ID will be null if it's a unicode emote. + if (emoteID && emoteID in stats) { + // There is a simple count property on a reaction, but that doesn't separate users from bots. + // So instead, I'll use that property to check for inconsistencies. + while (continueReactionLoop) { + // After logging users, it seems like the order is strictly numerical. As long as that stays consistent, this should work fine. + const users = await reaction.users.fetch({ + limit: 100, + after: lastUserID + }); + + if (users.size > 0) { + for (const user of users.values()) { + if (user.bot) { + stats[emoteID].bots++; + botReactions++; + } else { + stats[emoteID].users++; + totalUserEmoteUsage++; + userReactions++; + } + + lastUserID = user.id; + } + } else { + // Then halt the loop and send warnings of any inconsistencies. + continueReactionLoop = false; + + if ( + reaction.count !== + userReactions + botReactions + ) { + $.warn( + `[Channel: ${channel.id}, Message: ${msg.id}] A reaction count of ${reaction.count} was expected but was given ${userReactions} user reactions and ${botReactions} bot reactions.` + ); + warnings++; + } + } + } + } + } + + selected = msg.id; + messagesSearched++; + } + } else { + continueLoop = false; + channelsSearched++; + } + } + } + + // Mark the operation as ended. + const finishTime = Date.now(); + clearInterval(interval); + statusMessage.edit( + `Finished operation in ${moment + .duration(finishTime - startTime) + .humanize()} with ${$(warnings).pluralise( + "inconsistenc", + "ies", + "y" + )}.` + ); + $.log(`Finished operation in ${finishTime - startTime} ms.`); + $.channel.stopTyping(); + + // Display stats on emote usage. + // This can work outside the loop now that it's synchronous, and now it's clearer what code is meant to execute at the end. + let sortedEmoteIDs = Object.keys(stats).sort( + (a, b) => stats[b].users - stats[a].users + ); + const lines: string[] = []; + let rank = 1; + + // It's better to send all the lines at once rather than paginate the data because it's quite a memory-intensive task to search all the messages in a server for it, and I wouldn't want to activate the command again just to get to another page. + for (const emoteID of sortedEmoteIDs) { + const emote = stats[emoteID]; + const botInfo = emote.bots > 0 ? ` (Bots: ${emote.bots})` : ""; + lines.push( + `\`#${rank++}\` ${emote.formatted} x ${emote.users} - ${( + (emote.users / totalUserEmoteUsage) * 100 || 0 + ).toFixed(3)}%` + botInfo + ); + } + + $.channel.send(lines, {split: true}).catch($.handler.bind($)); } - - // Mark the operation as ended. - const finishTime = Date.now(); - clearInterval(interval); - statusMessage.edit( - `Finished operation in ${moment - .duration(finishTime - startTime) - .humanize()} with ${$(warnings).pluralise( - 'inconsistenc', - 'ies', - 'y', - )}.`, - ); - $.log(`Finished operation in ${finishTime - startTime} ms.`); - $.channel.stopTyping(); - - // Display stats on emote usage. - // This can work outside the loop now that it's synchronous, and now it's clearer what code is meant to execute at the end. - let sortedEmoteIDs = Object.keys(stats).sort( - (a, b) => stats[b].users - stats[a].users, - ); - const lines: string[] = []; - let rank = 1; - - // It's better to send all the lines at once rather than paginate the data because it's quite a memory-intensive task to search all the messages in a server for it, and I wouldn't want to activate the command again just to get to another page. - for (const emoteID of sortedEmoteIDs) { - const emote = stats[emoteID]; - const botInfo = emote.bots > 0 ? ` (Bots: ${emote.bots})` : ''; - lines.push( - `\`#${rank++}\` ${emote.formatted} x ${emote.users} - ${( - (emote.users / totalUserEmoteUsage) * 100 || 0 - ).toFixed(3)}%` + botInfo, - ); - } - - $.channel.send(lines, { split: true }).catch($.handler.bind($)); - }, }); diff --git a/src/commands/utilities/desc.ts b/src/commands/utilities/desc.ts index 5b96306..f375a2c 100644 --- a/src/commands/utilities/desc.ts +++ b/src/commands/utilities/desc.ts @@ -1,29 +1,31 @@ -import Command from '../../core/command'; -import { CommonLibrary } from '../../core/lib'; +import Command from "../../core/command"; +import {CommonLibrary} from "../../core/lib"; export default new Command({ description: "Renames current voice channel.", usage: "", async run($: CommonLibrary): Promise { const voiceChannel = $.message.member?.voice.channel; + if (!voiceChannel) - return $.channel.send('You are not in a voice channel.'); - if (!$.guild?.me?.hasPermission('MANAGE_CHANNELS')) - return $.channel.send( - 'I am lacking the required permissions to perform this action.', - ); + return $.channel.send("You are not in a voice channel."); + + if (!$.guild?.me?.hasPermission("MANAGE_CHANNELS")) + return $.channel.send( + "I am lacking the required permissions to perform this action." + ); + if ($.args.length === 0) - return $.channel.send( - 'Please provide a new voice channel name.', - ); + return $.channel.send("Please provide a new voice channel name."); + const changeVC = $.guild.channels.resolve(voiceChannel.id); $.channel - .send( - `Changed channel name from "${voiceChannel}" to "${$.args.join( - ' ', - )}".`, - ) - /// @ts-ignore - .then(changeVC?.setName($.args.join(' '))); + .send( + `Changed channel name from "${voiceChannel}" to "${$.args.join( + " " + )}".` + ) + /// @ts-ignore + .then(changeVC?.setName($.args.join(" "))); } -}) \ No newline at end of file +}); diff --git a/src/commands/utilities/emote.ts b/src/commands/utilities/emote.ts index c01129e..bc0291c 100644 --- a/src/commands/utilities/emote.ts +++ b/src/commands/utilities/emote.ts @@ -1,21 +1,21 @@ -import { MessageEmbed } from 'discord.js'; -import Command from '../../core/command'; -import { CommonLibrary } from '../../core/lib'; +import {MessageEmbed} from "discord.js"; +import Command from "../../core/command"; +import {CommonLibrary} from "../../core/lib"; export default new Command({ - description: 'Send the specified emote.', - run: 'Please provide a command name.', - any: new Command({ - description: 'The emote to send.', - usage: '', - async run($: CommonLibrary): Promise { - const search = $.args[0].toLowerCase(); - const emote = $.client.emojis.cache.find((emote) => - emote.name.toLowerCase().includes(search), - ); - if (!emote) return $.channel.send("That's not a valid emote name!"); - $.message.delete(); - $.channel.send(`${emote}`); - }, - }), + description: "Send the specified emote.", + run: "Please provide a command name.", + any: new Command({ + description: "The emote to send.", + usage: "", + async run($: CommonLibrary): Promise { + const search = $.args[0].toLowerCase(); + const emote = $.client.emojis.cache.find((emote) => + emote.name.toLowerCase().includes(search) + ); + if (!emote) return $.channel.send("That's not a valid emote name!"); + $.message.delete(); + $.channel.send(`${emote}`); + } + }) }); diff --git a/src/commands/utilities/lsemotes.ts b/src/commands/utilities/lsemotes.ts index 838d62e..a05106d 100644 --- a/src/commands/utilities/lsemotes.ts +++ b/src/commands/utilities/lsemotes.ts @@ -1,32 +1,36 @@ -import { MessageEmbed } from 'discord.js'; -import Command from '../../core/command'; -import { CommonLibrary } from '../../core/lib'; +import {MessageEmbed} from "discord.js"; +import Command from "../../core/command"; +import {CommonLibrary} from "../../core/lib"; export default new Command({ - description: "Lists all emotes the bot has in it's registry,", - endpoint: true, - async run($: CommonLibrary): Promise { - const nsfw: string | string[] = []; - const pages = $.client.emojis.cache - .filter((x) => !nsfw.includes(x.guild.id), this) - .array(); - const pagesSplit = $(pages).split(20); - $.log(pagesSplit); - var embed = new MessageEmbed().setTitle('**Emoji list!**').setColor('AQUA'); - let desc = ''; - for (const emote of pagesSplit[0]) { - desc += `${emote} | ${emote.name}\n`; - } - embed.setDescription(desc); - const msg = await $.channel.send({ embed }); + description: "Lists all emotes the bot has in it's registry,", + endpoint: true, + async run($: CommonLibrary): Promise { + const nsfw: string | string[] = []; + const pages = $.client.emojis.cache + .filter((x) => !nsfw.includes(x.guild.id), this) + .array(); + const pagesSplit = $(pages).split(20); + $.log(pagesSplit); + var embed = new MessageEmbed() + .setTitle("**Emoji list!**") + .setColor("AQUA"); + let desc = ""; - $.paginate(msg, $.author.id, pages.length, (page) => { - let desc = ''; - for (const emote of pagesSplit[page]) { - desc += `${emote} | ${emote.name}\n`; - } - embed.setDescription(desc); - msg.edit(embed); - }); - }, + for (const emote of pagesSplit[0]) { + desc += `${emote} | ${emote.name}\n`; + } + + embed.setDescription(desc); + const msg = await $.channel.send({embed}); + + $.paginate(msg, $.author.id, pages.length, (page) => { + let desc = ""; + for (const emote of pagesSplit[page]) { + desc += `${emote} | ${emote.name}\n`; + } + embed.setDescription(desc); + msg.edit(embed); + }); + } }); diff --git a/src/commands/utilities/react.ts b/src/commands/utilities/react.ts index d7135d4..f7f6080 100644 --- a/src/commands/utilities/react.ts +++ b/src/commands/utilities/react.ts @@ -1,68 +1,72 @@ -import Command from '../../core/command'; -import { CommonLibrary } from '../../core/lib'; - -export default new Command({ - description: - 'Reacts to the a previous message in your place. You have to react with the same emote before the bot removes that reaction.', - usage: 'react ()', - async run($: CommonLibrary): Promise { - let target; - let distance = 1; - - if ($.args.length >= 2) { - const last = $.args[$.args.length - 1]; - - if (/\d{17,19}/g.test(last)) { - try { - target = await $.channel.messages.fetch(last); - } catch { - return $.channel.send( - `No valid message found by the ID \`${last}\`!`, - ); - } - $.args.pop(); - } - // The entire string has to be a number for this to match. Prevents leaCheeseAmerican1 from triggering this. - else if (/^\d+$/g.test(last)) { - distance = parseInt(last); - - if (distance >= 0 && distance <= 99) $.args.pop(); - else return $.channel.send('Your distance must be between 0 and 99!'); - } - } - - if (!target) { - // Messages are ordered from latest to earliest. - // You also have to add 1 as well because fetchMessages includes your own message. - target = ( - await $.message.channel.messages.fetch({ - limit: distance + 1, - }) - ).last(); - } - - let anyEmoteIsValid = false; - - for (const search of $.args) { - const emoji = $.client.emojis.cache.find( - (emoji) => emoji.name === search, - ); - - if (emoji) { - // Call the delete function only once to avoid unnecessary errors. - if (!anyEmoteIsValid && distance !== 0) $.message.delete(); - anyEmoteIsValid = true; - const reaction = await target?.react(emoji); - - // This part is called with a promise because you don't want to wait 5 seconds between each reaction. - - setTimeout(() => { - /// @ts-ignore - reaction.users.remove($.client.user); - }, 5000); - } - } - - if (!anyEmoteIsValid && !$.message.deleted) $.message.react('❓'); - }, -}); +import Command from "../../core/command"; +import {CommonLibrary} from "../../core/lib"; + +export default new Command({ + description: + "Reacts to the a previous message in your place. You have to react with the same emote before the bot removes that reaction.", + usage: "react ()", + async run($: CommonLibrary): Promise { + let target; + let distance = 1; + + if ($.args.length >= 2) { + const last = $.args[$.args.length - 1]; + + if (/\d{17,19}/g.test(last)) { + try { + target = await $.channel.messages.fetch(last); + } catch { + return $.channel.send( + `No valid message found by the ID \`${last}\`!` + ); + } + + $.args.pop(); + } + // The entire string has to be a number for this to match. Prevents leaCheeseAmerican1 from triggering this. + else if (/^\d+$/g.test(last)) { + distance = parseInt(last); + + if (distance >= 0 && distance <= 99) $.args.pop(); + else + return $.channel.send( + "Your distance must be between 0 and 99!" + ); + } + } + + if (!target) { + // Messages are ordered from latest to earliest. + // You also have to add 1 as well because fetchMessages includes your own message. + target = ( + await $.message.channel.messages.fetch({ + limit: distance + 1 + }) + ).last(); + } + + let anyEmoteIsValid = false; + + for (const search of $.args) { + const emoji = $.client.emojis.cache.find( + (emoji) => emoji.name === search + ); + + if (emoji) { + // Call the delete function only once to avoid unnecessary errors. + if (!anyEmoteIsValid && distance !== 0) $.message.delete(); + anyEmoteIsValid = true; + const reaction = await target?.react(emoji); + + // This part is called with a promise because you don't want to wait 5 seconds between each reaction. + + setTimeout(() => { + /// @ts-ignore + reaction.users.remove($.client.user); + }, 5000); + } + } + + if (!anyEmoteIsValid && !$.message.deleted) $.message.react("❓"); + } +}); diff --git a/src/commands/utilities/say.ts b/src/commands/utilities/say.ts index 05695eb..f4d74f0 100644 --- a/src/commands/utilities/say.ts +++ b/src/commands/utilities/say.ts @@ -1,14 +1,14 @@ -import Command from '../../core/command'; -import { CommonLibrary } from '../../core/lib'; - -export default new Command({ - description: "Repeats your message.", - usage: "", - run: "Please provide a message for me to say!", - any: new Command({ - description: "Message to repeat.", - async run($: CommonLibrary): Promise { - $.channel.send(`*${$.author} says:*\n${$.args.join(' ')}`); - } - }) -}) \ No newline at end of file +import Command from "../../core/command"; +import {CommonLibrary} from "../../core/lib"; + +export default new Command({ + description: "Repeats your message.", + usage: "", + run: "Please provide a message for me to say!", + any: new Command({ + description: "Message to repeat.", + async run($: CommonLibrary): Promise { + $.channel.send(`*${$.author} says:*\n${$.args.join(" ")}`); + } + }) +}); diff --git a/src/commands/utilities/shorten.ts b/src/commands/utilities/shorten.ts index e58b528..26b8983 100644 --- a/src/commands/utilities/shorten.ts +++ b/src/commands/utilities/shorten.ts @@ -1,25 +1,25 @@ -import Command from '../../core/command'; -import { CommonLibrary } from '../../core/lib'; -import * as https from 'https'; - -export default new Command({ - description: 'Shortens a given URL.', - run: 'Please provide a URL.', - any: new Command({ - async run($: CommonLibrary): Promise { - https.get( - 'https://is.gd/create.php?format=simple&url=' + - encodeURIComponent($.args[0]), - function (res) { - var body = ''; - res.on('data', function (chunk) { - body += chunk; - }); - res.on('end', function () { - $.channel.send(`<${body}>`); - }); - }, - ); - }, - }), -}); +import Command from "../../core/command"; +import {CommonLibrary} from "../../core/lib"; +import * as https from "https"; + +export default new Command({ + description: "Shortens a given URL.", + run: "Please provide a URL.", + any: new Command({ + async run($: CommonLibrary): Promise { + https.get( + "https://is.gd/create.php?format=simple&url=" + + encodeURIComponent($.args[0]), + function (res) { + var body = ""; + res.on("data", function (chunk) { + body += chunk; + }); + res.on("end", function () { + $.channel.send(`<${body}>`); + }); + } + ); + } + }) +}); diff --git a/src/core/command.ts b/src/core/command.ts index bdf0b3b..e62e70a 100644 --- a/src/core/command.ts +++ b/src/core/command.ts @@ -1,155 +1,157 @@ -import $, { isType, parseVars, CommonLibrary } from './lib'; -import { Collection } from 'discord.js'; -import { generateHandler } from './storage'; -import { promises as ffs, existsSync, writeFile } from 'fs'; -import { PERMISSIONS } from './permissions'; -import { getPrefix } from '../core/structures'; +import $, {isType, parseVars, CommonLibrary} from "./lib"; +import {Collection} from "discord.js"; +import {generateHandler} from "./storage"; +import {promises as ffs, existsSync, writeFile} from "fs"; +import {PERMISSIONS} from "./permissions"; +import {getPrefix} from "../core/structures"; interface CommandOptions { - description?: string; - endpoint?: boolean; - usage?: string; - permission?: PERMISSIONS | null; - aliases?: string[]; - run?: (($: CommonLibrary) => Promise) | string; - subcommands?: { [key: string]: Command }; - user?: Command; - number?: Command; - any?: Command; + description?: string; + endpoint?: boolean; + usage?: string; + permission?: PERMISSIONS | null; + aliases?: string[]; + run?: (($: CommonLibrary) => Promise) | string; + subcommands?: {[key: string]: Command}; + user?: Command; + number?: Command; + any?: Command; } export enum TYPES { - SUBCOMMAND, - USER, - NUMBER, - ANY, - NONE, + SUBCOMMAND, + USER, + NUMBER, + ANY, + NONE } export default class Command { - public readonly description: string; - public readonly endpoint: boolean; - public readonly usage: string; - public readonly permission: PERMISSIONS | null; - public readonly aliases: string[]; // This is to keep the array intact for parent Command instances to use. It'll also be used when loading top-level aliases. - public originalCommandName: string | null; // If the command is an alias, what's the original name? - public run: (($: CommonLibrary) => Promise) | string; - public readonly subcommands: Collection; // This is the final data structure you'll actually use to work with the commands the aliases point to. - public user: Command | null; - public number: Command | null; - public any: Command | null; - public static readonly TYPES = TYPES; - public static readonly PERMISSIONS = PERMISSIONS; + public readonly description: string; + public readonly endpoint: boolean; + public readonly usage: string; + public readonly permission: PERMISSIONS | null; + public readonly aliases: string[]; // This is to keep the array intact for parent Command instances to use. It'll also be used when loading top-level aliases. + public originalCommandName: string | null; // If the command is an alias, what's the original name? + public run: (($: CommonLibrary) => Promise) | string; + public readonly subcommands: Collection; // This is the final data structure you'll actually use to work with the commands the aliases point to. + public user: Command | null; + public number: Command | null; + public any: Command | null; + public static readonly TYPES = TYPES; + public static readonly PERMISSIONS = PERMISSIONS; - constructor(options?: CommandOptions) { - this.description = options?.description || 'No description.'; - this.endpoint = options?.endpoint || false; - this.usage = options?.usage || ''; - this.permission = options?.permission ?? null; - this.aliases = options?.aliases ?? []; - this.originalCommandName = null; - this.run = options?.run || 'No action was set on this command!'; - this.subcommands = new Collection(); // Populate this collection after setting subcommands. - this.user = options?.user || null; - this.number = options?.number || null; - this.any = options?.any || null; + constructor(options?: CommandOptions) { + this.description = options?.description || "No description."; + this.endpoint = options?.endpoint || false; + this.usage = options?.usage || ""; + this.permission = options?.permission ?? null; + this.aliases = options?.aliases ?? []; + this.originalCommandName = null; + this.run = options?.run || "No action was set on this command!"; + this.subcommands = new Collection(); // Populate this collection after setting subcommands. + this.user = options?.user || null; + this.number = options?.number || null; + this.any = options?.any || null; - if (options?.subcommands) { - const baseSubcommands = Object.keys(options.subcommands); + if (options?.subcommands) { + const baseSubcommands = Object.keys(options.subcommands); - // Loop once to set the base subcommands. - for (const name in options.subcommands) - this.subcommands.set(name, options.subcommands[name]); + // Loop once to set the base subcommands. + for (const name in options.subcommands) + this.subcommands.set(name, options.subcommands[name]); - // Then loop again to make aliases point to the base subcommands and warn if something's not right. - // This shouldn't be a problem because I'm hoping that JS stores these as references that point to the same object. - for (const name in options.subcommands) { - const subcmd = options.subcommands[name]; - subcmd.originalCommandName = name; - const aliases = subcmd.aliases; + // Then loop again to make aliases point to the base subcommands and warn if something's not right. + // This shouldn't be a problem because I'm hoping that JS stores these as references that point to the same object. + for (const name in options.subcommands) { + const subcmd = options.subcommands[name]; + subcmd.originalCommandName = name; + const aliases = subcmd.aliases; - for (const alias of aliases) { - if (baseSubcommands.includes(alias)) - $.warn( - `"${alias}" in subcommand "${name}" was attempted to be declared as an alias but it already exists in the base commands! (Look at the next "Loading Command" line to see which command is affected.)`, - ); - else if (this.subcommands.has(alias)) - $.warn( - `Duplicate alias "${alias}" at subcommand "${name}"! (Look at the next "Loading Command" line to see which command is affected.)`, - ); - else this.subcommands.set(alias, subcmd); + for (const alias of aliases) { + if (baseSubcommands.includes(alias)) + $.warn( + `"${alias}" in subcommand "${name}" was attempted to be declared as an alias but it already exists in the base commands! (Look at the next "Loading Command" line to see which command is affected.)` + ); + else if (this.subcommands.has(alias)) + $.warn( + `Duplicate alias "${alias}" at subcommand "${name}"! (Look at the next "Loading Command" line to see which command is affected.)` + ); + else this.subcommands.set(alias, subcmd); + } + } } - } + + if (this.user && this.user.aliases.length > 0) + $.warn( + `There are aliases defined for a "user"-type subcommand, but those aliases won't be used. (Look at the next "Loading Command" line to see which command is affected.)` + ); + + if (this.number && this.number.aliases.length > 0) + $.warn( + `There are aliases defined for a "number"-type subcommand, but those aliases won't be used. (Look at the next "Loading Command" line to see which command is affected.)` + ); + + if (this.any && this.any.aliases.length > 0) + $.warn( + `There are aliases defined for an "any"-type subcommand, but those aliases won't be used. (Look at the next "Loading Command" line to see which command is affected.)` + ); } - if (this.user && this.user.aliases.length > 0) - $.warn( - `There are aliases defined for a "user"-type subcommand, but those aliases won't be used. (Look at the next "Loading Command" line to see which command is affected.)`, - ); - if (this.number && this.number.aliases.length > 0) - $.warn( - `There are aliases defined for a "number"-type subcommand, but those aliases won't be used. (Look at the next "Loading Command" line to see which command is affected.)`, - ); - if (this.any && this.any.aliases.length > 0) - $.warn( - `There are aliases defined for an "any"-type subcommand, but those aliases won't be used. (Look at the next "Loading Command" line to see which command is affected.)`, - ); - } - - public execute($: CommonLibrary) { - if (isType(this.run, String)) { - $.channel.send( - parseVars( - this.run as string, - { - author: $.author.toString(), - prefix: getPrefix($.guild), - }, - '???', - ), - ); - } else (this.run as Function)($).catch($.handler.bind($)); - } - - public resolve(param: string): TYPES { - if (this.subcommands.has(param)) return TYPES.SUBCOMMAND; - // Any Discord ID format will automatically format to a user ID. - else if (this.user && /\d{17,19}/.test(param)) return TYPES.USER; - // Disallow infinity and allow for 0. - else if ( - this.number && - (Number(param) || param === '0') && - !param.includes('Infinity') - ) - return TYPES.NUMBER; - else if (this.any) return TYPES.ANY; - else return TYPES.NONE; - } - - public get(param: string): Command { - const type = this.resolve(param); - let command: Command; - - switch (type) { - case TYPES.SUBCOMMAND: - command = this.subcommands.get(param) as Command; - break; - case TYPES.USER: - command = this.user as Command; - break; - case TYPES.NUMBER: - command = this.number as Command; - break; - case TYPES.ANY: - command = this.any as Command; - break; - default: - command = this; - break; + public execute($: CommonLibrary) { + if (isType(this.run, String)) { + $.channel.send( + parseVars( + this.run as string, + { + author: $.author.toString(), + prefix: getPrefix($.guild) + }, + "???" + ) + ); + } else (this.run as Function)($).catch($.handler.bind($)); } - return command; - } + public resolve(param: string): TYPES { + if (this.subcommands.has(param)) return TYPES.SUBCOMMAND; + // Any Discord ID format will automatically format to a user ID. + else if (this.user && /\d{17,19}/.test(param)) return TYPES.USER; + // Disallow infinity and allow for 0. + else if ( + this.number && + (Number(param) || param === "0") && + !param.includes("Infinity") + ) + return TYPES.NUMBER; + else if (this.any) return TYPES.ANY; + else return TYPES.NONE; + } + + public get(param: string): Command { + const type = this.resolve(param); + let command: Command; + + switch (type) { + case TYPES.SUBCOMMAND: + command = this.subcommands.get(param) as Command; + break; + case TYPES.USER: + command = this.user as Command; + break; + case TYPES.NUMBER: + command = this.number as Command; + break; + case TYPES.ANY: + command = this.any as Command; + break; + default: + command = this; + break; + } + + return command; + } } let commands: Collection | null = null; @@ -158,96 +160,95 @@ export const aliases: Collection = new Collection(); // Top-leve /** Returns the cache of the commands if it exists and searches the directory if not. */ export async function loadCommands(): Promise> { - if (commands) return commands; + if (commands) return commands; - if (process.argv[2] === 'dev' && !existsSync('src/commands/test.ts')) - writeFile( - 'src/commands/test.ts', - template, - generateHandler( - '"test.ts" (testing/template command) successfully generated.', - ), - ); + if (process.argv[2] === "dev" && !existsSync("src/commands/test.ts")) + writeFile( + "src/commands/test.ts", + template, + generateHandler( + '"test.ts" (testing/template command) successfully generated.' + ) + ); - commands = new Collection(); - const dir = await ffs.opendir('dist/commands'); - const listMisc: string[] = []; - let selected; + commands = new Collection(); + const dir = await ffs.opendir("dist/commands"); + const listMisc: string[] = []; + let selected; - // There will only be one level of directory searching (per category). - while ((selected = await dir.read())) { - if (selected.isDirectory()) { - if (selected.name === 'subcommands') continue; + // There will only be one level of directory searching (per category). + while ((selected = await dir.read())) { + if (selected.isDirectory()) { + if (selected.name === "subcommands") continue; - const subdir = await ffs.opendir(`dist/commands/${selected.name}`); - const category = $(selected.name).toTitleCase(); - const list: string[] = []; - let cmd; + const subdir = await ffs.opendir(`dist/commands/${selected.name}`); + const category = $(selected.name).toTitleCase(); + const list: string[] = []; + let cmd; - while ((cmd = await subdir.read())) { - if (cmd.isDirectory()) { - if (cmd.name === 'subcommands') continue; - else - $.warn( - `You can't have multiple levels of directories! From: "dist/commands/${cmd.name}"`, - ); - } else loadCommand(cmd.name, list, selected.name); - } + while ((cmd = await subdir.read())) { + if (cmd.isDirectory()) { + if (cmd.name === "subcommands") continue; + else + $.warn( + `You can't have multiple levels of directories! From: "dist/commands/${cmd.name}"` + ); + } else loadCommand(cmd.name, list, selected.name); + } - subdir.close(); - categories.set(category, list); - } else loadCommand(selected.name, listMisc); - } + subdir.close(); + categories.set(category, list); + } else loadCommand(selected.name, listMisc); + } - dir.close(); - categories.set('Miscellaneous', listMisc); + dir.close(); + categories.set("Miscellaneous", listMisc); - return commands; + return commands; } async function loadCommand( - filename: string, - list: string[], - category?: string, + filename: string, + list: string[], + category?: string ) { - if (!commands) - return $.error( - `Function "loadCommand" was called without first initializing commands!`, + if (!commands) + return $.error( + `Function "loadCommand" was called without first initializing commands!` + ); + + const prefix = category ?? ""; + const header = filename.substring(0, filename.indexOf(".js")); + const command = (await import(`../commands/${prefix}/${header}`)) + .default as Command | undefined; + + if (!command) + return $.warn( + `Command "${header}" has no default export which is a Command instance!` + ); + + command.originalCommandName = header; + list.push(header); + + if (commands.has(header)) + $.warn( + `Command "${header}" already exists! Make sure to make each command uniquely identifiable across categories!` + ); + else commands.set(header, command); + + for (const alias of command.aliases) { + if (commands.has(alias)) + $.warn( + `Top-level alias "${alias}" from command "${header}" already exists either as a command or alias!` + ); + else commands.set(alias, command); + } + + $.log( + `Loading Command: ${header} (${ + category ? $(category).toTitleCase() : "Miscellaneous" + })` ); - - const prefix = category ?? ''; - const header = filename.substring(0, filename.indexOf('.js')); - const command = (await import(`../commands/${prefix}/${header}`)).default as - | Command - | undefined; - - if (!command) - return $.warn( - `Command "${header}" has no default export which is a Command instance!`, - ); - - command.originalCommandName = header; - list.push(header); - - if (commands.has(header)) - $.warn( - `Command "${header}" already exists! Make sure to make each command uniquely identifiable across categories!`, - ); - else commands.set(header, command); - - for (const alias of command.aliases) { - if (commands.has(alias)) - $.warn( - `Top-level alias "${alias}" from command "${header}" already exists either as a command or alias!`, - ); - else commands.set(alias, command); - } - - $.log( - `Loading Command: ${header} (${ - category ? $(category).toTitleCase() : 'Miscellaneous' - })`, - ); } // The template should be built with a reductionist mentality. diff --git a/src/core/event.ts b/src/core/event.ts index fac4bc8..e84d5a6 100644 --- a/src/core/event.ts +++ b/src/core/event.ts @@ -1,41 +1,41 @@ -import { Client, ClientEvents, Constants } from 'discord.js'; -import Storage from './storage'; -import $ from './lib'; +import {Client, ClientEvents, Constants} from "discord.js"; +import Storage from "./storage"; +import $ from "./lib"; interface EventOptions { - readonly on?: (...args: ClientEvents[K]) => void; - readonly once?: (...args: ClientEvents[K]) => void; + readonly on?: (...args: ClientEvents[K]) => void; + readonly once?: (...args: ClientEvents[K]) => void; } export default class Event { - private readonly on?: (...args: ClientEvents[K]) => void; - private readonly once?: (...args: ClientEvents[K]) => void; + private readonly on?: (...args: ClientEvents[K]) => void; + private readonly once?: (...args: ClientEvents[K]) => void; - constructor(options: EventOptions) { - this.on = options.on; - this.once = options.once; - } + constructor(options: EventOptions) { + this.on = options.on; + this.once = options.once; + } - // For this function, I'm going to assume that the event is used with the correct arguments and that the event tag is checked in "storage.ts". - public attach(client: Client, event: K) { - if (this.on) client.on(event, this.on); - if (this.once) client.once(event, this.once); - } + // For this function, I'm going to assume that the event is used with the correct arguments and that the event tag is checked in "storage.ts". + public attach(client: Client, event: K) { + if (this.on) client.on(event, this.on); + if (this.once) client.once(event, this.once); + } } export async function loadEvents(client: Client) { - for (const file of Storage.open('dist/events', (filename: string) => - filename.endsWith('.js'), - )) { - const header = file.substring(0, file.indexOf('.js')); - const event = (await import(`../events/${header}`)).default; + for (const file of Storage.open("dist/events", (filename: string) => + filename.endsWith(".js") + )) { + const header = file.substring(0, file.indexOf(".js")); + const event = (await import(`../events/${header}`)).default; - if ((Object.values(Constants.Events) as string[]).includes(header)) { - event.attach(client, header); - $.log(`Loading Event: ${header}`); - } else - $.warn( - `"${header}" is not a valid event type! Did you misspell it? (Note: If you fixed the issue, delete "dist" because the compiler won't automatically delete any extra files.)`, - ); - } + if ((Object.values(Constants.Events) as string[]).includes(header)) { + event.attach(client, header); + $.log(`Loading Event: ${header}`); + } else + $.warn( + `"${header}" is not a valid event type! Did you misspell it? (Note: If you fixed the issue, delete "dist" because the compiler won't automatically delete any extra files.)` + ); + } } diff --git a/src/core/lib.ts b/src/core/lib.ts index b30bcb5..21ea8b8 100644 --- a/src/core/lib.ts +++ b/src/core/lib.ts @@ -1,506 +1,515 @@ -import { - GenericWrapper, - NumberWrapper, - StringWrapper, - ArrayWrapper, -} from './wrappers'; -import { - Client, - Message, - TextChannel, - DMChannel, - NewsChannel, - Guild, - User, - GuildMember, - Permissions, -} from 'discord.js'; -import chalk from 'chalk'; -import { get } from 'https'; -import FileManager from './storage'; -import { eventListeners } from '../events/messageReactionRemove'; -import { client } from '../index'; - -/** A type that describes what the library module does. */ -export interface CommonLibrary { - // Wrapper Object // - /** Wraps the value you enter with an object that provides extra functionality and provides common utility functions. */ - (value: number): NumberWrapper; - (value: string): StringWrapper; - (value: T[]): ArrayWrapper; - (value: T): GenericWrapper; - - // Common Library Functions // - /** .catch($.handler.bind($)) or .catch(error => $.handler(error)) */ - handler: (error: Error) => void; - log: (...args: any[]) => void; - warn: (...args: any[]) => void; - error: (...args: any[]) => void; - debug: (...args: any[]) => void; - ready: (...args: any[]) => void; - paginate: ( - message: Message, - senderID: string, - total: number, - callback: (page: number) => void, - duration?: number, - ) => void; - prompt: ( - message: Message, - senderID: string, - onConfirm: () => void, - duration?: number, - ) => void; - getMemberByUsername: ( - guild: Guild, - username: string, - ) => Promise; - callMemberByUsername: ( - message: Message, - username: string, - onSuccess: (member: GuildMember) => void, - ) => Promise; - - // Dynamic Properties // - args: any[]; - client: Client; - message: Message; - channel: TextChannel | DMChannel | NewsChannel; - guild: Guild | null; - author: User; - member: GuildMember | null; -} - -export default function $(value: number): NumberWrapper; -export default function $(value: string): StringWrapper; -export default function $(value: T[]): ArrayWrapper; -export default function $(value: T): GenericWrapper; -export default function $(value: any) { - if (isType(value, Number)) return new NumberWrapper(value); - else if (isType(value, String)) return new StringWrapper(value); - else if (isType(value, Array)) return new ArrayWrapper(value); - else return new GenericWrapper(value); -} - -// If you use promises, use this function to display the error in chat. -// Case #1: await $.channel.send(""); --> Automatically caught by Command.execute(). -// Case #2: $.channel.send("").catch($.handler.bind($)); --> Manually caught by the user. -$.handler = function (this: CommonLibrary, error: Error) { - if (this) - this.channel.send( - `There was an error while trying to execute that command!\`\`\`${ - error.stack ?? error - }\`\`\``, - ); - else - $.warn( - 'No context was attached to $.handler! Make sure to use .catch($.handler.bind($)) or .catch(error => $.handler(error)) instead!', - ); - - $.error(error); -}; - -// Logs with different levels of verbosity. -export const logs: { [type: string]: string } = { - error: '', - warn: '', - info: '', - verbose: '', -}; - -let enabled = true; -export function setConsoleActivated(activated: boolean) { - enabled = activated; -} - -// The custom console. In order of verbosity, error, warn, log, and debug. Ready is a variation of log. -// General Purpose Logger -$.log = (...args: any[]) => { - if (enabled) - console.log( - chalk.white.bgGray(formatTimestamp()), - chalk.black.bgWhite('INFO'), - ...args, - ); - const text = `[${formatUTCTimestamp()}] [INFO] ${args.join(' ')}\n`; - logs.info += text; - logs.verbose += text; -}; -// "It'll still work, but you should really check up on this." -$.warn = (...args: any[]) => { - if (enabled) - console.warn( - chalk.white.bgGray(formatTimestamp()), - chalk.black.bgYellow('WARN'), - ...args, - ); - const text = `[${formatUTCTimestamp()}] [WARN] ${args.join(' ')}\n`; - logs.warn += text; - logs.info += text; - logs.verbose += text; -}; -// Used for anything which prevents the program from actually running. -$.error = (...args: any[]) => { - if (enabled) - console.error( - chalk.white.bgGray(formatTimestamp()), - chalk.white.bgRed('ERROR'), - ...args, - ); - const text = `[${formatUTCTimestamp()}] [ERROR] ${args.join(' ')}\n`; - logs.error += text; - logs.warn += text; - logs.info += text; - logs.verbose += text; -}; -// Be as verbose as possible. If anything might help when debugging an error, then include it. This only shows in your console if you run this with "dev", but you can still get it from "logs.verbose". -// $.debug(`core/lib::parseArgs("testing \"in progress\"") = ["testing", "in progress"]`) --> /::(.)() = -// Would probably be more suited for debugging program logic rather than function logic, which can be checked using unit tests. -$.debug = (...args: any[]) => { - if (process.argv[2] === 'dev' && enabled) - console.debug( - chalk.white.bgGray(formatTimestamp()), - chalk.white.bgBlue('DEBUG'), - ...args, - ); - const text = `[${formatUTCTimestamp()}] [DEBUG] ${args.join(' ')}\n`; - logs.verbose += text; -}; -// Used once at the start of the program when the bot loads. -$.ready = (...args: any[]) => { - if (enabled) - console.log( - chalk.white.bgGray(formatTimestamp()), - chalk.black.bgGreen('READY'), - ...args, - ); - const text = `[${formatUTCTimestamp()}] [READY] ${args.join(' ')}\n`; - logs.info += text; - logs.verbose += text; -}; - -export function formatTimestamp(now = new Date()) { - const year = now.getFullYear(); - const month = (now.getMonth() + 1).toString().padStart(2, '0'); - const day = now.getDate().toString().padStart(2, '0'); - const hour = now.getHours().toString().padStart(2, '0'); - const minute = now.getMinutes().toString().padStart(2, '0'); - const second = now.getSeconds().toString().padStart(2, '0'); - return `${year}-${month}-${day} ${hour}:${minute}:${second}`; -} - -export function formatUTCTimestamp(now = new Date()) { - const year = now.getUTCFullYear(); - const month = (now.getUTCMonth() + 1).toString().padStart(2, '0'); - const day = now.getUTCDate().toString().padStart(2, '0'); - const hour = now.getUTCHours().toString().padStart(2, '0'); - const minute = now.getUTCMinutes().toString().padStart(2, '0'); - const second = now.getUTCSeconds().toString().padStart(2, '0'); - return `${year}-${month}-${day} ${hour}:${minute}:${second}`; -} - -export function botHasPermission( - guild: Guild | null, - permission: number, -): boolean { - return !!( - client.user && - guild?.members.resolve(client.user)?.hasPermission(permission) - ); -} - -// Pagination function that allows for customization via a callback. -// Define your own pages outside the function because this only manages the actual turning of pages. -$.paginate = async ( - message: Message, - senderID: string, - total: number, - callback: (page: number) => void, - duration = 60000, -) => { - let page = 0; - const turn = (amount: number) => { - page += amount; - - if (page < 0) page += total; - else if (page >= total) page -= total; - - callback(page); - }; - const handle = (emote: string, reacterID: string) => { - switch (emote) { - case '⬅️': - turn(-1); - break; - case '➡️': - turn(1); - break; - } - }; - - // Listen for reactions and call the handler. - await message.react('⬅️'); - await message.react('➡️'); - eventListeners.set(message.id, handle); - await message.awaitReactions( - (reaction, user) => { - if (user.id === senderID) { - // The reason this is inside the call is because it's possible to switch a user's permissions halfway and suddenly throw an error. - // This will dynamically adjust for that, switching modes depending on whether it currently has the "Manage Messages" permission. - const canDeleteEmotes = botHasPermission( - message.guild, - Permissions.FLAGS.MANAGE_MESSAGES, - ); - handle(reaction.emoji.name, user.id); - - if (canDeleteEmotes) reaction.users.remove(user); - } - return false; - }, - { time: duration }, - ); - // When time's up, remove the bot's own reactions. - eventListeners.delete(message.id); - message.reactions.cache.get('⬅️')?.users.remove(message.author); - message.reactions.cache.get('➡️')?.users.remove(message.author); -}; - -// Waits for the sender to either confirm an action or let it pass (and delete the message). -$.prompt = async ( - message: Message, - senderID: string, - onConfirm: () => void, - duration = 10000, -) => { - let isDeleted = false; - - message.react('✅'); - await message.awaitReactions( - (reaction, user) => { - if (user.id === senderID) { - if (reaction.emoji.name === '✅') onConfirm(); - isDeleted = true; - message.delete(); - } - - // CollectorFilter requires a boolean to be returned. - // My guess is that the return value of awaitReactions can be altered by making a boolean filter. - // However, because that's not my concern with this command, I don't have to worry about it. - // May as well just set it to false because I'm not concerned with collecting any reactions. - return false; - }, - { time: duration }, - ); - - if (!isDeleted) message.delete(); -}; - -$.getMemberByUsername = async (guild: Guild, username: string) => { - return ( - await guild.members.fetch({ - query: username, - limit: 1, - }) - ).first(); -}; - -/** Convenience function to handle false cases automatically. */ -$.callMemberByUsername = async ( - message: Message, - username: string, - onSuccess: (member: GuildMember) => void, -) => { - const guild = message.guild; - const send = message.channel.send; - - if (guild) { - const member = await $.getMemberByUsername(guild, username); - - if (member) onSuccess(member); - else send(`Couldn't find a user by the name of \`${username}\`!`); - } else send('You must execute this command in a server!'); -}; - -/** - * Splits a command by spaces while accounting for quotes which capture string arguments. - * - `\"` = `"` - * - `\\` = `\` - */ -export function parseArgs(line: string): string[] { - let result = []; - let selection = ''; - let inString = false; - let isEscaped = false; - - for (let c of line) { - if (isEscaped) { - if (['"', '\\'].includes(c)) selection += c; - else selection += '\\' + c; - - isEscaped = false; - } else if (c === '\\') isEscaped = true; - else if (c === '"') inString = !inString; - else if (c === ' ' && !inString) { - result.push(selection); - selection = ''; - } else selection += c; - } - - if (selection.length > 0) result.push(selection); - - return result; -} - -/** - * Allows you to store a template string with variable markers and parse it later. - * - Use `%name%` for variables - * - `%%` = `%` - * - If the invalid token is null/undefined, nothing is changed. - */ -export function parseVars( - line: string, - definitions: { [key: string]: string }, - invalid: string | null = '', -): string { - let result = ''; - let inVariable = false; - let token = ''; - - for (const c of line) { - if (c === '%') { - if (inVariable) { - if (token === '') result += '%'; - else { - if (token in definitions) result += definitions[token]; - else if (invalid === null) result += `%${token}%`; - else result += invalid; - - token = ''; - } - } - - inVariable = !inVariable; - } else if (inVariable) token += c; - else result += c; - } - - return result; -} - -export function isType(value: any, type: any): boolean { - if (value === undefined && type === undefined) return true; - else if (value === null && type === null) return true; - else - return value !== undefined && value !== null && value.constructor === type; -} - -/** - * Checks a value to see if it matches the fallback's type, otherwise returns the fallback. - * For the purposes of the templates system, this function will only check array types, objects should be checked under their own type (as you'd do anyway with something like a User object). - * If at any point the value doesn't match the data structure provided, the fallback is returned. - * Warning: Type checking is based on the fallback's type. Be sure that the "type" parameter is accurate to this! - */ -export function select( - value: any, - fallback: T, - type: Function, - isArray = false, -): T { - if (isArray && isType(value, Array)) { - for (let item of value) if (!isType(item, type)) return fallback; - return value; - } else { - if (isType(value, type)) return value; - else return fallback; - } -} - -export function clean(text: any) { - if (typeof text === 'string') - return text - .replace(/`/g, '`' + String.fromCharCode(8203)) - .replace(/@/g, '@' + String.fromCharCode(8203)); - else return text; -} - -export function trimArray(arr: any, maxLen = 10) { - if (arr.length > maxLen) { - const len = arr.length - maxLen; - arr = arr.slice(0, maxLen); - arr.push(`${len} more...`); - } - return arr; -} - -export function formatBytes(bytes: any) { - if (bytes === 0) return '0 Bytes'; - const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; - const i = Math.floor(Math.log(bytes) / Math.log(1024)); - return `${parseFloat((bytes / Math.pow(1024, i)).toFixed(2))} ${sizes[i]}`; -} - -export function getContent(url: any) { - return new Promise((resolve, reject) => { - get( - url, - (res: { - resume?: any; - setEncoding?: any; - on?: any; - statusCode?: any; - }) => { - const { statusCode } = res; - if (statusCode !== 200) { - res.resume(); - reject(`Request failed. Status code: ${statusCode}`); - } - res.setEncoding('utf8'); - let rawData = ''; - res.on('data', (chunk: string) => { - rawData += chunk; - }); - res.on('end', () => { - try { - const parsedData = JSON.parse(rawData); - resolve(parsedData); - } catch (e) { - reject(`Error: ${e.message}`); - } - }); - }, - ).on('error', (err: { message: any }) => { - reject(`Error: ${err.message}`); - }); - }); -} - -export interface GenericJSON { - [key: string]: any; -} - -export abstract class GenericStructure { - private __meta__ = 'generic'; - - constructor(tag?: string) { - this.__meta__ = tag || this.__meta__; - } - - public save(asynchronous = true) { - const tag = this.__meta__; - /// @ts-ignore - delete this.__meta__; - FileManager.write(tag, this, asynchronous); - this.__meta__ = tag; - } -} - -// A 50% chance would be "Math.random() < 0.5" because Math.random() can be [0, 1), so to make two equal ranges, you'd need [0, 0.5)U[0.5, 1). -// Similar logic would follow for any other percentage. Math.random() < 1 is always true (100% chance) and Math.random() < 0 is always false (0% chance). -export const Random = { - num: (min: number, max: number) => Math.random() * (max - min) + min, - int: (min: number, max: number) => Math.floor(Random.num(min, max)), - chance: (decimal: number) => Math.random() < decimal, - sign: (number = 1) => number * (Random.chance(0.5) ? -1 : 1), - deviation: (base: number, deviation: number) => - Random.num(base - deviation, base + deviation), -}; +import { + GenericWrapper, + NumberWrapper, + StringWrapper, + ArrayWrapper +} from "./wrappers"; +import { + Client, + Message, + TextChannel, + DMChannel, + NewsChannel, + Guild, + User, + GuildMember, + Permissions +} from "discord.js"; +import chalk from "chalk"; +import {get} from "https"; +import FileManager from "./storage"; +import {eventListeners} from "../events/messageReactionRemove"; +import {client} from "../index"; + +/** A type that describes what the library module does. */ +export interface CommonLibrary { + // Wrapper Object // + /** Wraps the value you enter with an object that provides extra functionality and provides common utility functions. */ + (value: number): NumberWrapper; + (value: string): StringWrapper; + (value: T[]): ArrayWrapper; + (value: T): GenericWrapper; + + // Common Library Functions // + /** .catch($.handler.bind($)) or .catch(error => $.handler(error)) */ + handler: (error: Error) => void; + log: (...args: any[]) => void; + warn: (...args: any[]) => void; + error: (...args: any[]) => void; + debug: (...args: any[]) => void; + ready: (...args: any[]) => void; + paginate: ( + message: Message, + senderID: string, + total: number, + callback: (page: number) => void, + duration?: number + ) => void; + prompt: ( + message: Message, + senderID: string, + onConfirm: () => void, + duration?: number + ) => void; + getMemberByUsername: ( + guild: Guild, + username: string + ) => Promise; + callMemberByUsername: ( + message: Message, + username: string, + onSuccess: (member: GuildMember) => void + ) => Promise; + + // Dynamic Properties // + args: any[]; + client: Client; + message: Message; + channel: TextChannel | DMChannel | NewsChannel; + guild: Guild | null; + author: User; + member: GuildMember | null; +} + +export default function $(value: number): NumberWrapper; +export default function $(value: string): StringWrapper; +export default function $(value: T[]): ArrayWrapper; +export default function $(value: T): GenericWrapper; +export default function $(value: any) { + if (isType(value, Number)) return new NumberWrapper(value); + else if (isType(value, String)) return new StringWrapper(value); + else if (isType(value, Array)) return new ArrayWrapper(value); + else return new GenericWrapper(value); +} + +// If you use promises, use this function to display the error in chat. +// Case #1: await $.channel.send(""); --> Automatically caught by Command.execute(). +// Case #2: $.channel.send("").catch($.handler.bind($)); --> Manually caught by the user. +$.handler = function (this: CommonLibrary, error: Error) { + if (this) + this.channel.send( + `There was an error while trying to execute that command!\`\`\`${ + error.stack ?? error + }\`\`\`` + ); + else + $.warn( + "No context was attached to $.handler! Make sure to use .catch($.handler.bind($)) or .catch(error => $.handler(error)) instead!" + ); + + $.error(error); +}; + +// Logs with different levels of verbosity. +export const logs: {[type: string]: string} = { + error: "", + warn: "", + info: "", + verbose: "" +}; + +let enabled = true; + +export function setConsoleActivated(activated: boolean) { + enabled = activated; +} + +// The custom console. In order of verbosity, error, warn, log, and debug. Ready is a variation of log. +// General Purpose Logger +$.log = (...args: any[]) => { + if (enabled) + console.log( + chalk.white.bgGray(formatTimestamp()), + chalk.black.bgWhite("INFO"), + ...args + ); + + const text = `[${formatUTCTimestamp()}] [INFO] ${args.join(" ")}\n`; + logs.info += text; + logs.verbose += text; +}; +// "It'll still work, but you should really check up on this." +$.warn = (...args: any[]) => { + if (enabled) + console.warn( + chalk.white.bgGray(formatTimestamp()), + chalk.black.bgYellow("WARN"), + ...args + ); + + const text = `[${formatUTCTimestamp()}] [WARN] ${args.join(" ")}\n`; + logs.warn += text; + logs.info += text; + logs.verbose += text; +}; +// Used for anything which prevents the program from actually running. +$.error = (...args: any[]) => { + if (enabled) + console.error( + chalk.white.bgGray(formatTimestamp()), + chalk.white.bgRed("ERROR"), + ...args + ); + + const text = `[${formatUTCTimestamp()}] [ERROR] ${args.join(" ")}\n`; + logs.error += text; + logs.warn += text; + logs.info += text; + logs.verbose += text; +}; +// Be as verbose as possible. If anything might help when debugging an error, then include it. This only shows in your console if you run this with "dev", but you can still get it from "logs.verbose". +// $.debug(`core/lib::parseArgs("testing \"in progress\"") = ["testing", "in progress"]`) --> /::(.)() = +// Would probably be more suited for debugging program logic rather than function logic, which can be checked using unit tests. +$.debug = (...args: any[]) => { + if (process.argv[2] === "dev" && enabled) + console.debug( + chalk.white.bgGray(formatTimestamp()), + chalk.white.bgBlue("DEBUG"), + ...args + ); + + const text = `[${formatUTCTimestamp()}] [DEBUG] ${args.join(" ")}\n`; + logs.verbose += text; +}; +// Used once at the start of the program when the bot loads. +$.ready = (...args: any[]) => { + if (enabled) + console.log( + chalk.white.bgGray(formatTimestamp()), + chalk.black.bgGreen("READY"), + ...args + ); + + const text = `[${formatUTCTimestamp()}] [READY] ${args.join(" ")}\n`; + logs.info += text; + logs.verbose += text; +}; + +export function formatTimestamp(now = new Date()) { + const year = now.getFullYear(); + const month = (now.getMonth() + 1).toString().padStart(2, "0"); + const day = now.getDate().toString().padStart(2, "0"); + const hour = now.getHours().toString().padStart(2, "0"); + const minute = now.getMinutes().toString().padStart(2, "0"); + const second = now.getSeconds().toString().padStart(2, "0"); + return `${year}-${month}-${day} ${hour}:${minute}:${second}`; +} + +export function formatUTCTimestamp(now = new Date()) { + const year = now.getUTCFullYear(); + const month = (now.getUTCMonth() + 1).toString().padStart(2, "0"); + const day = now.getUTCDate().toString().padStart(2, "0"); + const hour = now.getUTCHours().toString().padStart(2, "0"); + const minute = now.getUTCMinutes().toString().padStart(2, "0"); + const second = now.getUTCSeconds().toString().padStart(2, "0"); + return `${year}-${month}-${day} ${hour}:${minute}:${second}`; +} + +export function botHasPermission( + guild: Guild | null, + permission: number +): boolean { + return !!( + client.user && + guild?.members.resolve(client.user)?.hasPermission(permission) + ); +} + +// Pagination function that allows for customization via a callback. +// Define your own pages outside the function because this only manages the actual turning of pages. +$.paginate = async ( + message: Message, + senderID: string, + total: number, + callback: (page: number) => void, + duration = 60000 +) => { + let page = 0; + const turn = (amount: number) => { + page += amount; + + if (page < 0) page += total; + else if (page >= total) page -= total; + + callback(page); + }; + const handle = (emote: string, reacterID: string) => { + switch (emote) { + case "⬅️": + turn(-1); + break; + case "➡️": + turn(1); + break; + } + }; + + // Listen for reactions and call the handler. + await message.react("⬅️"); + await message.react("➡️"); + eventListeners.set(message.id, handle); + await message.awaitReactions( + (reaction, user) => { + if (user.id === senderID) { + // The reason this is inside the call is because it's possible to switch a user's permissions halfway and suddenly throw an error. + // This will dynamically adjust for that, switching modes depending on whether it currently has the "Manage Messages" permission. + const canDeleteEmotes = botHasPermission( + message.guild, + Permissions.FLAGS.MANAGE_MESSAGES + ); + handle(reaction.emoji.name, user.id); + + if (canDeleteEmotes) reaction.users.remove(user); + } + + return false; + }, + {time: duration} + ); + // When time's up, remove the bot's own reactions. + eventListeners.delete(message.id); + message.reactions.cache.get("⬅️")?.users.remove(message.author); + message.reactions.cache.get("➡️")?.users.remove(message.author); +}; + +// Waits for the sender to either confirm an action or let it pass (and delete the message). +$.prompt = async ( + message: Message, + senderID: string, + onConfirm: () => void, + duration = 10000 +) => { + let isDeleted = false; + + message.react("✅"); + await message.awaitReactions( + (reaction, user) => { + if (user.id === senderID) { + if (reaction.emoji.name === "✅") onConfirm(); + isDeleted = true; + message.delete(); + } + + // CollectorFilter requires a boolean to be returned. + // My guess is that the return value of awaitReactions can be altered by making a boolean filter. + // However, because that's not my concern with this command, I don't have to worry about it. + // May as well just set it to false because I'm not concerned with collecting any reactions. + return false; + }, + {time: duration} + ); + + if (!isDeleted) message.delete(); +}; + +$.getMemberByUsername = async (guild: Guild, username: string) => { + return ( + await guild.members.fetch({ + query: username, + limit: 1 + }) + ).first(); +}; + +/** Convenience function to handle false cases automatically. */ +$.callMemberByUsername = async ( + message: Message, + username: string, + onSuccess: (member: GuildMember) => void +) => { + const guild = message.guild; + const send = message.channel.send; + + if (guild) { + const member = await $.getMemberByUsername(guild, username); + + if (member) onSuccess(member); + else send(`Couldn't find a user by the name of \`${username}\`!`); + } else send("You must execute this command in a server!"); +}; + +/** + * Splits a command by spaces while accounting for quotes which capture string arguments. + * - `\"` = `"` + * - `\\` = `\` + */ +export function parseArgs(line: string): string[] { + let result = []; + let selection = ""; + let inString = false; + let isEscaped = false; + + for (let c of line) { + if (isEscaped) { + if (['"', "\\"].includes(c)) selection += c; + else selection += "\\" + c; + + isEscaped = false; + } else if (c === "\\") isEscaped = true; + else if (c === '"') inString = !inString; + else if (c === " " && !inString) { + result.push(selection); + selection = ""; + } else selection += c; + } + + if (selection.length > 0) result.push(selection); + + return result; +} + +/** + * Allows you to store a template string with variable markers and parse it later. + * - Use `%name%` for variables + * - `%%` = `%` + * - If the invalid token is null/undefined, nothing is changed. + */ +export function parseVars( + line: string, + definitions: {[key: string]: string}, + invalid: string | null = "" +): string { + let result = ""; + let inVariable = false; + let token = ""; + + for (const c of line) { + if (c === "%") { + if (inVariable) { + if (token === "") result += "%"; + else { + if (token in definitions) result += definitions[token]; + else if (invalid === null) result += `%${token}%`; + else result += invalid; + + token = ""; + } + } + + inVariable = !inVariable; + } else if (inVariable) token += c; + else result += c; + } + + return result; +} + +export function isType(value: any, type: any): boolean { + if (value === undefined && type === undefined) return true; + else if (value === null && type === null) return true; + else + return ( + value !== undefined && value !== null && value.constructor === type + ); +} + +/** + * Checks a value to see if it matches the fallback's type, otherwise returns the fallback. + * For the purposes of the templates system, this function will only check array types, objects should be checked under their own type (as you'd do anyway with something like a User object). + * If at any point the value doesn't match the data structure provided, the fallback is returned. + * Warning: Type checking is based on the fallback's type. Be sure that the "type" parameter is accurate to this! + */ +export function select( + value: any, + fallback: T, + type: Function, + isArray = false +): T { + if (isArray && isType(value, Array)) { + for (let item of value) if (!isType(item, type)) return fallback; + return value; + } else { + if (isType(value, type)) return value; + else return fallback; + } +} + +export function clean(text: any) { + if (typeof text === "string") + return text + .replace(/`/g, "`" + String.fromCharCode(8203)) + .replace(/@/g, "@" + String.fromCharCode(8203)); + else return text; +} + +export function trimArray(arr: any, maxLen = 10) { + if (arr.length > maxLen) { + const len = arr.length - maxLen; + arr = arr.slice(0, maxLen); + arr.push(`${len} more...`); + } + return arr; +} + +export function formatBytes(bytes: any) { + if (bytes === 0) return "0 Bytes"; + const sizes = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]; + const i = Math.floor(Math.log(bytes) / Math.log(1024)); + return `${parseFloat((bytes / Math.pow(1024, i)).toFixed(2))} ${sizes[i]}`; +} + +export function getContent(url: any) { + return new Promise((resolve, reject) => { + get( + url, + (res: { + resume?: any; + setEncoding?: any; + on?: any; + statusCode?: any; + }) => { + const {statusCode} = res; + if (statusCode !== 200) { + res.resume(); + reject(`Request failed. Status code: ${statusCode}`); + } + res.setEncoding("utf8"); + let rawData = ""; + res.on("data", (chunk: string) => { + rawData += chunk; + }); + res.on("end", () => { + try { + const parsedData = JSON.parse(rawData); + resolve(parsedData); + } catch (e) { + reject(`Error: ${e.message}`); + } + }); + } + ).on("error", (err: {message: any}) => { + reject(`Error: ${err.message}`); + }); + }); +} + +export interface GenericJSON { + [key: string]: any; +} + +export abstract class GenericStructure { + private __meta__ = "generic"; + + constructor(tag?: string) { + this.__meta__ = tag || this.__meta__; + } + + public save(asynchronous = true) { + const tag = this.__meta__; + /// @ts-ignore + delete this.__meta__; + FileManager.write(tag, this, asynchronous); + this.__meta__ = tag; + } +} + +// A 50% chance would be "Math.random() < 0.5" because Math.random() can be [0, 1), so to make two equal ranges, you'd need [0, 0.5)U[0.5, 1). +// Similar logic would follow for any other percentage. Math.random() < 1 is always true (100% chance) and Math.random() < 0 is always false (0% chance). +export const Random = { + num: (min: number, max: number) => Math.random() * (max - min) + min, + int: (min: number, max: number) => Math.floor(Random.num(min, max)), + chance: (decimal: number) => Math.random() < decimal, + sign: (number = 1) => number * (Random.chance(0.5) ? -1 : 1), + deviation: (base: number, deviation: number) => + Random.num(base - deviation, base + deviation) +}; diff --git a/src/core/permissions.ts b/src/core/permissions.ts index d190526..188ca9a 100644 --- a/src/core/permissions.ts +++ b/src/core/permissions.ts @@ -1,80 +1,81 @@ -import { GuildMember, Permissions } from 'discord.js'; -import { Config } from './structures'; -import $ from './lib'; +import {GuildMember, Permissions} from "discord.js"; +import {Config} from "./structures"; +import $ from "./lib"; export enum PERMISSIONS { - NONE, - MOD, - ADMIN, - OWNER, - BOT_SUPPORT, - BOT_ADMIN, - BOT_OWNER, + NONE, + MOD, + ADMIN, + OWNER, + BOT_SUPPORT, + BOT_ADMIN, + BOT_OWNER } + export const PermissionNames = [ - 'User', - 'Moderator', - 'Administrator', - 'Server Owner', - 'Bot Support', - 'Bot Admin', - 'Bot Owner', + "User", + "Moderator", + "Administrator", + "Server Owner", + "Bot Support", + "Bot Admin", + "Bot Owner" ]; // Here is where you enter in the functions that check for permissions. const PermissionChecker: ((member: GuildMember) => boolean)[] = [ - // NONE // - () => true, + // NONE // + () => true, - // MOD // - (member) => - member.hasPermission(Permissions.FLAGS.MANAGE_ROLES) || - member.hasPermission(Permissions.FLAGS.MANAGE_MESSAGES) || - member.hasPermission(Permissions.FLAGS.KICK_MEMBERS) || - member.hasPermission(Permissions.FLAGS.BAN_MEMBERS), + // MOD // + (member) => + member.hasPermission(Permissions.FLAGS.MANAGE_ROLES) || + member.hasPermission(Permissions.FLAGS.MANAGE_MESSAGES) || + member.hasPermission(Permissions.FLAGS.KICK_MEMBERS) || + member.hasPermission(Permissions.FLAGS.BAN_MEMBERS), - // ADMIN // - (member) => member.hasPermission(Permissions.FLAGS.ADMINISTRATOR), + // ADMIN // + (member) => member.hasPermission(Permissions.FLAGS.ADMINISTRATOR), - // OWNER // - (member) => member.guild.ownerID === member.id, + // OWNER // + (member) => member.guild.ownerID === member.id, - // BOT_SUPPORT // - (member) => Config.support.includes(member.id), + // BOT_SUPPORT // + (member) => Config.support.includes(member.id), - // BOT_ADMIN // - (member) => Config.admins.includes(member.id), + // BOT_ADMIN // + (member) => Config.admins.includes(member.id), - // BOT_OWNER // - (member) => Config.owner === member.id, + // BOT_OWNER // + (member) => Config.owner === member.id ]; // After checking the lengths of these three objects, use this as the length for consistency. const length = Object.keys(PERMISSIONS).length / 2; export function hasPermission( - member: GuildMember, - permission: PERMISSIONS, + member: GuildMember, + permission: PERMISSIONS ): boolean { - for (let i = length - 1; i >= permission; i--) - if (PermissionChecker[i](member)) return true; - return false; + for (let i = length - 1; i >= permission; i--) + if (PermissionChecker[i](member)) return true; + return false; } export function getPermissionLevel(member: GuildMember): number { - for (let i = length - 1; i >= 0; i--) - if (PermissionChecker[i](member)) return i; - return 0; + for (let i = length - 1; i >= 0; i--) + if (PermissionChecker[i](member)) return i; + return 0; } // Length Checking (() => { - const lenNames = PermissionNames.length; - const lenChecker = PermissionChecker.length; + const lenNames = PermissionNames.length; + const lenChecker = PermissionChecker.length; - // By transitive property, lenNames and lenChecker have to be equal to each other as well. - if (length !== lenNames || length !== lenChecker) - $.error( - `Permission object lengths aren't equal! Enum Length (${length}), Names Length (${lenNames}), and Functions Length (${lenChecker}). This WILL cause problems!`, - ); + // By transitive property, lenNames and lenChecker have to be equal to each other as well. + if (length !== lenNames || length !== lenChecker) + $.error( + `Permission object lengths aren't equal! Enum Length (${length}), Names Length (${lenNames}), and Functions Length (${lenChecker}). This WILL cause problems!` + ); })(); diff --git a/src/core/storage.ts b/src/core/storage.ts index 8048993..db3bc21 100644 --- a/src/core/storage.ts +++ b/src/core/storage.ts @@ -1,85 +1,87 @@ -import fs from 'fs'; -import $ from './lib'; +import fs from "fs"; +import $ from "./lib"; const Storage = { - read(header: string): object { - this.open('data'); - const path = `data/${header}.json`; - let data = {}; + read(header: string): object { + this.open("data"); + const path = `data/${header}.json`; + let data = {}; - if (fs.existsSync(path)) { - const file = fs.readFileSync(path, 'utf-8'); + if (fs.existsSync(path)) { + const file = fs.readFileSync(path, "utf-8"); - try { - data = JSON.parse(file); - } catch (error) { - if (process.argv[2] !== 'dev') { - $.warn( - `Malformed JSON data (header: ${header}), backing it up.`, - file, - ); - fs.writeFile( - `${path}.backup`, - file, - generateHandler( - `Backup file of "${header}" successfully written as ${file}.`, - ), - ); + try { + data = JSON.parse(file); + } catch (error) { + if (process.argv[2] !== "dev") { + $.warn( + `Malformed JSON data (header: ${header}), backing it up.`, + file + ); + fs.writeFile( + `${path}.backup`, + file, + generateHandler( + `Backup file of "${header}" successfully written as ${file}.` + ) + ); + } + } } - } + + return data; + }, + write(header: string, data: object, asynchronous = true) { + this.open("data"); + const path = `data/${header}.json`; + + if (process.argv[2] === "dev" || header === "config") { + const result = JSON.stringify(data, null, "\t"); + + if (asynchronous) + fs.writeFile( + path, + result, + generateHandler( + `"${header}" sucessfully spaced and written.` + ) + ); + else fs.writeFileSync(path, result); + } else { + const result = JSON.stringify(data); + + if (asynchronous) + fs.writeFile( + path, + result, + generateHandler(`"${header}" sucessfully written.`) + ); + else fs.writeFileSync(path, result); + } + }, + open( + path: string, + filter?: (value: string, index: number, array: string[]) => unknown + ): string[] { + if (!fs.existsSync(path)) fs.mkdirSync(path); + + let directory = fs.readdirSync(path); + + if (filter) directory = directory.filter(filter); + + return directory; + }, + close(path: string) { + if (fs.existsSync(path) && fs.readdirSync(path).length === 0) + fs.rmdir(path, generateHandler(`"${path}" successfully closed.`)); } - - return data; - }, - write(header: string, data: object, asynchronous = true) { - this.open('data'); - const path = `data/${header}.json`; - - if (process.argv[2] === 'dev' || header === 'config') { - const result = JSON.stringify(data, null, '\t'); - - if (asynchronous) - fs.writeFile( - path, - result, - generateHandler(`"${header}" sucessfully spaced and written.`), - ); - else fs.writeFileSync(path, result); - } else { - const result = JSON.stringify(data); - - if (asynchronous) - fs.writeFile( - path, - result, - generateHandler(`"${header}" sucessfully written.`), - ); - else fs.writeFileSync(path, result); - } - }, - open( - path: string, - filter?: (value: string, index: number, array: string[]) => unknown, - ): string[] { - if (!fs.existsSync(path)) fs.mkdirSync(path); - - let directory = fs.readdirSync(path); - - if (filter) directory = directory.filter(filter); - - return directory; - }, - close(path: string) { - if (fs.existsSync(path) && fs.readdirSync(path).length === 0) - fs.rmdir(path, generateHandler(`"${path}" successfully closed.`)); - }, }; export function generateHandler(message: string) { - return (error: Error | null) => { - if (error) $.error(error); - else $.debug(message); - }; + return (error: Error | null) => { + if (error) $.error(error); + else $.debug(message); + }; } export default Storage; diff --git a/src/core/structures.ts b/src/core/structures.ts index e958714..243ce34 100644 --- a/src/core/structures.ts +++ b/src/core/structures.ts @@ -1,112 +1,114 @@ -import FileManager from './storage'; -import $, { select, GenericJSON, GenericStructure } from './lib'; -import { watch } from 'fs'; -import { Guild as DiscordGuild } from 'discord.js'; +import FileManager from "./storage"; +import $, {select, GenericJSON, GenericStructure} from "./lib"; +import {watch} from "fs"; +import {Guild as DiscordGuild} from "discord.js"; class ConfigStructure extends GenericStructure { - public token: string; - public prefix: string; - public owner: string; - public admins: string[]; - public support: string[]; + public token: string; + public prefix: string; + public owner: string; + public admins: string[]; + public support: string[]; - constructor(data: GenericJSON) { - super('config'); - this.token = select(data.token, '', String); - this.prefix = select(data.prefix, '$', String); - this.owner = select(data.owner, '', String); - this.admins = select(data.admins, [], String, true); - this.support = select(data.support, [], String, true); - } + constructor(data: GenericJSON) { + super("config"); + this.token = select(data.token, "", String); + this.prefix = select(data.prefix, "$", String); + this.owner = select(data.owner, "", String); + this.admins = select(data.admins, [], String, true); + this.support = select(data.support, [], String, true); + } } class User { - public money: number; - public lastReceived: number; + public money: number; + public lastReceived: number; - constructor(data?: GenericJSON) { - this.money = select(data?.money, 0, Number); - this.lastReceived = select(data?.lastReceived, -1, Number); - } + constructor(data?: GenericJSON) { + this.money = select(data?.money, 0, Number); + this.lastReceived = select(data?.lastReceived, -1, Number); + } } class Guild { - public prefix: string | null; + public prefix: string | null; - constructor(data?: GenericJSON) { - this.prefix = select(data?.prefix, null, String); - } + constructor(data?: GenericJSON) { + this.prefix = select(data?.prefix, null, String); + } } class StorageStructure extends GenericStructure { - public users: { [id: string]: User }; - public guilds: { [id: string]: Guild }; + public users: {[id: string]: User}; + public guilds: {[id: string]: Guild}; - constructor(data: GenericJSON) { - super('storage'); - this.users = {}; - this.guilds = {}; + constructor(data: GenericJSON) { + super("storage"); + this.users = {}; + this.guilds = {}; - for (let id in data.users) - if (/\d{17,19}/g.test(id)) this.users[id] = new User(data.users[id]); + for (let id in data.users) + if (/\d{17,19}/g.test(id)) + this.users[id] = new User(data.users[id]); - for (let id in data.guilds) - if (/\d{17,19}/g.test(id)) this.guilds[id] = new Guild(data.guilds[id]); - } - - /** Gets a user's profile if they exist and generate one if not. */ - public getUser(id: string): User { - if (!/\d{17,19}/g.test(id)) - $.warn( - `"${id}" is not a valid user ID! It will be erased when the data loads again.`, - ); - - if (id in this.users) return this.users[id]; - else { - const user = new User(); - this.users[id] = user; - return user; + for (let id in data.guilds) + if (/\d{17,19}/g.test(id)) + this.guilds[id] = new Guild(data.guilds[id]); } - } - /** Gets a guild's settings if they exist and generate one if not. */ - public getGuild(id: string): Guild { - if (!/\d{17,19}/g.test(id)) - $.warn( - `"${id}" is not a valid guild ID! It will be erased when the data loads again.`, - ); + /** Gets a user's profile if they exist and generate one if not. */ + public getUser(id: string): User { + if (!/\d{17,19}/g.test(id)) + $.warn( + `"${id}" is not a valid user ID! It will be erased when the data loads again.` + ); - if (id in this.guilds) return this.guilds[id]; - else { - const guild = new Guild(); - this.guilds[id] = guild; - return guild; + if (id in this.users) return this.users[id]; + else { + const user = new User(); + this.users[id] = user; + return user; + } + } + + /** Gets a guild's settings if they exist and generate one if not. */ + public getGuild(id: string): Guild { + if (!/\d{17,19}/g.test(id)) + $.warn( + `"${id}" is not a valid guild ID! It will be erased when the data loads again.` + ); + + if (id in this.guilds) return this.guilds[id]; + else { + const guild = new Guild(); + this.guilds[id] = guild; + return guild; + } } - } } // Exports instances. Don't worry, importing it from different files will load the same instance. -export let Config = new ConfigStructure(FileManager.read('config')); -export let Storage = new StorageStructure(FileManager.read('storage')); +export let Config = new ConfigStructure(FileManager.read("config")); +export let Storage = new StorageStructure(FileManager.read("storage")); // This part will allow the user to manually edit any JSON files they want while the program is running which'll update the program's cache. // However, fs.watch is a buggy mess that should be avoided in production. While it helps test out stuff for development, it's not a good idea to have it running outside of development as it causes all sorts of issues. -if (process.argv[2] === 'dev') { - watch('data', (event, filename) => { - $.debug('File Watcher:', event, filename); - const header = filename.substring(0, filename.indexOf('.json')); +if (process.argv[2] === "dev") { + watch("data", (event, filename) => { + $.debug("File Watcher:", event, filename); + const header = filename.substring(0, filename.indexOf(".json")); - switch (header) { - case 'config': - Config = new ConfigStructure(FileManager.read('config')); - break; - case 'storage': - Storage = new StorageStructure(FileManager.read('storage')); - break; - } - }); + switch (header) { + case "config": + Config = new ConfigStructure(FileManager.read("config")); + break; + case "storage": + Storage = new StorageStructure(FileManager.read("storage")); + break; + } + }); } export function getPrefix(guild: DiscordGuild | null): string { - return Storage.getGuild(guild?.id || 'N/A').prefix ?? Config.prefix; + return Storage.getGuild(guild?.id || "N/A").prefix ?? Config.prefix; } diff --git a/src/core/wrappers.ts b/src/core/wrappers.ts index 96df202..9c103a4 100644 --- a/src/core/wrappers.ts +++ b/src/core/wrappers.ts @@ -1,87 +1,94 @@ export class GenericWrapper { - protected readonly value: T; + protected readonly value: T; - public constructor(value: T) { - this.value = value; - } + public constructor(value: T) { + this.value = value; + } } export class NumberWrapper extends GenericWrapper { - /** - * Pluralises a word and chooses a suffix attached to the root provided. - * - pluralise("credit", "s") = credit/credits - * - pluralise("part", "ies", "y") = party/parties - * - pluralise("sheep") = sheep - */ - public pluralise( - word: string, - plural = '', - singular = '', - excludeNumber = false, - ): string { - let result = excludeNumber ? '' : `${this.value} `; + /** + * Pluralises a word and chooses a suffix attached to the root provided. + * - pluralise("credit", "s") = credit/credits + * - pluralise("part", "ies", "y") = party/parties + * - pluralise("sheep") = sheep + */ + public pluralise( + word: string, + plural = "", + singular = "", + excludeNumber = false + ): string { + let result = excludeNumber ? "" : `${this.value} `; - if (this.value === 1) result += word + singular; - else result += word + plural; + if (this.value === 1) result += word + singular; + else result += word + plural; - return result; - } + return result; + } - /** - * Pluralises a word for changes. - * - (-1).pluraliseSigned() = '-1 credits' - * - (0).pluraliseSigned() = '+0 credits' - * - (1).pluraliseSigned() = '+1 credit' - */ - public pluraliseSigned( - word: string, - plural = '', - singular = '', - excludeNumber = false, - ): string { - const sign = this.value >= 0 ? '+' : ''; - return `${sign}${this.pluralise(word, plural, singular, excludeNumber)}`; - } + /** + * Pluralises a word for changes. + * - (-1).pluraliseSigned() = '-1 credits' + * - (0).pluraliseSigned() = '+0 credits' + * - (1).pluraliseSigned() = '+1 credit' + */ + public pluraliseSigned( + word: string, + plural = "", + singular = "", + excludeNumber = false + ): string { + const sign = this.value >= 0 ? "+" : ""; + return `${sign}${this.pluralise( + word, + plural, + singular, + excludeNumber + )}`; + } } export class StringWrapper extends GenericWrapper { - public replaceAll(before: string, after: string): string { - let result = this.value; + public replaceAll(before: string, after: string): string { + let result = this.value; - while (result.indexOf(before) !== -1) - result = result.replace(before, after); + while (result.indexOf(before) !== -1) + result = result.replace(before, after); - return result; - } + return result; + } - public toTitleCase(): string { - return this.value.replace( - /([^\W_]+[^\s-]*) */g, - (txt) => txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase(), - ); - } + public toTitleCase(): string { + return this.value.replace( + /([^\W_]+[^\s-]*) */g, + (txt) => txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase() + ); + } } export class ArrayWrapper extends GenericWrapper { - /** Returns a random element from this array. */ - public random(): T { - return this.value[Math.floor(Math.random() * this.value.length)]; - } + /** Returns a random element from this array. */ + public random(): T { + return this.value[Math.floor(Math.random() * this.value.length)]; + } - /** - * Splits up this array into a specified length. - * `$([1,2,3,4,5,6,7,8,9,10]).split(3)` = `[[1,2,3],[4,5,6],[7,8,9],[10]]` - */ - public split(lengthOfEachSection: number): T[][] { - const amountOfSections = Math.ceil(this.value.length / lengthOfEachSection); - const sections: T[][] = new Array(amountOfSections); + /** + * Splits up this array into a specified length. + * `$([1,2,3,4,5,6,7,8,9,10]).split(3)` = `[[1,2,3],[4,5,6],[7,8,9],[10]]` + */ + public split(lengthOfEachSection: number): T[][] { + const amountOfSections = Math.ceil( + this.value.length / lengthOfEachSection + ); + const sections: T[][] = new Array(amountOfSections); - for (let index = 0; index < amountOfSections; index++) - sections[index] = this.value.slice( - index * lengthOfEachSection, - (index + 1) * lengthOfEachSection, - ); + for (let index = 0; index < amountOfSections; index++) + sections[index] = this.value.slice( + index * lengthOfEachSection, + (index + 1) * lengthOfEachSection + ); - return sections; - } + return sections; + } } diff --git a/src/defs/info.ts b/src/defs/info.ts index 30b4786..abb13a5 100644 --- a/src/defs/info.ts +++ b/src/defs/info.ts @@ -1,45 +1,47 @@ // Flags a user can have. // They're basically your profile badges. -export const flags: { [index: string]: any } = { - DISCORD_EMPLOYEE: 'Discord Employee', - DISCORD_PARTNER: 'Discord Partner', - BUGHUNTER_LEVEL_1: 'Bug Hunter (Level 1)', - BUGHUNTER_LEVEL_2: 'Bug Hunter (Level 2)', - HYPESQUAD_EVENTS: 'HypeSquad Events', - HOUSE_BRAVERY: 'House of Bravery', - HOUSE_BRILLIANCE: 'House of Brilliance', - HOUSE_BALANCE: 'House of Balance', - EARLY_SUPPORTER: 'Early Supporter', - TEAM_USER: 'Team User', - SYSTEM: 'System', - VERIFIED_BOT: 'Verified Bot', - VERIFIED_DEVELOPER: 'Verified Bot Developer', +export const flags: {[index: string]: any} = { + DISCORD_EMPLOYEE: "Discord Employee", + DISCORD_PARTNER: "Discord Partner", + BUGHUNTER_LEVEL_1: "Bug Hunter (Level 1)", + BUGHUNTER_LEVEL_2: "Bug Hunter (Level 2)", + HYPESQUAD_EVENTS: "HypeSquad Events", + HOUSE_BRAVERY: "House of Bravery", + HOUSE_BRILLIANCE: "House of Brilliance", + HOUSE_BALANCE: "House of Balance", + EARLY_SUPPORTER: "Early Supporter", + TEAM_USER: "Team User", + SYSTEM: "System", + VERIFIED_BOT: "Verified Bot", + VERIFIED_DEVELOPER: "Verified Bot Developer" }; -export const filterLevels: { [index: string]: any } = { - DISABLED: 'Off', - MEMBERS_WITHOUT_ROLES: 'No Role', - ALL_MEMBERS: 'Everyone', +export const filterLevels: {[index: string]: any} = { + DISABLED: "Off", + MEMBERS_WITHOUT_ROLES: "No Role", + ALL_MEMBERS: "Everyone" }; -export const verificationLevels: { [index: string]: any } = { - NONE: 'None', - LOW: 'Low', - MEDIUM: 'Medium', - HIGH: '(╯°□°)╯︵ ┻━┻', - VERY_HIGH: '┻━┻ ミヽ(ಠ益ಠ)ノ彡┻━┻', + +export const verificationLevels: {[index: string]: any} = { + NONE: "None", + LOW: "Low", + MEDIUM: "Medium", + HIGH: "(╯°□°)╯︵ ┻━┻", + VERY_HIGH: "┻━┻ ミヽ(ಠ益ಠ)ノ彡┻━┻" }; -export const regions: { [index: string]: any } = { - brazil: 'Brazil', - europe: 'Europe', - hongkong: 'Hong Kong', - india: 'India', - japan: 'Japan', - russia: 'Russia', - singapore: 'Singapore', - southafrica: 'South Africa', - sydney: 'Sydney', - 'us-central': 'US Central', - 'us-east': 'US East', - 'us-west': 'US West', - 'us-south': 'US South', + +export const regions: {[index: string]: any} = { + brazil: "Brazil", + europe: "Europe", + hongkong: "Hong Kong", + india: "India", + japan: "Japan", + russia: "Russia", + singapore: "Singapore", + southafrica: "South Africa", + sydney: "Sydney", + "us-central": "US Central", + "us-east": "US East", + "us-west": "US West", + "us-south": "US South" }; diff --git a/src/events/channelCreate.ts b/src/events/channelCreate.ts index a1fbd89..656dcc9 100644 --- a/src/events/channelCreate.ts +++ b/src/events/channelCreate.ts @@ -1,16 +1,16 @@ -import Event from '../core/event'; -import { client } from '../index'; -import $ from '../core/lib'; -import * as discord from 'discord.js'; +import Event from "../core/event"; +import {client} from "../index"; +import $ from "../core/lib"; +import * as discord from "discord.js"; -export default new Event<'channelCreate'>({ - async on(channel) { - const botGuilds = client.guilds; - if (channel instanceof discord.GuildChannel) { - const createdGuild = await botGuilds.fetch(channel.guild.id); - $.log( - `Channel created in '${createdGuild.name}' called '#${channel.name}'`, - ); +export default new Event<"channelCreate">({ + async on(channel) { + const botGuilds = client.guilds; + if (channel instanceof discord.GuildChannel) { + const createdGuild = await botGuilds.fetch(channel.guild.id); + $.log( + `Channel created in '${createdGuild.name}' called '#${channel.name}'` + ); + } } - }, }); diff --git a/src/events/channelDelete.ts b/src/events/channelDelete.ts index c7836d8..656a9e3 100644 --- a/src/events/channelDelete.ts +++ b/src/events/channelDelete.ts @@ -1,16 +1,16 @@ -import Event from '../core/event'; -import { client } from '../index'; -import $ from '../core/lib'; -import * as discord from 'discord.js'; +import Event from "../core/event"; +import {client} from "../index"; +import $ from "../core/lib"; +import * as discord from "discord.js"; -export default new Event<'channelDelete'>({ - async on(channel) { - const botGuilds = client.guilds; - if (channel instanceof discord.GuildChannel) { - const createdGuild = await botGuilds.fetch(channel.guild.id); - $.log( - `Channel deleted in '${createdGuild.name}' called '#${channel.name}'`, - ); +export default new Event<"channelDelete">({ + async on(channel) { + const botGuilds = client.guilds; + if (channel instanceof discord.GuildChannel) { + const createdGuild = await botGuilds.fetch(channel.guild.id); + $.log( + `Channel deleted in '${createdGuild.name}' called '#${channel.name}'` + ); + } } - }, }); diff --git a/src/events/message.ts b/src/events/message.ts index dab5e7b..7bb5285 100644 --- a/src/events/message.ts +++ b/src/events/message.ts @@ -1,137 +1,143 @@ -import Event from '../core/event'; -import Command, { loadCommands } from '../core/command'; +import Event from "../core/event"; +import Command, {loadCommands} from "../core/command"; import { - hasPermission, - getPermissionLevel, - PermissionNames, -} from '../core/permissions'; -import { Permissions, Collection } from 'discord.js'; -import { getPrefix } from '../core/structures'; -import $ from '../core/lib'; + hasPermission, + getPermissionLevel, + PermissionNames +} from "../core/permissions"; +import {Permissions, Collection} from "discord.js"; +import {getPrefix} from "../core/structures"; +import $ from "../core/lib"; // It's a rather hacky solution, but since there's no top-level await, I just have to make the loading conditional. let commands: Collection | null = null; -export default new Event<'message'>({ - async on(message) { - // Load commands if it hasn't already done so. Luckily, it's called once at most. - if (!commands) commands = await loadCommands(); +export default new Event<"message">({ + async on(message) { + // Load commands if it hasn't already done so. Luckily, it's called once at most. + if (!commands) commands = await loadCommands(); - // Message Setup // - if (message.author.bot) return; + // Message Setup // + if (message.author.bot) return; - const prefix = getPrefix(message.guild); + const prefix = getPrefix(message.guild); - if (!message.content.startsWith(prefix)) { - if (message.client.user && message.mentions.has(message.client.user)) - message.channel.send( - `${message.author.toString()}, my prefix on this guild is \`${prefix}\`.`, - ); - return; - } - - const [header, ...args] = message.content - .substring(prefix.length) - .split(/ +/); - - if (!commands.has(header)) return; - - if ( - message.channel.type === 'text' && - !message.channel - .permissionsFor(message.client.user || '') - ?.has(Permissions.FLAGS.SEND_MESSAGES) - ) { - let status; - - if (message.member?.hasPermission(Permissions.FLAGS.ADMINISTRATOR)) - status = - "Because you're a server admin, you have the ability to change that channel's permissions to match if that's what you intended."; - else - status = - "Try using a different channel or contacting a server admin to change permissions of that channel if you think something's wrong."; - - return message.author.send( - `I don't have permission to send messages in ${message.channel.toString()}. ${status}`, - ); - } - - $.log( - `${message.author.username}#${message.author.discriminator} executed the command "${header}" with arguments "${args}".`, - ); - - // Subcommand Recursion // - let command = commands.get(header); - if (!command) - return $.warn( - `Command "${header}" was called but for some reason it's still undefined!`, - ); - const params: any[] = []; - let isEndpoint = false; - let permLevel = command.permission ?? Command.PERMISSIONS.NONE; - - for (let param of args) { - if (command.endpoint) { - if ( - command.subcommands.size > 0 || - command.user || - command.number || - command.any - ) - $.warn( - `An endpoint cannot have subcommands! Check ${prefix}${header} again.`, - ); - isEndpoint = true; - break; - } - - const type = command.resolve(param); - command = command.get(param); - permLevel = command.permission ?? permLevel; - - if (type === Command.TYPES.USER) { - const id = param.match(/\d+/g)![0]; - try { - params.push(await message.client.users.fetch(id)); - } catch (error) { - return message.channel.send(`No user found by the ID \`${id}\`!`); + if (!message.content.startsWith(prefix)) { + if ( + message.client.user && + message.mentions.has(message.client.user) + ) + message.channel.send( + `${message.author.toString()}, my prefix on this guild is \`${prefix}\`.` + ); + return; } - } else if (type === Command.TYPES.NUMBER) params.push(Number(param)); - else if (type !== Command.TYPES.SUBCOMMAND) params.push(param); + + const [header, ...args] = message.content + .substring(prefix.length) + .split(/ +/); + + if (!commands.has(header)) return; + + if ( + message.channel.type === "text" && + !message.channel + .permissionsFor(message.client.user || "") + ?.has(Permissions.FLAGS.SEND_MESSAGES) + ) { + let status; + + if (message.member?.hasPermission(Permissions.FLAGS.ADMINISTRATOR)) + status = + "Because you're a server admin, you have the ability to change that channel's permissions to match if that's what you intended."; + else + status = + "Try using a different channel or contacting a server admin to change permissions of that channel if you think something's wrong."; + + return message.author.send( + `I don't have permission to send messages in ${message.channel.toString()}. ${status}` + ); + } + + $.log( + `${message.author.username}#${message.author.discriminator} executed the command "${header}" with arguments "${args}".` + ); + + // Subcommand Recursion // + let command = commands.get(header); + if (!command) + return $.warn( + `Command "${header}" was called but for some reason it's still undefined!` + ); + const params: any[] = []; + let isEndpoint = false; + let permLevel = command.permission ?? Command.PERMISSIONS.NONE; + + for (let param of args) { + if (command.endpoint) { + if ( + command.subcommands.size > 0 || + command.user || + command.number || + command.any + ) + $.warn( + `An endpoint cannot have subcommands! Check ${prefix}${header} again.` + ); + isEndpoint = true; + break; + } + + const type = command.resolve(param); + command = command.get(param); + permLevel = command.permission ?? permLevel; + + if (type === Command.TYPES.USER) { + const id = param.match(/\d+/g)![0]; + try { + params.push(await message.client.users.fetch(id)); + } catch (error) { + return message.channel.send( + `No user found by the ID \`${id}\`!` + ); + } + } else if (type === Command.TYPES.NUMBER) + params.push(Number(param)); + else if (type !== Command.TYPES.SUBCOMMAND) params.push(param); + } + + if (!message.member) + return $.warn( + "This command was likely called from a DM channel meaning the member object is null." + ); + + if (!hasPermission(message.member, permLevel)) { + const userPermLevel = getPermissionLevel(message.member); + return message.channel.send( + `You don't have access to this command! Your permission level is \`${PermissionNames[userPermLevel]}\` (${userPermLevel}), but this command requires a permission level of \`${PermissionNames[permLevel]}\` (${permLevel}).` + ); + } + + if (isEndpoint) return message.channel.send("Too many arguments!"); + + // Execute with dynamic library attached. // + // The purpose of using $.bind($) is to clone the function so as to not modify the original $. + // The cloned function doesn't copy the properties, so Object.assign() is used. + // Object.assign() modifies the first element and returns that, the second element applies its properties and the third element applies its own overriding the second one. + command.execute( + Object.assign( + $.bind($), + { + args: params, + author: message.author, + channel: message.channel, + client: message.client, + guild: message.guild, + member: message.member, + message: message + }, + $ + ) + ); } - - if (!message.member) - return $.warn( - 'This command was likely called from a DM channel meaning the member object is null.', - ); - - if (!hasPermission(message.member, permLevel)) { - const userPermLevel = getPermissionLevel(message.member); - return message.channel.send( - `You don't have access to this command! Your permission level is \`${PermissionNames[userPermLevel]}\` (${userPermLevel}), but this command requires a permission level of \`${PermissionNames[permLevel]}\` (${permLevel}).`, - ); - } - - if (isEndpoint) return message.channel.send('Too many arguments!'); - - // Execute with dynamic library attached. // - // The purpose of using $.bind($) is to clone the function so as to not modify the original $. - // The cloned function doesn't copy the properties, so Object.assign() is used. - // Object.assign() modifies the first element and returns that, the second element applies its properties and the third element applies its own overriding the second one. - command.execute( - Object.assign( - $.bind($), - { - args: params, - author: message.author, - channel: message.channel, - client: message.client, - guild: message.guild, - member: message.member, - message: message, - }, - $, - ), - ); - }, }); diff --git a/src/events/messageReactionRemove.ts b/src/events/messageReactionRemove.ts index 8957a24..15b77a2 100644 --- a/src/events/messageReactionRemove.ts +++ b/src/events/messageReactionRemove.ts @@ -1,24 +1,24 @@ -import Event from '../core/event'; -import { Permissions } from 'discord.js'; -import { botHasPermission } from '../core/lib'; +import Event from "../core/event"; +import {Permissions} from "discord.js"; +import {botHasPermission} from "../core/lib"; // A list of message ID and callback pairs. You get the emote name and ID of the user reacting. export const eventListeners: Map< - string, - (emote: string, id: string) => void + string, + (emote: string, id: string) => void > = new Map(); // Attached to the client, there can be one event listener attached to a message ID which is executed if present. -export default new Event<'messageReactionRemove'>({ - on(reaction, user) { - const canDeleteEmotes = botHasPermission( - reaction.message.guild, - Permissions.FLAGS.MANAGE_MESSAGES, - ); +export default new Event<"messageReactionRemove">({ + on(reaction, user) { + const canDeleteEmotes = botHasPermission( + reaction.message.guild, + Permissions.FLAGS.MANAGE_MESSAGES + ); - if (!canDeleteEmotes) { - const callback = eventListeners.get(reaction.message.id); - callback && callback(reaction.emoji.name, user.id); + if (!canDeleteEmotes) { + const callback = eventListeners.get(reaction.message.id); + callback && callback(reaction.emoji.name, user.id); + } } - }, }); diff --git a/src/events/ready.ts b/src/events/ready.ts index 10ca87a..364b98f 100644 --- a/src/events/ready.ts +++ b/src/events/ready.ts @@ -1,18 +1,18 @@ -import Event from '../core/event'; -import { client } from '../index'; -import $ from '../core/lib'; -import { Config } from '../core/structures'; +import Event from "../core/event"; +import {client} from "../index"; +import $ from "../core/lib"; +import {Config} from "../core/structures"; -export default new Event<'ready'>({ - once() { - if (client.user) { - $.ready( - `Logged in as ${client.user.username}#${client.user.discriminator}.`, - ); - client.user.setActivity({ - type: 'LISTENING', - name: `${Config.prefix}help`, - }); +export default new Event<"ready">({ + once() { + if (client.user) { + $.ready( + `Logged in as ${client.user.username}#${client.user.discriminator}.` + ); + client.user.setActivity({ + type: "LISTENING", + name: `${Config.prefix}help` + }); + } } - }, }); diff --git a/src/index.ts b/src/index.ts index 207bb4b..9721784 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,38 +1,38 @@ -import { Client } from 'discord.js'; -import setup from './setup'; -import { Config } from './core/structures'; -import { loadCommands } from './core/command'; -import { loadEvents } from './core/event'; -import 'discord.js-lavalink-lib'; -import LavalinkMusic from 'discord.js-lavalink-lib'; +import {Client} from "discord.js"; +import setup from "./setup"; +import {Config} from "./core/structures"; +import {loadCommands} from "./core/command"; +import {loadEvents} from "./core/event"; +import "discord.js-lavalink-lib"; +import LavalinkMusic from "discord.js-lavalink-lib"; // This is here in order to make it much less of a headache to access the client from other files. // This of course won't actually do anything until the setup process is complete and it logs in. export const client = new Client(); (client as any).music = LavalinkMusic(client, { - lavalink: { - restnode: { - host: 'localhost', - port: 2333, - password: 'youshallnotpass', + lavalink: { + restnode: { + host: "localhost", + port: 2333, + password: "youshallnotpass" + }, + nodes: [ + { + host: "localhost", + port: 2333, + password: "youshallnotpass" + } + ] }, - nodes: [ - { - host: 'localhost', - port: 2333, - password: 'youshallnotpass', - }, - ], - }, - prefix: '!!', - helpCmd: 'mhelp', - admins: ['717352467280691331'], + prefix: "!!", + helpCmd: "mhelp", + admins: ["717352467280691331"] }); // Begin the command loading here rather than when it's needed like in the message event. setup.init().then(() => { - loadCommands(); - loadEvents(client); - client.login(Config.token).catch(setup.again); + loadCommands(); + loadEvents(client); + client.login(Config.token).catch(setup.again); }); diff --git a/src/setup.ts b/src/setup.ts index 6559236..d2d9fbd 100644 --- a/src/setup.ts +++ b/src/setup.ts @@ -1,64 +1,65 @@ -import { existsSync as exists } from 'fs'; -import inquirer from 'inquirer'; -import Storage from './core/storage'; -import { Config } from './core/structures'; -import $, { setConsoleActivated } from './core/lib'; +import {existsSync as exists} from "fs"; +import inquirer from "inquirer"; +import Storage from "./core/storage"; +import {Config} from "./core/structures"; +import $, {setConsoleActivated} from "./core/lib"; // This file is called (or at least should be called) automatically as long as a config file doesn't exist yet. // And that file won't be written until the data is successfully initialized. const prompts = [ - { - type: 'password', - name: 'token', - message: "What's your bot's token?", - mask: true, - }, - { - type: 'input', - name: 'prefix', - message: "What do you want your bot's prefix to be?", - default: '$', - }, - { - type: 'input', - name: 'owner', - message: "Enter the owner's user ID here.", - }, - { - type: 'input', - name: 'admins', - message: 'Enter a list of bot admins (by their IDs) separated by spaces.', - }, - { - type: 'input', - name: 'support', - message: - 'Enter a list of bot troubleshooters (by their IDs) separated by spaces.', - }, + { + type: "password", + name: "token", + message: "What's your bot's token?", + mask: true + }, + { + type: "input", + name: "prefix", + message: "What do you want your bot's prefix to be?", + default: "$" + }, + { + type: "input", + name: "owner", + message: "Enter the owner's user ID here." + }, + { + type: "input", + name: "admins", + message: + "Enter a list of bot admins (by their IDs) separated by spaces." + }, + { + type: "input", + name: "support", + message: + "Enter a list of bot troubleshooters (by their IDs) separated by spaces." + } ]; export default { - async init() { - while (!exists('data/config.json')) { - const answers = await inquirer.prompt(prompts); - Storage.open('data'); - Config.token = answers.token as string; - Config.prefix = answers.prefix as string; - Config.owner = answers.owner as string; - const admins = answers.admins as string; - Config.admins = admins !== '' ? admins.split(' ') : []; - const support = answers.support as string; - Config.support = support !== '' ? support.split(' ') : []; - Config.save(false); + async init() { + while (!exists("data/config.json")) { + const answers = await inquirer.prompt(prompts); + Storage.open("data"); + Config.token = answers.token as string; + Config.prefix = answers.prefix as string; + Config.owner = answers.owner as string; + const admins = answers.admins as string; + Config.admins = admins !== "" ? admins.split(" ") : []; + const support = answers.support as string; + Config.support = support !== "" ? support.split(" ") : []; + Config.save(false); + } + }, + /** Prompt the user to set their token again. */ + async again() { + $.error("It seems that the token you provided is invalid."); + setConsoleActivated(false); + const answers = await inquirer.prompt(prompts.slice(0, 1)); + Config.token = answers.token as string; + Config.save(false); + process.exit(); } - }, - /** Prompt the user to set their token again. */ - async again() { - $.error('It seems that the token you provided is invalid.'); - setConsoleActivated(false); - const answers = await inquirer.prompt(prompts.slice(0, 1)); - Config.token = answers.token as string; - Config.save(false); - process.exit(); - }, }; diff --git a/test/wrappers.ts b/test/wrappers.ts index 9597e77..7d995f3 100644 --- a/test/wrappers.ts +++ b/test/wrappers.ts @@ -1,111 +1,107 @@ -import { strict as assert } from 'assert'; -import { - NumberWrapper, - StringWrapper, - ArrayWrapper, -} from '../src/core/wrappers'; +import {strict as assert} from "assert"; +import {NumberWrapper, StringWrapper, ArrayWrapper} from "../src/core/wrappers"; // I can't figure out a way to run the test suite while running the bot. -describe('Wrappers', () => { - describe('NumberWrapper', () => { - describe('#pluralise()', () => { - it('should return "5 credits"', () => { - assert.strictEqual( - new NumberWrapper(5).pluralise('credit', 's'), - '5 credits', - ); - }); +describe("Wrappers", () => { + describe("NumberWrapper", () => { + describe("#pluralise()", () => { + it('should return "5 credits"', () => { + assert.strictEqual( + new NumberWrapper(5).pluralise("credit", "s"), + "5 credits" + ); + }); - it('should return "1 credit"', () => { - assert.strictEqual( - new NumberWrapper(1).pluralise('credit', 's'), - '1 credit', - ); - }); + it('should return "1 credit"', () => { + assert.strictEqual( + new NumberWrapper(1).pluralise("credit", "s"), + "1 credit" + ); + }); - it('should return "-1 credits"', () => { - assert.strictEqual( - new NumberWrapper(-1).pluralise('credit', 's'), - '-1 credits', - ); - }); + it('should return "-1 credits"', () => { + assert.strictEqual( + new NumberWrapper(-1).pluralise("credit", "s"), + "-1 credits" + ); + }); - it('should be able to work with a plural suffix', () => { - assert.strictEqual( - new NumberWrapper(2).pluralise('part', 'ies', 'y'), - '2 parties', - ); - }); + it("should be able to work with a plural suffix", () => { + assert.strictEqual( + new NumberWrapper(2).pluralise("part", "ies", "y"), + "2 parties" + ); + }); - it('should be able to work with a singular suffix', () => { - assert.strictEqual( - new NumberWrapper(1).pluralise('part', 'ies', 'y'), - '1 party', - ); - }); + it("should be able to work with a singular suffix", () => { + assert.strictEqual( + new NumberWrapper(1).pluralise("part", "ies", "y"), + "1 party" + ); + }); - it('should be able to exclude the number', () => { - assert.strictEqual( - new NumberWrapper(1).pluralise('credit', 's', '', true), - 'credit', - ); - }); + it("should be able to exclude the number", () => { + assert.strictEqual( + new NumberWrapper(1).pluralise("credit", "s", "", true), + "credit" + ); + }); + }); + + describe("#pluraliseSigned()", () => { + it('should return "-1 credits"', () => { + assert.strictEqual( + new NumberWrapper(-1).pluraliseSigned("credit", "s"), + "-1 credits" + ); + }); + + it('should return "+0 credits"', () => { + assert.strictEqual( + new NumberWrapper(0).pluraliseSigned("credit", "s"), + "+0 credits" + ); + }); + + it('should return "+1 credit"', () => { + assert.strictEqual( + new NumberWrapper(1).pluraliseSigned("credit", "s"), + "+1 credit" + ); + }); + }); }); - describe('#pluraliseSigned()', () => { - it('should return "-1 credits"', () => { - assert.strictEqual( - new NumberWrapper(-1).pluraliseSigned('credit', 's'), - '-1 credits', - ); - }); + describe("StringWrapper", () => { + describe("#replaceAll()", () => { + it('should convert "test" to "zesz"', () => { + assert.strictEqual( + new StringWrapper("test").replaceAll("t", "z"), + "zesz" + ); + }); + }); - it('should return "+0 credits"', () => { - assert.strictEqual( - new NumberWrapper(0).pluraliseSigned('credit', 's'), - '+0 credits', - ); - }); - - it('should return "+1 credit"', () => { - assert.strictEqual( - new NumberWrapper(1).pluraliseSigned('credit', 's'), - '+1 credit', - ); - }); - }); - }); - - describe('StringWrapper', () => { - describe('#replaceAll()', () => { - it('should convert "test" to "zesz"', () => { - assert.strictEqual( - new StringWrapper('test').replaceAll('t', 'z'), - 'zesz', - ); - }); + describe("#toTitleCase()", () => { + it("should capitalize the first letter of each word", () => { + assert.strictEqual( + new StringWrapper( + "yeetus deletus find salvation from jesus" + ).toTitleCase(), + "Yeetus Deletus Find Salvation From Jesus" + ); + }); + }); }); - describe('#toTitleCase()', () => { - it('should capitalize the first letter of each word', () => { - assert.strictEqual( - new StringWrapper( - 'yeetus deletus find salvation from jesus', - ).toTitleCase(), - 'Yeetus Deletus Find Salvation From Jesus', - ); - }); + describe("ArrayWrapper", () => { + describe("#split()", () => { + it("should split [1,2,3,4,5,6,7,8,9,10] into [[1,2,3],[4,5,6],[7,8,9],[10]]", () => { + assert.deepStrictEqual( + new ArrayWrapper([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]).split(3), + [[1, 2, 3], [4, 5, 6], [7, 8, 9], [10]] + ); + }); + }); }); - }); - - describe('ArrayWrapper', () => { - describe('#split()', () => { - it('should split [1,2,3,4,5,6,7,8,9,10] into [[1,2,3],[4,5,6],[7,8,9],[10]]', () => { - assert.deepStrictEqual( - new ArrayWrapper([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]).split(3), - [[1, 2, 3], [4, 5, 6], [7, 8, 9], [10]], - ); - }); - }); - }); }); diff --git a/tsconfig.json b/tsconfig.json index 74603be..ba4dd11 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,17 +1,17 @@ { - "compilerOptions": { - "rootDir": "src", - "outDir": "dist", - "target": "ES6", - "module": "CommonJS", - "moduleResolution": "node", - "esModuleInterop": true, - "noImplicitAny": true, - "noImplicitReturns": true, - "strictNullChecks": true, - "strictFunctionTypes": true, - "strictPropertyInitialization": true, - "removeComments": true - }, - "exclude": ["test"] + "compilerOptions": { + "rootDir": "src", + "outDir": "dist", + "target": "ES6", + "module": "CommonJS", + "moduleResolution": "node", + "esModuleInterop": true, + "noImplicitAny": true, + "noImplicitReturns": true, + "strictNullChecks": true, + "strictFunctionTypes": true, + "strictPropertyInitialization": true, + "removeComments": true + }, + "exclude": ["test"] } From 5165c5ec4bd564d2415b11218671b164489058b6 Mon Sep 17 00:00:00 2001 From: WatDuhHekBro <44940783+WatDuhHekBro@users.noreply.github.com> Date: Tue, 15 Dec 2020 01:56:09 -0600 Subject: [PATCH 066/178] Changed max width to 120 columns --- prettier.config.js | 2 +- src/commands/admin.ts | 75 +++----- src/commands/fun/cookie.ts | 11 +- src/commands/fun/eco.ts | 9 +- src/commands/fun/neko.ts | 7 +- src/commands/fun/ok.ts | 4 +- src/commands/fun/owoify.ts | 4 +- src/commands/fun/subcommands/eco-core.ts | 33 +--- .../fun/subcommands/eco-shop-items.ts | 8 +- src/commands/fun/subcommands/eco-shop.ts | 24 +-- src/commands/fun/subcommands/eco-utils.ts | 28 +-- src/commands/help.ts | 36 +--- src/commands/info.ts | 164 ++++------------- src/commands/scanemotes.ts | 20 +-- src/commands/utilities/desc.ts | 16 +- src/commands/utilities/emote.ts | 4 +- src/commands/utilities/lsemotes.ts | 8 +- src/commands/utilities/react.ts | 13 +- src/commands/utilities/shorten.ts | 22 +-- src/core/command.ts | 48 ++--- src/core/event.ts | 4 +- src/core/lib.ts | 168 ++++-------------- src/core/permissions.ts | 11 +- src/core/storage.ts | 29 +-- src/core/structures.ts | 16 +- src/core/wrappers.ts | 33 +--- src/events/channelCreate.ts | 4 +- src/events/channelDelete.ts | 4 +- src/events/message.ts | 50 ++---- src/events/messageReactionRemove.ts | 10 +- src/events/ready.ts | 4 +- src/setup.ts | 6 +- test/wrappers.ts | 64 ++----- 33 files changed, 227 insertions(+), 712 deletions(-) diff --git a/prettier.config.js b/prettier.config.js index f5d98bc..002b0a1 100644 --- a/prettier.config.js +++ b/prettier.config.js @@ -1,5 +1,5 @@ module.exports = { - printWidth: 80, + printWidth: 120, tabWidth: 4, useTabs: false, semi: true, diff --git a/src/commands/admin.ts b/src/commands/admin.ts index 48320c0..645b1fc 100644 --- a/src/commands/admin.ts +++ b/src/commands/admin.ts @@ -29,9 +29,7 @@ export default new Command({ ); const permLevel = getPermissionLevel($.member); $.channel.send( - `${$.author.toString()}, your permission level is \`${ - PermissionNames[permLevel] - }\` (${permLevel}).` + `${$.author.toString()}, your permission level is \`${PermissionNames[permLevel]}\` (${permLevel}).` ); }, subcommands: { @@ -41,8 +39,7 @@ export default new Command({ permission: Command.PERMISSIONS.ADMIN, subcommands: { prefix: new Command({ - description: - "Set a custom prefix for your guild. Removes your custom prefix if none is provided.", + description: "Set a custom prefix for your guild. Removes your custom prefix if none is provided.", usage: "()", async run($: CommonLibrary): Promise { Storage.getGuild($.guild?.id || "N/A").prefix = null; @@ -53,28 +50,22 @@ export default new Command({ }, any: new Command({ async run($: CommonLibrary): Promise { - Storage.getGuild($.guild?.id || "N/A").prefix = - $.args[0]; + Storage.getGuild($.guild?.id || "N/A").prefix = $.args[0]; Storage.save(); - $.channel.send( - `The custom prefix for this guild is now \`${$.args[0]}\`.` - ); + $.channel.send(`The custom prefix for this guild is now \`${$.args[0]}\`.`); } }) }) } }), diag: new Command({ - description: - 'Requests a debug log with the "info" verbosity level.', + description: 'Requests a debug log with the "info" verbosity level.', permission: Command.PERMISSIONS.BOT_SUPPORT, async run($: CommonLibrary): Promise { $.channel.send(getLogBuffer("info")); }, any: new Command({ - description: `Select a verbosity to listen to. Available levels: \`[${Object.keys( - logs - ).join(", ")}]\``, + description: `Select a verbosity to listen to. Available levels: \`[${Object.keys(logs).join(", ")}]\``, async run($: CommonLibrary): Promise { const type = $.args[0]; @@ -95,12 +86,9 @@ export default new Command({ $.channel.send("Setting status to `online`..."); }, any: new Command({ - description: `Select a status to set to. Available statuses: \`[${statuses.join( - ", " - )}]\`.`, + description: `Select a status to set to. Available statuses: \`[${statuses.join(", ")}]\`.`, async run($: CommonLibrary): Promise { - if (!statuses.includes($.args[0])) - return $.channel.send("That status doesn't exist!"); + if (!statuses.includes($.args[0])) return $.channel.send("That status doesn't exist!"); else { $.client.user?.setStatus($.args[0]); $.channel.send(`Setting status to \`${$.args[0]}\`...`); @@ -119,17 +107,13 @@ export default new Command({ const msgs = await $.channel.messages.fetch({ limit: 100 }); - const travMessages = msgs.filter( - (m) => m.author.id === $.client.user?.id - ); + const travMessages = msgs.filter((m) => m.author.id === $.client.user?.id); - await $.message.channel - .send(`Found ${travMessages.size} messages to delete.`) - .then((m) => - m.delete({ - timeout: 5000 - }) - ); + await $.message.channel.send(`Found ${travMessages.size} messages to delete.`).then((m) => + m.delete({ + timeout: 5000 + }) + ); await $.message.channel.bulkDelete(travMessages); } }), @@ -147,9 +131,7 @@ export default new Command({ $.channel /// @ts-ignore .bulkDelete(fetched) - .catch((error: any) => - $.channel.send(`Error: ${error}`) - ); + .catch((error: any) => $.channel.send(`Error: ${error}`)); } }) }), @@ -162,8 +144,7 @@ export default new Command({ const code = $.args.join(" "); let evaled = eval(code); - if (typeof evaled !== "string") - evaled = require("util").inspect(evaled); + if (typeof evaled !== "string") evaled = require("util").inspect(evaled); $.channel.send(clean(evaled), {code: "x1"}); } catch (err) { $.channel.send(`\`ERROR\` \`\`\`x1\n${clean(err)}\n\`\`\``); @@ -175,26 +156,18 @@ export default new Command({ permission: Command.PERMISSIONS.BOT_SUPPORT, async run($: CommonLibrary): Promise { const nickName = $.args.join(" "); - const trav = $.guild?.members.cache.find( - (member) => member.id === $.client.user?.id - ); + const trav = $.guild?.members.cache.find((member) => member.id === $.client.user?.id); await trav?.setNickname(nickName); - if ( - botHasPermission($.guild, Permissions.FLAGS.MANAGE_MESSAGES) - ) + if (botHasPermission($.guild, Permissions.FLAGS.MANAGE_MESSAGES)) $.message.delete({timeout: 5000}).catch($.handler.bind($)); - $.channel - .send(`Nickname set to \`${nickName}\``) - .then((m) => m.delete({timeout: 5000})); + $.channel.send(`Nickname set to \`${nickName}\``).then((m) => m.delete({timeout: 5000})); } }), guilds: new Command({ description: "Shows a list of all guilds the bot is a member of.", permission: Command.PERMISSIONS.BOT_SUPPORT, async run($: CommonLibrary): Promise { - const guildList = $.client.guilds.cache - .array() - .map((e) => e.name); + const guildList = $.client.guilds.cache.array().map((e) => e.name); $.channel.send(guildList); } }), @@ -209,9 +182,7 @@ export default new Command({ $.channel.send("Activity set to default."); }, any: new Command({ - description: `Select an activity type to set. Available levels: \`[${activities.join( - ", " - )}]\``, + description: `Select an activity type to set. Available levels: \`[${activities.join(", ")}]\``, async run($: CommonLibrary): Promise { const type = $.args[0]; @@ -220,9 +191,7 @@ export default new Command({ type: $.args[0].toUpperCase() }); $.channel.send( - `Set activity to \`${$.args[0].toUpperCase()}\` \`${$.args - .slice(1) - .join(" ")}\`.` + `Set activity to \`${$.args[0].toUpperCase()}\` \`${$.args.slice(1).join(" ")}\`.` ); } else $.channel.send( diff --git a/src/commands/fun/cookie.ts b/src/commands/fun/cookie.ts index f9be54f..5c545e9 100644 --- a/src/commands/fun/cookie.ts +++ b/src/commands/fun/cookie.ts @@ -7,8 +7,7 @@ export default new Command({ run: ":cookie: Here's a cookie!", any: new Command({ async run($: CommonLibrary): Promise { - if ($.args[0] == "all") - return $.channel.send(`${$.author} gave everybody a cookie!`); + if ($.args[0] == "all") return $.channel.send(`${$.author} gave everybody a cookie!`); } }), user: new Command({ @@ -42,13 +41,9 @@ export default new Command({ `bakes <@${mention.id}> fresh cookies, it smells amazing.` ]; - if (mention.id == sender.id) - return $.channel.send("You can't give yourself cookies!"); + if (mention.id == sender.id) return $.channel.send("You can't give yourself cookies!"); - $.channel.send( - `:cookie: <@${sender.id}> ` + - cookies[Math.floor(Math.random() * cookies.length)] - ); + $.channel.send(`:cookie: <@${sender.id}> ` + cookies[Math.floor(Math.random() * cookies.length)]); } }) }); diff --git a/src/commands/fun/eco.ts b/src/commands/fun/eco.ts index 9e5978c..bb07e05 100644 --- a/src/commands/fun/eco.ts +++ b/src/commands/fun/eco.ts @@ -16,16 +16,13 @@ export default new Command({ shop: ShopCommand }, user: new Command({ - description: - "See how much money someone else has by using their user ID or pinging them.", + description: "See how much money someone else has by using their user ID or pinging them.", async run({guild, channel, args}) { - if (isAuthorized(guild, channel)) - channel.send(getMoneyEmbed(args[0])); + if (isAuthorized(guild, channel)) channel.send(getMoneyEmbed(args[0])); } }), any: new Command({ - description: - "See how much money someone else has by using their username.", + description: "See how much money someone else has by using their username.", async run({guild, channel, args, callMemberByUsername, message}) { if (isAuthorized(guild, channel)) callMemberByUsername(message, args.join(" "), (member) => { diff --git a/src/commands/fun/neko.ts b/src/commands/fun/neko.ts index 73901fa..63a8073 100644 --- a/src/commands/fun/neko.ts +++ b/src/commands/fun/neko.ts @@ -11,16 +11,13 @@ export default new Command({ async run($: CommonLibrary): Promise { console.log(endpoints.sfw); $.channel.send( - `Please provide an image type. Available arguments:\n\`[${Object.keys( - endpoints.sfw - ).join(", ")}]\`.` + `Please provide an image type. Available arguments:\n\`[${Object.keys(endpoints.sfw).join(", ")}]\`.` ); }, any: new Command({ description: "Image type to send.", async run($: CommonLibrary): Promise { - if (!($.args[0] in endpoints.sfw)) - return $.channel.send("Couldn't find that endpoint!"); + if (!($.args[0] in endpoints.sfw)) return $.channel.send("Couldn't find that endpoint!"); let baseURL = "https://nekos.life/api/v2"; let url = new URL(`${baseURL}${endpoints.sfw[$.args[0]]}`); diff --git a/src/commands/fun/ok.ts b/src/commands/fun/ok.ts index 882021c..c7b3570 100644 --- a/src/commands/fun/ok.ts +++ b/src/commands/fun/ok.ts @@ -62,8 +62,6 @@ export default new Command({ "large man" ]; - $.channel.send( - "ok " + responses[Math.floor(Math.random() * responses.length)] - ); + $.channel.send("ok " + responses[Math.floor(Math.random() * responses.length)]); } }); diff --git a/src/commands/fun/owoify.ts b/src/commands/fun/owoify.ts index 6c02084..ca5d18a 100644 --- a/src/commands/fun/owoify.ts +++ b/src/commands/fun/owoify.ts @@ -5,9 +5,7 @@ import {CommonLibrary, getContent} from "../../core/lib"; export default new Command({ description: "OwO-ifies the input.", async run($: CommonLibrary): Promise { - let url = new URL( - `https://nekos.life/api/v2/owoify?text=${$.args.join(" ")}` - ); + let url = new URL(`https://nekos.life/api/v2/owoify?text=${$.args.join(" ")}`); const content = await getContent(url.toString()); $.channel.send(content.owo); } diff --git a/src/commands/fun/subcommands/eco-core.ts b/src/commands/fun/subcommands/eco-core.ts index 5bf937a..66272b7 100644 --- a/src/commands/fun/subcommands/eco-core.ts +++ b/src/commands/fun/subcommands/eco-core.ts @@ -4,8 +4,7 @@ import {Storage} from "../../../core/structures"; import {isAuthorized, getMoneyEmbed, getSendEmbed} from "./eco-utils"; export const DailyCommand = new Command({ - description: - "Pick up your daily Mons. The cooldown is per user and every 22 hours to allow for some leeway.", + description: "Pick up your daily Mons. The cooldown is per user and every 22 hours to allow for some leeway.", async run({author, channel, guild}) { if (isAuthorized(guild, channel)) { const user = Storage.getUser(author.id); @@ -81,15 +80,10 @@ export const PayCommand = new Command({ const target = args[0]; const receiver = Storage.getUser(target.id); - if (amount <= 0) - return channel.send("You must send at least one Mon!"); + if (amount <= 0) return channel.send("You must send at least one Mon!"); else if (sender.money < amount) - return channel.send( - "You don't have enough Mons for that.", - getMoneyEmbed(author) - ); - else if (target.id === author.id) - return channel.send("You can't send Mons to yourself!"); + return channel.send("You don't have enough Mons for that.", getMoneyEmbed(author)); + else if (target.id === author.id) return channel.send("You can't send Mons to yourself!"); else if (target.bot && process.argv[2] !== "dev") return channel.send("You can't send Mons to a bot!"); @@ -110,24 +104,16 @@ export const PayCommand = new Command({ const last = args.pop(); if (!/\d+/g.test(last) && args.length === 0) - return channel.send( - "You need to enter an amount you're sending!" - ); + return channel.send("You need to enter an amount you're sending!"); const amount = Math.floor(last); const sender = Storage.getUser(author.id); - if (amount <= 0) - return channel.send("You must send at least one credit!"); + if (amount <= 0) return channel.send("You must send at least one credit!"); else if (sender.money < amount) - return channel.send( - "You don't have enough money to do that!", - getMoneyEmbed(author) - ); + return channel.send("You don't have enough money to do that!", getMoneyEmbed(author)); else if (!guild) - return channel.send( - "You have to use this in a server if you want to send money with a username!" - ); + return channel.send("You have to use this in a server if you want to send money with a username!"); const username = args.join(" "); const member = ( @@ -141,8 +127,7 @@ export const PayCommand = new Command({ return channel.send( `Couldn't find a user by the name of \`${username}\`! If you want to send money to someone in a different server, you have to use their user ID!` ); - else if (member.user.id === author.id) - return channel.send("You can't send money to yourself!"); + else if (member.user.id === author.id) return channel.send("You can't send money to yourself!"); else if (member.user.bot && process.argv[2] !== "dev") return channel.send("You can't send money to a bot!"); diff --git a/src/commands/fun/subcommands/eco-shop-items.ts b/src/commands/fun/subcommands/eco-shop-items.ts index 107959d..cd0580c 100644 --- a/src/commands/fun/subcommands/eco-shop-items.ts +++ b/src/commands/fun/subcommands/eco-shop-items.ts @@ -16,9 +16,7 @@ export const ShopItems: ShopItem[] = [ description: "Hug Monika.", usage: "hug", run(message, cost) { - message.channel.send( - `Transaction of ${cost} Mon completed successfully. <@394808963356688394>` - ); + message.channel.send(`Transaction of ${cost} Mon completed successfully. <@394808963356688394>`); } }, { @@ -27,9 +25,7 @@ export const ShopItems: ShopItem[] = [ description: "Hold Monika's hand.", usage: "handhold", run(message, cost) { - message.channel.send( - `Transaction of ${cost} Mons completed successfully. <@394808963356688394>` - ); + message.channel.send(`Transaction of ${cost} Mons completed successfully. <@394808963356688394>`); } }, { diff --git a/src/commands/fun/subcommands/eco-shop.ts b/src/commands/fun/subcommands/eco-shop.ts index 48422a5..682b5a2 100644 --- a/src/commands/fun/subcommands/eco-shop.ts +++ b/src/commands/fun/subcommands/eco-shop.ts @@ -14,12 +14,8 @@ export const ShopCommand = new Command({ for (const item of selection) fields.push({ - name: `**${item.title}** (${getPrefix(guild)}eco buy ${ - item.usage - })`, - value: `${item.description} Costs ${$( - item.cost - ).pluralise("Mon", "s")}.`, + name: `**${item.title}** (${getPrefix(guild)}eco buy ${item.usage})`, + value: `${item.description} Costs ${$(item.cost).pluralise("Mon", "s")}.`, inline: false }); @@ -40,17 +36,10 @@ export const ShopCommand = new Command({ else { const shopPages = $(ShopItems).split(5); const pageAmount = shopPages.length; - const msg = await channel.send( - getShopEmbed(shopPages[0], `Shop (Page 1 of ${pageAmount})`) - ); + const msg = await channel.send(getShopEmbed(shopPages[0], `Shop (Page 1 of ${pageAmount})`)); $.paginate(msg, author.id, pageAmount, (page) => { - msg.edit( - getShopEmbed( - shopPages[page], - `Shop (Page ${page + 1} of ${pageAmount})` - ) - ); + msg.edit(getShopEmbed(shopPages[page], `Shop (Page ${page + 1} of ${pageAmount})`)); }); } } @@ -90,10 +79,7 @@ export const BuyCommand = new Command({ } } - if (!found) - channel.send( - `There's no item in the shop that goes by \`${requested}\`!` - ); + if (!found) channel.send(`There's no item in the shop that goes by \`${requested}\`!`); } } }); diff --git a/src/commands/fun/subcommands/eco-utils.ts b/src/commands/fun/subcommands/eco-utils.ts index fb58435..ab5af4f 100644 --- a/src/commands/fun/subcommands/eco-utils.ts +++ b/src/commands/fun/subcommands/eco-utils.ts @@ -25,11 +25,7 @@ export function getMoneyEmbed(user: User): object { }; } -export function getSendEmbed( - sender: User, - receiver: User, - amount: number -): object { +export function getSendEmbed(sender: User, receiver: User, amount: number): object { return { embed: { color: 0xffff00, @@ -48,17 +44,11 @@ export function getSendEmbed( fields: [ { name: `Sender: ${sender.username}#${sender.discriminator}`, - value: $(Storage.getUser(sender.id).money).pluralise( - "credit", - "s" - ) + value: $(Storage.getUser(sender.id).money).pluralise("credit", "s") }, { name: `Receiver: ${receiver.username}#${receiver.discriminator}`, - value: $(Storage.getUser(receiver.id).money).pluralise( - "credit", - "s" - ) + value: $(Storage.getUser(receiver.id).money).pluralise("credit", "s") } ], footer: { @@ -72,16 +62,10 @@ export function getSendEmbed( }; } -export function isAuthorized( - guild: Guild | null, - channel: TextChannel | DMChannel | NewsChannel -): boolean { - if (guild?.id === "637512823676600330" || process.argv[2] === "dev") - return true; +export function isAuthorized(guild: Guild | null, channel: TextChannel | DMChannel | NewsChannel): boolean { + if (guild?.id === "637512823676600330" || process.argv[2] === "dev") return true; else { - channel.send( - "Sorry, this command can only be used in Monika's emote server." - ); + channel.send("Sorry, this command can only be used in Monika's emote server."); return false; } } diff --git a/src/commands/help.ts b/src/commands/help.ts index 0424b10..cf6459b 100644 --- a/src/commands/help.ts +++ b/src/commands/help.ts @@ -4,8 +4,7 @@ import {loadCommands, categories} from "../core/command"; import {PermissionNames} from "../core/permissions"; export default new Command({ - description: - "Lists all commands. If a command is specified, their arguments are listed as well.", + description: "Lists all commands. If a command is specified, their arguments are listed as well.", usage: "([command, [subcommand/type], ...])", aliases: ["h"], async run($: CommonLibrary): Promise { @@ -20,9 +19,7 @@ export default new Command({ const command = commands.get(header); if (!command) - return $.warn( - `Command "${header}" of category "${category}" unexpectedly doesn't exist!` - ); + return $.warn(`Command "${header}" of category "${category}" unexpectedly doesn't exist!`); output += `\n- \`${header}\`: ${command.description}`; } @@ -37,13 +34,9 @@ export default new Command({ let header = $.args.shift() as string; let command = commands.get(header); - if (!command || header === "test") - return $.channel.send( - `No command found by the name \`${header}\`!` - ); + if (!command || header === "test") return $.channel.send(`No command found by the name \`${header}\`!`); - if (command.originalCommandName) - header = command.originalCommandName; + if (command.originalCommandName) header = command.originalCommandName; else $.warn(`originalCommandName isn't defined for ${header}?!`); let permLevel = command.permission ?? Command.PERMISSIONS.NONE; @@ -91,10 +84,7 @@ export default new Command({ } } - if (invalid) - return $.channel.send( - `No command found by the name \`${header}\`!` - ); + if (invalid) return $.channel.send(`No command found by the name \`${header}\`!`); let append = ""; @@ -104,21 +94,15 @@ export default new Command({ command.subcommands.forEach((subcmd, subtag) => { // Don't capture duplicates generated from aliases. if (subcmd.originalCommandName === subtag) { - const customUsage = subcmd.usage - ? ` ${subcmd.usage}` - : ""; - list.push( - `- \`${header} ${subtag}${customUsage}\` - ${subcmd.description}` - ); + const customUsage = subcmd.usage ? ` ${subcmd.usage}` : ""; + list.push(`- \`${header} ${subtag}${customUsage}\` - ${subcmd.description}`); } }); const addDynamicType = (cmd: Command | null, type: string) => { if (cmd) { const customUsage = cmd.usage ? ` ${cmd.usage}` : ""; - list.push( - `- \`${header} <${type}>${customUsage}\` - ${cmd.description}` - ); + list.push(`- \`${header} <${type}>${customUsage}\` - ${cmd.description}`); } }; @@ -126,9 +110,7 @@ export default new Command({ addDynamicType(command.number, "number"); addDynamicType(command.any, "any"); - append = - "Usages:" + - (list.length > 0 ? `\n${list.join("\n")}` : " None."); + append = "Usages:" + (list.length > 0 ? `\n${list.join("\n")}` : " None."); } else append = `Usage: \`${header} ${usage}\``; let aliases = "None"; diff --git a/src/commands/info.ts b/src/commands/info.ts index c9153a1..d8ddda6 100644 --- a/src/commands/info.ts +++ b/src/commands/info.ts @@ -10,17 +10,14 @@ import moment from "moment"; import utc from "moment"; export default new Command({ - description: - "Command to provide all sorts of info about the current server, a user, etc.", + description: "Command to provide all sorts of info about the current server, a user, etc.", run: "Please provide an argument.\nFor help, run `%prefix%help info`.", subcommands: { avatar: new Command({ description: "Shows your own, or another user's avatar.", usage: "()", async run($: CommonLibrary): Promise { - $.channel.send( - $.author.displayAvatarURL({dynamic: true, size: 2048}) - ); + $.channel.send($.author.displayAvatarURL({dynamic: true, size: 2048})); }, user: new Command({ description: "Shows your own, or another user's avatar.", @@ -51,16 +48,10 @@ export default new Command({ `**❯ Client:** ${$.client.user?.tag} (${$.client.user?.id})`, `**❯ Servers:** ${$.client.guilds.cache.size.toLocaleString()}`, `**❯ Users:** ${$.client.guilds.cache - .reduce( - (a: any, b: {memberCount: any}) => - a + b.memberCount, - 0 - ) + .reduce((a: any, b: {memberCount: any}) => a + b.memberCount, 0) .toLocaleString()}`, `**❯ Channels:** ${$.client.channels.cache.size.toLocaleString()}`, - `**❯ Creation Date:** ${utc( - $.client.user?.createdTimestamp - ).format("Do MMMM YYYY HH:mm:ss")}`, + `**❯ Creation Date:** ${utc($.client.user?.createdTimestamp).format("Do MMMM YYYY HH:mm:ss")}`, `**❯ Node.JS:** ${process.version}`, `**❯ Version:** v${version}`, `**❯ Discord.JS:** ${djsversion}`, @@ -76,12 +67,8 @@ export default new Command({ `\u3000 • Model: ${core.model}`, `\u3000 • Speed: ${core.speed}MHz`, `**❯ Memory:**`, - `\u3000 • Total: ${formatBytes( - process.memoryUsage().heapTotal - )}`, - `\u3000 • Used: ${formatBytes( - process.memoryUsage().heapTotal - )}` + `\u3000 • Total: ${formatBytes(process.memoryUsage().heapTotal)}`, + `\u3000 • Used: ${formatBytes(process.memoryUsage().heapTotal)}` ]) .setTimestamp(); $.channel.send(embed); @@ -99,9 +86,7 @@ export default new Command({ const emojis = $.guild.emojis.cache; const iconURL = $.guild.iconURL({dynamic: true}); const embed = new MessageEmbed() - .setDescription( - `**Guild information for __${$.guild.name}__**` - ) + .setDescription(`**Guild information for __${$.guild.name}__**`) .setColor("BLUE"); if (iconURL) embed @@ -111,97 +96,41 @@ export default new Command({ `**❯ ID:** ${$.guild.id}`, `**❯ Owner:** ${$.guild.owner?.user.tag} (${$.guild.ownerID})`, `**❯ Region:** ${regions[$.guild.region]}`, - `**❯ Boost Tier:** ${ - $.guild.premiumTier - ? `Tier ${$.guild.premiumTier}` - : "None" - }`, - `**❯ Explicit Filter:** ${ - filterLevels[$.guild.explicitContentFilter] - }`, - `**❯ Verification Level:** ${ - verificationLevels[ - $.guild.verificationLevel - ] - }`, - `**❯ Time Created:** ${moment( + `**❯ Boost Tier:** ${$.guild.premiumTier ? `Tier ${$.guild.premiumTier}` : "None"}`, + `**❯ Explicit Filter:** ${filterLevels[$.guild.explicitContentFilter]}`, + `**❯ Verification Level:** ${verificationLevels[$.guild.verificationLevel]}`, + `**❯ Time Created:** ${moment($.guild.createdTimestamp).format("LT")} ${moment( $.guild.createdTimestamp - ).format("LT")} ${moment( - $.guild.createdTimestamp - ).format("LL")} ${moment( - $.guild.createdTimestamp - ).fromNow()})`, + ).format("LL")} ${moment($.guild.createdTimestamp).fromNow()})`, "\u200b" ]) .addField("Statistics", [ `**❯ Role Count:** ${roles.length}`, `**❯ Emoji Count:** ${emojis.size}`, - `**❯ Regular Emoji Count:** ${ - emojis.filter((emoji) => !emoji.animated) - .size - }`, - `**❯ Animated Emoji Count:** ${ - emojis.filter((emoji) => emoji.animated) - .size - }`, + `**❯ Regular Emoji Count:** ${emojis.filter((emoji) => !emoji.animated).size}`, + `**❯ Animated Emoji Count:** ${emojis.filter((emoji) => emoji.animated).size}`, `**❯ Member Count:** ${$.guild.memberCount}`, - `**❯ Humans:** ${ - members.filter((member) => !member.user.bot) - .size - }`, - `**❯ Bots:** ${ - members.filter((member) => member.user.bot) - .size - }`, - `**❯ Text Channels:** ${ - channels.filter( - (channel) => channel.type === "text" - ).size - }`, - `**❯ Voice Channels:** ${ - channels.filter( - (channel) => channel.type === "voice" - ).size - }`, - `**❯ Boost Count:** ${ - $.guild.premiumSubscriptionCount || "0" - }`, + `**❯ Humans:** ${members.filter((member) => !member.user.bot).size}`, + `**❯ Bots:** ${members.filter((member) => member.user.bot).size}`, + `**❯ Text Channels:** ${channels.filter((channel) => channel.type === "text").size}`, + `**❯ Voice Channels:** ${channels.filter((channel) => channel.type === "voice").size}`, + `**❯ Boost Count:** ${$.guild.premiumSubscriptionCount || "0"}`, `\u200b` ]) .addField("Presence", [ - `**❯ Online:** ${ - members.filter( - (member) => - member.presence.status === "online" - ).size - }`, - `**❯ Idle:** ${ - members.filter( - (member) => - member.presence.status === "idle" - ).size - }`, + `**❯ Online:** ${members.filter((member) => member.presence.status === "online").size}`, + `**❯ Idle:** ${members.filter((member) => member.presence.status === "idle").size}`, `**❯ Do Not Disturb:** ${ - members.filter( - (member) => - member.presence.status === "dnd" - ).size + members.filter((member) => member.presence.status === "dnd").size }`, `**❯ Offline:** ${ - members.filter( - (member) => - member.presence.status === "offline" - ).size + members.filter((member) => member.presence.status === "offline").size }`, "\u200b" ]) .addField( `Roles [${roles.length - 1}]`, - roles.length < 10 - ? roles.join(", ") - : roles.length > 10 - ? trimArray(roles) - : "None" + roles.length < 10 ? roles.join(", ") : roles.length > 10 ? trimArray(roles) : "None" ) .setTimestamp(); @@ -224,56 +153,35 @@ export default new Command({ ); const roles = member.roles.cache - .sort( - (a: {position: number}, b: {position: number}) => - b.position - a.position - ) + .sort((a: {position: number}, b: {position: number}) => b.position - a.position) .map((role: {toString: () => any}) => role.toString()) .slice(0, -1); // @ts-ignore - Discord.js' typings seem to be outdated here. According to their v12 docs, it's User.fetchFlags() instead of User.flags. const userFlags = ((await member.user.fetchFlags()) as UserFlags).toArray(); const embed = new MessageEmbed() - .setThumbnail( - member.user.displayAvatarURL({dynamic: true, size: 512}) - ) + .setThumbnail(member.user.displayAvatarURL({dynamic: true, size: 512})) .setColor(member.displayHexColor || "BLUE") .addField("User", [ `**❯ Username:** ${member.user.username}`, `**❯ Discriminator:** ${member.user.discriminator}`, `**❯ ID:** ${member.id}`, - `**❯ Flags:** ${ - userFlags.length ? userFlags.join(", ") : "None" - }`, - `**❯ Avatar:** [Link to avatar](${member.user.displayAvatarURL( - { - dynamic: true - } - )})`, - `**❯ Time Created:** ${moment( + `**❯ Flags:** ${userFlags.length ? userFlags.join(", ") : "None"}`, + `**❯ Avatar:** [Link to avatar](${member.user.displayAvatarURL({ + dynamic: true + })})`, + `**❯ Time Created:** ${moment(member.user.createdTimestamp).format("LT")} ${moment( member.user.createdTimestamp - ).format("LT")} ${moment( - member.user.createdTimestamp - ).format("LL")} ${moment( - member.user.createdTimestamp - ).fromNow()}`, + ).format("LL")} ${moment(member.user.createdTimestamp).fromNow()}`, `**❯ Status:** ${member.user.presence.status}`, - `**❯ Game:** ${ - member.user.presence.activities || "Not playing a game." - }` + `**❯ Game:** ${member.user.presence.activities || "Not playing a game."}` ]) .addField("Member", [ `**❯ Highest Role:** ${ - member.roles.highest.id === $.guild?.id - ? "None" - : member.roles.highest.name - }`, - `**❯ Server Join Date:** ${moment(member.joinedAt).format( - "LL LTS" - )}`, - `**❯ Hoist Role:** ${ - member.roles.hoist ? member.roles.hoist.name : "None" + member.roles.highest.id === $.guild?.id ? "None" : member.roles.highest.name }`, + `**❯ Server Join Date:** ${moment(member.joinedAt).format("LL LTS")}`, + `**❯ Hoist Role:** ${member.roles.hoist ? member.roles.hoist.name : "None"}`, `**❯ Roles:** [${roles.length}]: ${ roles.length < 10 ? roles.join(", ") diff --git a/src/commands/scanemotes.ts b/src/commands/scanemotes.ts index ed93a3f..1ad5cd8 100644 --- a/src/commands/scanemotes.ts +++ b/src/commands/scanemotes.ts @@ -9,8 +9,7 @@ export default new Command({ description: "Scans all text channels in the current guild and returns the number of times each emoji specific to the guild has been used. Has a cooldown of 24 hours per guild.", async run($: CommonLibrary): Promise { - if (!$.guild) - return $.channel.send(`You must use this command on a server!`); + if (!$.guild) return $.channel.send(`You must use this command on a server!`); // Test if the command is on cooldown. This isn't the strictest cooldown possible, because in the event that the bot crashes, the cooldown will be reset. But for all intends and purposes, it's a good enough cooldown. It's a per-server cooldown. const startTime = Date.now(); @@ -53,9 +52,7 @@ export default new Command({ // If you don't include the "a" for animated emotes, it'll not only not show up, but also cause all other emotes in the same message to not show up. The emote name is self-correcting but it's better to keep the right value since it'll be used to calculate message lengths that fit. stats[emote.id] = { name: emote.name, - formatted: `<${emote.animated ? "a" : ""}:${emote.name}:${ - emote.id - }>`, + formatted: `<${emote.animated ? "a" : ""}:${emote.name}:${emote.id}>`, users: 0, bots: 0 }; @@ -133,10 +130,7 @@ export default new Command({ // Then halt the loop and send warnings of any inconsistencies. continueReactionLoop = false; - if ( - reaction.count !== - userReactions + botReactions - ) { + if (reaction.count !== userReactions + botReactions) { $.warn( `[Channel: ${channel.id}, Message: ${msg.id}] A reaction count of ${reaction.count} was expected but was given ${userReactions} user reactions and ${botReactions} bot reactions.` ); @@ -161,9 +155,7 @@ export default new Command({ const finishTime = Date.now(); clearInterval(interval); statusMessage.edit( - `Finished operation in ${moment - .duration(finishTime - startTime) - .humanize()} with ${$(warnings).pluralise( + `Finished operation in ${moment.duration(finishTime - startTime).humanize()} with ${$(warnings).pluralise( "inconsistenc", "ies", "y" @@ -174,9 +166,7 @@ export default new Command({ // Display stats on emote usage. // This can work outside the loop now that it's synchronous, and now it's clearer what code is meant to execute at the end. - let sortedEmoteIDs = Object.keys(stats).sort( - (a, b) => stats[b].users - stats[a].users - ); + let sortedEmoteIDs = Object.keys(stats).sort((a, b) => stats[b].users - stats[a].users); const lines: string[] = []; let rank = 1; diff --git a/src/commands/utilities/desc.ts b/src/commands/utilities/desc.ts index f375a2c..467053c 100644 --- a/src/commands/utilities/desc.ts +++ b/src/commands/utilities/desc.ts @@ -7,24 +7,16 @@ export default new Command({ async run($: CommonLibrary): Promise { const voiceChannel = $.message.member?.voice.channel; - if (!voiceChannel) - return $.channel.send("You are not in a voice channel."); + if (!voiceChannel) return $.channel.send("You are not in a voice channel."); if (!$.guild?.me?.hasPermission("MANAGE_CHANNELS")) - return $.channel.send( - "I am lacking the required permissions to perform this action." - ); + return $.channel.send("I am lacking the required permissions to perform this action."); - if ($.args.length === 0) - return $.channel.send("Please provide a new voice channel name."); + if ($.args.length === 0) return $.channel.send("Please provide a new voice channel name."); const changeVC = $.guild.channels.resolve(voiceChannel.id); $.channel - .send( - `Changed channel name from "${voiceChannel}" to "${$.args.join( - " " - )}".` - ) + .send(`Changed channel name from "${voiceChannel}" to "${$.args.join(" ")}".`) /// @ts-ignore .then(changeVC?.setName($.args.join(" "))); } diff --git a/src/commands/utilities/emote.ts b/src/commands/utilities/emote.ts index bc0291c..916b7cd 100644 --- a/src/commands/utilities/emote.ts +++ b/src/commands/utilities/emote.ts @@ -10,9 +10,7 @@ export default new Command({ usage: "", async run($: CommonLibrary): Promise { const search = $.args[0].toLowerCase(); - const emote = $.client.emojis.cache.find((emote) => - emote.name.toLowerCase().includes(search) - ); + const emote = $.client.emojis.cache.find((emote) => emote.name.toLowerCase().includes(search)); if (!emote) return $.channel.send("That's not a valid emote name!"); $.message.delete(); $.channel.send(`${emote}`); diff --git a/src/commands/utilities/lsemotes.ts b/src/commands/utilities/lsemotes.ts index a05106d..b84fbb8 100644 --- a/src/commands/utilities/lsemotes.ts +++ b/src/commands/utilities/lsemotes.ts @@ -7,14 +7,10 @@ export default new Command({ endpoint: true, async run($: CommonLibrary): Promise { const nsfw: string | string[] = []; - const pages = $.client.emojis.cache - .filter((x) => !nsfw.includes(x.guild.id), this) - .array(); + const pages = $.client.emojis.cache.filter((x) => !nsfw.includes(x.guild.id), this).array(); const pagesSplit = $(pages).split(20); $.log(pagesSplit); - var embed = new MessageEmbed() - .setTitle("**Emoji list!**") - .setColor("AQUA"); + var embed = new MessageEmbed().setTitle("**Emoji list!**").setColor("AQUA"); let desc = ""; for (const emote of pagesSplit[0]) { diff --git a/src/commands/utilities/react.ts b/src/commands/utilities/react.ts index f7f6080..1517147 100644 --- a/src/commands/utilities/react.ts +++ b/src/commands/utilities/react.ts @@ -16,9 +16,7 @@ export default new Command({ try { target = await $.channel.messages.fetch(last); } catch { - return $.channel.send( - `No valid message found by the ID \`${last}\`!` - ); + return $.channel.send(`No valid message found by the ID \`${last}\`!`); } $.args.pop(); @@ -28,10 +26,7 @@ export default new Command({ distance = parseInt(last); if (distance >= 0 && distance <= 99) $.args.pop(); - else - return $.channel.send( - "Your distance must be between 0 and 99!" - ); + else return $.channel.send("Your distance must be between 0 and 99!"); } } @@ -48,9 +43,7 @@ export default new Command({ let anyEmoteIsValid = false; for (const search of $.args) { - const emoji = $.client.emojis.cache.find( - (emoji) => emoji.name === search - ); + const emoji = $.client.emojis.cache.find((emoji) => emoji.name === search); if (emoji) { // Call the delete function only once to avoid unnecessary errors. diff --git a/src/commands/utilities/shorten.ts b/src/commands/utilities/shorten.ts index 26b8983..5d0b854 100644 --- a/src/commands/utilities/shorten.ts +++ b/src/commands/utilities/shorten.ts @@ -7,19 +7,15 @@ export default new Command({ run: "Please provide a URL.", any: new Command({ async run($: CommonLibrary): Promise { - https.get( - "https://is.gd/create.php?format=simple&url=" + - encodeURIComponent($.args[0]), - function (res) { - var body = ""; - res.on("data", function (chunk) { - body += chunk; - }); - res.on("end", function () { - $.channel.send(`<${body}>`); - }); - } - ); + https.get("https://is.gd/create.php?format=simple&url=" + encodeURIComponent($.args[0]), function (res) { + var body = ""; + res.on("data", function (chunk) { + body += chunk; + }); + res.on("end", function () { + $.channel.send(`<${body}>`); + }); + }); } }) }); diff --git a/src/core/command.ts b/src/core/command.ts index e62e70a..7ba0f02 100644 --- a/src/core/command.ts +++ b/src/core/command.ts @@ -58,8 +58,7 @@ export default class Command { const baseSubcommands = Object.keys(options.subcommands); // Loop once to set the base subcommands. - for (const name in options.subcommands) - this.subcommands.set(name, options.subcommands[name]); + for (const name in options.subcommands) this.subcommands.set(name, options.subcommands[name]); // Then loop again to make aliases point to the base subcommands and warn if something's not right. // This shouldn't be a problem because I'm hoping that JS stores these as references that point to the same object. @@ -118,12 +117,7 @@ export default class Command { // Any Discord ID format will automatically format to a user ID. else if (this.user && /\d{17,19}/.test(param)) return TYPES.USER; // Disallow infinity and allow for 0. - else if ( - this.number && - (Number(param) || param === "0") && - !param.includes("Infinity") - ) - return TYPES.NUMBER; + else if (this.number && (Number(param) || param === "0") && !param.includes("Infinity")) return TYPES.NUMBER; else if (this.any) return TYPES.ANY; else return TYPES.NONE; } @@ -166,9 +160,7 @@ export async function loadCommands(): Promise> { writeFile( "src/commands/test.ts", template, - generateHandler( - '"test.ts" (testing/template command) successfully generated.' - ) + generateHandler('"test.ts" (testing/template command) successfully generated.') ); commands = new Collection(); @@ -189,10 +181,7 @@ export async function loadCommands(): Promise> { while ((cmd = await subdir.read())) { if (cmd.isDirectory()) { if (cmd.name === "subcommands") continue; - else - $.warn( - `You can't have multiple levels of directories! From: "dist/commands/${cmd.name}"` - ); + else $.warn(`You can't have multiple levels of directories! From: "dist/commands/${cmd.name}"`); } else loadCommand(cmd.name, list, selected.name); } @@ -207,25 +196,14 @@ export async function loadCommands(): Promise> { return commands; } -async function loadCommand( - filename: string, - list: string[], - category?: string -) { - if (!commands) - return $.error( - `Function "loadCommand" was called without first initializing commands!` - ); +async function loadCommand(filename: string, list: string[], category?: string) { + if (!commands) return $.error(`Function "loadCommand" was called without first initializing commands!`); const prefix = category ?? ""; const header = filename.substring(0, filename.indexOf(".js")); - const command = (await import(`../commands/${prefix}/${header}`)) - .default as Command | undefined; + const command = (await import(`../commands/${prefix}/${header}`)).default as Command | undefined; - if (!command) - return $.warn( - `Command "${header}" has no default export which is a Command instance!` - ); + if (!command) return $.warn(`Command "${header}" has no default export which is a Command instance!`); command.originalCommandName = header; list.push(header); @@ -238,17 +216,11 @@ async function loadCommand( for (const alias of command.aliases) { if (commands.has(alias)) - $.warn( - `Top-level alias "${alias}" from command "${header}" already exists either as a command or alias!` - ); + $.warn(`Top-level alias "${alias}" from command "${header}" already exists either as a command or alias!`); else commands.set(alias, command); } - $.log( - `Loading Command: ${header} (${ - category ? $(category).toTitleCase() : "Miscellaneous" - })` - ); + $.log(`Loading Command: ${header} (${category ? $(category).toTitleCase() : "Miscellaneous"})`); } // The template should be built with a reductionist mentality. diff --git a/src/core/event.ts b/src/core/event.ts index e84d5a6..c5b2979 100644 --- a/src/core/event.ts +++ b/src/core/event.ts @@ -24,9 +24,7 @@ export default class Event { } export async function loadEvents(client: Client) { - for (const file of Storage.open("dist/events", (filename: string) => - filename.endsWith(".js") - )) { + for (const file of Storage.open("dist/events", (filename: string) => filename.endsWith(".js"))) { const header = file.substring(0, file.indexOf(".js")); const event = (await import(`../events/${header}`)).default; diff --git a/src/core/lib.ts b/src/core/lib.ts index 21ea8b8..976514f 100644 --- a/src/core/lib.ts +++ b/src/core/lib.ts @@ -1,20 +1,5 @@ -import { - GenericWrapper, - NumberWrapper, - StringWrapper, - ArrayWrapper -} from "./wrappers"; -import { - Client, - Message, - TextChannel, - DMChannel, - NewsChannel, - Guild, - User, - GuildMember, - Permissions -} from "discord.js"; +import {GenericWrapper, NumberWrapper, StringWrapper, ArrayWrapper} from "./wrappers"; +import {Client, Message, TextChannel, DMChannel, NewsChannel, Guild, User, GuildMember, Permissions} from "discord.js"; import chalk from "chalk"; import {get} from "https"; import FileManager from "./storage"; @@ -45,16 +30,8 @@ export interface CommonLibrary { callback: (page: number) => void, duration?: number ) => void; - prompt: ( - message: Message, - senderID: string, - onConfirm: () => void, - duration?: number - ) => void; - getMemberByUsername: ( - guild: Guild, - username: string - ) => Promise; + prompt: (message: Message, senderID: string, onConfirm: () => void, duration?: number) => void; + getMemberByUsername: (guild: Guild, username: string) => Promise; callMemberByUsername: ( message: Message, username: string, @@ -88,9 +65,7 @@ export default function $(value: any) { $.handler = function (this: CommonLibrary, error: Error) { if (this) this.channel.send( - `There was an error while trying to execute that command!\`\`\`${ - error.stack ?? error - }\`\`\`` + `There was an error while trying to execute that command!\`\`\`${error.stack ?? error}\`\`\`` ); else $.warn( @@ -117,12 +92,7 @@ export function setConsoleActivated(activated: boolean) { // The custom console. In order of verbosity, error, warn, log, and debug. Ready is a variation of log. // General Purpose Logger $.log = (...args: any[]) => { - if (enabled) - console.log( - chalk.white.bgGray(formatTimestamp()), - chalk.black.bgWhite("INFO"), - ...args - ); + if (enabled) console.log(chalk.white.bgGray(formatTimestamp()), chalk.black.bgWhite("INFO"), ...args); const text = `[${formatUTCTimestamp()}] [INFO] ${args.join(" ")}\n`; logs.info += text; @@ -130,12 +100,7 @@ $.log = (...args: any[]) => { }; // "It'll still work, but you should really check up on this." $.warn = (...args: any[]) => { - if (enabled) - console.warn( - chalk.white.bgGray(formatTimestamp()), - chalk.black.bgYellow("WARN"), - ...args - ); + if (enabled) console.warn(chalk.white.bgGray(formatTimestamp()), chalk.black.bgYellow("WARN"), ...args); const text = `[${formatUTCTimestamp()}] [WARN] ${args.join(" ")}\n`; logs.warn += text; @@ -144,12 +109,7 @@ $.warn = (...args: any[]) => { }; // Used for anything which prevents the program from actually running. $.error = (...args: any[]) => { - if (enabled) - console.error( - chalk.white.bgGray(formatTimestamp()), - chalk.white.bgRed("ERROR"), - ...args - ); + if (enabled) console.error(chalk.white.bgGray(formatTimestamp()), chalk.white.bgRed("ERROR"), ...args); const text = `[${formatUTCTimestamp()}] [ERROR] ${args.join(" ")}\n`; logs.error += text; @@ -162,23 +122,14 @@ $.error = (...args: any[]) => { // Would probably be more suited for debugging program logic rather than function logic, which can be checked using unit tests. $.debug = (...args: any[]) => { if (process.argv[2] === "dev" && enabled) - console.debug( - chalk.white.bgGray(formatTimestamp()), - chalk.white.bgBlue("DEBUG"), - ...args - ); + console.debug(chalk.white.bgGray(formatTimestamp()), chalk.white.bgBlue("DEBUG"), ...args); const text = `[${formatUTCTimestamp()}] [DEBUG] ${args.join(" ")}\n`; logs.verbose += text; }; // Used once at the start of the program when the bot loads. $.ready = (...args: any[]) => { - if (enabled) - console.log( - chalk.white.bgGray(formatTimestamp()), - chalk.black.bgGreen("READY"), - ...args - ); + if (enabled) console.log(chalk.white.bgGray(formatTimestamp()), chalk.black.bgGreen("READY"), ...args); const text = `[${formatUTCTimestamp()}] [READY] ${args.join(" ")}\n`; logs.info += text; @@ -205,14 +156,8 @@ export function formatUTCTimestamp(now = new Date()) { return `${year}-${month}-${day} ${hour}:${minute}:${second}`; } -export function botHasPermission( - guild: Guild | null, - permission: number -): boolean { - return !!( - client.user && - guild?.members.resolve(client.user)?.hasPermission(permission) - ); +export function botHasPermission(guild: Guild | null, permission: number): boolean { + return !!(client.user && guild?.members.resolve(client.user)?.hasPermission(permission)); } // Pagination function that allows for customization via a callback. @@ -253,10 +198,7 @@ $.paginate = async ( if (user.id === senderID) { // The reason this is inside the call is because it's possible to switch a user's permissions halfway and suddenly throw an error. // This will dynamically adjust for that, switching modes depending on whether it currently has the "Manage Messages" permission. - const canDeleteEmotes = botHasPermission( - message.guild, - Permissions.FLAGS.MANAGE_MESSAGES - ); + const canDeleteEmotes = botHasPermission(message.guild, Permissions.FLAGS.MANAGE_MESSAGES); handle(reaction.emoji.name, user.id); if (canDeleteEmotes) reaction.users.remove(user); @@ -273,12 +215,7 @@ $.paginate = async ( }; // Waits for the sender to either confirm an action or let it pass (and delete the message). -$.prompt = async ( - message: Message, - senderID: string, - onConfirm: () => void, - duration = 10000 -) => { +$.prompt = async (message: Message, senderID: string, onConfirm: () => void, duration = 10000) => { let isDeleted = false; message.react("✅"); @@ -312,11 +249,7 @@ $.getMemberByUsername = async (guild: Guild, username: string) => { }; /** Convenience function to handle false cases automatically. */ -$.callMemberByUsername = async ( - message: Message, - username: string, - onSuccess: (member: GuildMember) => void -) => { +$.callMemberByUsername = async (message: Message, username: string, onSuccess: (member: GuildMember) => void) => { const guild = message.guild; const send = message.channel.send; @@ -364,11 +297,7 @@ export function parseArgs(line: string): string[] { * - `%%` = `%` * - If the invalid token is null/undefined, nothing is changed. */ -export function parseVars( - line: string, - definitions: {[key: string]: string}, - invalid: string | null = "" -): string { +export function parseVars(line: string, definitions: {[key: string]: string}, invalid: string | null = ""): string { let result = ""; let inVariable = false; let token = ""; @@ -397,10 +326,7 @@ export function parseVars( export function isType(value: any, type: any): boolean { if (value === undefined && type === undefined) return true; else if (value === null && type === null) return true; - else - return ( - value !== undefined && value !== null && value.constructor === type - ); + else return value !== undefined && value !== null && value.constructor === type; } /** @@ -409,12 +335,7 @@ export function isType(value: any, type: any): boolean { * If at any point the value doesn't match the data structure provided, the fallback is returned. * Warning: Type checking is based on the fallback's type. Be sure that the "type" parameter is accurate to this! */ -export function select( - value: any, - fallback: T, - type: Function, - isArray = false -): T { +export function select(value: any, fallback: T, type: Function, isArray = false): T { if (isArray && isType(value, Array)) { for (let item of value) if (!isType(item, type)) return fallback; return value; @@ -426,9 +347,7 @@ export function select( export function clean(text: any) { if (typeof text === "string") - return text - .replace(/`/g, "`" + String.fromCharCode(8203)) - .replace(/@/g, "@" + String.fromCharCode(8203)); + return text.replace(/`/g, "`" + String.fromCharCode(8203)).replace(/@/g, "@" + String.fromCharCode(8203)); else return text; } @@ -450,34 +369,26 @@ export function formatBytes(bytes: any) { export function getContent(url: any) { return new Promise((resolve, reject) => { - get( - url, - (res: { - resume?: any; - setEncoding?: any; - on?: any; - statusCode?: any; - }) => { - const {statusCode} = res; - if (statusCode !== 200) { - res.resume(); - reject(`Request failed. Status code: ${statusCode}`); - } - res.setEncoding("utf8"); - let rawData = ""; - res.on("data", (chunk: string) => { - rawData += chunk; - }); - res.on("end", () => { - try { - const parsedData = JSON.parse(rawData); - resolve(parsedData); - } catch (e) { - reject(`Error: ${e.message}`); - } - }); + get(url, (res: {resume?: any; setEncoding?: any; on?: any; statusCode?: any}) => { + const {statusCode} = res; + if (statusCode !== 200) { + res.resume(); + reject(`Request failed. Status code: ${statusCode}`); } - ).on("error", (err: {message: any}) => { + res.setEncoding("utf8"); + let rawData = ""; + res.on("data", (chunk: string) => { + rawData += chunk; + }); + res.on("end", () => { + try { + const parsedData = JSON.parse(rawData); + resolve(parsedData); + } catch (e) { + reject(`Error: ${e.message}`); + } + }); + }).on("error", (err: {message: any}) => { reject(`Error: ${err.message}`); }); }); @@ -510,6 +421,5 @@ export const Random = { int: (min: number, max: number) => Math.floor(Random.num(min, max)), chance: (decimal: number) => Math.random() < decimal, sign: (number = 1) => number * (Random.chance(0.5) ? -1 : 1), - deviation: (base: number, deviation: number) => - Random.num(base - deviation, base + deviation) + deviation: (base: number, deviation: number) => Random.num(base - deviation, base + deviation) }; diff --git a/src/core/permissions.ts b/src/core/permissions.ts index 188ca9a..06f9a5b 100644 --- a/src/core/permissions.ts +++ b/src/core/permissions.ts @@ -53,18 +53,13 @@ const PermissionChecker: ((member: GuildMember) => boolean)[] = [ // After checking the lengths of these three objects, use this as the length for consistency. const length = Object.keys(PERMISSIONS).length / 2; -export function hasPermission( - member: GuildMember, - permission: PERMISSIONS -): boolean { - for (let i = length - 1; i >= permission; i--) - if (PermissionChecker[i](member)) return true; +export function hasPermission(member: GuildMember, permission: PERMISSIONS): boolean { + for (let i = length - 1; i >= permission; i--) if (PermissionChecker[i](member)) return true; return false; } export function getPermissionLevel(member: GuildMember): number { - for (let i = length - 1; i >= 0; i--) - if (PermissionChecker[i](member)) return i; + for (let i = length - 1; i >= 0; i--) if (PermissionChecker[i](member)) return i; return 0; } diff --git a/src/core/storage.ts b/src/core/storage.ts index db3bc21..a28e665 100644 --- a/src/core/storage.ts +++ b/src/core/storage.ts @@ -14,16 +14,11 @@ const Storage = { data = JSON.parse(file); } catch (error) { if (process.argv[2] !== "dev") { - $.warn( - `Malformed JSON data (header: ${header}), backing it up.`, - file - ); + $.warn(`Malformed JSON data (header: ${header}), backing it up.`, file); fs.writeFile( `${path}.backup`, file, - generateHandler( - `Backup file of "${header}" successfully written as ${file}.` - ) + generateHandler(`Backup file of "${header}" successfully written as ${file}.`) ); } } @@ -39,30 +34,16 @@ const Storage = { const result = JSON.stringify(data, null, "\t"); if (asynchronous) - fs.writeFile( - path, - result, - generateHandler( - `"${header}" sucessfully spaced and written.` - ) - ); + fs.writeFile(path, result, generateHandler(`"${header}" sucessfully spaced and written.`)); else fs.writeFileSync(path, result); } else { const result = JSON.stringify(data); - if (asynchronous) - fs.writeFile( - path, - result, - generateHandler(`"${header}" sucessfully written.`) - ); + if (asynchronous) fs.writeFile(path, result, generateHandler(`"${header}" sucessfully written.`)); else fs.writeFileSync(path, result); } }, - open( - path: string, - filter?: (value: string, index: number, array: string[]) => unknown - ): string[] { + open(path: string, filter?: (value: string, index: number, array: string[]) => unknown): string[] { if (!fs.existsSync(path)) fs.mkdirSync(path); let directory = fs.readdirSync(path); diff --git a/src/core/structures.ts b/src/core/structures.ts index 243ce34..818fb85 100644 --- a/src/core/structures.ts +++ b/src/core/structures.ts @@ -47,21 +47,15 @@ class StorageStructure extends GenericStructure { this.users = {}; this.guilds = {}; - for (let id in data.users) - if (/\d{17,19}/g.test(id)) - this.users[id] = new User(data.users[id]); + for (let id in data.users) if (/\d{17,19}/g.test(id)) this.users[id] = new User(data.users[id]); - for (let id in data.guilds) - if (/\d{17,19}/g.test(id)) - this.guilds[id] = new Guild(data.guilds[id]); + for (let id in data.guilds) if (/\d{17,19}/g.test(id)) this.guilds[id] = new Guild(data.guilds[id]); } /** Gets a user's profile if they exist and generate one if not. */ public getUser(id: string): User { if (!/\d{17,19}/g.test(id)) - $.warn( - `"${id}" is not a valid user ID! It will be erased when the data loads again.` - ); + $.warn(`"${id}" is not a valid user ID! It will be erased when the data loads again.`); if (id in this.users) return this.users[id]; else { @@ -74,9 +68,7 @@ class StorageStructure extends GenericStructure { /** Gets a guild's settings if they exist and generate one if not. */ public getGuild(id: string): Guild { if (!/\d{17,19}/g.test(id)) - $.warn( - `"${id}" is not a valid guild ID! It will be erased when the data loads again.` - ); + $.warn(`"${id}" is not a valid guild ID! It will be erased when the data loads again.`); if (id in this.guilds) return this.guilds[id]; else { diff --git a/src/core/wrappers.ts b/src/core/wrappers.ts index 9c103a4..edf9b35 100644 --- a/src/core/wrappers.ts +++ b/src/core/wrappers.ts @@ -13,12 +13,7 @@ export class NumberWrapper extends GenericWrapper { * - pluralise("part", "ies", "y") = party/parties * - pluralise("sheep") = sheep */ - public pluralise( - word: string, - plural = "", - singular = "", - excludeNumber = false - ): string { + public pluralise(word: string, plural = "", singular = "", excludeNumber = false): string { let result = excludeNumber ? "" : `${this.value} `; if (this.value === 1) result += word + singular; @@ -33,19 +28,9 @@ export class NumberWrapper extends GenericWrapper { * - (0).pluraliseSigned() = '+0 credits' * - (1).pluraliseSigned() = '+1 credit' */ - public pluraliseSigned( - word: string, - plural = "", - singular = "", - excludeNumber = false - ): string { + public pluraliseSigned(word: string, plural = "", singular = "", excludeNumber = false): string { const sign = this.value >= 0 ? "+" : ""; - return `${sign}${this.pluralise( - word, - plural, - singular, - excludeNumber - )}`; + return `${sign}${this.pluralise(word, plural, singular, excludeNumber)}`; } } @@ -53,8 +38,7 @@ export class StringWrapper extends GenericWrapper { public replaceAll(before: string, after: string): string { let result = this.value; - while (result.indexOf(before) !== -1) - result = result.replace(before, after); + while (result.indexOf(before) !== -1) result = result.replace(before, after); return result; } @@ -78,16 +62,11 @@ export class ArrayWrapper extends GenericWrapper { * `$([1,2,3,4,5,6,7,8,9,10]).split(3)` = `[[1,2,3],[4,5,6],[7,8,9],[10]]` */ public split(lengthOfEachSection: number): T[][] { - const amountOfSections = Math.ceil( - this.value.length / lengthOfEachSection - ); + const amountOfSections = Math.ceil(this.value.length / lengthOfEachSection); const sections: T[][] = new Array(amountOfSections); for (let index = 0; index < amountOfSections; index++) - sections[index] = this.value.slice( - index * lengthOfEachSection, - (index + 1) * lengthOfEachSection - ); + sections[index] = this.value.slice(index * lengthOfEachSection, (index + 1) * lengthOfEachSection); return sections; } diff --git a/src/events/channelCreate.ts b/src/events/channelCreate.ts index 656dcc9..640cd51 100644 --- a/src/events/channelCreate.ts +++ b/src/events/channelCreate.ts @@ -8,9 +8,7 @@ export default new Event<"channelCreate">({ const botGuilds = client.guilds; if (channel instanceof discord.GuildChannel) { const createdGuild = await botGuilds.fetch(channel.guild.id); - $.log( - `Channel created in '${createdGuild.name}' called '#${channel.name}'` - ); + $.log(`Channel created in '${createdGuild.name}' called '#${channel.name}'`); } } }); diff --git a/src/events/channelDelete.ts b/src/events/channelDelete.ts index 656a9e3..48fa2df 100644 --- a/src/events/channelDelete.ts +++ b/src/events/channelDelete.ts @@ -8,9 +8,7 @@ export default new Event<"channelDelete">({ const botGuilds = client.guilds; if (channel instanceof discord.GuildChannel) { const createdGuild = await botGuilds.fetch(channel.guild.id); - $.log( - `Channel deleted in '${createdGuild.name}' called '#${channel.name}'` - ); + $.log(`Channel deleted in '${createdGuild.name}' called '#${channel.name}'`); } } }); diff --git a/src/events/message.ts b/src/events/message.ts index 7bb5285..558208e 100644 --- a/src/events/message.ts +++ b/src/events/message.ts @@ -1,10 +1,6 @@ import Event from "../core/event"; import Command, {loadCommands} from "../core/command"; -import { - hasPermission, - getPermissionLevel, - PermissionNames -} from "../core/permissions"; +import {hasPermission, getPermissionLevel, PermissionNames} from "../core/permissions"; import {Permissions, Collection} from "discord.js"; import {getPrefix} from "../core/structures"; import $ from "../core/lib"; @@ -23,27 +19,18 @@ export default new Event<"message">({ const prefix = getPrefix(message.guild); if (!message.content.startsWith(prefix)) { - if ( - message.client.user && - message.mentions.has(message.client.user) - ) - message.channel.send( - `${message.author.toString()}, my prefix on this guild is \`${prefix}\`.` - ); + if (message.client.user && message.mentions.has(message.client.user)) + message.channel.send(`${message.author.toString()}, my prefix on this guild is \`${prefix}\`.`); return; } - const [header, ...args] = message.content - .substring(prefix.length) - .split(/ +/); + const [header, ...args] = message.content.substring(prefix.length).split(/ +/); if (!commands.has(header)) return; if ( message.channel.type === "text" && - !message.channel - .permissionsFor(message.client.user || "") - ?.has(Permissions.FLAGS.SEND_MESSAGES) + !message.channel.permissionsFor(message.client.user || "")?.has(Permissions.FLAGS.SEND_MESSAGES) ) { let status; @@ -65,25 +52,15 @@ export default new Event<"message">({ // Subcommand Recursion // let command = commands.get(header); - if (!command) - return $.warn( - `Command "${header}" was called but for some reason it's still undefined!` - ); + if (!command) return $.warn(`Command "${header}" was called but for some reason it's still undefined!`); const params: any[] = []; let isEndpoint = false; let permLevel = command.permission ?? Command.PERMISSIONS.NONE; for (let param of args) { if (command.endpoint) { - if ( - command.subcommands.size > 0 || - command.user || - command.number || - command.any - ) - $.warn( - `An endpoint cannot have subcommands! Check ${prefix}${header} again.` - ); + if (command.subcommands.size > 0 || command.user || command.number || command.any) + $.warn(`An endpoint cannot have subcommands! Check ${prefix}${header} again.`); isEndpoint = true; break; } @@ -97,19 +74,14 @@ export default new Event<"message">({ try { params.push(await message.client.users.fetch(id)); } catch (error) { - return message.channel.send( - `No user found by the ID \`${id}\`!` - ); + return message.channel.send(`No user found by the ID \`${id}\`!`); } - } else if (type === Command.TYPES.NUMBER) - params.push(Number(param)); + } else if (type === Command.TYPES.NUMBER) params.push(Number(param)); else if (type !== Command.TYPES.SUBCOMMAND) params.push(param); } if (!message.member) - return $.warn( - "This command was likely called from a DM channel meaning the member object is null." - ); + return $.warn("This command was likely called from a DM channel meaning the member object is null."); if (!hasPermission(message.member, permLevel)) { const userPermLevel = getPermissionLevel(message.member); diff --git a/src/events/messageReactionRemove.ts b/src/events/messageReactionRemove.ts index 15b77a2..10b1af2 100644 --- a/src/events/messageReactionRemove.ts +++ b/src/events/messageReactionRemove.ts @@ -3,18 +3,12 @@ import {Permissions} from "discord.js"; import {botHasPermission} from "../core/lib"; // A list of message ID and callback pairs. You get the emote name and ID of the user reacting. -export const eventListeners: Map< - string, - (emote: string, id: string) => void -> = new Map(); +export const eventListeners: Map void> = new Map(); // Attached to the client, there can be one event listener attached to a message ID which is executed if present. export default new Event<"messageReactionRemove">({ on(reaction, user) { - const canDeleteEmotes = botHasPermission( - reaction.message.guild, - Permissions.FLAGS.MANAGE_MESSAGES - ); + const canDeleteEmotes = botHasPermission(reaction.message.guild, Permissions.FLAGS.MANAGE_MESSAGES); if (!canDeleteEmotes) { const callback = eventListeners.get(reaction.message.id); diff --git a/src/events/ready.ts b/src/events/ready.ts index 364b98f..9cef5ec 100644 --- a/src/events/ready.ts +++ b/src/events/ready.ts @@ -6,9 +6,7 @@ import {Config} from "../core/structures"; export default new Event<"ready">({ once() { if (client.user) { - $.ready( - `Logged in as ${client.user.username}#${client.user.discriminator}.` - ); + $.ready(`Logged in as ${client.user.username}#${client.user.discriminator}.`); client.user.setActivity({ type: "LISTENING", name: `${Config.prefix}help` diff --git a/src/setup.ts b/src/setup.ts index d2d9fbd..64a854a 100644 --- a/src/setup.ts +++ b/src/setup.ts @@ -27,14 +27,12 @@ const prompts = [ { type: "input", name: "admins", - message: - "Enter a list of bot admins (by their IDs) separated by spaces." + message: "Enter a list of bot admins (by their IDs) separated by spaces." }, { type: "input", name: "support", - message: - "Enter a list of bot troubleshooters (by their IDs) separated by spaces." + message: "Enter a list of bot troubleshooters (by their IDs) separated by spaces." } ]; diff --git a/test/wrappers.ts b/test/wrappers.ts index 7d995f3..7613ee3 100644 --- a/test/wrappers.ts +++ b/test/wrappers.ts @@ -6,68 +6,41 @@ describe("Wrappers", () => { describe("NumberWrapper", () => { describe("#pluralise()", () => { it('should return "5 credits"', () => { - assert.strictEqual( - new NumberWrapper(5).pluralise("credit", "s"), - "5 credits" - ); + assert.strictEqual(new NumberWrapper(5).pluralise("credit", "s"), "5 credits"); }); it('should return "1 credit"', () => { - assert.strictEqual( - new NumberWrapper(1).pluralise("credit", "s"), - "1 credit" - ); + assert.strictEqual(new NumberWrapper(1).pluralise("credit", "s"), "1 credit"); }); it('should return "-1 credits"', () => { - assert.strictEqual( - new NumberWrapper(-1).pluralise("credit", "s"), - "-1 credits" - ); + assert.strictEqual(new NumberWrapper(-1).pluralise("credit", "s"), "-1 credits"); }); it("should be able to work with a plural suffix", () => { - assert.strictEqual( - new NumberWrapper(2).pluralise("part", "ies", "y"), - "2 parties" - ); + assert.strictEqual(new NumberWrapper(2).pluralise("part", "ies", "y"), "2 parties"); }); it("should be able to work with a singular suffix", () => { - assert.strictEqual( - new NumberWrapper(1).pluralise("part", "ies", "y"), - "1 party" - ); + assert.strictEqual(new NumberWrapper(1).pluralise("part", "ies", "y"), "1 party"); }); it("should be able to exclude the number", () => { - assert.strictEqual( - new NumberWrapper(1).pluralise("credit", "s", "", true), - "credit" - ); + assert.strictEqual(new NumberWrapper(1).pluralise("credit", "s", "", true), "credit"); }); }); describe("#pluraliseSigned()", () => { it('should return "-1 credits"', () => { - assert.strictEqual( - new NumberWrapper(-1).pluraliseSigned("credit", "s"), - "-1 credits" - ); + assert.strictEqual(new NumberWrapper(-1).pluraliseSigned("credit", "s"), "-1 credits"); }); it('should return "+0 credits"', () => { - assert.strictEqual( - new NumberWrapper(0).pluraliseSigned("credit", "s"), - "+0 credits" - ); + assert.strictEqual(new NumberWrapper(0).pluraliseSigned("credit", "s"), "+0 credits"); }); it('should return "+1 credit"', () => { - assert.strictEqual( - new NumberWrapper(1).pluraliseSigned("credit", "s"), - "+1 credit" - ); + assert.strictEqual(new NumberWrapper(1).pluraliseSigned("credit", "s"), "+1 credit"); }); }); }); @@ -75,19 +48,14 @@ describe("Wrappers", () => { describe("StringWrapper", () => { describe("#replaceAll()", () => { it('should convert "test" to "zesz"', () => { - assert.strictEqual( - new StringWrapper("test").replaceAll("t", "z"), - "zesz" - ); + assert.strictEqual(new StringWrapper("test").replaceAll("t", "z"), "zesz"); }); }); describe("#toTitleCase()", () => { it("should capitalize the first letter of each word", () => { assert.strictEqual( - new StringWrapper( - "yeetus deletus find salvation from jesus" - ).toTitleCase(), + new StringWrapper("yeetus deletus find salvation from jesus").toTitleCase(), "Yeetus Deletus Find Salvation From Jesus" ); }); @@ -97,10 +65,12 @@ describe("Wrappers", () => { describe("ArrayWrapper", () => { describe("#split()", () => { it("should split [1,2,3,4,5,6,7,8,9,10] into [[1,2,3],[4,5,6],[7,8,9],[10]]", () => { - assert.deepStrictEqual( - new ArrayWrapper([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]).split(3), - [[1, 2, 3], [4, 5, 6], [7, 8, 9], [10]] - ); + assert.deepStrictEqual(new ArrayWrapper([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]).split(3), [ + [1, 2, 3], + [4, 5, 6], + [7, 8, 9], + [10] + ]); }); }); }); From 5665a91af4a01400728ad97f52768b9a22cfe5db Mon Sep 17 00:00:00 2001 From: WatDuhHekBro <44940783+WatDuhHekBro@users.noreply.github.com> Date: Tue, 15 Dec 2020 03:22:32 -0600 Subject: [PATCH 067/178] Added more functionality to the react command --- src/commands/utilities/react.ts | 64 ++++++++++++++++++++++++++++++--- 1 file changed, 59 insertions(+), 5 deletions(-) diff --git a/src/commands/utilities/react.ts b/src/commands/utilities/react.ts index 1517147..035ae44 100644 --- a/src/commands/utilities/react.ts +++ b/src/commands/utilities/react.ts @@ -1,18 +1,72 @@ import Command from "../../core/command"; import {CommonLibrary} from "../../core/lib"; +import {Message, Channel, TextChannel} from "discord.js"; export default new Command({ description: "Reacts to the a previous message in your place. You have to react with the same emote before the bot removes that reaction.", - usage: "react ()", + usage: 'react ()', async run($: CommonLibrary): Promise { - let target; + let target: Message | undefined; let distance = 1; if ($.args.length >= 2) { - const last = $.args[$.args.length - 1]; + const last = $.args[$.args.length - 1]; // Because this is optional, do not .pop() unless you're sure it's a message link indicator. + const URLPattern = /^(?:https:\/\/discord.com\/channels\/(\d{17,19})\/(\d{17,19})\/(\d{17,19}))$/; + const copyIDPattern = /^(?:(\d{17,19})-(\d{17,19}))$/; - if (/\d{17,19}/g.test(last)) { + // https://discord.com/channels/// ("Copy Message Link" Button) + if (URLPattern.test(last)) { + const match = URLPattern.exec(last)!; + const guildID = match[1]; + const channelID = match[2]; + const messageID = match[3]; + let guild = $.guild; + let channel: Channel | undefined = $.channel; + + if (guild?.id !== guildID) { + try { + guild = await $.client.guilds.fetch(guildID); + } catch { + return $.channel.send(`\`${guildID}\` is an invalid guild ID!`); + } + } + + if (channel.id !== channelID) channel = guild.channels.cache.get(channelID); + if (!channel) return $.channel.send(`\`${channelID}\` is an invalid channel ID!`); + + if ($.message.id !== messageID) { + try { + target = await (channel as TextChannel).messages.fetch(messageID); + } catch { + return $.channel.send(`\`${messageID}\` is an invalid message ID!`); + } + } + + $.args.pop(); + } + // - ("Copy ID" Button) + else if (copyIDPattern.test(last)) { + const match = copyIDPattern.exec(last)!; + const channelID = match[1]; + const messageID = match[2]; + let channel: Channel | undefined = $.channel; + + if (channel.id !== channelID) channel = $.guild?.channels.cache.get(channelID); + if (!channel) return $.channel.send(`\`${channelID}\` is an invalid channel ID!`); + + if ($.message.id !== messageID) { + try { + target = await (channel as TextChannel).messages.fetch(messageID); + } catch { + return $.channel.send(`\`${messageID}\` is an invalid message ID!`); + } + } + + $.args.pop(); + } + // + else if (/^\d{17,19}$/.test(last)) { try { target = await $.channel.messages.fetch(last); } catch { @@ -22,7 +76,7 @@ export default new Command({ $.args.pop(); } // The entire string has to be a number for this to match. Prevents leaCheeseAmerican1 from triggering this. - else if (/^\d+$/g.test(last)) { + else if (/^\d+$/.test(last)) { distance = parseInt(last); if (distance >= 0 && distance <= 99) $.args.pop(); From 4752adc7b4eaddf5101380252be5f88e6914ddf2 Mon Sep 17 00:00:00 2001 From: WatDuhHekBro <44940783+WatDuhHekBro@users.noreply.github.com> Date: Tue, 15 Dec 2020 05:15:28 -0600 Subject: [PATCH 068/178] Ported the old eco guild command --- src/commands/fun/eco.ts | 3 +- src/commands/fun/subcommands/eco-core.ts | 60 +++++++++++++++++++---- src/commands/fun/subcommands/eco-shop.ts | 4 +- src/commands/fun/subcommands/eco-utils.ts | 17 +++---- 4 files changed, 62 insertions(+), 22 deletions(-) diff --git a/src/commands/fun/eco.ts b/src/commands/fun/eco.ts index bb07e05..555f1de 100644 --- a/src/commands/fun/eco.ts +++ b/src/commands/fun/eco.ts @@ -1,6 +1,6 @@ import Command from "../../core/command"; import {isAuthorized, getMoneyEmbed} from "./subcommands/eco-utils"; -import {DailyCommand, PayCommand, GuildCommand} from "./subcommands/eco-core"; +import {DailyCommand, PayCommand, GuildCommand, LeaderboardCommand} from "./subcommands/eco-core"; import {BuyCommand, ShopCommand} from "./subcommands/eco-shop"; export default new Command({ @@ -12,6 +12,7 @@ export default new Command({ daily: DailyCommand, pay: PayCommand, guild: GuildCommand, + leaderboard: LeaderboardCommand, buy: BuyCommand, shop: ShopCommand }, diff --git a/src/commands/fun/subcommands/eco-core.ts b/src/commands/fun/subcommands/eco-core.ts index 66272b7..38d1725 100644 --- a/src/commands/fun/subcommands/eco-core.ts +++ b/src/commands/fun/subcommands/eco-core.ts @@ -1,7 +1,7 @@ import Command from "../../../core/command"; import $ from "../../../core/lib"; import {Storage} from "../../../core/structures"; -import {isAuthorized, getMoneyEmbed, getSendEmbed} from "./eco-utils"; +import {isAuthorized, getMoneyEmbed, getSendEmbed, ECO_EMBED_COLOR} from "./eco-utils"; export const DailyCommand = new Command({ description: "Pick up your daily Mons. The cooldown is per user and every 22 hours to allow for some leeway.", @@ -18,18 +18,18 @@ export const DailyCommand = new Command({ embed: { title: "Daily Reward", description: "You received 1 Mon!", - color: 0xf1c40f + color: ECO_EMBED_COLOR } }); } else channel.send({ embed: { title: "Daily Reward", - description: `It's too soon to pick up your daily credits. You have about ${( + description: `It's too soon to pick up your daily Mons. You have about ${( (user.lastReceived + 79200000 - now) / 3600000 ).toFixed(1)} hours to go.`, - color: 0xf1c40f + color: ECO_EMBED_COLOR } }); } @@ -37,6 +37,43 @@ export const DailyCommand = new Command({ }); export const GuildCommand = new Command({ + description: "Get info on the guild's economy as a whole.", + async run({guild, channel}) { + if (isAuthorized(guild, channel)) { + const users = Storage.users; + let totalAmount = 0; + + for (const ID in users) { + const user = users[ID]; + totalAmount += user.money; + } + + channel.send({ + embed: { + title: `The Bank of ${guild!.name}`, + color: ECO_EMBED_COLOR, + fields: [ + { + name: "Accounts", + value: Object.keys(users).length, + inline: true + }, + { + name: "Total Mons", + value: totalAmount, + inline: true + } + ], + thumbnail: { + url: guild?.iconURL() ?? "" + } + } + }); + } + } +}); + +export const LeaderboardCommand = new Command({ description: "See the richest players.", async run({guild, channel, client}) { if (isAuthorized(guild, channel)) { @@ -51,15 +88,18 @@ export const GuildCommand = new Command({ fields.push({ name: `#${i + 1}. ${user.username}#${user.discriminator}`, - value: $(users[id].money).pluralise("credit", "s") + value: $(users[id].money).pluralise("Mon", "s") }); } channel.send({ embed: { title: "Top 10 Richest Players", - color: "#ffff00", - fields: fields + color: ECO_EMBED_COLOR, + fields: fields, + thumbnail: { + url: guild?.iconURL() ?? "" + } } }); } @@ -109,7 +149,7 @@ export const PayCommand = new Command({ const amount = Math.floor(last); const sender = Storage.getUser(author.id); - if (amount <= 0) return channel.send("You must send at least one credit!"); + if (amount <= 0) return channel.send("You must send at least one Mon!"); else if (sender.money < amount) return channel.send("You don't have enough money to do that!", getMoneyEmbed(author)); else if (!guild) @@ -136,12 +176,12 @@ export const PayCommand = new Command({ return prompt( await channel.send( `Are you sure you want to send ${$(amount).pluralise( - "credit", + "Mon", "s" )} to this person?\n*(This message will automatically be deleted after 10 seconds.)*`, { embed: { - color: "#ffff00", + color: ECO_EMBED_COLOR, author: { name: `${target.username}#${target.discriminator}`, icon_url: target.displayAvatarURL({ diff --git a/src/commands/fun/subcommands/eco-shop.ts b/src/commands/fun/subcommands/eco-shop.ts index 682b5a2..650cd8f 100644 --- a/src/commands/fun/subcommands/eco-shop.ts +++ b/src/commands/fun/subcommands/eco-shop.ts @@ -1,7 +1,7 @@ import Command from "../../../core/command"; import $ from "../../../core/lib"; import {Storage, getPrefix} from "../../../core/structures"; -import {isAuthorized} from "./eco-utils"; +import {isAuthorized, ECO_EMBED_COLOR} from "./eco-utils"; import {ShopItems, ShopItem} from "./eco-shop-items"; import {EmbedField} from "discord.js"; @@ -21,7 +21,7 @@ export const ShopCommand = new Command({ return { embed: { - color: 0xf1c40f, + color: ECO_EMBED_COLOR, title: title, fields: fields, footer: { diff --git a/src/commands/fun/subcommands/eco-utils.ts b/src/commands/fun/subcommands/eco-utils.ts index ab5af4f..4ad0694 100644 --- a/src/commands/fun/subcommands/eco-utils.ts +++ b/src/commands/fun/subcommands/eco-utils.ts @@ -2,12 +2,14 @@ import $ from "../../../core/lib"; import {Storage} from "../../../core/structures"; import {User, Guild, TextChannel, DMChannel, NewsChannel} from "discord.js"; +export const ECO_EMBED_COLOR = 0xf1c40f; + export function getMoneyEmbed(user: User): object { const profile = Storage.getUser(user.id); return { embed: { - color: 0xffff00, + color: ECO_EMBED_COLOR, author: { name: user.username, icon_url: user.displayAvatarURL({ @@ -18,7 +20,7 @@ export function getMoneyEmbed(user: User): object { fields: [ { name: "Balance", - value: $(profile.money).pluralise("credit", "s") + value: $(profile.money).pluralise("Mon", "s") } ] } @@ -28,7 +30,7 @@ export function getMoneyEmbed(user: User): object { export function getSendEmbed(sender: User, receiver: User, amount: number): object { return { embed: { - color: 0xffff00, + color: ECO_EMBED_COLOR, author: { name: sender.username, icon_url: sender.displayAvatarURL({ @@ -37,18 +39,15 @@ export function getSendEmbed(sender: User, receiver: User, amount: number): obje }) }, title: "Transaction", - description: `${sender.toString()} has sent ${$(amount).pluralise( - "credit", - "s" - )} to ${receiver.toString()}!`, + description: `${sender.toString()} has sent ${$(amount).pluralise("Mon", "s")} to ${receiver.toString()}!`, fields: [ { name: `Sender: ${sender.username}#${sender.discriminator}`, - value: $(Storage.getUser(sender.id).money).pluralise("credit", "s") + value: $(Storage.getUser(sender.id).money).pluralise("Mon", "s") }, { name: `Receiver: ${receiver.username}#${receiver.discriminator}`, - value: $(Storage.getUser(receiver.id).money).pluralise("credit", "s") + value: $(Storage.getUser(receiver.id).money).pluralise("Mon", "s") } ], footer: { From 5b3b5d0611ce543d19749529a41fad314cde96ae Mon Sep 17 00:00:00 2001 From: WatDuhHekBro <44940783+WatDuhHekBro@users.noreply.github.com> Date: Tue, 15 Dec 2020 17:29:40 -0600 Subject: [PATCH 069/178] Added some eco aliases and fixed eval command --- src/commands/admin.ts | 9 +++++---- src/commands/fun/subcommands/eco-core.ts | 2 ++ 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/commands/admin.ts b/src/commands/admin.ts index 645b1fc..47279f9 100644 --- a/src/commands/admin.ts +++ b/src/commands/admin.ts @@ -139,15 +139,16 @@ export default new Command({ description: "Evaluate code.", usage: "", permission: Command.PERMISSIONS.BOT_OWNER, - async run($: CommonLibrary): Promise { + // You have to bring everything into scope to use them. AFAIK, there isn't a more maintainable way to do this, but at least TS will let you know if anything gets removed. + async run({args, author, channel, client, guild, member, message}): Promise { try { - const code = $.args.join(" "); + const code = args.join(" "); let evaled = eval(code); if (typeof evaled !== "string") evaled = require("util").inspect(evaled); - $.channel.send(clean(evaled), {code: "x1"}); + channel.send(clean(evaled), {code: "js", split: true}); } catch (err) { - $.channel.send(`\`ERROR\` \`\`\`x1\n${clean(err)}\n\`\`\``); + channel.send(`\`ERROR\` \`\`\`js\n${clean(err)}\n\`\`\``); } } }), diff --git a/src/commands/fun/subcommands/eco-core.ts b/src/commands/fun/subcommands/eco-core.ts index 38d1725..8aa4726 100644 --- a/src/commands/fun/subcommands/eco-core.ts +++ b/src/commands/fun/subcommands/eco-core.ts @@ -5,6 +5,7 @@ import {isAuthorized, getMoneyEmbed, getSendEmbed, ECO_EMBED_COLOR} from "./eco- export const DailyCommand = new Command({ description: "Pick up your daily Mons. The cooldown is per user and every 22 hours to allow for some leeway.", + aliases: ["get"], async run({author, channel, guild}) { if (isAuthorized(guild, channel)) { const user = Storage.getUser(author.id); @@ -75,6 +76,7 @@ export const GuildCommand = new Command({ export const LeaderboardCommand = new Command({ description: "See the richest players.", + aliases: ["top"], async run({guild, channel, client}) { if (isAuthorized(guild, channel)) { const users = Storage.users; From 3d3631c65a5a727bc7cd6470e033ccf2a8f3bd26 Mon Sep 17 00:00:00 2001 From: WatDuhHekBro <44940783+WatDuhHekBro@users.noreply.github.com> Date: Tue, 15 Dec 2020 17:46:03 -0600 Subject: [PATCH 070/178] Fixed some inaccuracies in eco --- src/commands/fun/subcommands/eco-core.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/commands/fun/subcommands/eco-core.ts b/src/commands/fun/subcommands/eco-core.ts index 8aa4726..5723137 100644 --- a/src/commands/fun/subcommands/eco-core.ts +++ b/src/commands/fun/subcommands/eco-core.ts @@ -138,7 +138,7 @@ export const PayCommand = new Command({ }) }), number: new Command({ - run: "You must use the format `money send `!" + run: "You must use the format `eco pay `!" }), any: new Command({ async run({args, author, channel, guild, prompt}) { @@ -153,9 +153,9 @@ export const PayCommand = new Command({ if (amount <= 0) return channel.send("You must send at least one Mon!"); else if (sender.money < amount) - return channel.send("You don't have enough money to do that!", getMoneyEmbed(author)); + return channel.send("You don't have enough Mons to do that!", getMoneyEmbed(author)); else if (!guild) - return channel.send("You have to use this in a server if you want to send money with a username!"); + return channel.send("You have to use this in a server if you want to send Mons with a username!"); const username = args.join(" "); const member = ( @@ -167,11 +167,11 @@ export const PayCommand = new Command({ if (!member) return channel.send( - `Couldn't find a user by the name of \`${username}\`! If you want to send money to someone in a different server, you have to use their user ID!` + `Couldn't find a user by the name of \`${username}\`! If you want to send Mons to someone in a different server, you have to use their user ID!` ); - else if (member.user.id === author.id) return channel.send("You can't send money to yourself!"); + else if (member.user.id === author.id) return channel.send("You can't send Mons to yourself!"); else if (member.user.bot && process.argv[2] !== "dev") - return channel.send("You can't send money to a bot!"); + return channel.send("You can't send Mons to a bot!"); const target = member.user; From 9ab588468e89cc73cd9243dd7614f7acb1083631 Mon Sep 17 00:00:00 2001 From: WatDuhHekBro <44940783+WatDuhHekBro@users.noreply.github.com> Date: Wed, 16 Dec 2020 01:24:26 -0600 Subject: [PATCH 071/178] Added eco monday --- src/commands/fun/eco.ts | 4 ++- src/commands/fun/subcommands/eco-extras.ts | 34 ++++++++++++++++++++++ src/core/structures.ts | 2 ++ 3 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 src/commands/fun/subcommands/eco-extras.ts diff --git a/src/commands/fun/eco.ts b/src/commands/fun/eco.ts index 555f1de..5be316f 100644 --- a/src/commands/fun/eco.ts +++ b/src/commands/fun/eco.ts @@ -2,6 +2,7 @@ import Command from "../../core/command"; import {isAuthorized, getMoneyEmbed} from "./subcommands/eco-utils"; import {DailyCommand, PayCommand, GuildCommand, LeaderboardCommand} from "./subcommands/eco-core"; import {BuyCommand, ShopCommand} from "./subcommands/eco-shop"; +import {MondayCommand} from "./subcommands/eco-extras"; export default new Command({ description: "Economy command for Monika.", @@ -14,7 +15,8 @@ export default new Command({ guild: GuildCommand, leaderboard: LeaderboardCommand, buy: BuyCommand, - shop: ShopCommand + shop: ShopCommand, + monday: MondayCommand }, user: new Command({ description: "See how much money someone else has by using their user ID or pinging them.", diff --git a/src/commands/fun/subcommands/eco-extras.ts b/src/commands/fun/subcommands/eco-extras.ts new file mode 100644 index 0000000..274bb16 --- /dev/null +++ b/src/commands/fun/subcommands/eco-extras.ts @@ -0,0 +1,34 @@ +import Command from "../../../core/command"; +import {Storage} from "../../../core/structures"; +import {isAuthorized, getMoneyEmbed} from "./eco-utils"; + +const WEEKDAY = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]; + +export const MondayCommand = new Command({ + description: "Use this on a UTC Monday to get an extra Mon. Does not affect your 22 hour timer for `eco daily`.", + async run({guild, channel, author}) { + if (isAuthorized(guild, channel)) { + const user = Storage.getUser(author.id); + const now = new Date(); + const weekday = now.getUTCDay(); + + // If it's a UTC Monday + if (weekday === 1) { + // If the user hasn't already claimed their Monday reward (checks the last 24 hours because that'll block up the entire day) + if (now.getTime() - user.lastMonday >= 86400000) { + user.money++; + user.lastMonday = now.getTime(); + Storage.save(); + channel.send("It is **Mon**day, my dudes.", getMoneyEmbed(author)); + } else channel.send("You've already claimed your **Mon**day reward for this week."); + } else { + const weekdayName = WEEKDAY[weekday]; + const hourText = now.getUTCHours().toString().padStart(2, "0"); + const minuteText = now.getUTCMinutes().toString().padStart(2, "0"); + channel.send( + `Come back when it's **Mon**day. Right now, it's ${weekdayName}, ${hourText}:${minuteText} (UTC).` + ); + } + } + } +}); diff --git a/src/core/structures.ts b/src/core/structures.ts index 818fb85..1513d0b 100644 --- a/src/core/structures.ts +++ b/src/core/structures.ts @@ -23,10 +23,12 @@ class ConfigStructure extends GenericStructure { class User { public money: number; public lastReceived: number; + public lastMonday: number; constructor(data?: GenericJSON) { this.money = select(data?.money, 0, Number); this.lastReceived = select(data?.lastReceived, -1, Number); + this.lastMonday = select(data?.lastMonday, -1, Number); } } From 2017e4540386d13058989529bf4e1e0c63f5c01d Mon Sep 17 00:00:00 2001 From: WatDuhHekBro <44940783+WatDuhHekBro@users.noreply.github.com> Date: Wed, 16 Dec 2020 23:12:17 -0600 Subject: [PATCH 072/178] Tried adding some heuristics to searching emotes --- prettier.config.js | 2 +- src/commands/utilities/emote.ts | 20 +++++------ src/commands/utilities/react.ts | 27 +++++---------- .../utilities/subcommands/emote-utils.ts | 33 +++++++++++++++++++ 4 files changed, 53 insertions(+), 29 deletions(-) create mode 100644 src/commands/utilities/subcommands/emote-utils.ts diff --git a/prettier.config.js b/prettier.config.js index 002b0a1..f27d170 100644 --- a/prettier.config.js +++ b/prettier.config.js @@ -10,5 +10,5 @@ module.exports = { bracketSpacing: false, jsxBracketSameLine: false, arrowParens: "always", - endOfLine: "lf" + endOfLine: "auto" // Apparently, the GitHub repository still uses CRLF. I don't know how to force it to use LF, and until someone figures that out, I'm changing this to auto because I don't want more than one line ending commit. }; diff --git a/src/commands/utilities/emote.ts b/src/commands/utilities/emote.ts index 916b7cd..7b841e5 100644 --- a/src/commands/utilities/emote.ts +++ b/src/commands/utilities/emote.ts @@ -1,19 +1,19 @@ -import {MessageEmbed} from "discord.js"; import Command from "../../core/command"; -import {CommonLibrary} from "../../core/lib"; +import {queryClosestEmoteByName} from "./subcommands/emote-utils"; +import {botHasPermission} from "../../core/lib"; +import {Permissions} from "discord.js"; export default new Command({ description: "Send the specified emote.", run: "Please provide a command name.", any: new Command({ - description: "The emote to send.", - usage: "", - async run($: CommonLibrary): Promise { - const search = $.args[0].toLowerCase(); - const emote = $.client.emojis.cache.find((emote) => emote.name.toLowerCase().includes(search)); - if (!emote) return $.channel.send("That's not a valid emote name!"); - $.message.delete(); - $.channel.send(`${emote}`); + description: "The emote(s) to send.", + usage: "", + async run({guild, channel, message, args}) { + let output = ""; + for (const query of args) output += queryClosestEmoteByName(query).toString(); + if (botHasPermission(guild, Permissions.FLAGS.MANAGE_MESSAGES)) message.delete(); + channel.send(output); } }) }); diff --git a/src/commands/utilities/react.ts b/src/commands/utilities/react.ts index 035ae44..ceef800 100644 --- a/src/commands/utilities/react.ts +++ b/src/commands/utilities/react.ts @@ -1,6 +1,7 @@ import Command from "../../core/command"; import {CommonLibrary} from "../../core/lib"; import {Message, Channel, TextChannel} from "discord.js"; +import {queryClosestEmoteByName} from "./subcommands/emote-utils"; export default new Command({ description: @@ -94,26 +95,16 @@ export default new Command({ ).last(); } - let anyEmoteIsValid = false; - for (const search of $.args) { - const emoji = $.client.emojis.cache.find((emoji) => emoji.name === search); + // Even though the bot will always grab *some* emote, the user can choose not to keep that emote there if it isn't what they want + const emote = queryClosestEmoteByName(search); + const reaction = await target!.react(emote); - if (emoji) { - // Call the delete function only once to avoid unnecessary errors. - if (!anyEmoteIsValid && distance !== 0) $.message.delete(); - anyEmoteIsValid = true; - const reaction = await target?.react(emoji); - - // This part is called with a promise because you don't want to wait 5 seconds between each reaction. - - setTimeout(() => { - /// @ts-ignore - reaction.users.remove($.client.user); - }, 5000); - } + // This part is called with a promise because you don't want to wait 5 seconds between each reaction. + setTimeout(() => { + // This reason for this null assertion is that by the time you use this command, the client is going to be loaded. + reaction.users.remove($.client.user!); + }, 5000); } - - if (!anyEmoteIsValid && !$.message.deleted) $.message.react("❓"); } }); diff --git a/src/commands/utilities/subcommands/emote-utils.ts b/src/commands/utilities/subcommands/emote-utils.ts new file mode 100644 index 0000000..9e2cbf8 --- /dev/null +++ b/src/commands/utilities/subcommands/emote-utils.ts @@ -0,0 +1,33 @@ +import {client} from "../../../index"; + +// Calculate and match the list of emotes against the queried emote, then sort the IDs based on calculated priority. +export function queryClosestEmoteByName(query: string) { + const priorityTable: {[id: string]: number} = {}; + + for (const emote of client.emojis.cache.values()) priorityTable[emote.id] = compareEmoteNames(emote.name, query); + + const resultingIDs = Object.keys(priorityTable).sort((a, b) => priorityTable[b] - priorityTable[a]); + return client.emojis.cache.get(resultingIDs[0])!; +} + +// Compare an emote's name against a query to see how alike the two are. The higher the number, the closer they are. Takes into account length and capitalization. +function compareEmoteNames(emote: string, query: string) { + let likeness = -Math.abs(emote.length - query.length); + const isQueryLonger = query.length > emote.length; + + // Loop through all indexes that the two strings share then compare each letter. + for (let i = 0; i < (isQueryLonger ? emote.length : query.length); i++) { + const c = emote[i]; + const q = query[i]; + + // If they're the exact same character + if (c === q) likeness += 3; + // If the query is uppercase but the emote is lowercase + else if (c === q.toLowerCase()) likeness += 2; + // If the query is lowercase but the emote is uppercase + else if (c === q.toUpperCase()) likeness += 1; + // Otherwise, if they're different characters, don't add anything (this isn't a spellchecker) + } + + return likeness; +} From 799c477bafa68af5cc003cf7131a520055f61bd0 Mon Sep 17 00:00:00 2001 From: WatDuhHekBro <44940783+WatDuhHekBro@users.noreply.github.com> Date: Wed, 16 Dec 2020 23:37:07 -0600 Subject: [PATCH 073/178] Tried a different emote capitalization heuristic --- src/commands/utilities/subcommands/emote-utils.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/commands/utilities/subcommands/emote-utils.ts b/src/commands/utilities/subcommands/emote-utils.ts index 9e2cbf8..0e18652 100644 --- a/src/commands/utilities/subcommands/emote-utils.ts +++ b/src/commands/utilities/subcommands/emote-utils.ts @@ -21,11 +21,11 @@ function compareEmoteNames(emote: string, query: string) { const q = query[i]; // If they're the exact same character - if (c === q) likeness += 3; - // If the query is uppercase but the emote is lowercase - else if (c === q.toLowerCase()) likeness += 2; - // If the query is lowercase but the emote is uppercase + if (c === q) likeness += 1.5; + // If the emote is uppercase but the query is lowercase else if (c === q.toUpperCase()) likeness += 1; + // If the emote is lowercase but the query is uppercase + else if (c === q.toLowerCase()) likeness += 0.5; // Otherwise, if they're different characters, don't add anything (this isn't a spellchecker) } From 2e2bd57598128cf9c84a149d0b2e9b9bf417750f Mon Sep 17 00:00:00 2001 From: WatDuhHekBro <44940783+WatDuhHekBro@users.noreply.github.com> Date: Wed, 23 Dec 2020 04:36:18 -0600 Subject: [PATCH 074/178] Fixed the info command and its problems (kinda) --- src/commands/info.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands/info.ts b/src/commands/info.ts index d8ddda6..d87044f 100644 --- a/src/commands/info.ts +++ b/src/commands/info.ts @@ -145,7 +145,7 @@ export default new Command({ description: "Displays info about mentioned user.", async run($: CommonLibrary): Promise { // Transforms the User object into a GuildMember object of the current guild. - const member = $.guild?.members.resolve($.args[0]); + const member = $.guild?.members.resolve($.args[0]) ?? (await $.guild?.members.fetch($.args[0])); if (!member) return $.channel.send( From c5baf34b7e105899d4281b6eb75145ec7aedb1fc Mon Sep 17 00:00:00 2001 From: Keanu Date: Tue, 29 Dec 2020 13:01:02 +0000 Subject: [PATCH 075/178] Attempt to merge two workflows. --- .github/workflows/codeql-analysis.yml | 41 --------------------------- .github/workflows/image.yml | 29 +++++++++++++++++++ 2 files changed, 29 insertions(+), 41 deletions(-) delete mode 100644 .github/workflows/codeql-analysis.yml diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml deleted file mode 100644 index 200682a..0000000 --- a/.github/workflows/codeql-analysis.yml +++ /dev/null @@ -1,41 +0,0 @@ -name: "CodeQL" - -on: - push: - branches: [typescript] - pull_request: - branches: [typescript] - schedule: - - cron: "0 5 * * 1" - -jobs: - analyze: - name: Analyze - runs-on: ubuntu-latest - - steps: - - name: Checkout repository - uses: actions/checkout@v2 - with: - fetch-depth: 2 - - - run: git checkout HEAD^2 - if: ${{ github.event_name == 'pull_request' }} - - - name: Setup Node.JS - uses: actions/setup-node@v2-beta - with: - node-version: "12" - - run: npm ci - - - name: Build codebase - run: npm run build - - - name: Initialize CodeQL - uses: github/codeql-action/init@v1 - with: - config-file: ./.github/codeql/codeql-config.yml - languages: javascript - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v1 diff --git a/.github/workflows/image.yml b/.github/workflows/image.yml index dac2e7a..efd172f 100644 --- a/.github/workflows/image.yml +++ b/.github/workflows/image.yml @@ -6,7 +6,36 @@ on: - docker jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + with: + fetch-depth: 2 + + - name: Setup Node.JS + uses: actions/setup-node@v2-beta + with: + node-version: "12" + - run: npm ci + + - name: Build codebase + run: npm run build + + - name: Initialize CodeQL + uses: github/codeql-action/init@v1 + with: + config-file: ./.github/codeql/codeql-config.yml + languages: javascript + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v1 + build: + needs: analyze runs-on: ubuntu-latest steps: - name: Checkout code From aeff8ac832e829ae3096e5a65f3af7bc23fefd1c Mon Sep 17 00:00:00 2001 From: Keanu Date: Tue, 29 Dec 2020 13:12:03 +0000 Subject: [PATCH 076/178] Minor changes. --- .github/workflows/image.yml | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/.github/workflows/image.yml b/.github/workflows/image.yml index efd172f..c575066 100644 --- a/.github/workflows/image.yml +++ b/.github/workflows/image.yml @@ -1,4 +1,4 @@ -name: Build Docker Image + Push +name: CodeQL + Docker Image on: push: branches: @@ -7,9 +7,8 @@ on: jobs: analyze: - name: Analyze + name: CodeQL Analysis runs-on: ubuntu-latest - steps: - name: Checkout repository uses: actions/checkout@v2 @@ -35,18 +34,24 @@ jobs: uses: github/codeql-action/analyze@v1 build: + name: Build Docker Image needs: analyze runs-on: ubuntu-latest steps: - - name: Checkout code + - name: Checkout repository uses: actions/checkout@v2 + with: + fetch-depth: 2 + - name: Install Docker BuildX id: buildx uses: crazy-max/ghaction-docker-buildx@v1 with: buildx-version: latest + - name: Login to Docker Hub run: echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u "${{ secrets.DOCKER_USERNAME }}" --password-stdin + - name: Build the image run: | docker buildx build \ From a99386c43120933f44c17d37f0f9ac981a747ad8 Mon Sep 17 00:00:00 2001 From: Keanu Date: Thu, 31 Dec 2020 16:10:56 +0000 Subject: [PATCH 077/178] Added (currently broken) emote dumper. --- src/core/lib.ts | 27 +++++++++++++++++++++++++++ src/core/structures.ts | 18 +++++++++++++++++- src/events/emojiCreate.ts | 10 ++++++++++ src/events/emojiDelete.ts | 10 ++++++++++ src/events/emojiUpdate.ts | 10 ++++++++++ src/events/guildCreate.ts | 10 ++++++++++ src/events/guildDelete.ts | 10 ++++++++++ src/events/ready.ts | 2 ++ 8 files changed, 96 insertions(+), 1 deletion(-) create mode 100644 src/events/emojiCreate.ts create mode 100644 src/events/emojiDelete.ts create mode 100644 src/events/emojiUpdate.ts create mode 100644 src/events/guildCreate.ts create mode 100644 src/events/guildDelete.ts diff --git a/src/core/lib.ts b/src/core/lib.ts index 976514f..ad7dba6 100644 --- a/src/core/lib.ts +++ b/src/core/lib.ts @@ -1,3 +1,4 @@ +import {writeFileSync} from "fs"; import {GenericWrapper, NumberWrapper, StringWrapper, ArrayWrapper} from "./wrappers"; import {Client, Message, TextChannel, DMChannel, NewsChannel, Guild, User, GuildMember, Permissions} from "discord.js"; import chalk from "chalk"; @@ -5,6 +6,7 @@ import {get} from "https"; import FileManager from "./storage"; import {eventListeners} from "../events/messageReactionRemove"; import {client} from "../index"; +import {EmoteRegistryDump, EmoteRegistryDumpEntry} from "./structures"; /** A type that describes what the library module does. */ export interface CommonLibrary { @@ -160,6 +162,31 @@ export function botHasPermission(guild: Guild | null, permission: number): boole return !!(client.user && guild?.members.resolve(client.user)?.hasPermission(permission)); } +export function updateGlobalEmoteRegistry(): void { + const data: EmoteRegistryDump = {version: 1, list: []}; + for (const guild of client.guilds.cache.values()) { + $.debug(client.guilds.cache.size); + for (const emote of guild.emojis.cache.values()) { + $.debug(client.emojis.cache.size); + console.log("HELLO?!"); + data.list.push({ + ref: emote.name, + id: emote.id, + name: emote.name, + requires_colons: emote.requiresColons || false, + animated: emote.animated, + url: emote.url, + guild_id: emote.guild.name, + guild_name: emote.guild.name + }); + } + } + const sData = JSON.stringify(data); + $.debug(data); + $.debug(sData); + writeFileSync("emote-registry.json", sData); +} + // Pagination function that allows for customization via a callback. // Define your own pages outside the function because this only manages the actual turning of pages. $.paginate = async ( diff --git a/src/core/structures.ts b/src/core/structures.ts index 1513d0b..0d78e13 100644 --- a/src/core/structures.ts +++ b/src/core/structures.ts @@ -1,7 +1,7 @@ import FileManager from "./storage"; import $, {select, GenericJSON, GenericStructure} from "./lib"; import {watch} from "fs"; -import {Guild as DiscordGuild} from "discord.js"; +import {Guild as DiscordGuild, Snowflake} from "discord.js"; class ConfigStructure extends GenericStructure { public token: string; @@ -106,3 +106,19 @@ if (process.argv[2] === "dev") { export function getPrefix(guild: DiscordGuild | null): string { return Storage.getGuild(guild?.id || "N/A").prefix ?? Config.prefix; } + +export interface EmoteRegistryDumpEntry { + ref: string; + id: Snowflake; + name: string; + requires_colons: boolean; + animated: boolean; + url: string; + guild_id: Snowflake; + guild_name: string; +} + +export interface EmoteRegistryDump { + version: number; + list: EmoteRegistryDumpEntry[]; +} diff --git a/src/events/emojiCreate.ts b/src/events/emojiCreate.ts new file mode 100644 index 0000000..dffd9aa --- /dev/null +++ b/src/events/emojiCreate.ts @@ -0,0 +1,10 @@ +import Event from "../core/event"; +import $ from "../core/lib"; +import {updateGlobalEmoteRegistry} from "../core/lib"; + +export default new Event<"emojiCreate">({ + on(emote) { + $.log(`Updated emote registry. ${emote.name}`); + updateGlobalEmoteRegistry(); + } +}); diff --git a/src/events/emojiDelete.ts b/src/events/emojiDelete.ts new file mode 100644 index 0000000..a3d5d43 --- /dev/null +++ b/src/events/emojiDelete.ts @@ -0,0 +1,10 @@ +import Event from "../core/event"; +import $ from "../core/lib"; +import {updateGlobalEmoteRegistry} from "../core/lib"; + +export default new Event<"emojiDelete">({ + on() { + $.log("Updated emote registry."); + updateGlobalEmoteRegistry(); + } +}); diff --git a/src/events/emojiUpdate.ts b/src/events/emojiUpdate.ts new file mode 100644 index 0000000..a7a8e11 --- /dev/null +++ b/src/events/emojiUpdate.ts @@ -0,0 +1,10 @@ +import Event from "../core/event"; +import $ from "../core/lib"; +import {updateGlobalEmoteRegistry} from "../core/lib"; + +export default new Event<"emojiUpdate">({ + on() { + $.log("Updated emote registry."); + updateGlobalEmoteRegistry(); + } +}); diff --git a/src/events/guildCreate.ts b/src/events/guildCreate.ts new file mode 100644 index 0000000..2820db7 --- /dev/null +++ b/src/events/guildCreate.ts @@ -0,0 +1,10 @@ +import Event from "../core/event"; +import $ from "../core/lib"; +import {updateGlobalEmoteRegistry} from "../core/lib"; + +export default new Event<"guildCreate">({ + on() { + $.log("Updated emote registry."); + updateGlobalEmoteRegistry(); + } +}); diff --git a/src/events/guildDelete.ts b/src/events/guildDelete.ts new file mode 100644 index 0000000..8fed108 --- /dev/null +++ b/src/events/guildDelete.ts @@ -0,0 +1,10 @@ +import Event from "../core/event"; +import $ from "../core/lib"; +import {updateGlobalEmoteRegistry} from "../core/lib"; + +export default new Event<"guildDelete">({ + on() { + $.log("Updated emote registry."); + updateGlobalEmoteRegistry(); + } +}); diff --git a/src/events/ready.ts b/src/events/ready.ts index 9cef5ec..e3fe021 100644 --- a/src/events/ready.ts +++ b/src/events/ready.ts @@ -2,6 +2,7 @@ import Event from "../core/event"; import {client} from "../index"; import $ from "../core/lib"; import {Config} from "../core/structures"; +import {updateGlobalEmoteRegistry} from "../core/lib"; export default new Event<"ready">({ once() { @@ -12,5 +13,6 @@ export default new Event<"ready">({ name: `${Config.prefix}help` }); } + updateGlobalEmoteRegistry(); } }); From 68d8eed4edd63ec02474c8547da1a23c0d17b443 Mon Sep 17 00:00:00 2001 From: Keanu Date: Thu, 31 Dec 2020 16:11:51 +0000 Subject: [PATCH 078/178] Added emote-registry to gitignore. --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 7f1e29d..db69620 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ test* !test/ *.bat desktop.ini +emote-registry.json # Logs logs @@ -68,4 +69,4 @@ typings/ .env config.json -.vscode/ \ No newline at end of file +.vscode/ From 5537cec29a0d512cae5509c39a00d71dcf9c6c35 Mon Sep 17 00:00:00 2001 From: WatDuhHekBro <44940783+WatDuhHekBro@users.noreply.github.com> Date: Fri, 1 Jan 2021 19:44:43 -0600 Subject: [PATCH 079/178] Removed structure redundancy and saves to data/ --- .gitignore | 1 - src/core/lib.ts | 17 +++++------------ src/core/structures.ts | 2 -- 3 files changed, 5 insertions(+), 15 deletions(-) diff --git a/.gitignore b/.gitignore index db69620..a3becd9 100644 --- a/.gitignore +++ b/.gitignore @@ -7,7 +7,6 @@ test* !test/ *.bat desktop.ini -emote-registry.json # Logs logs diff --git a/src/core/lib.ts b/src/core/lib.ts index ad7dba6..7a12538 100644 --- a/src/core/lib.ts +++ b/src/core/lib.ts @@ -1,4 +1,3 @@ -import {writeFileSync} from "fs"; import {GenericWrapper, NumberWrapper, StringWrapper, ArrayWrapper} from "./wrappers"; import {Client, Message, TextChannel, DMChannel, NewsChannel, Guild, User, GuildMember, Permissions} from "discord.js"; import chalk from "chalk"; @@ -163,28 +162,22 @@ export function botHasPermission(guild: Guild | null, permission: number): boole } export function updateGlobalEmoteRegistry(): void { - const data: EmoteRegistryDump = {version: 1, list: []}; + const list: EmoteRegistryDumpEntry[] = []; + for (const guild of client.guilds.cache.values()) { - $.debug(client.guilds.cache.size); for (const emote of guild.emojis.cache.values()) { - $.debug(client.emojis.cache.size); - console.log("HELLO?!"); - data.list.push({ - ref: emote.name, + list.push({ id: emote.id, name: emote.name, requires_colons: emote.requiresColons || false, animated: emote.animated, url: emote.url, - guild_id: emote.guild.name, guild_name: emote.guild.name }); } } - const sData = JSON.stringify(data); - $.debug(data); - $.debug(sData); - writeFileSync("emote-registry.json", sData); + + FileManager.write("emote-registry", {version: 1, list}, true); } // Pagination function that allows for customization via a callback. diff --git a/src/core/structures.ts b/src/core/structures.ts index 0d78e13..a921931 100644 --- a/src/core/structures.ts +++ b/src/core/structures.ts @@ -108,13 +108,11 @@ export function getPrefix(guild: DiscordGuild | null): string { } export interface EmoteRegistryDumpEntry { - ref: string; id: Snowflake; name: string; requires_colons: boolean; animated: boolean; url: string; - guild_id: Snowflake; guild_name: string; } From f8d6c0d33603d3610fdb87565edc6d609d81c94c Mon Sep 17 00:00:00 2001 From: WatDuhHekBro <44940783+WatDuhHekBro@users.noreply.github.com> Date: Sat, 2 Jan 2021 20:16:15 -0600 Subject: [PATCH 080/178] Reverted changes except for the file location --- src/core/lib.ts | 8 +++++--- src/core/structures.ts | 2 ++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/core/lib.ts b/src/core/lib.ts index 7a12538..0c44fbc 100644 --- a/src/core/lib.ts +++ b/src/core/lib.ts @@ -162,22 +162,24 @@ export function botHasPermission(guild: Guild | null, permission: number): boole } export function updateGlobalEmoteRegistry(): void { - const list: EmoteRegistryDumpEntry[] = []; + const data: EmoteRegistryDump = {version: 1, list: []}; for (const guild of client.guilds.cache.values()) { for (const emote of guild.emojis.cache.values()) { - list.push({ + data.list.push({ + ref: emote.name, id: emote.id, name: emote.name, requires_colons: emote.requiresColons || false, animated: emote.animated, url: emote.url, + guild_id: emote.guild.name, guild_name: emote.guild.name }); } } - FileManager.write("emote-registry", {version: 1, list}, true); + FileManager.write("emote-registry", data, true); } // Pagination function that allows for customization via a callback. diff --git a/src/core/structures.ts b/src/core/structures.ts index a921931..0d78e13 100644 --- a/src/core/structures.ts +++ b/src/core/structures.ts @@ -108,11 +108,13 @@ export function getPrefix(guild: DiscordGuild | null): string { } export interface EmoteRegistryDumpEntry { + ref: string; id: Snowflake; name: string; requires_colons: boolean; animated: boolean; url: string; + guild_id: Snowflake; guild_name: string; } From eec6aa7b96a8e3533e4a087d0aeb4c0b4a44f636 Mon Sep 17 00:00:00 2001 From: Keanu Date: Wed, 6 Jan 2021 14:20:38 +0100 Subject: [PATCH 081/178] Upgraded dependencies. --- package-lock.json | 155 ++++++++++++++++++++++++---------------------- package.json | 16 ++--- 2 files changed, 90 insertions(+), 81 deletions(-) diff --git a/package-lock.json b/package-lock.json index c7e8e8f..67cb5d2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -54,9 +54,9 @@ } }, "@types/mocha": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-8.0.3.tgz", - "integrity": "sha512-vyxR57nv8NfcU0GZu8EUXZLTbCMupIUwy95LJ6lllN+JRPG25CwMHoB1q5xKh8YKhQnHYRAn4yW2yuHbf/5xgg==", + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-8.2.0.tgz", + "integrity": "sha512-/Sge3BymXo4lKc31C8OINJgXLaw+7vL1/L1pGiBNpGrBiT8FQiaFpSYV0uhTaG4y78vcMBTMFsWaHDvuD+xGzQ==", "dev": true }, "@types/ms": { @@ -66,9 +66,9 @@ "dev": true }, "@types/node": { - "version": "14.14.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.2.tgz", - "integrity": "sha512-jeYJU2kl7hL9U5xuI/BhKPZ4vqGM/OmK6whiFAXVhlstzZhVamWhDSmHyGLIp+RVyuF9/d0dqr2P85aFj4BvJg==", + "version": "14.14.20", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.20.tgz", + "integrity": "sha512-Y93R97Ouif9JEOWPIUyU+eyIdyRqQR0I8Ez1dzku4hDx34NWh4HbtIc3WNzwB1Y9ULvNGeu5B8h8bVL5cAk4/A==", "dev": true }, "@types/through": { @@ -81,9 +81,9 @@ } }, "@types/ws": { - "version": "7.2.7", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-7.2.7.tgz", - "integrity": "sha512-UUFC/xxqFLP17hTva8/lVT0SybLUrfSD9c+iapKb0fEiC8uoDbA+xuZ3pAN603eW+bY8ebSMLm9jXdIPnD0ZgA==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-7.4.0.tgz", + "integrity": "sha512-Y29uQ3Uy+58bZrFLhX36hcI3Np37nqWE7ky5tjiDoy1GDZnIwVxS0CgF+s+1bXMzjKBFy+fqaRfb708iNzdinw==", "dev": true, "requires": { "@types/node": "*" @@ -162,11 +162,11 @@ "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" }, "axios": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.19.2.tgz", - "integrity": "sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA==", + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz", + "integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==", "requires": { - "follow-redirects": "1.5.10" + "follow-redirects": "^1.10.0" } }, "balanced-match": { @@ -339,6 +339,12 @@ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "dev": true }, + "create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, "cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -357,6 +363,14 @@ "dev": true, "requires": { "ms": "2.1.2" + }, + "dependencies": { + "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 + } } }, "decamelize": { @@ -377,9 +391,9 @@ "dev": true }, "discord.js": { - "version": "12.4.0", - "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-12.4.0.tgz", - "integrity": "sha512-Lc+/vKzih1DPEya/0MO0BAp4Ru/4+MfDsOJkfbyzGoyIlFqiWSQ78RdyRk4tqAtabl6d5QR4Ygo0Ub0TGEsXBg==", + "version": "12.5.1", + "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-12.5.1.tgz", + "integrity": "sha512-VwZkVaUAIOB9mKdca0I5MefPMTQJTNg0qdgi1huF3iwsFwJ0L5s/Y69AQe+iPmjuV6j9rtKoG0Ta0n9vgEIL6w==", "requires": { "@discordjs/collection": "^0.1.6", "@discordjs/form-data": "^3.0.1", @@ -392,12 +406,12 @@ } }, "discord.js-lavalink-lib": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/discord.js-lavalink-lib/-/discord.js-lavalink-lib-0.1.7.tgz", - "integrity": "sha512-czjsdRcbB+sdfTXsko76JYAAPJzoRhTiaphZJXqFpsAYdv0hjkNVLHxuuG2d4hVuXjt/UU7HDYyWEb2e/3g2Ew==", + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/discord.js-lavalink-lib/-/discord.js-lavalink-lib-0.1.8.tgz", + "integrity": "sha512-Kv4ps5AW4xYxMuJypHJrPL1occlV817CjHiIL7J09EdgzNRcKRIif0U6XMxWxi45Pg3DOl41Lw1V+J2GRsMgcw==", "requires": { "@lavacord/discord.js": "0.0.5", - "axios": "^0.19.2", + "axios": ">=0.21.1", "discord.js": "^12.2.0", "lavacord": "^1.1.7" } @@ -488,27 +502,9 @@ "dev": true }, "follow-redirects": { - "version": "1.5.10", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz", - "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==", - "requires": { - "debug": "=3.1.0" - }, - "dependencies": { - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - } - } + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.1.tgz", + "integrity": "sha512-SSG5xmZh1mkPGyKzjZP8zLjltIfpW32Y5QpdNJyjcfGxK3qo3NDDkZOZSFiGn1A6SclQxY9GzEwAHQ3dmYRWpg==" }, "from": { "version": "0.1.7", @@ -721,16 +717,16 @@ "dev": true }, "mime-db": { - "version": "1.44.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", - "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==" + "version": "1.45.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.45.0.tgz", + "integrity": "sha512-CkqLUxUk15hofLoLyljJSrukZi8mAtgd+yE5uO4tqRZsdsAJKv0O+rFMhVDRJgozy+yG6md5KwuXhD4ocIoP+w==" }, "mime-types": { - "version": "2.1.27", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", - "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", + "version": "2.1.28", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.28.tgz", + "integrity": "sha512-0TO2yJ5YHYr7M2zzT7gDU1tbwHxEUWBCLt0lscSNpcdAfFyJOVEpRYNS7EXVcTLNj/25QO8gulHC5JtTzSE2UQ==", "requires": { - "mime-db": "1.44.0" + "mime-db": "1.45.0" } }, "mimic-fn": { @@ -748,9 +744,9 @@ } }, "mocha": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-8.2.0.tgz", - "integrity": "sha512-lEWEMq2LMfNJMKeuEwb5UELi+OgFDollXaytR5ggQcHpzG3NP/R7rvixAvF+9/lLsTWhWG+4yD2M70GsM06nxw==", + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-8.2.1.tgz", + "integrity": "sha512-cuLBVfyFfFqbNR0uUKbDGXKGk+UDFe6aR4os78XIrMQpZl/nv7JYHcvP5MFIAb374b2zFXsdgEGwmzMtP0Xg8w==", "dev": true, "requires": { "@ungap/promise-all-settled": "1.1.2", @@ -786,6 +782,12 @@ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true }, + "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 + }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -803,9 +805,9 @@ "integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==" }, "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, "mute-stream": { "version": "0.0.8", @@ -863,12 +865,12 @@ "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" }, "p-limit": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.0.2.tgz", - "integrity": "sha512-iwqZSOoWIW+Ew4kAGUlN16J4M7OB3ysMLSZtnhmqx7njIHFPlxWBX8xo3lVTyFVq6mI/lL9qt2IsN1sHwaxJkg==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, "requires": { - "p-try": "^2.0.0" + "yocto-queue": "^0.1.0" } }, "p-locate": { @@ -926,9 +928,9 @@ "dev": true }, "prism-media": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/prism-media/-/prism-media-1.2.2.tgz", - "integrity": "sha512-I+nkWY212lJ500jLe4tN9tWO7nRiBAVdMv76P9kffZjYhw20raMlW1HSSvS+MLXC9MmbNZCazMrAr+5jEEgTuw==" + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/prism-media/-/prism-media-1.2.3.tgz", + "integrity": "sha512-fSrR66n0l6roW9Rx4rSLMyTPTjRTiXy5RVqDOurACQ6si1rKHHKDU5gwBJoCsIV0R3o9gi+K50akl/qyw1C74A==" }, "ps-tree": { "version": "1.2.0", @@ -1143,12 +1145,13 @@ } }, "ts-node": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-9.0.0.tgz", - "integrity": "sha512-/TqB4SnererCDR/vb4S/QvSZvzQMJN8daAslg7MeaiHvD8rDZsSfXmNeNumyZZzMned72Xoq/isQljYSt8Ynfg==", + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-9.1.1.tgz", + "integrity": "sha512-hPlt7ZACERQGf03M253ytLY3dHbGNGrAq9qIHWUY9XHYl1z7wYngSr3OQ5xmui8o2AaxsONxIzjafLUiWBo1Fg==", "dev": true, "requires": { "arg": "^4.1.0", + "create-require": "^1.1.0", "diff": "^4.0.1", "make-error": "^1.1.1", "source-map-support": "^0.5.17", @@ -1334,14 +1337,14 @@ "dev": true }, "ws": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.3.1.tgz", - "integrity": "sha512-D3RuNkynyHmEJIpD2qrgVkc9DQ23OrN/moAwZX4L8DfvszsJxpjQuUq3LMx6HoYji9fbIOBY18XWBsAux1ZZUA==" + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.2.tgz", + "integrity": "sha512-T4tewALS3+qsrpGI/8dqNMLIVdq/g/85U98HPMa6F0m6xTbvhXU6RCQLqPH3+SlomNV/LdY6RXEbBpMH6EOJnA==" }, "y18n": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", - "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.1.tgz", + "integrity": "sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ==", "dev": true }, "yargs": { @@ -1468,9 +1471,9 @@ }, "dependencies": { "camelcase": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.1.0.tgz", - "integrity": "sha512-WCMml9ivU60+8rEJgELlFp1gxFcEGxwYleE3bziHEDeqsqAWGHdimB7beBFGjLzVNgPGyDsfgXLQEYMpmIFnVQ==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", + "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", "dev": true }, "decamelize": { @@ -1486,6 +1489,12 @@ "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", "dev": true + }, + "yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true } } } diff --git a/package.json b/package.json index 27612ab..b76715e 100644 --- a/package.json +++ b/package.json @@ -6,22 +6,22 @@ "private": true, "dependencies": { "chalk": "^4.1.0", - "discord.js": "^12.4.0", - "discord.js-lavalink-lib": "^0.1.7", + "discord.js": "^12.5.1", + "discord.js-lavalink-lib": "^0.1.8", "inquirer": "^7.3.3", "moment": "^2.29.1", - "ms": "^2.1.2", + "ms": "^2.1.3", "os": "^0.1.1" }, "devDependencies": { "@types/inquirer": "^6.5.0", - "@types/mocha": "^8.0.3", + "@types/mocha": "^8.2.0", "@types/ms": "^0.7.31", - "@types/node": "^14.14.2", - "@types/ws": "^7.2.7", - "mocha": "^8.2.0", + "@types/node": "^14.14.20", + "@types/ws": "^7.4.0", + "mocha": "^8.2.1", "prettier": "2.1.2", - "ts-node": "^9.0.0", + "ts-node": "^9.1.1", "tsc-watch": "^4.2.9", "typescript": "^3.9.7" }, From 7b4d8b934cb156fefc94bb862b83ac3f7ee5b3b7 Mon Sep 17 00:00:00 2001 From: WatDuhHekBro <44940783+WatDuhHekBro@users.noreply.github.com> Date: Sun, 24 Jan 2021 08:07:58 -0600 Subject: [PATCH 082/178] Added time command for user-submitted timezones --- src/commands/utilities/time.ts | 293 +++++++++++++++++++++++++++++++++ src/core/lib.ts | 116 ++++++++++++- src/core/structures.ts | 6 + src/events/message.ts | 10 +- 4 files changed, 420 insertions(+), 5 deletions(-) create mode 100644 src/commands/utilities/time.ts diff --git a/src/commands/utilities/time.ts b/src/commands/utilities/time.ts new file mode 100644 index 0000000..de4fe12 --- /dev/null +++ b/src/commands/utilities/time.ts @@ -0,0 +1,293 @@ +import Command from "../../core/command"; +import {Storage} from "../../core/structures"; +import {User} from "discord.js"; +import moment from "moment"; + +const DATE_FORMAT = "D MMMM YYYY"; +const TIME_FORMAT = "HH:mm:ss"; +type DST = "na" | "eu" | "sh"; + +const DAYLIGHT_SAVINGS_REGIONS: {[region in DST]: string} = { + na: "North America", + eu: "Europe", + sh: "Southern Hemisphere" +}; + +const DST_NOTE_INFO = `*Note: To make things simple, the way the bot will handle specific points in time when switching Daylight Savings is just to switch at UTC 00:00, ignoring local timezones. After all, there's no need to get this down to the exact hour.* + +North America +- Starts: 2nd Sunday March +- Ends: 1st Sunday November + +Europe +- Starts: Last Sunday March +- Ends: Last Sunday October + +Southern Hemisphere +- Starts: 1st Sunday of October +- Ends: 1st Sunday of April`; + +const DST_NOTE_SETUP = `Which daylight savings region most closely matches your own? + +North America (1️⃣) +- Starts: 2nd Sunday March +- Ends: 1st Sunday November + +Europe (2️⃣) +- Starts: Last Sunday March +- Ends: Last Sunday October + +Southern Hemisphere (3️⃣) +- Starts: 1st Sunday of October +- Ends: 1st Sunday of April`; + +const DAYS_OF_MONTH = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; + +// Returns an integer of the specific day the Sunday falls on, -1 if not found +// Also modifies the date object to the specified day as a side effect +function getSunday(date: Date, order: number) { + const daysInCurrentMonth = DAYS_OF_MONTH[date.getUTCMonth()]; + let occurrencesLeft = order - 1; + + // Search for the last Sunday of the month + if (order === 0) { + for (let day = daysInCurrentMonth; day >= 1; day--) { + date.setUTCDate(day); + + if (date.getUTCDay() === 0) { + return day; + } + } + } else if (order > 0) { + for (let day = 1; day <= daysInCurrentMonth; day++) { + date.setUTCDate(day); + + if (date.getUTCDay() === 0) { + if (occurrencesLeft > 0) { + occurrencesLeft--; + } else { + return day; + } + } + } + } + + return -1; +} + +// region: [firstMonth (0-11), firstOrder, secondMonth (0-11), secondOrder] +const DST_REGION_TABLE = { + na: [2, 2, 10, 1], + eu: [2, 0, 9, 0], + sh: [3, 1, 9, 1] // this one is reversed for the sake of code simplicity +}; + +// capturing: northern hemisphere is concave, southern hemisphere is convex +function hasDaylightSavings(region: DST) { + const [firstMonth, firstOrder, secondMonth, secondOrder] = DST_REGION_TABLE[region]; + const date = new Date(); + const now = date.getTime(); + const currentYear = date.getUTCFullYear(); + const firstDate = new Date(Date.UTC(currentYear, firstMonth)); + const secondDate = new Date(Date.UTC(currentYear, secondMonth)); + getSunday(firstDate, firstOrder); + getSunday(secondDate, secondOrder); + const insideBounds = now >= firstDate.getTime() && now < secondDate.getTime(); + return region !== "sh" ? insideBounds : !insideBounds; +} + +function getTimeEmbed(user: User) { + const {timezone, daylightSavingsRegion} = Storage.getUser(user.id); + let localDate = "N/A"; + let localTime = "N/A"; + let timezoneOffset = "N/A"; + + if (timezone !== null) { + const daylightSavingsOffset = daylightSavingsRegion && hasDaylightSavings(daylightSavingsRegion) ? 1 : 0; + const daylightTimezone = timezone + daylightSavingsOffset; + const now = moment().utcOffset(daylightTimezone * 60); + localDate = now.format(DATE_FORMAT); + localTime = now.format(TIME_FORMAT); + timezoneOffset = daylightTimezone > 0 ? `+${daylightTimezone}` : daylightTimezone.toString(); + } + + const embed = { + embed: { + color: 0x000080, + author: { + name: user.username, + icon_url: user.displayAvatarURL({ + format: "png", + dynamic: true + }) + }, + fields: [ + { + name: "Local Date", + value: localDate + }, + { + name: "Local Time", + value: localTime + }, + { + name: timezone !== null ? "Current Timezone Offset" : "Timezone Offset", + value: timezoneOffset + }, + { + name: "Observes Daylight Savings?", + value: daylightSavingsRegion ? "Yes" : "No" + } + ] + } + }; + + if (daylightSavingsRegion) { + embed.embed.fields.push( + { + name: "Daylight Savings Active?", + value: hasDaylightSavings(daylightSavingsRegion) ? "Yes" : "No" + }, + { + name: "Daylight Savings Region", + value: DAYLIGHT_SAVINGS_REGIONS[daylightSavingsRegion] + } + ); + } + + return embed; +} + +export default new Command({ + description: "Show others what time it is for you.", + async run({channel, author}) { + channel.send(getTimeEmbed(author)); + }, + subcommands: { + setup: new Command({ + description: "Registers your timezone information for the bot.", + async run({author, channel, ask, askYesOrNo, askMultipleChoice, prompt}) { + const profile = Storage.getUser(author.id); + + ask( + await channel.send( + "What hour (0 to 23) is it for you right now?\n*(Note: Make sure to use Discord's inline reply feature or this won't work!)*" + ), + author.id, + (reply) => { + const hour = parseInt(reply); + + if (isNaN(hour)) { + return false; + } + + const isValidHour = hour >= 0 && hour <= 23; + + if (isValidHour) { + const date = new Date(); + profile.timezone = hour - date.getUTCHours(); + } + + return isValidHour; + }, + async () => { + askYesOrNo( + await channel.send("Does your timezone change based on daylight savings?"), + author.id, + async (hasDST) => { + const finalize = () => { + Storage.save(); + channel.send( + "You've finished setting up your timezone! Just check to see if this looks right, and if it doesn't, run this setup again.", + getTimeEmbed(author) + ); + }; + + if (hasDST) { + const finalizeDST = (region: DST) => { + profile.daylightSavingsRegion = region; + + // If daylight savings is active, subtract the timezone offset by one to store the standard time. + if (hasDaylightSavings(region)) { + (profile.timezone as number)--; + } + + finalize(); + }; + + askMultipleChoice(await channel.send(DST_NOTE_SETUP), author.id, [ + () => finalizeDST("na"), + () => finalizeDST("eu"), + () => finalizeDST("sh") + ]); + } else { + finalize(); + } + } + ); + }, + () => { + return "you need to enter in a valid integer between 0 to 23"; + } + ); + } + }), + delete: new Command({ + description: "Delete your timezone information.", + async run({channel, author, prompt}) { + prompt( + await channel.send( + "Are you sure you want to delete your timezone information?\n*(This message will automatically be deleted after 10 seconds.)*" + ), + author.id, + () => { + const profile = Storage.getUser(author.id); + profile.timezone = null; + profile.daylightSavingsRegion = null; + Storage.save(); + } + ); + } + }), + utc: new Command({ + description: "Displays UTC time.", + async run({channel}) { + const time = moment().utc(); + + channel.send({ + embed: { + color: 0x000080, + fields: [ + { + name: "Local Date", + value: time.format(DATE_FORMAT) + }, + { + name: "Local Time", + value: time.format(TIME_FORMAT) + } + ] + } + }); + } + }), + daylight: new Command({ + description: "Provides information on the daylight savings region", + run: DST_NOTE_INFO + }) + }, + user: new Command({ + description: "See what time it is for someone else.", + async run({channel, args}) { + channel.send(getTimeEmbed(args[0])); + } + }), + any: new Command({ + description: "See what time it is for someone else (by their username).", + async run({channel, args, message, callMemberByUsername}) { + callMemberByUsername(message, args.join(" "), (member) => { + channel.send(getTimeEmbed(member.user)); + }); + } + }) +}); diff --git a/src/core/lib.ts b/src/core/lib.ts index 0c44fbc..f8efea6 100644 --- a/src/core/lib.ts +++ b/src/core/lib.ts @@ -38,6 +38,20 @@ export interface CommonLibrary { username: string, onSuccess: (member: GuildMember) => void ) => Promise; + ask: ( + message: Message, + senderID: string, + condition: (reply: string) => boolean, + onSuccess: () => void, + onReject: () => string, + timeout?: number + ) => void; + askYesOrNo: (message: Message, senderID: string, onSuccess: (condition: boolean) => void, timeout?: number) => void; + askMultipleChoice: ( + message: Message, + senderID: string, + callbackStack: (() => void)[] | ((choice: number) => void) + ) => void; // Dynamic Properties // args: any[]; @@ -237,6 +251,8 @@ $.paginate = async ( }; // Waits for the sender to either confirm an action or let it pass (and delete the message). +// This should probably be renamed to "confirm" now that I think of it, "prompt" is better used elsewhere. +// Append "\n*(This message will automatically be deleted after 10 seconds.)*" in the future? $.prompt = async (message: Message, senderID: string, onConfirm: () => void, duration = 10000) => { let isDeleted = false; @@ -244,9 +260,11 @@ $.prompt = async (message: Message, senderID: string, onConfirm: () => void, dur await message.awaitReactions( (reaction, user) => { if (user.id === senderID) { - if (reaction.emoji.name === "✅") onConfirm(); - isDeleted = true; - message.delete(); + if (reaction.emoji.name === "✅") { + onConfirm(); + isDeleted = true; + message.delete(); + } } // CollectorFilter requires a boolean to be returned. @@ -261,6 +279,98 @@ $.prompt = async (message: Message, senderID: string, onConfirm: () => void, dur if (!isDeleted) message.delete(); }; +// A list of "channel-message" and callback pairs. Also, I imagine that the callback will be much more maintainable when discord.js v13 comes out with a dedicated message.referencedMessage property. +// Also, I'm defining it here instead of the message event because the load order screws up if you export it from there. Yeah... I'm starting to notice just how much technical debt has been built up. The command handler needs to be modularized and refactored sooner rather than later. Define all constants in one area then grab from there. +export const replyEventListeners = new Map void>(); + +// Asks the user for some input using the inline reply feature. The message here is a message you send beforehand. +// If the reply is rejected, reply with an error message (when stable support comes from discord.js). +// Append "\n*(Note: Make sure to use Discord's inline reply feature or this won't work!)*" in the future? And also the "you can now reply to this message" edit. +$.ask = async ( + message: Message, + senderID: string, + condition: (reply: string) => boolean, + onSuccess: () => void, + onReject: () => string, + timeout = 60000 +) => { + const referenceID = `${message.channel.id}-${message.id}`; + + replyEventListeners.set(referenceID, (reply) => { + if (reply.author.id === senderID) { + if (condition(reply.content)) { + onSuccess(); + replyEventListeners.delete(referenceID); + } else { + reply.reply(onReject()); + } + } + }); + + setTimeout(() => { + replyEventListeners.set(referenceID, (reply) => { + reply.reply("that action timed out, try using the command again"); + replyEventListeners.delete(referenceID); + }); + }, timeout); +}; + +$.askYesOrNo = async (message: Message, senderID: string, onSuccess: (condition: boolean) => void, timeout = 30000) => { + let isDeleted = false; + + await message.react("✅"); + message.react("❌"); + await message.awaitReactions( + (reaction, user) => { + if (user.id === senderID) { + const isCheckReacted = reaction.emoji.name === "✅"; + + if (isCheckReacted || reaction.emoji.name === "❌") { + onSuccess(isCheckReacted); + isDeleted = true; + message.delete(); + } + } + + return false; + }, + {time: timeout} + ); + + if (!isDeleted) message.delete(); +}; + +// This MUST be split into an array. These emojis are made up of several characters each, adding up to 29 in length. +const multiNumbers = ["1️⃣", "2️⃣", "3️⃣", "4️⃣", "5️⃣", "6️⃣", "7️⃣", "8️⃣", "9️⃣", "🔟"]; + +// This will bring up an option to let the user choose between one option out of many. +$.askMultipleChoice = async (message: Message, senderID: string, callbackStack: (() => void)[], timeout = 90000) => { + let isDeleted = false; + + for (let i = 0; i < callbackStack.length; i++) { + await message.react(multiNumbers[i]); + } + + await message.awaitReactions( + (reaction, user) => { + if (user.id === senderID) { + const index = multiNumbers.indexOf(reaction.emoji.name); + + if (index !== -1) { + callbackStack[index](); + isDeleted = true; + message.delete(); + } + } + + return false; + }, + {time: timeout} + ); + + if (!isDeleted) message.delete(); +}; + $.getMemberByUsername = async (guild: Guild, username: string) => { return ( await guild.members.fetch({ diff --git a/src/core/structures.ts b/src/core/structures.ts index 0d78e13..b37e169 100644 --- a/src/core/structures.ts +++ b/src/core/structures.ts @@ -24,11 +24,17 @@ class User { public money: number; public lastReceived: number; public lastMonday: number; + public timezone: number | null; // This is for the standard timezone only, not the daylight savings timezone + public daylightSavingsRegion: "na" | "eu" | "sh" | null; constructor(data?: GenericJSON) { this.money = select(data?.money, 0, Number); this.lastReceived = select(data?.lastReceived, -1, Number); this.lastMonday = select(data?.lastMonday, -1, Number); + this.timezone = data?.timezone ?? null; + this.daylightSavingsRegion = /^((na)|(eu)|(sh))$/.test(data?.daylightSavingsRegion) + ? data?.daylightSavingsRegion + : null; } } diff --git a/src/events/message.ts b/src/events/message.ts index 558208e..00e6458 100644 --- a/src/events/message.ts +++ b/src/events/message.ts @@ -3,7 +3,7 @@ import Command, {loadCommands} from "../core/command"; import {hasPermission, getPermissionLevel, PermissionNames} from "../core/permissions"; import {Permissions, Collection} from "discord.js"; import {getPrefix} from "../core/structures"; -import $ from "../core/lib"; +import $, {replyEventListeners} from "../core/lib"; // It's a rather hacky solution, but since there's no top-level await, I just have to make the loading conditional. let commands: Collection | null = null; @@ -16,9 +16,15 @@ export default new Event<"message">({ // Message Setup // if (message.author.bot) return; + // If there's an inline reply, fire off that event listener (if it exists). + if (message.reference) { + const reference = message.reference; + replyEventListeners.get(`${reference.channelID}-${reference.messageID}`)?.(message); + } + const prefix = getPrefix(message.guild); - if (!message.content.startsWith(prefix)) { + if (!message.content.startsWith(prefix) && !message.reference) { if (message.client.user && message.mentions.has(message.client.user)) message.channel.send(`${message.author.toString()}, my prefix on this guild is \`${prefix}\`.`); return; From 8da5ad0ca640834eee802fe5d5ebbf5cdb03b51a Mon Sep 17 00:00:00 2001 From: WatDuhHekBro <44940783+WatDuhHekBro@users.noreply.github.com> Date: Sun, 24 Jan 2021 19:12:43 -0600 Subject: [PATCH 083/178] Addressed issue with inline replies using prefix --- src/core/structures.ts | 13 ++++++++++++- src/events/message.ts | 37 +++++++++++++++++++++++++++++++------ 2 files changed, 43 insertions(+), 7 deletions(-) diff --git a/src/core/structures.ts b/src/core/structures.ts index b37e169..37b2194 100644 --- a/src/core/structures.ts +++ b/src/core/structures.ts @@ -110,7 +110,18 @@ if (process.argv[2] === "dev") { } export function getPrefix(guild: DiscordGuild | null): string { - return Storage.getGuild(guild?.id || "N/A").prefix ?? Config.prefix; + let prefix = Config.prefix; + + if (guild) { + const possibleGuildPrefix = Storage.getGuild(guild.id).prefix; + + // Here, lossy comparison works in our favor because you wouldn't want an empty string to trigger the prefix. + if (possibleGuildPrefix) { + prefix = possibleGuildPrefix; + } + } + + return prefix; } export interface EmoteRegistryDumpEntry { diff --git a/src/events/message.ts b/src/events/message.ts index 00e6458..107c8cf 100644 --- a/src/events/message.ts +++ b/src/events/message.ts @@ -22,16 +22,41 @@ export default new Event<"message">({ replyEventListeners.get(`${reference.channelID}-${reference.messageID}`)?.(message); } - const prefix = getPrefix(message.guild); + let prefix = getPrefix(message.guild); + const originalPrefix = prefix; + let exitEarly = !message.content.startsWith(prefix); + const clientUser = message.client.user; + let usesBotSpecificPrefix = false; - if (!message.content.startsWith(prefix) && !message.reference) { - if (message.client.user && message.mentions.has(message.client.user)) - message.channel.send(`${message.author.toString()}, my prefix on this guild is \`${prefix}\`.`); - return; + // If the client user exists, check if it starts with the bot-specific prefix. + if (clientUser) { + // If the prefix starts with the bot-specific prefix, go off that instead (these two options must mutually exclude each other). + // The pattern here has an optional space at the end to capture that and make it not mess with the header and args. + const matches = message.content.match(new RegExp(`^<@!?${clientUser.id}> ?`)); + + if (matches) { + prefix = matches[0]; + exitEarly = false; + usesBotSpecificPrefix = true; + } } + // If it doesn't start with the current normal prefix or the bot-specific unique prefix, exit the thread of execution early. + // Inline replies should still be captured here because if it doesn't exit early, two characters for a two-length prefix would still trigger commands. + if (exitEarly) return; + const [header, ...args] = message.content.substring(prefix.length).split(/ +/); + // If the message is just the prefix itself, move onto this block. + if (header === "" && args.length === 0) { + // I moved the bot-specific prefix to a separate conditional block to separate the logic. + // And because it listens for the mention as a prefix instead of a free-form mention, inline replies (probably) shouldn't ever trigger this unintentionally. + if (usesBotSpecificPrefix) { + message.channel.send(`${message.author.toString()}, my prefix on this guild is \`${originalPrefix}\`.`); + return; + } + } + if (!commands.has(header)) return; if ( @@ -66,7 +91,7 @@ export default new Event<"message">({ for (let param of args) { if (command.endpoint) { if (command.subcommands.size > 0 || command.user || command.number || command.any) - $.warn(`An endpoint cannot have subcommands! Check ${prefix}${header} again.`); + $.warn(`An endpoint cannot have subcommands! Check ${originalPrefix}${header} again.`); isEndpoint = true; break; } From d7c18d1b061202f932f7e4006e51f0b5b7ecf89c Mon Sep 17 00:00:00 2001 From: WatDuhHekBro <44940783+WatDuhHekBro@users.noreply.github.com> Date: Sun, 24 Jan 2021 22:46:48 -0600 Subject: [PATCH 084/178] Fixed time setup accounting for differences in day --- src/commands/utilities/time.ts | 202 ++++++++++++++++++++++++--------- src/core/lib.ts | 53 +++++---- 2 files changed, 184 insertions(+), 71 deletions(-) diff --git a/src/commands/utilities/time.ts b/src/commands/utilities/time.ts index de4fe12..8cdecc1 100644 --- a/src/commands/utilities/time.ts +++ b/src/commands/utilities/time.ts @@ -4,8 +4,10 @@ import {User} from "discord.js"; import moment from "moment"; const DATE_FORMAT = "D MMMM YYYY"; +const DOW_FORMAT = "dddd"; const TIME_FORMAT = "HH:mm:ss"; type DST = "na" | "eu" | "sh"; +const TIME_EMBED_COLOR = 0x191970; const DAYLIGHT_SAVINGS_REGIONS: {[region in DST]: string} = { na: "North America", @@ -16,12 +18,12 @@ const DAYLIGHT_SAVINGS_REGIONS: {[region in DST]: string} = { const DST_NOTE_INFO = `*Note: To make things simple, the way the bot will handle specific points in time when switching Daylight Savings is just to switch at UTC 00:00, ignoring local timezones. After all, there's no need to get this down to the exact hour.* North America -- Starts: 2nd Sunday March -- Ends: 1st Sunday November +- Starts: 2nd Sunday of March +- Ends: 1st Sunday of November Europe -- Starts: Last Sunday March -- Ends: Last Sunday October +- Starts: Last Sunday of March +- Ends: Last Sunday of October Southern Hemisphere - Starts: 1st Sunday of October @@ -30,12 +32,12 @@ Southern Hemisphere const DST_NOTE_SETUP = `Which daylight savings region most closely matches your own? North America (1️⃣) -- Starts: 2nd Sunday March -- Ends: 1st Sunday November +- Starts: 2nd Sunday of March +- Ends: 1st Sunday of November Europe (2️⃣) -- Starts: Last Sunday March -- Ends: Last Sunday October +- Starts: Last Sunday of March +- Ends: Last Sunday of October Southern Hemisphere (3️⃣) - Starts: 1st Sunday of October @@ -99,6 +101,7 @@ function hasDaylightSavings(region: DST) { function getTimeEmbed(user: User) { const {timezone, daylightSavingsRegion} = Storage.getUser(user.id); let localDate = "N/A"; + let dayOfWeek = "N/A"; let localTime = "N/A"; let timezoneOffset = "N/A"; @@ -107,13 +110,14 @@ function getTimeEmbed(user: User) { const daylightTimezone = timezone + daylightSavingsOffset; const now = moment().utcOffset(daylightTimezone * 60); localDate = now.format(DATE_FORMAT); + dayOfWeek = now.format(DOW_FORMAT); localTime = now.format(TIME_FORMAT); - timezoneOffset = daylightTimezone > 0 ? `+${daylightTimezone}` : daylightTimezone.toString(); + timezoneOffset = daylightTimezone >= 0 ? `+${daylightTimezone}` : daylightTimezone.toString(); } const embed = { embed: { - color: 0x000080, + color: TIME_EMBED_COLOR, author: { name: user.username, icon_url: user.displayAvatarURL({ @@ -126,12 +130,16 @@ function getTimeEmbed(user: User) { name: "Local Date", value: localDate }, + { + name: "Day of the Week", + value: dayOfWeek + }, { name: "Local Time", value: localTime }, { - name: timezone !== null ? "Current Timezone Offset" : "Timezone Offset", + name: daylightSavingsRegion !== null ? "Current Timezone Offset" : "Timezone Offset", value: timezoneOffset }, { @@ -160,14 +168,19 @@ function getTimeEmbed(user: User) { export default new Command({ description: "Show others what time it is for you.", + aliases: ["tz"], async run({channel, author}) { channel.send(getTimeEmbed(author)); }, subcommands: { + // Welcome to callback hell. We hope you enjoy your stay here! setup: new Command({ description: "Registers your timezone information for the bot.", - async run({author, channel, ask, askYesOrNo, askMultipleChoice, prompt}) { + async run({author, channel, ask, askYesOrNo, askMultipleChoice}) { const profile = Storage.getUser(author.id); + profile.timezone = null; + profile.daylightSavingsRegion = null; + let hour: number; ask( await channel.send( @@ -175,60 +188,141 @@ export default new Command({ ), author.id, (reply) => { - const hour = parseInt(reply); + hour = parseInt(reply); if (isNaN(hour)) { return false; } - const isValidHour = hour >= 0 && hour <= 23; - - if (isValidHour) { - const date = new Date(); - profile.timezone = hour - date.getUTCHours(); - } - - return isValidHour; + return hour >= 0 && hour <= 23; }, async () => { - askYesOrNo( - await channel.send("Does your timezone change based on daylight savings?"), - author.id, - async (hasDST) => { - const finalize = () => { - Storage.save(); - channel.send( - "You've finished setting up your timezone! Just check to see if this looks right, and if it doesn't, run this setup again.", - getTimeEmbed(author) - ); - }; + // You need to also take into account whether or not it's the same day in UTC or not. + // The problem this setup avoids is messing up timezones by 24 hours. + // For example, the old logic was just (hour - hourUTC). When I setup my timezone (UTC-6) at 18:00, it was UTC 00:00. + // That means that that formula was doing (18 - 0) getting me UTC+18 instead of UTC-6 because that naive formula didn't take into account a difference in days. - if (hasDST) { - const finalizeDST = (region: DST) => { - profile.daylightSavingsRegion = region; + // (day * 24 + hour) - (day * 24 + hour) + // Since the timezones will be restricted to -12 to +14, you'll be given three options. + // The end of the month should be calculated automatically, you should have enough information at that point. - // If daylight savings is active, subtract the timezone offset by one to store the standard time. - if (hasDaylightSavings(region)) { - (profile.timezone as number)--; - } + // But after mapping out the edge cases, I figured out that you can safely gather accurate information just based on whether the UTC day matches the user's day. + // 21:xx (-12, -d) -- 09:xx (+0, 0d) -- 23:xx (+14, 0d) + // 22:xx (-12, -d) -- 10:xx (+0, 0d) -- 00:xx (+14, +d) + // 23:xx (-12, -d) -- 11:xx (+0, 0d) -- 01:xx (+14, +d) + // 00:xx (-12, 0d) -- 12:xx (+0, 0d) -- 02:xx (+14, +d) - finalize(); - }; + // For example, 23:xx (-12) - 01:xx (+14) shows that even the edge cases of UTC-12 and UTC+14 cannot overlap, so the dataset can be reduced to a yes or no option. + // - 23:xx same day = +0, 23:xx diff day = -1 + // - 00:xx same day = +0, 00:xx diff day = +1 + // - 01:xx same day = +0, 01:xx diff day = +1 - askMultipleChoice(await channel.send(DST_NOTE_SETUP), author.id, [ - () => finalizeDST("na"), - () => finalizeDST("eu"), - () => finalizeDST("sh") - ]); - } else { - finalize(); + // First, create a tuple list matching each possible hour-dayOffset-timezoneOffset combination. In the above example, it'd go something like this: + // [[23, -1, -12], [0, 0, -11], ..., [23, 0, 12], [0, 1, 13], [1, 1, 14]] + // Then just find the matching one by filtering through dayOffset (equals zero or not), then the hour from user input. + // Remember that you don't know where the difference in day might be at this point, so you can't do (hour - hourUTC) safely. + // In terms of the equation, you're missing a variable in (--> day <-- * 24 + hour) - (day * 24 + hour). That's what the loop is for. + + // Now you might be seeing a problem with setting this up at the end/beginning of a month, but that actually isn't a problem. + // Let's say that it's 00:xx of the first UTC day of a month. hourSumUTC = 24 + // UTC-12 --> hourSumLowerBound (hourSumUTC - 12) = 12 + // UTC+14 --> hourSumUpperBound (hourSumUTC + 14) = 38 + // Remember, the nice thing about making these bounds relative to the UTC hour sum is that there can't be any conflicts even at the edges of months. + // And remember that we aren't using this question: (day * 24 + hour) - (day * 24 + hour). We're drawing from a list which does not store its data in absolute terms. + // That means there's no 31st, 1st, 2nd, it's -1, 0, +1. I just need to make sure to calculate an offset to subtract from the hour sums. + + const date = new Date(); // e.g. 2021-05-01 @ 05:00 + const day = date.getUTCDate(); // e.g. 1 + const hourSumUTC = day * 24 + date.getUTCHours(); // e.g. 29 + const timezoneTupleList: [number, number, number][] = []; + const uniques: number[] = []; // only for temporary use + const duplicates = []; + + // Setup the tuple list in a separate block. + for (let timezoneOffset = -12; timezoneOffset <= 14; timezoneOffset++) { + const hourSum = hourSumUTC + timezoneOffset; // e.g. 23, UTC-6 (17 to 43) + const hour = hourSum % 24; // e.g. 23 + // This works because you get the # of days w/o hours minus UTC days without hours. + // Since it's all relative to UTC, it'll end up being -1, 0, or 1. + const dayOffset = Math.floor(hourSum / 24) - day; // e.g. -1 + timezoneTupleList.push([hour, dayOffset, timezoneOffset]); + + if (uniques.includes(hour)) { + duplicates.push(hour); + } else { + uniques.push(hour); + } + } + + // I calculate the list beforehand and check for duplicates to reduce unnecessary asking. + if (duplicates.includes(hour)) { + const isSameDay = await askYesOrNo( + await channel.send( + `Is the current day of the month the ${moment().utc().format("Do")} for you?` + ), + author.id + ); + + // Filter through isSameDay (aka !hasDayOffset) then hour from user-generated input. + // isSameDay is checked first to reduce the amount of conditionals per loop. + if (isSameDay) { + for (const [hourPoint, dayOffset, timezoneOffset] of timezoneTupleList) { + if (dayOffset === 0 && hour === hourPoint) { + profile.timezone = timezoneOffset; + } + } + } else { + for (const [hourPoint, dayOffset, timezoneOffset] of timezoneTupleList) { + if (dayOffset !== 0 && hour === hourPoint) { + profile.timezone = timezoneOffset; + } } } + } else { + // If it's a unique hour, just search through the tuple list and find the matching entry. + for (const [hourPoint, dayOffset, timezoneOffset] of timezoneTupleList) { + if (hour === hourPoint) { + profile.timezone = timezoneOffset; + } + } + } + + // I should note that error handling should be added sometime because await throws an exception on Promise.reject. + const hasDST = await askYesOrNo( + await channel.send("Does your timezone change based on daylight savings?"), + author.id ); + + const finalize = () => { + Storage.save(); + channel.send( + "You've finished setting up your timezone! Just check to see if this looks right, and if it doesn't, run this setup again.", + getTimeEmbed(author) + ); + }; + + if (hasDST) { + const finalizeDST = (region: DST) => { + profile.daylightSavingsRegion = region; + + // If daylight savings is active, subtract the timezone offset by one to store the standard time. + if (hasDaylightSavings(region)) { + profile.timezone!--; + } + + finalize(); + }; + + askMultipleChoice(await channel.send(DST_NOTE_SETUP), author.id, [ + () => finalizeDST("na"), + () => finalizeDST("eu"), + () => finalizeDST("sh") + ]); + } else { + finalize(); + } }, - () => { - return "you need to enter in a valid integer between 0 to 23"; - } + () => "you need to enter in a valid integer between 0 to 23" ); } }), @@ -256,12 +350,16 @@ export default new Command({ channel.send({ embed: { - color: 0x000080, + color: TIME_EMBED_COLOR, fields: [ { name: "Local Date", value: time.format(DATE_FORMAT) }, + { + name: "Day of the Week", + value: time.format(DOW_FORMAT) + }, { name: "Local Time", value: time.format(TIME_FORMAT) diff --git a/src/core/lib.ts b/src/core/lib.ts index f8efea6..42852d9 100644 --- a/src/core/lib.ts +++ b/src/core/lib.ts @@ -46,7 +46,7 @@ export interface CommonLibrary { onReject: () => string, timeout?: number ) => void; - askYesOrNo: (message: Message, senderID: string, onSuccess: (condition: boolean) => void, timeout?: number) => void; + askYesOrNo: (message: Message, senderID: string, timeout?: number) => Promise; askMultipleChoice: ( message: Message, senderID: string, @@ -196,6 +196,8 @@ export function updateGlobalEmoteRegistry(): void { FileManager.write("emote-registry", data, true); } +// Maybe promisify this section to reduce the potential for creating callback hell? Especially if multiple questions in a row are being asked. + // Pagination function that allows for customization via a callback. // Define your own pages outside the function because this only manages the actual turning of pages. $.paginate = async ( @@ -315,36 +317,49 @@ $.ask = async ( }, timeout); }; -$.askYesOrNo = async (message: Message, senderID: string, onSuccess: (condition: boolean) => void, timeout = 30000) => { - let isDeleted = false; +$.askYesOrNo = (message: Message, senderID: string, timeout = 30000): Promise => { + return new Promise(async (resolve, reject) => { + let isDeleted = false; - await message.react("✅"); - message.react("❌"); - await message.awaitReactions( - (reaction, user) => { - if (user.id === senderID) { - const isCheckReacted = reaction.emoji.name === "✅"; + await message.react("✅"); + message.react("❌"); + await message.awaitReactions( + (reaction, user) => { + if (user.id === senderID) { + const isCheckReacted = reaction.emoji.name === "✅"; - if (isCheckReacted || reaction.emoji.name === "❌") { - onSuccess(isCheckReacted); - isDeleted = true; - message.delete(); + if (isCheckReacted || reaction.emoji.name === "❌") { + resolve(isCheckReacted); + isDeleted = true; + message.delete(); + } } - } - return false; - }, - {time: timeout} - ); + return false; + }, + {time: timeout} + ); - if (!isDeleted) message.delete(); + if (!isDeleted) { + message.delete(); + reject("Prompt timed out."); + } + }); }; // This MUST be split into an array. These emojis are made up of several characters each, adding up to 29 in length. const multiNumbers = ["1️⃣", "2️⃣", "3️⃣", "4️⃣", "5️⃣", "6️⃣", "7️⃣", "8️⃣", "9️⃣", "🔟"]; // This will bring up an option to let the user choose between one option out of many. +// This definitely needs a single callback alternative, because using the numerical version isn't actually that uncommon of a pattern. $.askMultipleChoice = async (message: Message, senderID: string, callbackStack: (() => void)[], timeout = 90000) => { + if (callbackStack.length > multiNumbers.length) { + message.channel.send( + `\`ERROR: The amount of callbacks in "askMultipleChoice" must not exceed the total amount of allowed options (${multiNumbers.length})!\`` + ); + return; + } + let isDeleted = false; for (let i = 0; i < callbackStack.length; i++) { From 7105c6284f7583a09fc6d1f9b6ceb5ae6f6e2093 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=83=95=E3=82=BA=E3=82=AD?= Date: Mon, 25 Jan 2021 23:06:12 +0100 Subject: [PATCH 085/178] Add 'bet' command to the 'eco' module --- src/commands/fun/eco.ts | 4 +- src/commands/fun/subcommands/eco-bet.ts | 137 ++++++++++++++++++++++++ 2 files changed, 140 insertions(+), 1 deletion(-) create mode 100644 src/commands/fun/subcommands/eco-bet.ts diff --git a/src/commands/fun/eco.ts b/src/commands/fun/eco.ts index 5be316f..557b2aa 100644 --- a/src/commands/fun/eco.ts +++ b/src/commands/fun/eco.ts @@ -3,6 +3,7 @@ import {isAuthorized, getMoneyEmbed} from "./subcommands/eco-utils"; import {DailyCommand, PayCommand, GuildCommand, LeaderboardCommand} from "./subcommands/eco-core"; import {BuyCommand, ShopCommand} from "./subcommands/eco-shop"; import {MondayCommand} from "./subcommands/eco-extras"; +import {BetCommand} from "./subcommands/eco-bet"; export default new Command({ description: "Economy command for Monika.", @@ -16,7 +17,8 @@ export default new Command({ leaderboard: LeaderboardCommand, buy: BuyCommand, shop: ShopCommand, - monday: MondayCommand + monday: MondayCommand, + bet: BetCommand }, user: new Command({ description: "See how much money someone else has by using their user ID or pinging them.", diff --git a/src/commands/fun/subcommands/eco-bet.ts b/src/commands/fun/subcommands/eco-bet.ts new file mode 100644 index 0000000..5046196 --- /dev/null +++ b/src/commands/fun/subcommands/eco-bet.ts @@ -0,0 +1,137 @@ +import Command from "../../../core/command"; +import $ from "../../../core/lib"; +import {Storage} from "../../../core/structures"; +import {isAuthorized, getMoneyEmbed, getSendEmbed, ECO_EMBED_COLOR} from "./eco-utils"; + +export const BetCommand = new Command({ + description: "Bet your Mons with other people [TBD]", + usage: " ", + run: "Who are you betting with?", + user: new Command({ + run: "How much are you betting?", + number: new Command({ + run: "How long until the bet ends?", + any: new Command({ + async run({ client, args, author, channel, guild}): Promise { + if (isAuthorized(guild, channel)) { + const sender = Storage.getUser(author.id); + const target = args[0]; + const receiver = Storage.getUser(target); + const amount = Math.floor(args[1]); + const duration = parseDuration(args[2].trim()); + + if (amount <= 0) return channel.send("You must bet at least one Mon!"); + else if (sender.money < amount) + return channel.send("You don't have enough Mons for that.", getMoneyEmbed(author)); + else if (target.id == author.id) return channel.send("You can't bet Mons with yourself!"); + else if (target.bot && process.argv[2] !== "dev") + return channel.send("You can't bet Mons with a bot!"); + + if (duration <= 0) + return channel.send("Invalid duration"); + // else if (duration <= {threshold}) + // return channel.send("Too short idk"); + // else if (duration >= {threshold}) + // return channel.send("Too long idk"); + + // SEND MESSAGE WITH 2 REACTIONS (OK / NO) + const msg = await channel.send(`<@${target.id}>, do you want to take this bet of ${$(amount).pluralise("Mon", "s")}`); + await msg.react("✅"); + await msg.react("⛔"); + + // SET UP ACCEPT TIMEOUT + // ON REACTION CHANGE, CHECK IF NEW REACTION IS FROM TARGET + await msg.awaitReactions( + async (reaction, user) => { + // IF OK + if (user.id === target.id && reaction.emoji.name === "✅") { + // REMOVE AMOUNT FROM AUTHOR + sender.money -= amount; + // REMOVE AMOUNT FROM TARGET + receiver.money -= amount; + // SET BET POOL AS AMOUNT*2 + // => BET POOL ALWAYS EVEN + const pool = amount * 2; + + // SET UP BET TIMEOUT FROM DURATION + client.setTimeout(async () => { + // ON BET TIMEOUT + // GIVE VOTE WITH 2 REACTIONS (OK / NO) + const voteMsg = await channel.send(`VOTE: do you think that <@${target.id} has won the bet?`); + await voteMsg.react("✅"); + await voteMsg.react("⛔"); + + // SET UP VOTE TIMEOUT + voteMsg.awaitReactions( + (reaction, user) => { + return ["✅", "⛔"].includes(reaction.emoji.name); + }, + // waiting for a day for now, might need to make configurable + { time: 8640000 } + ).then(reactions => { + // ON VOTE TIMEOUT + // COUNT OK VOTES + const ok = 0; + // COUNT NO VOTES + const no = 0; + // IF OK > NO + // GIVE TARGET THE BET POOL + if (ok > no) receiver.money += pool; + // ELSE IF OK < NO + // GIVE AUTHOR BET POOL + else if (ok < no) sender.money += pool; + // ELSE + // GIVE TARGET BET POOL / 2 + // GIVE AUTHOR BET POOL / 2 + // => BET EFFECT CANCELLED + else { + sender.money += amount; + receiver.money += amount; + } + }); + }, duration); + } + // IF NO + // DROP + return false; + }, + // waiting for a minute + { time: 60000 } + ); + // ON ACCEPT TIMEOUT + // DROP + } + } + }) + }) + }) +}); + + +/** + * Parses a duration string into milliseconds + * Examples: + * - 3d -> 3 days -> 259200000ms + * - 3s -> 3 seconds -> 3000ms + * - 2h -> 2 hours -> 7200000ms + */ +function parseDuration(duration: string): number { + // extract last char as unit + const unit = duration[duration.length - 1].toLowerCase(); + // get the rest as value + let value: number = +duration.substring(0, duration.length - 1); + + if (!["d","h","m","s"].includes(unit) || isNaN(value)) + return 0; + + if (unit === "d") + value *= 86400000; // 1000ms * 60s * 60m * 24h + else if (unit === "h") + value *= 3600000; // 1000ms * 60s * 60m + else if (unit === "m") + value *= 60000; // 1000ms * 60s + else if (unit === "s") + value *= 1000; // 1000ms + + return value; +} \ No newline at end of file From 5ed9d797150506226562da75daf72a98ca597ef6 Mon Sep 17 00:00:00 2001 From: WatDuhHekBro <44940783+WatDuhHekBro@users.noreply.github.com> Date: Tue, 26 Jan 2021 03:52:39 -0600 Subject: [PATCH 086/178] Changed command loading to use a glob pattern --- package-lock.json | 39 +++++---- package.json | 2 + src/commands/help.ts | 10 +-- src/commands/template.ts | 57 ++++++++++++ src/core/command.ts | 184 ++++++++++++++------------------------- src/events/message.ts | 10 +-- src/index.ts | 4 +- src/setup.ts | 15 +++- tsconfig.json | 3 +- 9 files changed, 170 insertions(+), 154 deletions(-) create mode 100644 src/commands/template.ts diff --git a/package-lock.json b/package-lock.json index 67cb5d2..7a9b4e7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -43,6 +43,16 @@ "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==" }, + "@types/glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-SEYeGAIQIQX8NN6LDKprLjbrd5dARM5EXsd8GI/A5l0apYI1fGMWgPHSe4ZKL4eozlAyI+doUE9XbYS4xCkQ1w==", + "dev": true, + "requires": { + "@types/minimatch": "*", + "@types/node": "*" + } + }, "@types/inquirer": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/@types/inquirer/-/inquirer-6.5.0.tgz", @@ -53,6 +63,12 @@ "rxjs": "^6.4.0" } }, + "@types/minimatch": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", + "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==", + "dev": true + }, "@types/mocha": { "version": "8.2.0", "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-8.2.0.tgz", @@ -172,8 +188,7 @@ "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" }, "binary-extensions": { "version": "2.1.0", @@ -185,7 +200,6 @@ "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" @@ -336,8 +350,7 @@ "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 + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, "create-require": { "version": "1.1.1", @@ -515,8 +528,7 @@ "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, "fsevents": { "version": "2.1.3", @@ -535,7 +547,6 @@ "version": "7.1.6", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -583,7 +594,6 @@ "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" @@ -592,8 +602,7 @@ "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "inquirer": { "version": "7.3.3", @@ -738,7 +747,6 @@ "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" } @@ -841,7 +849,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, "requires": { "wrappy": "1" } @@ -897,8 +904,7 @@ "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 + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" }, "path-key": { "version": "3.1.1", @@ -1333,8 +1339,7 @@ "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, "ws": { "version": "7.4.2", diff --git a/package.json b/package.json index b76715e..342f5e7 100644 --- a/package.json +++ b/package.json @@ -8,12 +8,14 @@ "chalk": "^4.1.0", "discord.js": "^12.5.1", "discord.js-lavalink-lib": "^0.1.8", + "glob": "^7.1.6", "inquirer": "^7.3.3", "moment": "^2.29.1", "ms": "^2.1.3", "os": "^0.1.1" }, "devDependencies": { + "@types/glob": "^7.1.3", "@types/inquirer": "^6.5.0", "@types/mocha": "^8.2.0", "@types/ms": "^0.7.31", diff --git a/src/commands/help.ts b/src/commands/help.ts index cf6459b..4a6ff2f 100644 --- a/src/commands/help.ts +++ b/src/commands/help.ts @@ -1,6 +1,6 @@ import Command from "../core/command"; import {CommonLibrary} from "../core/lib"; -import {loadCommands, categories} from "../core/command"; +import {loadableCommands, categories} from "../core/command"; import {PermissionNames} from "../core/permissions"; export default new Command({ @@ -8,11 +8,11 @@ export default new Command({ usage: "([command, [subcommand/type], ...])", aliases: ["h"], async run($: CommonLibrary): Promise { - const commands = await loadCommands(); + const commands = await loadableCommands; let output = `Legend: \`\`, \`[list/of/stuff]\`, \`(optional)\`, \`()\`, \`([optional/list/...])\``; for (const [category, headers] of categories) { - output += `\n\n===[ ${category} ]===`; + output += `\n\n===[ ${$(category).toTitleCase()} ]===`; for (const header of headers) { if (header !== "test") { @@ -30,7 +30,7 @@ export default new Command({ }, any: new Command({ async run($: CommonLibrary): Promise { - const commands = await loadCommands(); + const commands = await loadableCommands; let header = $.args.shift() as string; let command = commands.get(header); @@ -51,7 +51,7 @@ export default new Command({ $.warn( `Command "${header}" is somehow in multiple categories. This means that the command loading stage probably failed in properly adding categories.` ); - else selectedCategory = category; + else selectedCategory = $(category).toTitleCase(); } } diff --git a/src/commands/template.ts b/src/commands/template.ts new file mode 100644 index 0000000..832fecd --- /dev/null +++ b/src/commands/template.ts @@ -0,0 +1,57 @@ +import Command from "../core/command"; +import {CommonLibrary} from "../core/lib"; + +export default new Command({ + description: + 'This is a template/testing command providing common functionality. Remove what you don\'t need, and rename/delete this file to generate a fresh command file here. This command should be automatically excluded from the help command. The "usage" parameter (string) overrides the default usage for the help command. The "endpoint" parameter (boolean) prevents further arguments from being passed. Also, as long as you keep the run function async, it\'ll return a promise allowing the program to automatically catch any synchronous errors. However, you\'ll have to do manual error handling if you go the then and catch route.', + endpoint: false, + usage: "", + permission: null, + aliases: [], + async run($: CommonLibrary): Promise { + // code + }, + subcommands: { + layer: new Command({ + description: + 'This is a named subcommand, meaning that the key name is what determines the keyword to use. With default settings for example, "$test layer".', + endpoint: false, + usage: "", + permission: null, + aliases: [], + async run($: CommonLibrary): Promise { + // code + } + }) + }, + user: new Command({ + description: + 'This is the subcommand for getting users by pinging them or copying their ID. With default settings for example, "$test 237359961842253835". The argument will be a user object and won\'t run if no user is found by that ID.', + endpoint: false, + usage: "", + permission: null, + async run($: CommonLibrary): Promise { + // code + } + }), + number: new Command({ + description: + 'This is a numeric subcommand, meaning that any type of number (excluding Infinity/NaN) will route to this command if present. With default settings for example, "$test -5.2". The argument with the number is already parsed so you can just use it without converting it.', + endpoint: false, + usage: "", + permission: null, + async run($: CommonLibrary): Promise { + // code + } + }), + any: new Command({ + description: + "This is a generic subcommand, meaning that if there isn't a more specific subcommand that's called, it falls to this. With default settings for example, \"$test reeee\".", + endpoint: false, + usage: "", + permission: null, + async run($: CommonLibrary): Promise { + // code + } + }) +}); diff --git a/src/core/command.ts b/src/core/command.ts index 7ba0f02..fd4eb64 100644 --- a/src/core/command.ts +++ b/src/core/command.ts @@ -1,9 +1,8 @@ import $, {isType, parseVars, CommonLibrary} from "./lib"; import {Collection} from "discord.js"; -import {generateHandler} from "./storage"; -import {promises as ffs, existsSync, writeFile} from "fs"; import {PERMISSIONS} from "./permissions"; import {getPrefix} from "../core/structures"; +import glob from "glob"; interface CommandOptions { description?: string; @@ -148,133 +147,80 @@ export default class Command { } } -let commands: Collection | null = null; -export const categories: Collection = new Collection(); -export const aliases: Collection = new Collection(); // Top-level aliases only. +// Internally, it'll keep its original capitalization. It's up to you to convert it to title case when you make a help command. +export const categories = new Collection(); /** Returns the cache of the commands if it exists and searches the directory if not. */ -export async function loadCommands(): Promise> { - if (commands) return commands; +export const loadableCommands = (async () => { + const commands = new Collection(); + // Include all .ts files recursively in "src/commands/". + const files = await globP("src/commands/**/*.ts"); + // Extract the usable parts from "src/commands/" if: + // - The path is 1 to 2 subdirectories (a or a/b, not a/b/c) + // - Any leading directory isn't "modules" + // - The filename doesn't end in .test.ts (for jest testing) + // - The filename cannot be the hardcoded top-level "template.ts", reserved for generating templates + const pattern = /src\/commands\/(?!template\.ts)(?!modules\/)(\w+(?:\/\w+)?)(?:test\.)?\.ts/; + const lists: {[category: string]: string[]} = {}; - if (process.argv[2] === "dev" && !existsSync("src/commands/test.ts")) - writeFile( - "src/commands/test.ts", - template, - generateHandler('"test.ts" (testing/template command) successfully generated.') - ); + for (const path of files) { + const match = pattern.exec(path); - commands = new Collection(); - const dir = await ffs.opendir("dist/commands"); - const listMisc: string[] = []; - let selected; + if (match) { + const commandID = match[1]; // e.g. "utilities/info" + const slashIndex = commandID.indexOf("/"); + const isMiscCommand = slashIndex !== -1; + const category = isMiscCommand ? commandID.substring(0, slashIndex) : "miscellaneous"; + const commandName = isMiscCommand ? commandID.substring(slashIndex + 1) : commandID; // e.g. "info" + // If the dynamic import works, it must be an object at the very least. Then, just test to see if it's a proper instance. + const command = (await import(`../commands/${commandID}`)).default as unknown; - // There will only be one level of directory searching (per category). - while ((selected = await dir.read())) { - if (selected.isDirectory()) { - if (selected.name === "subcommands") continue; + if (command instanceof Command) { + command.originalCommandName = commandName; - const subdir = await ffs.opendir(`dist/commands/${selected.name}`); - const category = $(selected.name).toTitleCase(); - const list: string[] = []; - let cmd; + if (commands.has(commandName)) { + $.warn( + `Command "${commandName}" already exists! Make sure to make each command uniquely identifiable across categories!` + ); + } else { + commands.set(commandName, command); + } - while ((cmd = await subdir.read())) { - if (cmd.isDirectory()) { - if (cmd.name === "subcommands") continue; - else $.warn(`You can't have multiple levels of directories! From: "dist/commands/${cmd.name}"`); - } else loadCommand(cmd.name, list, selected.name); + for (const alias of command.aliases) { + if (commands.has(alias)) { + $.warn( + `Top-level alias "${alias}" from command "${commandID}" already exists either as a command or alias!` + ); + } else { + commands.set(alias, command); + } + } + + if (!(category in lists)) lists[category] = []; + lists[category].push(commandName); + + $.log(`Loading Command: ${commandID}`); + } else { + $.warn(`Command "${commandID}" has no default export which is a Command instance!`); } - - subdir.close(); - categories.set(category, list); - } else loadCommand(selected.name, listMisc); + } } - dir.close(); - categories.set("Miscellaneous", listMisc); + for (const category in lists) { + categories.set(category, lists[category]); + } return commands; +})(); + +function globP(path: string) { + return new Promise((resolve, reject) => { + glob(path, (error, files) => { + if (error) { + reject(error); + } else { + resolve(files); + } + }); + }); } - -async function loadCommand(filename: string, list: string[], category?: string) { - if (!commands) return $.error(`Function "loadCommand" was called without first initializing commands!`); - - const prefix = category ?? ""; - const header = filename.substring(0, filename.indexOf(".js")); - const command = (await import(`../commands/${prefix}/${header}`)).default as Command | undefined; - - if (!command) return $.warn(`Command "${header}" has no default export which is a Command instance!`); - - command.originalCommandName = header; - list.push(header); - - if (commands.has(header)) - $.warn( - `Command "${header}" already exists! Make sure to make each command uniquely identifiable across categories!` - ); - else commands.set(header, command); - - for (const alias of command.aliases) { - if (commands.has(alias)) - $.warn(`Top-level alias "${alias}" from command "${header}" already exists either as a command or alias!`); - else commands.set(alias, command); - } - - $.log(`Loading Command: ${header} (${category ? $(category).toTitleCase() : "Miscellaneous"})`); -} - -// The template should be built with a reductionist mentality. -// Provide everything the user needs and then let them remove whatever they want. -// That way, they aren't focusing on what's missing, but rather what they need for their command. -const template = `import Command from '../core/command'; -import {CommonLibrary} from '../core/lib'; - -export default new Command({ - description: "This is a template/testing command providing common functionality. Remove what you don't need, and rename/delete this file to generate a fresh command file here. This command should be automatically excluded from the help command. The \\"usage\\" parameter (string) overrides the default usage for the help command. The \\"endpoint\\" parameter (boolean) prevents further arguments from being passed. Also, as long as you keep the run function async, it'll return a promise allowing the program to automatically catch any synchronous errors. However, you'll have to do manual error handling if you go the then and catch route.", - endpoint: false, - usage: '', - permission: null, - aliases: [], - async run($: CommonLibrary): Promise { - - }, - subcommands: { - layer: new Command({ - description: "This is a named subcommand, meaning that the key name is what determines the keyword to use. With default settings for example, \\"$test layer\\".", - endpoint: false, - usage: '', - permission: null, - aliases: [], - async run($: CommonLibrary): Promise { - - } - }) - }, - user: new Command({ - description: "This is the subcommand for getting users by pinging them or copying their ID. With default settings for example, \\"$test 237359961842253835\\". The argument will be a user object and won't run if no user is found by that ID.", - endpoint: false, - usage: '', - permission: null, - async run($: CommonLibrary): Promise { - - } - }), - number: new Command({ - description: "This is a numeric subcommand, meaning that any type of number (excluding Infinity/NaN) will route to this command if present. With default settings for example, \\"$test -5.2\\". The argument with the number is already parsed so you can just use it without converting it.", - endpoint: false, - usage: '', - permission: null, - async run($: CommonLibrary): Promise { - - } - }), - any: new Command({ - description: "This is a generic subcommand, meaning that if there isn't a more specific subcommand that's called, it falls to this. With default settings for example, \\"$test reeee\\".", - endpoint: false, - usage: '', - permission: null, - async run($: CommonLibrary): Promise { - - } - }) -});`; diff --git a/src/events/message.ts b/src/events/message.ts index 107c8cf..dc9c6d5 100644 --- a/src/events/message.ts +++ b/src/events/message.ts @@ -1,17 +1,13 @@ import Event from "../core/event"; -import Command, {loadCommands} from "../core/command"; +import Command, {loadableCommands} from "../core/command"; import {hasPermission, getPermissionLevel, PermissionNames} from "../core/permissions"; -import {Permissions, Collection} from "discord.js"; +import {Permissions} from "discord.js"; import {getPrefix} from "../core/structures"; import $, {replyEventListeners} from "../core/lib"; -// It's a rather hacky solution, but since there's no top-level await, I just have to make the loading conditional. -let commands: Collection | null = null; - export default new Event<"message">({ async on(message) { - // Load commands if it hasn't already done so. Luckily, it's called once at most. - if (!commands) commands = await loadCommands(); + const commands = await loadableCommands; // Message Setup // if (message.author.bot) return; diff --git a/src/index.ts b/src/index.ts index 9721784..15d64f7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,7 +1,6 @@ import {Client} from "discord.js"; import setup from "./setup"; import {Config} from "./core/structures"; -import {loadCommands} from "./core/command"; import {loadEvents} from "./core/event"; import "discord.js-lavalink-lib"; import LavalinkMusic from "discord.js-lavalink-lib"; @@ -30,9 +29,8 @@ export const client = new Client(); admins: ["717352467280691331"] }); -// Begin the command loading here rather than when it's needed like in the message event. +// Command loading will start as soon as an instance of "core/command" is loaded, which is loaded during "events/message". setup.init().then(() => { - loadCommands(); loadEvents(client); client.login(Config.token).catch(setup.again); }); diff --git a/src/setup.ts b/src/setup.ts index 64a854a..7edde85 100644 --- a/src/setup.ts +++ b/src/setup.ts @@ -1,9 +1,20 @@ -import {existsSync as exists} from "fs"; +import {existsSync as exists, readFileSync as read, writeFile as write} from "fs"; import inquirer from "inquirer"; -import Storage from "./core/storage"; +import Storage, {generateHandler} from "./core/storage"; import {Config} from "./core/structures"; import $, {setConsoleActivated} from "./core/lib"; +// The template should be built with a reductionist mentality. +// Provide everything the user needs and then let them remove whatever they want. +// That way, they aren't focusing on what's missing, but rather what they need for their command. +if (process.argv[2] === "dev" && !exists("src/commands/test.ts")) { + write( + "src/commands/test.ts", + read("src/commands/template.ts"), + generateHandler('"test.ts" (testing/template command) successfully generated.') + ); +} + // This file is called (or at least should be called) automatically as long as a config file doesn't exist yet. // And that file won't be written until the data is successfully initialized. const prompts = [ diff --git a/tsconfig.json b/tsconfig.json index ba4dd11..be15e8f 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -11,7 +11,8 @@ "strictNullChecks": true, "strictFunctionTypes": true, "strictPropertyInitialization": true, - "removeComments": true + "removeComments": true, + "sourceMap": true }, "exclude": ["test"] } From 1fd8634ef1e42673e7ac3926d632ec511bbca0b4 Mon Sep 17 00:00:00 2001 From: WatDuhHekBro <44940783+WatDuhHekBro@users.noreply.github.com> Date: Tue, 26 Jan 2021 07:22:23 -0600 Subject: [PATCH 087/178] Tinkered with pre-commit, jest, and tsconfig --- jest.config.js | 7 + package-lock.json | 5077 +++++++++++++++-- package.json | 23 +- src/commands/admin.ts | 2 +- src/commands/info.ts | 10 +- src/core/lib.ts | 6 +- test/wrappers.ts => src/core/wrappers.test.ts | 2 +- tsconfig.json | 47 +- tsconfig.prod.json | 8 + 9 files changed, 4629 insertions(+), 553 deletions(-) create mode 100644 jest.config.js rename test/wrappers.ts => src/core/wrappers.test.ts (97%) create mode 100644 tsconfig.prod.json diff --git a/jest.config.js b/jest.config.js new file mode 100644 index 0000000..09097b3 --- /dev/null +++ b/jest.config.js @@ -0,0 +1,7 @@ +module.exports = { + roots: ["/src"], + testMatch: ["**/*.test.+(ts|tsx)"], + transform: { + "^.+\\.(ts|tsx)$": "ts-jest" + } +}; diff --git a/package-lock.json b/package-lock.json index 7a9b4e7..dcccab1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,6 +4,414 @@ "lockfileVersion": 1, "requires": 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/core": { + "version": "7.12.10", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.12.10.tgz", + "integrity": "sha512-eTAlQKq65zHfkHZV0sIVODCPGVgoo1HdBlbSLi9CqOzuZanMv2ihzY+4paiKr1mH+XmYESMAmJ/dpZ68eN6d8w==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/generator": "^7.12.10", + "@babel/helper-module-transforms": "^7.12.1", + "@babel/helpers": "^7.12.5", + "@babel/parser": "^7.12.10", + "@babel/template": "^7.12.7", + "@babel/traverse": "^7.12.10", + "@babel/types": "^7.12.10", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.1", + "json5": "^2.1.2", + "lodash": "^4.17.19", + "semver": "^5.4.1", + "source-map": "^0.5.0" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "@babel/generator": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.12.11.tgz", + "integrity": "sha512-Ggg6WPOJtSi8yYQvLVjG8F/TlpWDlKx0OpS4Kt+xMQPs5OaGYWy+v1A+1TvxI6sAMGZpKWWoAQ1DaeQbImlItA==", + "dev": true, + "requires": { + "@babel/types": "^7.12.11", + "jsesc": "^2.5.1", + "source-map": "^0.5.0" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "@babel/helper-function-name": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.12.11.tgz", + "integrity": "sha512-AtQKjtYNolKNi6nNNVLQ27CP6D9oFR6bq/HPYSizlzbp7uC1M59XJe8L+0uXjbIaZaUJF99ruHqVGiKXU/7ybA==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.12.10", + "@babel/template": "^7.12.7", + "@babel/types": "^7.12.11" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.12.10", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.10.tgz", + "integrity": "sha512-mm0n5BPjR06wh9mPQaDdXWDoll/j5UpCAPl1x8fS71GHm7HA6Ua2V4ylG1Ju8lvcTOietbPNNPaSilKj+pj+Ag==", + "dev": true, + "requires": { + "@babel/types": "^7.12.10" + } + }, + "@babel/helper-member-expression-to-functions": { + "version": "7.12.7", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.12.7.tgz", + "integrity": "sha512-DCsuPyeWxeHgh1Dus7APn7iza42i/qXqiFPWyBDdOFtvS581JQePsc1F/nD+fHrcswhLlRc2UpYS1NwERxZhHw==", + "dev": true, + "requires": { + "@babel/types": "^7.12.7" + } + }, + "@babel/helper-module-imports": { + "version": "7.12.5", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.12.5.tgz", + "integrity": "sha512-SR713Ogqg6++uexFRORf/+nPXMmWIn80TALu0uaFb+iQIUoR7bOC7zBWyzBs5b3tBBJXuyD0cRu1F15GyzjOWA==", + "dev": true, + "requires": { + "@babel/types": "^7.12.5" + } + }, + "@babel/helper-module-transforms": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.12.1.tgz", + "integrity": "sha512-QQzehgFAZ2bbISiCpmVGfiGux8YVFXQ0abBic2Envhej22DVXV9nCFaS5hIQbkyo1AdGb+gNME2TSh3hYJVV/w==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.12.1", + "@babel/helper-replace-supers": "^7.12.1", + "@babel/helper-simple-access": "^7.12.1", + "@babel/helper-split-export-declaration": "^7.11.0", + "@babel/helper-validator-identifier": "^7.10.4", + "@babel/template": "^7.10.4", + "@babel/traverse": "^7.12.1", + "@babel/types": "^7.12.1", + "lodash": "^4.17.19" + } + }, + "@babel/helper-optimise-call-expression": { + "version": "7.12.10", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.12.10.tgz", + "integrity": "sha512-4tpbU0SrSTjjt65UMWSrUOPZTsgvPgGG4S8QSTNHacKzpS51IVWGDj0yCwyeZND/i+LSN2g/O63jEXEWm49sYQ==", + "dev": true, + "requires": { + "@babel/types": "^7.12.10" + } + }, + "@babel/helper-plugin-utils": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz", + "integrity": "sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg==", + "dev": true + }, + "@babel/helper-replace-supers": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.12.11.tgz", + "integrity": "sha512-q+w1cqmhL7R0FNzth/PLLp2N+scXEK/L2AHbXUyydxp828F4FEa5WcVoqui9vFRiHDQErj9Zof8azP32uGVTRA==", + "dev": true, + "requires": { + "@babel/helper-member-expression-to-functions": "^7.12.7", + "@babel/helper-optimise-call-expression": "^7.12.10", + "@babel/traverse": "^7.12.10", + "@babel/types": "^7.12.11" + } + }, + "@babel/helper-simple-access": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.12.1.tgz", + "integrity": "sha512-OxBp7pMrjVewSSC8fXDFrHrBcJATOOFssZwv16F3/6Xtc138GHybBfPbm9kfiqQHKhYQrlamWILwlDCeyMFEaA==", + "dev": true, + "requires": { + "@babel/types": "^7.12.1" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.11.tgz", + "integrity": "sha512-LsIVN8j48gHgwzfocYUSkO/hjYAOJqlpJEc7tGXcIm4cubjVUf8LGW6eWRyxEu7gA25q02p0rQUWoCI33HNS5g==", + "dev": true, + "requires": { + "@babel/types": "^7.12.11" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz", + "integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==", + "dev": true + }, + "@babel/helpers": { + "version": "7.12.5", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.12.5.tgz", + "integrity": "sha512-lgKGMQlKqA8meJqKsW6rUnc4MdUk35Ln0ATDqdM1a/UpARODdI4j5Y5lVfUScnSNkJcdCRAaWkspykNoFg9sJA==", + "dev": true, + "requires": { + "@babel/template": "^7.10.4", + "@babel/traverse": "^7.12.5", + "@babel/types": "^7.12.5" + } + }, + "@babel/highlight": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", + "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "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" + } + } + } + }, + "@babel/parser": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.12.11.tgz", + "integrity": "sha512-N3UxG+uuF4CMYoNj8AhnbAcJF0PiuJ9KHuy1lQmkYsxTer/MAH9UBNHsBoAX/4s6NvlDD047No8mYVGGzLL4hg==", + "dev": true + }, + "@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-class-properties": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.1.tgz", + "integrity": "sha512-U40A76x5gTwmESz+qiqssqmeEsKvcSyvtgktrm0uzcARAmM9I1jR221f6Oq+GmHrcD+LvZDag1UTOTe2fL3TeA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-top-level-await": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.12.1.tgz", + "integrity": "sha512-i7ooMZFS+a/Om0crxZodrTzNEPJHZrlMVGMTEpFAj6rYY/bKCddB0Dk/YxfPuYXOopuhKk/e1jV6h+WUU9XN3A==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/template": { + "version": "7.12.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.7.tgz", + "integrity": "sha512-GkDzmHS6GV7ZeXfJZ0tLRBhZcMcY0/Lnb+eEbXDBfCAcZCjrZKe6p3J4we/D24O9Y8enxWAg1cWwof59yLh2ow==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/parser": "^7.12.7", + "@babel/types": "^7.12.7" + } + }, + "@babel/traverse": { + "version": "7.12.12", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.12.12.tgz", + "integrity": "sha512-s88i0X0lPy45RrLM8b9mz8RPH5FqO9G9p7ti59cToE44xFm1Q+Pjh5Gq4SXBbtb88X7Uy7pexeqRIQDDMNkL0w==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.12.11", + "@babel/generator": "^7.12.11", + "@babel/helper-function-name": "^7.12.11", + "@babel/helper-split-export-declaration": "^7.12.11", + "@babel/parser": "^7.12.11", + "@babel/types": "^7.12.12", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.19" + } + }, + "@babel/types": { + "version": "7.12.12", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.12.12.tgz", + "integrity": "sha512-lnIX7piTxOH22xE7fDXDbSHg9MM1/6ORnafpJmov5rs0kX5g4BZxeXNJLXsMRiO0U5Rb8/FvMS6xlTnTHvxonQ==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.12.11", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + }, + "@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true + }, + "@cnakazawa/watch": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@cnakazawa/watch/-/watch-1.0.4.tgz", + "integrity": "sha512-v9kIhKwjeZThiWrLmj0y17CWoyddASLj9O2yvbZkbvw/N3rWOYy9zkV66ursAoVr0mV15bL8g0c4QZUE6cdDoQ==", + "dev": true, + "requires": { + "exec-sh": "^0.3.2", + "minimist": "^1.2.0" + } + }, "@discordjs/collection": { "version": "0.1.6", "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-0.1.6.tgz", @@ -19,6 +427,256 @@ "mime-types": "^2.1.12" } }, + "@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "requires": { + "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" + }, + "dependencies": { + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + } + } + }, + "@istanbuljs/schema": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.2.tgz", + "integrity": "sha512-tsAQNx32a8CoFhjhijUIhI4kccIAgmGhy8LZMZgGfmXcpMbPRUqn5LWmgRttILi6yeGmBJd2xsPkFMs0PzgPCw==", + "dev": true + }, + "@jest/console": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-26.6.2.tgz", + "integrity": "sha512-IY1R2i2aLsLr7Id3S6p2BA82GNWryt4oSvEXLAKc+L2zdi89dSkE8xC1C+0kpATG4JhBJREnQOH7/zmccM2B0g==", + "dev": true, + "requires": { + "@jest/types": "^26.6.2", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^26.6.2", + "jest-util": "^26.6.2", + "slash": "^3.0.0" + } + }, + "@jest/core": { + "version": "26.6.3", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-26.6.3.tgz", + "integrity": "sha512-xvV1kKbhfUqFVuZ8Cyo+JPpipAHHAV3kcDBftiduK8EICXmTFddryy3P7NfZt8Pv37rA9nEJBKCCkglCPt/Xjw==", + "dev": true, + "requires": { + "@jest/console": "^26.6.2", + "@jest/reporters": "^26.6.2", + "@jest/test-result": "^26.6.2", + "@jest/transform": "^26.6.2", + "@jest/types": "^26.6.2", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.4", + "jest-changed-files": "^26.6.2", + "jest-config": "^26.6.3", + "jest-haste-map": "^26.6.2", + "jest-message-util": "^26.6.2", + "jest-regex-util": "^26.0.0", + "jest-resolve": "^26.6.2", + "jest-resolve-dependencies": "^26.6.3", + "jest-runner": "^26.6.3", + "jest-runtime": "^26.6.3", + "jest-snapshot": "^26.6.2", + "jest-util": "^26.6.2", + "jest-validate": "^26.6.2", + "jest-watcher": "^26.6.2", + "micromatch": "^4.0.2", + "p-each-series": "^2.1.0", + "rimraf": "^3.0.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "@jest/environment": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-26.6.2.tgz", + "integrity": "sha512-nFy+fHl28zUrRsCeMB61VDThV1pVTtlEokBRgqPrcT1JNq4yRNIyTHfyht6PqtUvY9IsuLGTrbG8kPXjSZIZwA==", + "dev": true, + "requires": { + "@jest/fake-timers": "^26.6.2", + "@jest/types": "^26.6.2", + "@types/node": "*", + "jest-mock": "^26.6.2" + } + }, + "@jest/fake-timers": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-26.6.2.tgz", + "integrity": "sha512-14Uleatt7jdzefLPYM3KLcnUl1ZNikaKq34enpb5XG9i81JpppDb5muZvonvKyrl7ftEHkKS5L5/eB/kxJ+bvA==", + "dev": true, + "requires": { + "@jest/types": "^26.6.2", + "@sinonjs/fake-timers": "^6.0.1", + "@types/node": "*", + "jest-message-util": "^26.6.2", + "jest-mock": "^26.6.2", + "jest-util": "^26.6.2" + } + }, + "@jest/globals": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-26.6.2.tgz", + "integrity": "sha512-85Ltnm7HlB/KesBUuALwQ68YTU72w9H2xW9FjZ1eL1U3lhtefjjl5c2MiUbpXt/i6LaPRvoOFJ22yCBSfQ0JIA==", + "dev": true, + "requires": { + "@jest/environment": "^26.6.2", + "@jest/types": "^26.6.2", + "expect": "^26.6.2" + } + }, + "@jest/reporters": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-26.6.2.tgz", + "integrity": "sha512-h2bW53APG4HvkOnVMo8q3QXa6pcaNt1HkwVsOPMBV6LD/q9oSpxNSYZQYkAnjdMjrJ86UuYeLo+aEZClV6opnw==", + "dev": true, + "requires": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^26.6.2", + "@jest/test-result": "^26.6.2", + "@jest/transform": "^26.6.2", + "@jest/types": "^26.6.2", + "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": "^26.6.2", + "jest-resolve": "^26.6.2", + "jest-util": "^26.6.2", + "jest-worker": "^26.6.2", + "node-notifier": "^8.0.0", + "slash": "^3.0.0", + "source-map": "^0.6.0", + "string-length": "^4.0.1", + "terminal-link": "^2.0.0", + "v8-to-istanbul": "^7.0.0" + } + }, + "@jest/source-map": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-26.6.2.tgz", + "integrity": "sha512-YwYcCwAnNmOVsZ8mr3GfnzdXDAl4LaenZP5z+G0c8bzC9/dugL8zRmxZzdoTl4IaS3CryS1uWnROLPFmb6lVvA==", + "dev": true, + "requires": { + "callsites": "^3.0.0", + "graceful-fs": "^4.2.4", + "source-map": "^0.6.0" + } + }, + "@jest/test-result": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-26.6.2.tgz", + "integrity": "sha512-5O7H5c/7YlojphYNrK02LlDIV2GNPYisKwHm2QTKjNZeEzezCbwYs9swJySv2UfPMyZ0VdsmMv7jIlD/IKYQpQ==", + "dev": true, + "requires": { + "@jest/console": "^26.6.2", + "@jest/types": "^26.6.2", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + } + }, + "@jest/test-sequencer": { + "version": "26.6.3", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-26.6.3.tgz", + "integrity": "sha512-YHlVIjP5nfEyjlrSr8t/YdNfU/1XEt7c5b4OxcXCjyRhjzLYu/rO69/WHPuYcbCWkz8kAeZVZp2N2+IOLLEPGw==", + "dev": true, + "requires": { + "@jest/test-result": "^26.6.2", + "graceful-fs": "^4.2.4", + "jest-haste-map": "^26.6.2", + "jest-runner": "^26.6.3", + "jest-runtime": "^26.6.3" + } + }, + "@jest/transform": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-26.6.2.tgz", + "integrity": "sha512-E9JjhUgNzvuQ+vVAL21vlyfy12gP0GhazGgJC4h6qUt1jSdUXGWJ1wfu/X7Sd8etSgxV4ovT1pb9v5D6QW4XgA==", + "dev": true, + "requires": { + "@babel/core": "^7.1.0", + "@jest/types": "^26.6.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": "^26.6.2", + "jest-regex-util": "^26.0.0", + "jest-util": "^26.6.2", + "micromatch": "^4.0.2", + "pirates": "^4.0.1", + "slash": "^3.0.0", + "source-map": "^0.6.1", + "write-file-atomic": "^3.0.0" + } + }, + "@jest/types": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", + "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^15.0.0", + "chalk": "^4.0.0" + } + }, "@lavacord/discord.js": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/@lavacord/discord.js/-/discord.js-0.0.5.tgz", @@ -38,6 +696,65 @@ } } }, + "@sinonjs/commons": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.2.tgz", + "integrity": "sha512-sruwd86RJHdsVf/AtBoijDmUqJp3B6hF/DGC23C+JaegnDHaZyewCjoVGTdg3J0uz3Zs7NnIT05OBOmML72lQw==", + "dev": true, + "requires": { + "type-detect": "4.0.8" + } + }, + "@sinonjs/fake-timers": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-6.0.1.tgz", + "integrity": "sha512-MZPUxrmFubI36XS1DI3qmI0YdN1gks62JtFZvxR67ljjSNCeK6U08Zx4msEWOXuofgqUt6zPHSi1H9fbjR/NRA==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.7.0" + } + }, + "@types/babel__core": { + "version": "7.1.12", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.12.tgz", + "integrity": "sha512-wMTHiiTiBAAPebqaPiPDLFA4LYPKr6Ph0Xq/6rq1Ur3v66HXyG+clfR9CNETkD7MQS8ZHvpQOtA53DLws5WAEQ==", + "dev": true, + "requires": { + "@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.2", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.2.tgz", + "integrity": "sha512-MdSJnBjl+bdwkLskZ3NGFp9YcXGx5ggLpQQPqtgakVhsWK0hTtNYhjpZLlWQTviGTvF8at+Bvli3jV7faPdgeQ==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@types/babel__template": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.0.tgz", + "integrity": "sha512-NTPErx4/FiPCGScH7foPyr+/1Dkzkni+rHiYHHoTjvwou7AQzJkNeD60A9CXRy+ZEN2B1bggmkTMCDb+Mv5k+A==", + "dev": true, + "requires": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@types/babel__traverse": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.11.0.tgz", + "integrity": "sha512-kSjgDMZONiIfSH1Nxcr5JIRMwUetDki63FSQfpTCz8ogF3Ulqm8+mr5f78dUYs6vMiB6gBusQqfQmBvHZj/lwg==", + "dev": true, + "requires": { + "@babel/types": "^7.3.0" + } + }, "@types/color-name": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", @@ -53,6 +770,15 @@ "@types/node": "*" } }, + "@types/graceful-fs": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.4.tgz", + "integrity": "sha512-mWA/4zFQhfvOA8zWkXobwJvBD7vzcxgrOQ0J5CH1votGqdq9m7+FwtGaqyCZqC3NyyBkc9z4m+iry4LlqcMWJg==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/inquirer": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/@types/inquirer/-/inquirer-6.5.0.tgz", @@ -63,18 +789,46 @@ "rxjs": "^6.4.0" } }, + "@types/istanbul-lib-coverage": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz", + "integrity": "sha512-sz7iLqvVUg1gIedBOvlkxPlc8/uVzyS5OwGz1cKjXzkl3FpL3al0crU8YGU1WoHkxn0Wxbw5tyi6hvzJKNzFsw==", + "dev": true + }, + "@types/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "*" + } + }, + "@types/istanbul-reports": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", + "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", + "dev": true, + "requires": { + "@types/istanbul-lib-report": "*" + } + }, + "@types/jest": { + "version": "26.0.20", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-26.0.20.tgz", + "integrity": "sha512-9zi2Y+5USJRxd0FsahERhBwlcvFh6D2GLQnY2FH2BzK8J9s9omvNHIbvABwIluXa0fD8XVKMLTO0aOEuUfACAA==", + "dev": true, + "requires": { + "jest-diff": "^26.0.0", + "pretty-format": "^26.0.0" + } + }, "@types/minimatch": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==", "dev": true }, - "@types/mocha": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-8.2.0.tgz", - "integrity": "sha512-/Sge3BymXo4lKc31C8OINJgXLaw+7vL1/L1pGiBNpGrBiT8FQiaFpSYV0uhTaG4y78vcMBTMFsWaHDvuD+xGzQ==", - "dev": true - }, "@types/ms": { "version": "0.7.31", "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz", @@ -87,6 +841,24 @@ "integrity": "sha512-Y93R97Ouif9JEOWPIUyU+eyIdyRqQR0I8Ez1dzku4hDx34NWh4HbtIc3WNzwB1Y9ULvNGeu5B8h8bVL5cAk4/A==", "dev": true }, + "@types/normalize-package-data": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz", + "integrity": "sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==", + "dev": true + }, + "@types/prettier": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.1.6.tgz", + "integrity": "sha512-6gOkRe7OIioWAXfnO/2lFiv+SJichKVSys1mSsgyrYHSEjk8Ctv4tSR/Odvnu+HWlH2C8j53dahU03XmQdd5fA==", + "dev": true + }, + "@types/stack-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.0.tgz", + "integrity": "sha512-RJJrrySY7A8havqpGObOB4W92QXKJo63/jFLLgpvOtsGUqbQZ9Sbgl35KMm1DjC6j7AvmmU2bIno+3IyEaemaw==", + "dev": true + }, "@types/through": { "version": "0.0.30", "resolved": "https://registry.npmjs.org/@types/through/-/through-0.0.30.tgz", @@ -105,10 +877,25 @@ "@types/node": "*" } }, - "@ungap/promise-all-settled": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", - "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", + "@types/yargs": { + "version": "15.0.12", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.12.tgz", + "integrity": "sha512-f+fD/fQAo3BCbCDlrUpznF1A5Zp9rB0noS5vnoormHSIPFKL0Z2DcUJ3Gxp5ytH4uLRNxy7AwYUC9exZzqGMAw==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, + "@types/yargs-parser": { + "version": "20.2.0", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-20.2.0.tgz", + "integrity": "sha512-37RSHht+gzzgYeobbG+KWryeAW8J33Nhr69cjTqSYymXVZEN9NbRYWoYlRtDhHKPVT1FyNKwaTPC1NynKZpzRA==", + "dev": true + }, + "abab": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.5.tgz", + "integrity": "sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q==", "dev": true }, "abort-controller": { @@ -119,12 +906,40 @@ "event-target-shim": "^5.0.0" } }, - "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==", + "acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", "dev": true }, + "acorn-globals": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz", + "integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==", + "dev": true, + "requires": { + "acorn": "^7.1.1", + "acorn-walk": "^7.1.1" + } + }, + "acorn-walk": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", + "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", + "dev": true + }, + "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-escapes": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.1.tgz", @@ -157,12 +972,6 @@ "picomatch": "^2.0.4" } }, - "arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true - }, "argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -172,11 +981,74 @@ "sprintf-js": "~1.0.2" } }, + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "dev": true + }, + "arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", + "dev": true + }, + "arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", + "dev": true + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "dev": true + }, + "asn1": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "dev": true, + "requires": { + "safer-buffer": "~2.1.0" + } + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true + }, + "assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", + "dev": true + }, "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" }, + "atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", + "dev": true + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", + "dev": true + }, + "aws4": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", + "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==", + "dev": true + }, "axios": { "version": "0.21.1", "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz", @@ -185,16 +1057,153 @@ "follow-redirects": "^1.10.0" } }, + "babel-jest": { + "version": "26.6.3", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-26.6.3.tgz", + "integrity": "sha512-pl4Q+GAVOHwvjrck6jKjvmGhnO3jHX/xuB9d27f+EJZ/6k+6nMuPjorrYp7s++bKKdANwzElBWnLWaObvTnaZA==", + "dev": true, + "requires": { + "@jest/transform": "^26.6.2", + "@jest/types": "^26.6.2", + "@types/babel__core": "^7.1.7", + "babel-plugin-istanbul": "^6.0.0", + "babel-preset-jest": "^26.6.2", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.4", + "slash": "^3.0.0" + } + }, + "babel-plugin-istanbul": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.0.0.tgz", + "integrity": "sha512-AF55rZXpe7trmEylbaE1Gv54wn6rwU03aptvRoVIGP8YykoSxqdVLV1TfwflBCE/QtHmqtP8SWlTENqbK8GCSQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^4.0.0", + "test-exclude": "^6.0.0" + } + }, + "babel-plugin-jest-hoist": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-26.6.2.tgz", + "integrity": "sha512-PO9t0697lNTmcEHH69mdtYiOIkkOlj9fySqfO3K1eCcdISevLAE0xY59VLLUj0SoiPiTX/JU2CYFpILydUa5Lw==", + "dev": true, + "requires": { + "@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": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", + "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", + "dev": true, + "requires": { + "@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": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-26.6.2.tgz", + "integrity": "sha512-YvdtlVm9t3k777c5NPQIv6cxFFFapys25HiUmuSgHwIZhfifweR5c5Sf5nwE3MAbfu327CYSvps8Yx6ANLyleQ==", + "dev": true, + "requires": { + "babel-plugin-jest-hoist": "^26.6.2", + "babel-preset-current-node-syntax": "^1.0.0" + } + }, "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" }, - "binary-extensions": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz", - "integrity": "sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==", - "dev": true + "base": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", + "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", + "dev": true, + "requires": { + "cache-base": "^1.0.1", + "class-utils": "^0.3.5", + "component-emitter": "^1.2.1", + "define-property": "^1.0.0", + "isobject": "^3.0.1", + "mixin-deep": "^1.2.0", + "pascalcase": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "dev": true, + "requires": { + "tweetnacl": "^0.14.3" + }, + "dependencies": { + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "dev": true + } + } }, "brace-expansion": { "version": "1.1.11", @@ -214,24 +1223,80 @@ "fill-range": "^7.0.1" } }, - "browser-stdout": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", - "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "browser-process-hrtime": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", + "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==", "dev": true }, + "bs-logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", + "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", + "dev": true, + "requires": { + "fast-json-stable-stringify": "2.x" + } + }, + "bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "requires": { + "node-int64": "^0.4.0" + } + }, "buffer-from": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", "dev": true }, + "cache-base": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", + "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "dev": true, + "requires": { + "collection-visit": "^1.0.0", + "component-emitter": "^1.2.1", + "get-value": "^2.0.6", + "has-value": "^1.0.0", + "isobject": "^3.0.1", + "set-value": "^2.0.0", + "to-object-path": "^0.3.0", + "union-value": "^1.0.0", + "unset-value": "^1.0.0" + } + }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true + }, "camelcase": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", "dev": true }, + "capture-exit": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/capture-exit/-/capture-exit-2.0.0.tgz", + "integrity": "sha512-PiT/hQmTonHhl/HFGN+Lx3JJUznrVYJ3+AQsnthneZbvW7x+f08Tk7yLJTLEOUvBTbduLeeBkxEaYXUOUrRq6g==", + "dev": true, + "requires": { + "rsvp": "^4.8.4" + } + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", + "dev": true + }, "chalk": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", @@ -241,25 +1306,50 @@ "supports-color": "^7.1.0" } }, + "char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true + }, "chardet": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==" }, - "chokidar": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.3.tgz", - "integrity": "sha512-DtM3g7juCXQxFVSNPNByEC2+NImtBuxQQvWlHunpJIS5Ocr0lG306cC7FCi7cEA0fzmybPUIl4txBIobk1gGOQ==", + "ci-info": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", + "dev": true + }, + "cjs-module-lexer": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-0.6.0.tgz", + "integrity": "sha512-uc2Vix1frTfnuzxxu1Hp4ktSvM3QaI4oXl4ZUqL1wjTu/BGki9TrCWoqLTg/drR1KwAEarXuRFCG2Svr1GxPFw==", + "dev": true + }, + "class-utils": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", + "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", "dev": true, "requires": { - "anymatch": "~3.1.1", - "braces": "~3.0.2", - "fsevents": "~2.1.2", - "glob-parent": "~5.1.0", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.5.0" + "arr-union": "^3.1.0", + "define-property": "^0.2.5", + "isobject": "^3.0.0", + "static-extend": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + } } }, "cli-cursor": { @@ -275,55 +1365,26 @@ "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==" }, - "cliui": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", - "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", + "dev": true + }, + "collect-v8-coverage": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", + "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==", + "dev": true + }, + "collection-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", + "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", "dev": true, "requires": { - "string-width": "^3.1.0", - "strip-ansi": "^5.2.0", - "wrap-ansi": "^5.1.0" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - }, - "emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - } - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - } - } + "map-visit": "^1.0.0", + "object-visit": "^1.0.0" } }, "color-convert": { @@ -347,15 +1408,56 @@ "delayed-stream": "~1.0.0" } }, + "component-emitter": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", + "dev": true + }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, - "create-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "convert-source-map": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", + "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.1" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + } + } + }, + "copy-descriptor": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", + "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", + "dev": true + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", "dev": true }, "cross-spawn": { @@ -369,6 +1471,49 @@ "which": "^2.0.1" } }, + "cssom": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz", + "integrity": "sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw==", + "dev": true + }, + "cssstyle": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", + "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", + "dev": true, + "requires": { + "cssom": "~0.3.6" + }, + "dependencies": { + "cssom": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", + "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", + "dev": true + } + } + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "data-urls": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz", + "integrity": "sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ==", + "dev": true, + "requires": { + "abab": "^2.0.3", + "whatwg-mimetype": "^2.3.0", + "whatwg-url": "^8.0.0" + } + }, "debug": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", @@ -392,15 +1537,86 @@ "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", "dev": true }, + "decimal.js": { + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.2.1.tgz", + "integrity": "sha512-KaL7+6Fw6i5A2XSnsbhm/6B+NuEA7TZ4vqxnd5tXz9sbKtrN9Srj8ab4vKVdK8YAqZO9P1kg45Y6YLoduPf+kw==", + "dev": true + }, + "decode-uri-component": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", + "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", + "dev": true + }, + "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 + }, + "deepmerge": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", + "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", + "dev": true + }, + "define-property": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "dev": true, + "requires": { + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + }, + "dependencies": { + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" }, - "diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true + }, + "diff-sequences": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-26.6.2.tgz", + "integrity": "sha512-Mv/TDa3nZ9sbc5soK+OoA74BsS3mL37yixCvUAQkiuA4Wz6YtwP/K47n2rv2ovzHZvoiQeA5FTQOschKkEwB0Q==", "dev": true }, "discord.js": { @@ -429,28 +1645,104 @@ "lavacord": "^1.1.7" } }, + "domexception": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz", + "integrity": "sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg==", + "dev": true, + "requires": { + "webidl-conversions": "^5.0.0" + }, + "dependencies": { + "webidl-conversions": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz", + "integrity": "sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==", + "dev": true + } + } + }, "duplexer": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=", "dev": true }, + "ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "dev": true, + "requires": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "emittery": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.7.2.tgz", + "integrity": "sha512-A8OG5SR/ij3SsJdWDJdkkSYUjQdCUx6APQXem0SaEePBSRg4eymGYwBkKo1Y6DU+af/Jn2dBQqDBvjnr9Vi8nQ==", + "dev": true + }, "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==" }, + "end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, + "requires": { + "once": "^1.4.0" + } + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "requires": { + "is-arrayish": "^0.2.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=" }, + "escodegen": { + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz", + "integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==", + "dev": true, + "requires": { + "esprima": "^4.0.1", + "estraverse": "^4.2.0", + "esutils": "^2.0.2", + "optionator": "^0.8.1", + "source-map": "~0.6.1" + } + }, "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 }, + "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-stream": { "version": "3.3.4", "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz", @@ -471,6 +1763,169 @@ "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==" }, + "exec-sh": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/exec-sh/-/exec-sh-0.3.4.tgz", + "integrity": "sha512-sEFIkc61v75sWeOe72qyrqg2Qg0OuLESziUDk/O/z2qgS15y2gWVFrI6f2Qn/qw/0/NCfCEsmNA4zOjkwEZT1A==", + "dev": true + }, + "execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "dev": true, + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + }, + "dependencies": { + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, + "exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", + "dev": true + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "dev": true, + "requires": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "expect": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/expect/-/expect-26.6.2.tgz", + "integrity": "sha512-9/hlOBkQl2l/PLHJx6JjoDF6xPKcJEsUlWKb23rKE7KzeDqUZKXKNMW27KIue5JMdBV9HgmoJPcc8HtO85t9IA==", + "dev": true, + "requires": { + "@jest/types": "^26.6.2", + "ansi-styles": "^4.0.0", + "jest-get-type": "^26.3.0", + "jest-matcher-utils": "^26.6.2", + "jest-message-util": "^26.6.2", + "jest-regex-util": "^26.0.0" + } + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + }, + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "dev": true, + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, "external-editor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", @@ -481,6 +1936,104 @@ "tmp": "^0.0.33" } }, + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "dev": true, + "requires": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", + "dev": true + }, + "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 + }, + "fb-watchman": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.1.tgz", + "integrity": "sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg==", + "dev": true, + "requires": { + "bser": "2.1.1" + } + }, "figures": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", @@ -498,27 +2051,43 @@ "to-regex-range": "^5.0.1" } }, - "find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "requires": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - } - }, - "flat": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", - "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", - "dev": true - }, "follow-redirects": { "version": "1.13.1", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.1.tgz", "integrity": "sha512-SSG5xmZh1mkPGyKzjZP8zLjltIfpW32Y5QpdNJyjcfGxK3qo3NDDkZOZSFiGn1A6SclQxY9GzEwAHQ3dmYRWpg==" }, + "for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", + "dev": true + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", + "dev": true + }, + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "fragment-cache": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", + "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", + "dev": true, + "requires": { + "map-cache": "^0.2.2" + } + }, "from": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz", @@ -537,12 +2106,54 @@ "dev": true, "optional": true }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true + }, "get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true }, + "get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true + }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", + "dev": true + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, "glob": { "version": "7.1.6", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", @@ -556,30 +2167,143 @@ "path-is-absolute": "^1.0.0" } }, - "glob-parent": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", - "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true + }, + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", + "dev": true + }, + "growly": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz", + "integrity": "sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=", + "dev": true, + "optional": true + }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", + "dev": true + }, + "har-validator": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", + "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", "dev": true, "requires": { - "is-glob": "^4.0.1" + "ajv": "^6.12.3", + "har-schema": "^2.0.0" } }, - "growl": { - "version": "1.10.5", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", - "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", - "dev": true + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" }, - "he": { + "has-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", + "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", + "dev": true, + "requires": { + "get-value": "^2.0.6", + "has-values": "^1.0.0", + "isobject": "^3.0.0" + } + }, + "has-values": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", + "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "kind-of": "^4.0.0" + }, + "dependencies": { + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "hosted-git-info": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz", + "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==", + "dev": true + }, + "html-encoding-sniffer": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz", + "integrity": "sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ==", + "dev": true, + "requires": { + "whatwg-encoding": "^1.0.5" + } + }, + "html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, + "http-signature": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, + "human-signals": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", + "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", "dev": true }, "iconv-lite": { @@ -590,6 +2314,22 @@ "safer-buffer": ">= 2.1.2 < 3" } }, + "import-local": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.0.2.tgz", + "integrity": "sha512-vjL3+w0oulAVZ0hBHnxa/Nm5TAurf9YLQJDhqRZyqb+VKGOB6LU8t9H1Nr5CIo16vh9XfJTOoHwU0B71S557gA==", + "dev": true, + "requires": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.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", @@ -624,19 +2364,112 @@ "through": "^2.3.6" } }, - "is-binary-path": { + "ip-regex": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz", + "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=", + "dev": true + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", "dev": true, "requires": { - "binary-extensions": "^2.0.0" + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } } }, - "is-extglob": { + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, + "is-ci": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", + "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", + "dev": true, + "requires": { + "ci-info": "^2.0.0" + } + }, + "is-core-module": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.2.0.tgz", + "integrity": "sha512-XRAfAdyyY5F5cOXn7hYQDqh2Xmii+DEfIcQGxK/uNwMHhIkPWO0g8msXcbzLe+MpGoR951MlqM/2iIlU4vKDdQ==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "is-docker": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.1.1.tgz", + "integrity": "sha512-ZOoqiXfEwtGknTiuDEy8pN2CfE3TxMHprvNer1mXiqwkOT77Rw3YVrUQ52EqAOU3QAWDQ+bQdx7HJzrv7LS2Hw==", + "dev": true, + "optional": true + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", "dev": true }, "is-fullwidth-code-point": { @@ -644,14 +2477,11 @@ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" }, - "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" - } + "is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true }, "is-number": { "version": "7.0.0", @@ -659,10 +2489,53 @@ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true }, - "is-plain-obj": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", - "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + }, + "is-potential-custom-element-name": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.0.tgz", + "integrity": "sha1-DFLlS8yjkbssSUsh6GJtczbG45c=", + "dev": true + }, + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", + "dev": true + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "dev": true + }, + "is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true + }, + "is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "optional": true, + "requires": { + "is-docker": "^2.0.0" + } + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", "dev": true }, "isexe": { @@ -671,6 +2544,749 @@ "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", "dev": true }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", + "dev": true + }, + "istanbul-lib-coverage": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz", + "integrity": "sha512-UiUIqxMgRDET6eR+o5HbfRYP1l0hqkWOs7vNxC/mggutCMUIhWMm8gAHb8tHlyfD3/l6rlgNA5cKdDzEAf6hEg==", + "dev": true + }, + "istanbul-lib-instrument": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", + "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", + "dev": true, + "requires": { + "@babel/core": "^7.7.5", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.0.0", + "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", + "dev": true, + "requires": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^3.0.0", + "supports-color": "^7.1.0" + } + }, + "istanbul-lib-source-maps": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.0.tgz", + "integrity": "sha512-c16LpFRkR8vQXyHZ5nLpY35JZtzj1PQY1iZmesUbf1FZHbIupcWfjgOXBY9YHkLEQ6puz1u4Dgj6qmU/DisrZg==", + "dev": true, + "requires": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + } + }, + "istanbul-reports": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.0.2.tgz", + "integrity": "sha512-9tZvz7AiR3PEDNGiV9vIouQ/EAcqMXFmkcA1CDFTwOB98OZVDL0PH9glHotf5Ugp6GCOTypfzGWI/OqjWNCRUw==", + "dev": true, + "requires": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + } + }, + "jest": { + "version": "26.6.3", + "resolved": "https://registry.npmjs.org/jest/-/jest-26.6.3.tgz", + "integrity": "sha512-lGS5PXGAzR4RF7V5+XObhqz2KZIDUA1yD0DG6pBVmy10eh0ZIXQImRuzocsI/N2XZ1GrLFwTS27In2i2jlpq1Q==", + "dev": true, + "requires": { + "@jest/core": "^26.6.3", + "import-local": "^3.0.2", + "jest-cli": "^26.6.3" + }, + "dependencies": { + "cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "jest-cli": { + "version": "26.6.3", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-26.6.3.tgz", + "integrity": "sha512-GF9noBSa9t08pSyl3CY4frMrqp+aQXFGFkf5hEPbh/pIUFYWMK6ZLTfbmadxJVcJrdRoChlWQsA2VkJcDFK8hg==", + "dev": true, + "requires": { + "@jest/core": "^26.6.3", + "@jest/test-result": "^26.6.2", + "@jest/types": "^26.6.2", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.4", + "import-local": "^3.0.2", + "is-ci": "^2.0.0", + "jest-config": "^26.6.3", + "jest-util": "^26.6.2", + "jest-validate": "^26.6.2", + "prompts": "^2.0.1", + "yargs": "^15.4.1" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, + "yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "dev": true, + "requires": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + } + }, + "yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } + } + }, + "jest-changed-files": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-26.6.2.tgz", + "integrity": "sha512-fDS7szLcY9sCtIip8Fjry9oGf3I2ht/QT21bAHm5Dmf0mD4X3ReNUf17y+bO6fR8WgbIZTlbyG1ak/53cbRzKQ==", + "dev": true, + "requires": { + "@jest/types": "^26.6.2", + "execa": "^4.0.0", + "throat": "^5.0.0" + }, + "dependencies": { + "execa": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz", + "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.0", + "get-stream": "^5.0.0", + "human-signals": "^1.1.1", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.0", + "onetime": "^5.1.0", + "signal-exit": "^3.0.2", + "strip-final-newline": "^2.0.0" + } + }, + "get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "is-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", + "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", + "dev": true + }, + "npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "requires": { + "path-key": "^3.0.0" + } + } + } + }, + "jest-config": { + "version": "26.6.3", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-26.6.3.tgz", + "integrity": "sha512-t5qdIj/bCj2j7NFVHb2nFB4aUdfucDn3JRKgrZnplb8nieAirAzRSHP8uDEd+qV6ygzg9Pz4YG7UTJf94LPSyg==", + "dev": true, + "requires": { + "@babel/core": "^7.1.0", + "@jest/test-sequencer": "^26.6.3", + "@jest/types": "^26.6.2", + "babel-jest": "^26.6.3", + "chalk": "^4.0.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.1", + "graceful-fs": "^4.2.4", + "jest-environment-jsdom": "^26.6.2", + "jest-environment-node": "^26.6.2", + "jest-get-type": "^26.3.0", + "jest-jasmine2": "^26.6.3", + "jest-regex-util": "^26.0.0", + "jest-resolve": "^26.6.2", + "jest-util": "^26.6.2", + "jest-validate": "^26.6.2", + "micromatch": "^4.0.2", + "pretty-format": "^26.6.2" + } + }, + "jest-diff": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-26.6.2.tgz", + "integrity": "sha512-6m+9Z3Gv9wN0WFVasqjCL/06+EFCMTqDEUl/b87HYK2rAPTyfz4ZIuSlPhY51PIQRWx5TaxeF1qmXKe9gfN3sA==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "diff-sequences": "^26.6.2", + "jest-get-type": "^26.3.0", + "pretty-format": "^26.6.2" + } + }, + "jest-docblock": { + "version": "26.0.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-26.0.0.tgz", + "integrity": "sha512-RDZ4Iz3QbtRWycd8bUEPxQsTlYazfYn/h5R65Fc6gOfwozFhoImx+affzky/FFBuqISPTqjXomoIGJVKBWoo0w==", + "dev": true, + "requires": { + "detect-newline": "^3.0.0" + } + }, + "jest-each": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-26.6.2.tgz", + "integrity": "sha512-Mer/f0KaATbjl8MCJ+0GEpNdqmnVmDYqCTJYTvoo7rqmRiDllmp2AYN+06F93nXcY3ur9ShIjS+CO/uD+BbH4A==", + "dev": true, + "requires": { + "@jest/types": "^26.6.2", + "chalk": "^4.0.0", + "jest-get-type": "^26.3.0", + "jest-util": "^26.6.2", + "pretty-format": "^26.6.2" + } + }, + "jest-environment-jsdom": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-26.6.2.tgz", + "integrity": "sha512-jgPqCruTlt3Kwqg5/WVFyHIOJHsiAvhcp2qiR2QQstuG9yWox5+iHpU3ZrcBxW14T4fe5Z68jAfLRh7joCSP2Q==", + "dev": true, + "requires": { + "@jest/environment": "^26.6.2", + "@jest/fake-timers": "^26.6.2", + "@jest/types": "^26.6.2", + "@types/node": "*", + "jest-mock": "^26.6.2", + "jest-util": "^26.6.2", + "jsdom": "^16.4.0" + } + }, + "jest-environment-node": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-26.6.2.tgz", + "integrity": "sha512-zhtMio3Exty18dy8ee8eJ9kjnRyZC1N4C1Nt/VShN1apyXc8rWGtJ9lI7vqiWcyyXS4BVSEn9lxAM2D+07/Tag==", + "dev": true, + "requires": { + "@jest/environment": "^26.6.2", + "@jest/fake-timers": "^26.6.2", + "@jest/types": "^26.6.2", + "@types/node": "*", + "jest-mock": "^26.6.2", + "jest-util": "^26.6.2" + } + }, + "jest-get-type": { + "version": "26.3.0", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-26.3.0.tgz", + "integrity": "sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig==", + "dev": true + }, + "jest-haste-map": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-26.6.2.tgz", + "integrity": "sha512-easWIJXIw71B2RdR8kgqpjQrbMRWQBgiBwXYEhtGUTaX+doCjBheluShdDMeR8IMfJiTqH4+zfhtg29apJf/8w==", + "dev": true, + "requires": { + "@jest/types": "^26.6.2", + "@types/graceful-fs": "^4.1.2", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "fsevents": "^2.1.2", + "graceful-fs": "^4.2.4", + "jest-regex-util": "^26.0.0", + "jest-serializer": "^26.6.2", + "jest-util": "^26.6.2", + "jest-worker": "^26.6.2", + "micromatch": "^4.0.2", + "sane": "^4.0.3", + "walker": "^1.0.7" + } + }, + "jest-jasmine2": { + "version": "26.6.3", + "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-26.6.3.tgz", + "integrity": "sha512-kPKUrQtc8aYwBV7CqBg5pu+tmYXlvFlSFYn18ev4gPFtrRzB15N2gW/Roew3187q2w2eHuu0MU9TJz6w0/nPEg==", + "dev": true, + "requires": { + "@babel/traverse": "^7.1.0", + "@jest/environment": "^26.6.2", + "@jest/source-map": "^26.6.2", + "@jest/test-result": "^26.6.2", + "@jest/types": "^26.6.2", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "expect": "^26.6.2", + "is-generator-fn": "^2.0.0", + "jest-each": "^26.6.2", + "jest-matcher-utils": "^26.6.2", + "jest-message-util": "^26.6.2", + "jest-runtime": "^26.6.3", + "jest-snapshot": "^26.6.2", + "jest-util": "^26.6.2", + "pretty-format": "^26.6.2", + "throat": "^5.0.0" + } + }, + "jest-leak-detector": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-26.6.2.tgz", + "integrity": "sha512-i4xlXpsVSMeKvg2cEKdfhh0H39qlJlP5Ex1yQxwF9ubahboQYMgTtz5oML35AVA3B4Eu+YsmwaiKVev9KCvLxg==", + "dev": true, + "requires": { + "jest-get-type": "^26.3.0", + "pretty-format": "^26.6.2" + } + }, + "jest-matcher-utils": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-26.6.2.tgz", + "integrity": "sha512-llnc8vQgYcNqDrqRDXWwMr9i7rS5XFiCwvh6DTP7Jqa2mqpcCBBlpCbn+trkG0KNhPu/h8rzyBkriOtBstvWhw==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "jest-diff": "^26.6.2", + "jest-get-type": "^26.3.0", + "pretty-format": "^26.6.2" + } + }, + "jest-message-util": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-26.6.2.tgz", + "integrity": "sha512-rGiLePzQ3AzwUshu2+Rn+UMFk0pHN58sOG+IaJbk5Jxuqo3NYO1U2/MIR4S1sKgsoYSXSzdtSa0TgrmtUwEbmA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@jest/types": "^26.6.2", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.4", + "micromatch": "^4.0.2", + "pretty-format": "^26.6.2", + "slash": "^3.0.0", + "stack-utils": "^2.0.2" + } + }, + "jest-mock": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-26.6.2.tgz", + "integrity": "sha512-YyFjePHHp1LzpzYcmgqkJ0nm0gg/lJx2aZFzFy1S6eUqNjXsOqTK10zNRff2dNfssgokjkG65OlWNcIlgd3zew==", + "dev": true, + "requires": { + "@jest/types": "^26.6.2", + "@types/node": "*" + } + }, + "jest-pnp-resolver": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz", + "integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==", + "dev": true + }, + "jest-regex-util": { + "version": "26.0.0", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-26.0.0.tgz", + "integrity": "sha512-Gv3ZIs/nA48/Zvjrl34bf+oD76JHiGDUxNOVgUjh3j890sblXryjY4rss71fPtD/njchl6PSE2hIhvyWa1eT0A==", + "dev": true + }, + "jest-resolve": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-26.6.2.tgz", + "integrity": "sha512-sOxsZOq25mT1wRsfHcbtkInS+Ek7Q8jCHUB0ZUTP0tc/c41QHriU/NunqMfCUWsL4H3MHpvQD4QR9kSYhS7UvQ==", + "dev": true, + "requires": { + "@jest/types": "^26.6.2", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.4", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^26.6.2", + "read-pkg-up": "^7.0.1", + "resolve": "^1.18.1", + "slash": "^3.0.0" + } + }, + "jest-resolve-dependencies": { + "version": "26.6.3", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-26.6.3.tgz", + "integrity": "sha512-pVwUjJkxbhe4RY8QEWzN3vns2kqyuldKpxlxJlzEYfKSvY6/bMvxoFrYYzUO1Gx28yKWN37qyV7rIoIp2h8fTg==", + "dev": true, + "requires": { + "@jest/types": "^26.6.2", + "jest-regex-util": "^26.0.0", + "jest-snapshot": "^26.6.2" + } + }, + "jest-runner": { + "version": "26.6.3", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-26.6.3.tgz", + "integrity": "sha512-atgKpRHnaA2OvByG/HpGA4g6CSPS/1LK0jK3gATJAoptC1ojltpmVlYC3TYgdmGp+GLuhzpH30Gvs36szSL2JQ==", + "dev": true, + "requires": { + "@jest/console": "^26.6.2", + "@jest/environment": "^26.6.2", + "@jest/test-result": "^26.6.2", + "@jest/types": "^26.6.2", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.7.1", + "exit": "^0.1.2", + "graceful-fs": "^4.2.4", + "jest-config": "^26.6.3", + "jest-docblock": "^26.0.0", + "jest-haste-map": "^26.6.2", + "jest-leak-detector": "^26.6.2", + "jest-message-util": "^26.6.2", + "jest-resolve": "^26.6.2", + "jest-runtime": "^26.6.3", + "jest-util": "^26.6.2", + "jest-worker": "^26.6.2", + "source-map-support": "^0.5.6", + "throat": "^5.0.0" + } + }, + "jest-runtime": { + "version": "26.6.3", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-26.6.3.tgz", + "integrity": "sha512-lrzyR3N8sacTAMeonbqpnSka1dHNux2uk0qqDXVkMv2c/A3wYnvQ4EXuI013Y6+gSKSCxdaczvf4HF0mVXHRdw==", + "dev": true, + "requires": { + "@jest/console": "^26.6.2", + "@jest/environment": "^26.6.2", + "@jest/fake-timers": "^26.6.2", + "@jest/globals": "^26.6.2", + "@jest/source-map": "^26.6.2", + "@jest/test-result": "^26.6.2", + "@jest/transform": "^26.6.2", + "@jest/types": "^26.6.2", + "@types/yargs": "^15.0.0", + "chalk": "^4.0.0", + "cjs-module-lexer": "^0.6.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.4", + "jest-config": "^26.6.3", + "jest-haste-map": "^26.6.2", + "jest-message-util": "^26.6.2", + "jest-mock": "^26.6.2", + "jest-regex-util": "^26.0.0", + "jest-resolve": "^26.6.2", + "jest-snapshot": "^26.6.2", + "jest-util": "^26.6.2", + "jest-validate": "^26.6.2", + "slash": "^3.0.0", + "strip-bom": "^4.0.0", + "yargs": "^15.4.1" + }, + "dependencies": { + "cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, + "yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "dev": true, + "requires": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + } + }, + "yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } + } + }, + "jest-serializer": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-26.6.2.tgz", + "integrity": "sha512-S5wqyz0DXnNJPd/xfIzZ5Xnp1HrJWBczg8mMfMpN78OJ5eDxXyf+Ygld9wX1DnUWbIbhM1YDY95NjR4CBXkb2g==", + "dev": true, + "requires": { + "@types/node": "*", + "graceful-fs": "^4.2.4" + } + }, + "jest-snapshot": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-26.6.2.tgz", + "integrity": "sha512-OLhxz05EzUtsAmOMzuupt1lHYXCNib0ECyuZ/PZOx9TrZcC8vL0x+DUG3TL+GLX3yHG45e6YGjIm0XwDc3q3og==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0", + "@jest/types": "^26.6.2", + "@types/babel__traverse": "^7.0.4", + "@types/prettier": "^2.0.0", + "chalk": "^4.0.0", + "expect": "^26.6.2", + "graceful-fs": "^4.2.4", + "jest-diff": "^26.6.2", + "jest-get-type": "^26.3.0", + "jest-haste-map": "^26.6.2", + "jest-matcher-utils": "^26.6.2", + "jest-message-util": "^26.6.2", + "jest-resolve": "^26.6.2", + "natural-compare": "^1.4.0", + "pretty-format": "^26.6.2", + "semver": "^7.3.2" + }, + "dependencies": { + "semver": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", + "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + } + } + }, + "jest-util": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-26.6.2.tgz", + "integrity": "sha512-MDW0fKfsn0OI7MS7Euz6h8HNDXVQ0gaM9uW6RjfDmd1DAFcaxX9OqIakHIqhbnmF08Cf2DLDG+ulq8YQQ0Lp0Q==", + "dev": true, + "requires": { + "@jest/types": "^26.6.2", + "@types/node": "*", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.4", + "is-ci": "^2.0.0", + "micromatch": "^4.0.2" + } + }, + "jest-validate": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-26.6.2.tgz", + "integrity": "sha512-NEYZ9Aeyj0i5rQqbq+tpIOom0YS1u2MVu6+euBsvpgIme+FOfRmoC4R5p0JiAUpaFvFy24xgrpMknarR/93XjQ==", + "dev": true, + "requires": { + "@jest/types": "^26.6.2", + "camelcase": "^6.0.0", + "chalk": "^4.0.0", + "jest-get-type": "^26.3.0", + "leven": "^3.1.0", + "pretty-format": "^26.6.2" + }, + "dependencies": { + "camelcase": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", + "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", + "dev": true + } + } + }, + "jest-watcher": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-26.6.2.tgz", + "integrity": "sha512-WKJob0P/Em2csiVthsI68p6aGKTIcsfjH9Gsx1f0A3Italz43e3ho0geSAVsmj09RWOELP1AZ/DXyJgOgDKxXQ==", + "dev": true, + "requires": { + "@jest/test-result": "^26.6.2", + "@jest/types": "^26.6.2", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "jest-util": "^26.6.2", + "string-length": "^4.0.1" + } + }, + "jest-worker": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.6.2.tgz", + "integrity": "sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==", + "dev": true, + "requires": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^7.0.0" + } + }, + "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.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz", @@ -681,6 +3297,109 @@ "esprima": "^4.0.0" } }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "dev": true + }, + "jsdom": { + "version": "16.4.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.4.0.tgz", + "integrity": "sha512-lYMm3wYdgPhrl7pDcRmvzPhhrGVBeVhPIqeHjzeiHN3DFmD1RBpbExbi8vU7BJdH8VAZYovR8DMt0PNNDM7k8w==", + "dev": true, + "requires": { + "abab": "^2.0.3", + "acorn": "^7.1.1", + "acorn-globals": "^6.0.0", + "cssom": "^0.4.4", + "cssstyle": "^2.2.0", + "data-urls": "^2.0.0", + "decimal.js": "^10.2.0", + "domexception": "^2.0.1", + "escodegen": "^1.14.1", + "html-encoding-sniffer": "^2.0.1", + "is-potential-custom-element-name": "^1.0.0", + "nwsapi": "^2.2.0", + "parse5": "5.1.1", + "request": "^2.88.2", + "request-promise-native": "^1.0.8", + "saxes": "^5.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^3.0.1", + "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.0.0", + "ws": "^7.2.3", + "xml-name-validator": "^3.0.0" + } + }, + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true + }, + "json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", + "dev": true + }, + "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-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "dev": true + }, + "json5": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz", + "integrity": "sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "dev": true, + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true + }, + "kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true + }, "lavacord": { "version": "1.1.9", "resolved": "https://registry.npmjs.org/lavacord/-/lavacord-1.1.9.tgz", @@ -690,27 +3409,69 @@ "ws": "^7.3.0" } }, - "locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true + }, + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", "dev": true, "requires": { - "p-locate": "^5.0.0" + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" } }, + "lines-and-columns": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", + "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=", + "dev": true + }, "lodash": { "version": "4.17.20", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==" }, - "log-symbols": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.0.0.tgz", - "integrity": "sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA==", + "lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=", + "dev": true + }, + "lodash.sortby": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", + "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=", + "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": { - "chalk": "^4.0.0" + "yallist": "^4.0.0" + } + }, + "make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "requires": { + "semver": "^6.0.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } } }, "make-error": { @@ -719,12 +3480,52 @@ "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", "dev": true }, + "makeerror": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.11.tgz", + "integrity": "sha1-4BpckQnyr3lmDk6LlYd5AYT1qWw=", + "dev": true, + "requires": { + "tmpl": "1.0.x" + } + }, + "map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", + "dev": true + }, "map-stream": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz", "integrity": "sha1-5WqpTEyAVaFkBKBnS3jyFffI4ZQ=", "dev": true }, + "map-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", + "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", + "dev": true, + "requires": { + "object-visit": "^1.0.0" + } + }, + "merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "micromatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", + "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", + "dev": true, + "requires": { + "braces": "^3.0.1", + "picomatch": "^2.0.5" + } + }, "mime-db": { "version": "1.45.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.45.0.tgz", @@ -751,62 +3552,39 @@ "brace-expansion": "^1.1.7" } }, - "mocha": { - "version": "8.2.1", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-8.2.1.tgz", - "integrity": "sha512-cuLBVfyFfFqbNR0uUKbDGXKGk+UDFe6aR4os78XIrMQpZl/nv7JYHcvP5MFIAb374b2zFXsdgEGwmzMtP0Xg8w==", + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true + }, + "mixin-deep": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", + "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", "dev": true, "requires": { - "@ungap/promise-all-settled": "1.1.2", - "ansi-colors": "4.1.1", - "browser-stdout": "1.3.1", - "chokidar": "3.4.3", - "debug": "4.2.0", - "diff": "4.0.2", - "escape-string-regexp": "4.0.0", - "find-up": "5.0.0", - "glob": "7.1.6", - "growl": "1.10.5", - "he": "1.2.0", - "js-yaml": "3.14.0", - "log-symbols": "4.0.0", - "minimatch": "3.0.4", - "ms": "2.1.2", - "nanoid": "3.1.12", - "serialize-javascript": "5.0.1", - "strip-json-comments": "3.1.1", - "supports-color": "7.2.0", - "which": "2.0.2", - "wide-align": "1.1.3", - "workerpool": "6.0.2", - "yargs": "13.3.2", - "yargs-parser": "13.1.2", - "yargs-unparser": "2.0.0" + "for-in": "^1.0.2", + "is-extendable": "^1.0.1" }, "dependencies": { - "escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true - }, - "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 - }, - "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==", + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", "dev": true, "requires": { - "has-flag": "^4.0.0" + "is-plain-object": "^2.0.4" } } } }, + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true + }, "moment": { "version": "2.29.1", "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz", @@ -822,10 +3600,35 @@ "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==" }, - "nanoid": { - "version": "3.1.12", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.12.tgz", - "integrity": "sha512-1qstj9z5+x491jfiC4Nelk+f8XBad7LN20PmyWINJEMRSf3wcAjAWysw1qaA8z6NSKe2sjq1hRSDpBH5paCb6A==", + "nanomatch": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", + "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "fragment-cache": "^0.2.1", + "is-windows": "^1.0.2", + "kind-of": "^6.0.2", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + } + }, + "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 + }, + "nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", "dev": true }, "node-cleanup": { @@ -839,12 +3642,141 @@ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==" }, + "node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs=", + "dev": true + }, + "node-modules-regexp": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz", + "integrity": "sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA=", + "dev": true + }, + "node-notifier": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-8.0.1.tgz", + "integrity": "sha512-BvEXF+UmsnAfYfoapKM9nGxnP+Wn7P91YfXmrKnfcYCx6VBeoN5Ez5Ogck6I8Bi5k4RlpqRYaw75pAwzX9OphA==", + "dev": true, + "optional": true, + "requires": { + "growly": "^1.3.0", + "is-wsl": "^2.2.0", + "semver": "^7.3.2", + "shellwords": "^0.1.1", + "uuid": "^8.3.0", + "which": "^2.0.2" + }, + "dependencies": { + "semver": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", + "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", + "dev": true, + "optional": true, + "requires": { + "lru-cache": "^6.0.0" + } + } + } + }, + "normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "requires": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, "normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "dev": true }, + "npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "dev": true, + "requires": { + "path-key": "^2.0.0" + }, + "dependencies": { + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true + } + } + }, + "nwsapi": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz", + "integrity": "sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ==", + "dev": true + }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "dev": true + }, + "object-copy": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", + "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", + "dev": true, + "requires": { + "copy-descriptor": "^0.1.0", + "define-property": "^0.2.5", + "kind-of": "^3.0.3" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "object-visit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", + "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", + "dev": true, + "requires": { + "isobject": "^3.0.0" + } + }, + "object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", + "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -861,33 +3793,47 @@ "mimic-fn": "^2.1.0" } }, + "optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "dev": true, + "requires": { + "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" + } + }, "os": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/os/-/os-0.1.1.tgz", "integrity": "sha1-IIhF6J4ZOtTZcUdLk5R3NqVtE/M=" }, + "os-shim": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/os-shim/-/os-shim-0.1.3.tgz", + "integrity": "sha1-a2LDeRz3kJ6jXtRuF2WLtBfLORc=", + "dev": true + }, "os-tmpdir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" }, - "p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "requires": { - "yocto-queue": "^0.1.0" - } + "p-each-series": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-2.2.0.tgz", + "integrity": "sha512-ycIL2+1V32th+8scbpTvyHNaHe02z0sjgh91XXjAk+ZeXoPN4Z46DVUnzdso0aX4KckKw0FNNFHdjZ2UsZvxiA==", + "dev": true }, - "p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "requires": { - "p-limit": "^3.0.2" - } + "p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", + "dev": true }, "p-try": { "version": "2.2.0", @@ -895,6 +3841,30 @@ "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "dev": true }, + "parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + } + }, + "parse5": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", + "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==", + "dev": true + }, + "pascalcase": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", + "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", + "dev": true + }, "path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -912,6 +3882,12 @@ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true }, + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "dev": true + }, "pause-stream": { "version": "0.0.11", "resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", @@ -921,23 +3897,190 @@ "through": "~2.3" } }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", + "dev": true + }, "picomatch": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", "dev": true }, + "pirates": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.1.tgz", + "integrity": "sha512-WuNqLTbMI3tmfef2TKxlQmAiLHKtFhlsCZnPIpuv2Ow0RDVO8lfy1Opf4NUzlMXLjPl+Men7AuVdX6TA+s+uGA==", + "dev": true, + "requires": { + "node-modules-regexp": "^1.0.0" + } + }, + "pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "requires": { + "find-up": "^4.0.0" + }, + "dependencies": { + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + } + } + }, + "posix-character-classes": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", + "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", + "dev": true + }, + "pre-commit": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/pre-commit/-/pre-commit-1.2.2.tgz", + "integrity": "sha1-287g7p3nI15X95xW186UZBpp7sY=", + "dev": true, + "requires": { + "cross-spawn": "^5.0.1", + "spawn-sync": "^1.0.15", + "which": "1.2.x" + }, + "dependencies": { + "cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "dev": true, + "requires": { + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "dev": true, + "requires": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true + }, + "which": { + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/which/-/which-1.2.14.tgz", + "integrity": "sha1-mofEN48D6CfOyvGs31bHNsAcFOU=", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", + "dev": true + } + } + }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "dev": true + }, "prettier": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.1.2.tgz", "integrity": "sha512-16c7K+x4qVlJg9rEbXl7HEGmQyZlG4R9AgP+oHKRMsMsuk8s+ATStlf1NpDqyBI1HpVyfjLOeMhH2LvuNvV5Vg==", "dev": true }, + "pretty-format": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", + "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", + "dev": true, + "requires": { + "@jest/types": "^26.6.2", + "ansi-regex": "^5.0.0", + "ansi-styles": "^4.0.0", + "react-is": "^17.0.1" + } + }, "prism-media": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/prism-media/-/prism-media-1.2.3.tgz", "integrity": "sha512-fSrR66n0l6roW9Rx4rSLMyTPTjRTiXy5RVqDOurACQ6si1rKHHKDU5gwBJoCsIV0R3o9gi+K50akl/qyw1C74A==" }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, + "prompts": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.0.tgz", + "integrity": "sha512-awZAKrk3vN6CroQukBL+R9051a4R3zCZBlJm/HBfrSZ8iTpYix3VX1vU4mveiLpiwmOJT4wokTF9m6HUk4KqWQ==", + "dev": true, + "requires": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + } + }, "ps-tree": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/ps-tree/-/ps-tree-1.2.0.tgz", @@ -947,22 +4090,249 @@ "event-stream": "=3.3.4" } }, - "randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", + "dev": true + }, + "psl": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", + "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==", + "dev": true + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", "dev": true, "requires": { - "safe-buffer": "^5.1.0" + "end-of-stream": "^1.1.0", + "once": "^1.3.1" } }, - "readdirp": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", - "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", + "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 + }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", + "dev": true + }, + "react-is": { + "version": "17.0.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.1.tgz", + "integrity": "sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA==", + "dev": true + }, + "read-pkg": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", + "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", "dev": true, "requires": { - "picomatch": "^2.2.1" + "@types/normalize-package-data": "^2.4.0", + "normalize-package-data": "^2.5.0", + "parse-json": "^5.0.0", + "type-fest": "^0.6.0" + }, + "dependencies": { + "type-fest": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", + "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", + "dev": true + } + } + }, + "read-pkg-up": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", + "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", + "dev": true, + "requires": { + "find-up": "^4.1.0", + "read-pkg": "^5.2.0", + "type-fest": "^0.8.1" + }, + "dependencies": { + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "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 + } + } + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + } + } + }, + "regex-not": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", + "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", + "dev": true, + "requires": { + "extend-shallow": "^3.0.2", + "safe-regex": "^1.1.0" + } + }, + "remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", + "dev": true + }, + "repeat-element": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", + "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==", + "dev": true + }, + "repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", + "dev": true + }, + "request": { + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", + "dev": true, + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + }, + "dependencies": { + "tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "dev": true, + "requires": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + } + }, + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "dev": true + } + } + }, + "request-promise-core": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.4.tgz", + "integrity": "sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw==", + "dev": true, + "requires": { + "lodash": "^4.17.19" + } + }, + "request-promise-native": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.9.tgz", + "integrity": "sha512-wcW+sIUiWnKgNY0dqCpOZkUbF/I+YPi+f09JZIDa39Ec+q82CpSYniDp+ISgTTbKmnpJWASeJBPZmoxH84wt3g==", + "dev": true, + "requires": { + "request-promise-core": "1.1.4", + "stealthy-require": "^1.1.1", + "tough-cookie": "^2.3.3" + }, + "dependencies": { + "tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "dev": true, + "requires": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + } + } } }, "require-directory": { @@ -977,6 +4347,37 @@ "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", "dev": true }, + "resolve": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.19.0.tgz", + "integrity": "sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg==", + "dev": true, + "requires": { + "is-core-module": "^2.1.0", + "path-parse": "^1.0.6" + } + }, + "resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "requires": { + "resolve-from": "^5.0.0" + } + }, + "resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true + }, + "resolve-url": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", + "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", + "dev": true + }, "restore-cursor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", @@ -986,6 +4387,27 @@ "signal-exit": "^3.0.2" } }, + "ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", + "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" + } + }, + "rsvp": { + "version": "4.8.5", + "resolved": "https://registry.npmjs.org/rsvp/-/rsvp-4.8.5.tgz", + "integrity": "sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA==", + "dev": true + }, "run-async": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", @@ -1005,26 +4427,205 @@ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", "dev": true }, + "safe-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", + "dev": true, + "requires": { + "ret": "~0.1.10" + } + }, "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, - "serialize-javascript": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-5.0.1.tgz", - "integrity": "sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==", + "sane": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/sane/-/sane-4.1.0.tgz", + "integrity": "sha512-hhbzAgTIX8O7SHfp2c8/kREfEn4qO/9q8C9beyY6+tvZ87EpoZ3i1RIEvp27YBswnNbY9mWd6paKVmKbAgLfZA==", "dev": true, "requires": { - "randombytes": "^2.1.0" + "@cnakazawa/watch": "^1.0.3", + "anymatch": "^2.0.0", + "capture-exit": "^2.0.0", + "exec-sh": "^0.3.2", + "execa": "^1.0.0", + "fb-watchman": "^2.0.0", + "micromatch": "^3.1.4", + "minimist": "^1.1.1", + "walker": "~1.0.5" + }, + "dependencies": { + "anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "dev": true, + "requires": { + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" + } + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true, + "requires": { + "remove-trailing-separator": "^1.0.1" + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } + } } }, + "saxes": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", + "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", + "dev": true, + "requires": { + "xmlchars": "^2.2.0" + } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, "set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", "dev": true }, + "set-value": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", + "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, "setimmediate": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", @@ -1045,17 +4646,177 @@ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true }, + "shellwords": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz", + "integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==", + "dev": true, + "optional": true + }, "signal-exit": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==" }, + "sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + }, + "snapdragon": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", + "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", + "dev": true, + "requires": { + "base": "^0.11.1", + "debug": "^2.2.0", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "map-cache": "^0.2.2", + "source-map": "^0.5.6", + "source-map-resolve": "^0.5.0", + "use": "^3.1.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "snapdragon-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", + "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", + "dev": true, + "requires": { + "define-property": "^1.0.0", + "isobject": "^3.0.0", + "snapdragon-util": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "snapdragon-util": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", + "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", + "dev": true, + "requires": { + "kind-of": "^3.2.0" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true }, + "source-map-resolve": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", + "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", + "dev": true, + "requires": { + "atob": "^2.1.2", + "decode-uri-component": "^0.2.0", + "resolve-url": "^0.2.1", + "source-map-url": "^0.4.0", + "urix": "^0.1.0" + } + }, "source-map-support": { "version": "0.5.19", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", @@ -1066,6 +4827,54 @@ "source-map": "^0.6.0" } }, + "source-map-url": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", + "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", + "dev": true + }, + "spawn-sync": { + "version": "1.0.15", + "resolved": "https://registry.npmjs.org/spawn-sync/-/spawn-sync-1.0.15.tgz", + "integrity": "sha1-sAeZVX63+wyDdsKdROih6mfldHY=", + "dev": true, + "requires": { + "concat-stream": "^1.4.7", + "os-shim": "^0.1.2" + } + }, + "spdx-correct": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", + "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", + "dev": true, + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", + "dev": true + }, + "spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.7.tgz", + "integrity": "sha512-U+MTEOO0AiDzxwFvoa4JVnMV6mZlJKk2sBLt90s7G0Gd0Mlknc7kxEn3nuDPNZRta7O2uy8oLcZLVT+4sqNZHQ==", + "dev": true + }, "split": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/split/-/split-0.3.3.tgz", @@ -1075,12 +4884,90 @@ "through": "2" } }, + "split-string": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", + "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "dev": true, + "requires": { + "extend-shallow": "^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 }, + "sshpk": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", + "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", + "dev": true, + "requires": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + }, + "dependencies": { + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "dev": true + } + } + }, + "stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-gL//fkxfWUsIlFL2Tl42Cl6+HFALEaB1FU76I/Fy+oZjRreP7OPMXFlGbxM7NQsI0ZpUfw76sHnv0WNYuTb7Iw==", + "dev": true, + "requires": { + "escape-string-regexp": "^2.0.0" + }, + "dependencies": { + "escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true + } + } + }, + "static-extend": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", + "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", + "dev": true, + "requires": { + "define-property": "^0.2.5", + "object-copy": "^0.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "stealthy-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", + "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=", + "dev": true + }, "stream-combiner": { "version": "0.0.4", "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz", @@ -1096,6 +4983,16 @@ "integrity": "sha512-mBqPGEOMNJKXRo7z0keX0wlAhbBAjilUdPW13nN0PecVryZxdHIeM7TqbsSUA7VYuS00HGC6mojP7DlQzfa9ZA==", "dev": true }, + "string-length": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.1.tgz", + "integrity": "sha512-PKyXUd0LK0ePjSOnWn34V2uD6acUWev9uy0Ft05k0E8xRW+SKcA0F7eMr7h5xlzfn+4O3N+55rduYyet3Jk+jw==", + "dev": true, + "requires": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + } + }, "string-width": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", @@ -1106,6 +5003,23 @@ "strip-ansi": "^6.0.0" } }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + } + } + }, "strip-ansi": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", @@ -1114,10 +5028,22 @@ "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==", + "strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true + }, + "strip-eof": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", + "dev": true + }, + "strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", "dev": true }, "supports-color": { @@ -1128,6 +5054,49 @@ "has-flag": "^4.0.0" } }, + "supports-hyperlinks": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.1.0.tgz", + "integrity": "sha512-zoE5/e+dnEijk6ASB6/qrK+oYdm2do1hjoLWrqUC/8WEIW1gbxFcKuBof7sW8ArN6e+AYvsE8HBGiVRWL/F5CA==", + "dev": true, + "requires": { + "has-flag": "^4.0.0", + "supports-color": "^7.0.0" + } + }, + "symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true + }, + "terminal-link": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", + "integrity": "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==", + "dev": true, + "requires": { + "ansi-escapes": "^4.2.1", + "supports-hyperlinks": "^2.0.0" + } + }, + "test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "requires": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + } + }, + "throat": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/throat/-/throat-5.0.0.tgz", + "integrity": "sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==", + "dev": true + }, "through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", @@ -1141,6 +5110,50 @@ "os-tmpdir": "~1.0.2" } }, + "tmpl": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.4.tgz", + "integrity": "sha1-I2QN17QtAEM5ERQIIOXPRA5SHdE=", + "dev": true + }, + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", + "dev": true + }, + "to-object-path": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", + "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "to-regex": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", + "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "dev": true, + "requires": { + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "regex-not": "^1.0.2", + "safe-regex": "^1.1.0" + } + }, "to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -1150,18 +5163,60 @@ "is-number": "^7.0.0" } }, - "ts-node": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-9.1.1.tgz", - "integrity": "sha512-hPlt7ZACERQGf03M253ytLY3dHbGNGrAq9qIHWUY9XHYl1z7wYngSr3OQ5xmui8o2AaxsONxIzjafLUiWBo1Fg==", + "tough-cookie": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-3.0.1.tgz", + "integrity": "sha512-yQyJ0u4pZsv9D4clxO69OEjLWYw+jbgspjTue4lTQZLfV0c5l1VmK2y1JK8E9ahdpltPOaAThPcp5nKPUgSnsg==", "dev": true, "requires": { - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "source-map-support": "^0.5.17", - "yn": "3.1.1" + "ip-regex": "^2.1.0", + "psl": "^1.1.28", + "punycode": "^2.1.1" + } + }, + "tr46": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.0.2.tgz", + "integrity": "sha512-3n1qG+/5kg+jrbTzwAykB5yRYtQCTqOGKq5U5PE3b0a1/mzo6snDhjGS0zJVJunO0NrT3Dg1MLy5TjWP/UJppg==", + "dev": true, + "requires": { + "punycode": "^2.1.1" + } + }, + "ts-jest": { + "version": "26.4.4", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-26.4.4.tgz", + "integrity": "sha512-3lFWKbLxJm34QxyVNNCgXX1u4o/RV0myvA2y2Bxm46iGIjKlaY0own9gIckbjZJPn+WaJEnfPPJ20HHGpoq4yg==", + "dev": true, + "requires": { + "@types/jest": "26.x", + "bs-logger": "0.x", + "buffer-from": "1.x", + "fast-json-stable-stringify": "2.x", + "jest-util": "^26.1.0", + "json5": "2.x", + "lodash.memoize": "4.x", + "make-error": "1.x", + "mkdirp": "1.x", + "semver": "7.x", + "yargs-parser": "20.x" + }, + "dependencies": { + "semver": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", + "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "yargs-parser": { + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "dev": true + } } }, "tsc-watch": { @@ -1182,22 +5237,246 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz", "integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==" }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "dev": true, + "requires": { + "safe-buffer": "^5.0.1" + } + }, "tweetnacl": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==" }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2" + } + }, + "type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true + }, "type-fest": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.11.0.tgz", "integrity": "sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==" }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", + "dev": true + }, + "typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "dev": true, + "requires": { + "is-typedarray": "^1.0.0" + } + }, "typescript": { "version": "3.9.7", "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.7.tgz", "integrity": "sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw==", "dev": true }, + "union-value": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", + "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", + "dev": true, + "requires": { + "arr-union": "^3.1.0", + "get-value": "^2.0.6", + "is-extendable": "^0.1.1", + "set-value": "^2.0.1" + } + }, + "unset-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", + "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", + "dev": true, + "requires": { + "has-value": "^0.3.1", + "isobject": "^3.0.0" + }, + "dependencies": { + "has-value": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", + "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", + "dev": true, + "requires": { + "get-value": "^2.0.3", + "has-values": "^0.1.4", + "isobject": "^2.0.0" + }, + "dependencies": { + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "dev": true, + "requires": { + "isarray": "1.0.0" + } + } + } + }, + "has-values": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", + "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", + "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" + } + }, + "urix": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", + "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", + "dev": true + }, + "use": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", + "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", + "dev": true + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, + "optional": true + }, + "v8-to-istanbul": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-7.1.0.tgz", + "integrity": "sha512-uXUVqNUCLa0AH1vuVxzi+MI4RfxEOKt9pBgKwHbgH7st8Kv2P1m+jvWNnektzBh5QShF3ODgKmUFCf38LnVz1g==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^1.6.0", + "source-map": "^0.7.3" + }, + "dependencies": { + "source-map": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", + "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", + "dev": true + } + } + }, + "validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "w3c-hr-time": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", + "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", + "dev": true, + "requires": { + "browser-process-hrtime": "^1.0.0" + } + }, + "w3c-xmlserializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz", + "integrity": "sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA==", + "dev": true, + "requires": { + "xml-name-validator": "^3.0.0" + } + }, + "walker": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.7.tgz", + "integrity": "sha1-L3+bj9ENZ3JisYqITijRlhjgKPs=", + "dev": true, + "requires": { + "makeerror": "1.0.x" + } + }, + "webidl-conversions": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz", + "integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==", + "dev": true + }, + "whatwg-encoding": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", + "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", + "dev": true, + "requires": { + "iconv-lite": "0.4.24" + } + }, + "whatwg-mimetype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", + "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==", + "dev": true + }, + "whatwg-url": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.4.0.tgz", + "integrity": "sha512-vwTUFf6V4zhcPkWp/4CQPr1TW9Ml6SF4lVyaIMBdJw5i6qUUJ1QWM4Z6YYVkfka0OUIzVo/0aNtGVGk256IKWw==", + "dev": true, + "requires": { + "lodash.sortby": "^4.7.0", + "tr46": "^2.0.2", + "webidl-conversions": "^6.1.0" + } + }, "which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -1213,292 +5492,56 @@ "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", "dev": true }, - "wide-align": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", - "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", - "dev": true, - "requires": { - "string-width": "^1.0.2 || 2" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, - "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - } - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0" - } - } - } - }, - "workerpool": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.0.2.tgz", - "integrity": "sha512-DSNyvOpFKrNusaaUwk+ej6cBj1bmhLcBfj80elGk+ZIo5JSkq+unB1dLKEOcNfJDZgjGICfhQ0Q5TbP0PvF4+Q==", + "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 }, - "wrap-ansi": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", - "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.0", - "string-width": "^3.0.0", - "strip-ansi": "^5.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - }, - "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" - } - }, - "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 - }, - "emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - } - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - } - } - } - }, "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, + "write-file-atomic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "dev": true, + "requires": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" + } + }, "ws": { "version": "7.4.2", "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.2.tgz", "integrity": "sha512-T4tewALS3+qsrpGI/8dqNMLIVdq/g/85U98HPMa6F0m6xTbvhXU6RCQLqPH3+SlomNV/LdY6RXEbBpMH6EOJnA==" }, + "xml-name-validator": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", + "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", + "dev": true + }, + "xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true + }, "y18n": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.1.tgz", "integrity": "sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ==", "dev": true }, - "yargs": { - "version": "13.3.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", - "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", - "dev": true, - "requires": { - "cliui": "^5.0.0", - "find-up": "^3.0.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^3.0.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^13.1.2" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - }, - "emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", - "dev": true - }, - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "requires": { - "locate-path": "^3.0.0" - } - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, - "requires": { - "p-limit": "^2.0.0" - } - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true - }, - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - } - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - } - } - } - }, - "yargs-parser": { - "version": "13.1.2", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", - "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", - "dev": true, - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } - }, - "yargs-unparser": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", - "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", - "dev": true, - "requires": { - "camelcase": "^6.0.0", - "decamelize": "^4.0.0", - "flat": "^5.0.2", - "is-plain-obj": "^2.1.0" - }, - "dependencies": { - "camelcase": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", - "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", - "dev": true - }, - "decamelize": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", - "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", - "dev": true - } - } - }, - "yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true - }, - "yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "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/package.json b/package.json index 342f5e7..8362778 100644 --- a/package.json +++ b/package.json @@ -17,24 +17,31 @@ "devDependencies": { "@types/glob": "^7.1.3", "@types/inquirer": "^6.5.0", - "@types/mocha": "^8.2.0", + "@types/jest": "^26.0.20", "@types/ms": "^0.7.31", "@types/node": "^14.14.20", "@types/ws": "^7.4.0", - "mocha": "^8.2.1", + "jest": "^26.6.3", + "pre-commit": "^1.2.2", "prettier": "2.1.2", - "ts-node": "^9.1.1", + "ts-jest": "^26.4.4", "tsc-watch": "^4.2.9", "typescript": "^3.9.7" }, "scripts": { - "build": "tsc && npm prune --production", - "start": "node dist/index.js", + "build": "tsc --project tsconfig.prod.json && npm prune --production", + "start": "node .", "once": "tsc && npm start", - "dev": "tsc-watch --onSuccess \"node dist/index.js dev\"", - "test": "mocha --require ts-node/register --extension ts --recursive", - "format": "prettier --write **/*" + "dev": "tsc-watch --onSuccess \"node . dev\"", + "test": "jest", + "format": "prettier --write **/*", + "precommit-message": "echo \"Running pre-commit formatting and testing...\"" }, + "pre-commit": [ + "precommit-message", + "test", + "format" + ], "keywords": [ "discord.js", "bot" diff --git a/src/commands/admin.ts b/src/commands/admin.ts index 47279f9..9f07dc3 100644 --- a/src/commands/admin.ts +++ b/src/commands/admin.ts @@ -148,7 +148,7 @@ export default new Command({ if (typeof evaled !== "string") evaled = require("util").inspect(evaled); channel.send(clean(evaled), {code: "js", split: true}); } catch (err) { - channel.send(`\`ERROR\` \`\`\`js\n${clean(err)}\n\`\`\``); + channel.send(clean(err), {code: "js", split: true}); } } }), diff --git a/src/commands/info.ts b/src/commands/info.ts index d87044f..d2af397 100644 --- a/src/commands/info.ts +++ b/src/commands/info.ts @@ -1,6 +1,4 @@ import {MessageEmbed, version as djsversion} from "discord.js"; -/// @ts-ignore -import {version} from "../../package.json"; import ms from "ms"; import os from "os"; import Command from "../core/command"; @@ -53,7 +51,7 @@ export default new Command({ `**❯ Channels:** ${$.client.channels.cache.size.toLocaleString()}`, `**❯ Creation Date:** ${utc($.client.user?.createdTimestamp).format("Do MMMM YYYY HH:mm:ss")}`, `**❯ Node.JS:** ${process.version}`, - `**❯ Version:** v${version}`, + `**❯ Version:** v${process.env.npm_package_version}`, `**❯ Discord.JS:** ${djsversion}`, "\u200b" ]) @@ -183,10 +181,10 @@ export default new Command({ `**❯ Server Join Date:** ${moment(member.joinedAt).format("LL LTS")}`, `**❯ Hoist Role:** ${member.roles.hoist ? member.roles.hoist.name : "None"}`, `**❯ Roles:** [${roles.length}]: ${ - roles.length < 10 + roles.length < 10 && roles.length > 0 ? roles.join(", ") - : roles.length > 10 - ? this.client.utils.trimArray(roles) + : roles.length >= 10 + ? roles.slice(0, 10) : "None" }` ]); diff --git a/src/core/lib.ts b/src/core/lib.ts index 42852d9..2ba6a91 100644 --- a/src/core/lib.ts +++ b/src/core/lib.ts @@ -47,11 +47,7 @@ export interface CommonLibrary { timeout?: number ) => void; askYesOrNo: (message: Message, senderID: string, timeout?: number) => Promise; - askMultipleChoice: ( - message: Message, - senderID: string, - callbackStack: (() => void)[] | ((choice: number) => void) - ) => void; + askMultipleChoice: (message: Message, senderID: string, callbackStack: (() => void)[], timeout?: number) => void; // Dynamic Properties // args: any[]; diff --git a/test/wrappers.ts b/src/core/wrappers.test.ts similarity index 97% rename from test/wrappers.ts rename to src/core/wrappers.test.ts index 7613ee3..e540c63 100644 --- a/test/wrappers.ts +++ b/src/core/wrappers.test.ts @@ -1,5 +1,5 @@ import {strict as assert} from "assert"; -import {NumberWrapper, StringWrapper, ArrayWrapper} from "../src/core/wrappers"; +import {NumberWrapper, StringWrapper, ArrayWrapper} from "./wrappers"; // I can't figure out a way to run the test suite while running the bot. describe("Wrappers", () => { diff --git a/tsconfig.json b/tsconfig.json index be15e8f..2b1714b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,18 +1,35 @@ +// Reference: https://www.typescriptlang.org/tsconfig { + "include": ["src/**/*"], // This makes it so that the compiler won't compile anything outside of "src". + //"exclude": ["src/**/*.test.ts"], // Exclude .test.ts files since they're for Jest only. "compilerOptions": { - "rootDir": "src", - "outDir": "dist", - "target": "ES6", - "module": "CommonJS", - "moduleResolution": "node", - "esModuleInterop": true, - "noImplicitAny": true, - "noImplicitReturns": true, - "strictNullChecks": true, - "strictFunctionTypes": true, - "strictPropertyInitialization": true, - "removeComments": true, - "sourceMap": true - }, - "exclude": ["test"] + // Project Structure // + "rootDir": "src", // rootDir only affects the STRUCTURE of the folders, not what gets compiled. For extra measure, make sure the structure conforms to "src". + "outDir": "dist", // Likewise, outDir only chooses which folder to compile to. Prevent the compiler from creating JS files right next to source files. + "moduleResolution": "node", // Specify how the compiler resolves modules, like going for node_modules first then searching elsewhere. The official docs just say to use this instead of classic. + + // Type Settings // + "strict": true, // Enables all strict checks possible. + "noImplicitReturns": true, // Makes sure you don't accidentally return something + undefined. + "noFallthroughCasesInSwitch": true, // Prevents accidentally forgetting to break every switch case. Of course, if you know what you're doing, feel free to add a @ts-ignore, which also signals that it's not a mistake. + "forceConsistentCasingInFileNames": true, // Make import paths case-sensitive. "./tEst" is no longer the same as "./test". + "esModuleInterop": true, // Enables compatibility with Node.js' module system since the entire export can be whatever you want. allowSyntheticDefaultImports doesn't address runtime issues and is made redundant by this setting. + "resolveJsonModule": true, // Allows you to import JSON files just like how you can require() them. Do note that if you're accessing any JSON files outside of src, it'll mess up dist. + "lib": ["ES2020"], // Specifies what common libraries you have access to. If you're working in Node.js, you'll want to leave out the DOM library. But do make sure to include "@types/node" because otherwise, variables like "console" won't be defined. + + // Output // + "module": "CommonJS", // Compiles ES6 imports to require() syntax. + "removeComments": false, + "sourceMap": true, // Used for displaying the original source when debugging in webpack. Allows you to set breakpoints directly on TypeScript code for VSCode's debugger. + + // Library Building // + "declaration": false, // Exports declaration files in addition, used for exporting a module. + "declarationMap": false, // Allows the user to go to the source file when hitting a go-to-implementation key like F12 in VSCode for example. + //"declarationDir": "typings", // declarationDir allows you to separate the compiled code from the declaration files, used in conjunction with package.json's "types" property. + + // Web Compatibility // + "target": "ES2020", // ES2017 supports async/await, reducing the amount of compiled code, especially for async-heavy projects. ES2020 is from the Node 14 base (https://github.com/tsconfig/bases/blob/master/bases/node14.json) + "downlevelIteration": false, // This flag adds extra support when targeting ES3, but adds extra bloat otherwise. + "importHelpers": false // Reduce the amount of bloat that comes from downlevelIteration (when polyfills are redeclared). + } } diff --git a/tsconfig.prod.json b/tsconfig.prod.json new file mode 100644 index 0000000..5e86c14 --- /dev/null +++ b/tsconfig.prod.json @@ -0,0 +1,8 @@ +{ + "extends": "./tsconfig.json", + "exclude": ["src/**/*.test.ts", "src/**/template.ts"], + "compilerOptions": { + "removeComments": true, + "sourceMap": false + } +} From 38eb0906eeec80c2405cc0736195e149da0bccee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=83=95=E3=82=BA=E3=82=AD?= Date: Tue, 26 Jan 2021 16:28:04 +0100 Subject: [PATCH 088/178] eco/bet: Clean up comments and add feedback --- src/commands/fun/subcommands/eco-bet.ts | 135 +++++++++++++++--------- 1 file changed, 88 insertions(+), 47 deletions(-) diff --git a/src/commands/fun/subcommands/eco-bet.ts b/src/commands/fun/subcommands/eco-bet.ts index 5046196..5329b49 100644 --- a/src/commands/fun/subcommands/eco-bet.ts +++ b/src/commands/fun/subcommands/eco-bet.ts @@ -8,11 +8,46 @@ export const BetCommand = new Command({ usage: " ", run: "Who are you betting with?", user: new Command({ - run: "How much are you betting?", + // handles missing amount argument + async run({args, author, channel, guild}): Promise { + if (isAuthorized(guild, channel)) { + const target = args[0]; + + // handle invalid target + if (target.id == author.id) + return channel.send("You can't bet Mons with yourself!"); + else if (target.bot && process.argv[2] !== "dev") + return channel.send("You can't bet Mons with a bot!"); + + return channel.send("How much are you betting?"); + } + }, number: new Command({ - run: "How long until the bet ends?", + // handles missing duration argument + async run({args, author, channel, guild}): Promise { + if (isAuthorized(guild, channel)) { + const sender = Storage.getUser(author.id); + const target = args[0]; + const receiver = Storage.getUser(target); + const amount = Math.floor(args[1]); + + // handle invalid target + if (target.id == author.id) + return channel.send("You can't bet Mons with yourself!"); + else if (target.bot && process.argv[2] !== "dev") + return channel.send("You can't bet Mons with a bot!"); + + // handle invalid amount + if (amount <= 0) + return channel.send("You must bet at least one Mon!"); + else if (sender.money < amount) + return channel.send("You don't have enough Mons for that.", getMoneyEmbed(author)); + + return channel.send("How long until the bet ends?"); + } + }, any: new Command({ - async run({ client, args, author, channel, guild}): Promise { + async run({client, args, author, channel, guild}): Promise { if (isAuthorized(guild, channel)) { const sender = Storage.getUser(author.id); const target = args[0]; @@ -20,13 +55,19 @@ export const BetCommand = new Command({ const amount = Math.floor(args[1]); const duration = parseDuration(args[2].trim()); - if (amount <= 0) return channel.send("You must bet at least one Mon!"); - else if (sender.money < amount) - return channel.send("You don't have enough Mons for that.", getMoneyEmbed(author)); - else if (target.id == author.id) return channel.send("You can't bet Mons with yourself!"); + // handle invalid target + if (target.id == author.id) + return channel.send("You can't bet Mons with yourself!"); else if (target.bot && process.argv[2] !== "dev") return channel.send("You can't bet Mons with a bot!"); + // handle invalid amount + if (amount <= 0) + return channel.send("You must bet at least one Mon!"); + else if (sender.money < amount) + return channel.send("You don't have enough Mons for that.", getMoneyEmbed(author)); + + // handle invalid duration if (duration <= 0) return channel.send("Invalid duration"); // else if (duration <= {threshold}) @@ -34,72 +75,71 @@ export const BetCommand = new Command({ // else if (duration >= {threshold}) // return channel.send("Too long idk"); - // SEND MESSAGE WITH 2 REACTIONS (OK / NO) + // [Potential pertinence to use the ask later on?] + // Ask target whether or not they want to take the bet. const msg = await channel.send(`<@${target.id}>, do you want to take this bet of ${$(amount).pluralise("Mon", "s")}`); await msg.react("✅"); await msg.react("⛔"); - // SET UP ACCEPT TIMEOUT - // ON REACTION CHANGE, CHECK IF NEW REACTION IS FROM TARGET - await msg.awaitReactions( + // Wait for a reaction. + msg.awaitReactions( async (reaction, user) => { - // IF OK + // If target accepts: set bet up. if (user.id === target.id && reaction.emoji.name === "✅") { - // REMOVE AMOUNT FROM AUTHOR + // [ISSUE: volatile storage] + // Remove amount money from both parts to avoid duplication of money. sender.money -= amount; - // REMOVE AMOUNT FROM TARGET receiver.money -= amount; - // SET BET POOL AS AMOUNT*2 - // => BET POOL ALWAYS EVEN - const pool = amount * 2; - // SET UP BET TIMEOUT FROM DURATION + // Notify both users. + await channel.send(`<@${target.id}> has taken <@${author.id}>'s bet, the bet amount of ${$(amount).pluralise("Mon", "s")} has been deducted from each of them.`); + + // Wait for the duration of the bet. client.setTimeout(async () => { - // ON BET TIMEOUT - // GIVE VOTE WITH 2 REACTIONS (OK / NO) - const voteMsg = await channel.send(`VOTE: do you think that <@${target.id} has won the bet?`); + // [Pertinence to reference the invocation message to let people find the bet more easily] + // When bet is over, give a vote to ask people their thoughts. + const voteMsg = await channel.send(`VOTE: do you think that <@${target.id}> has won the bet?`); await voteMsg.react("✅"); await voteMsg.react("⛔"); - // SET UP VOTE TIMEOUT + // Filter reactions to only collect the pertinent ones. voteMsg.awaitReactions( (reaction, user) => { return ["✅", "⛔"].includes(reaction.emoji.name); }, - // waiting for a day for now, might need to make configurable - { time: 8640000 } + // [Pertinence to make configurable on the fly.] + { time: parseDuration("2m") } ).then(reactions => { - // ON VOTE TIMEOUT - // COUNT OK VOTES - const ok = 0; - // COUNT NO VOTES - const no = 0; - // IF OK > NO - // GIVE TARGET THE BET POOL - if (ok > no) receiver.money += pool; - // ELSE IF OK < NO - // GIVE AUTHOR BET POOL - else if (ok < no) sender.money += pool; - // ELSE - // GIVE TARGET BET POOL / 2 - // GIVE AUTHOR BET POOL / 2 - // => BET EFFECT CANCELLED + // Count votes + const ok = reactions.filter(reaction => reaction.emoji.name === "✅").size; + const no = reactions.filter(reaction => reaction.emoji.name === "⛔").size; + + if (ok > no) { + receiver.money += amount * 2; + channel.send(`By the people's votes, <@${target.id}> has won the bet that <@${author.id}> had sent them.`); + } + else if (ok < no) { + sender.money += amount * 2; + channel.send(`By the people's votes, <@${target.id}> has lost the bet that <@${author.id}> had sent them.`); + } else { sender.money += amount; receiver.money += amount; + channel.send(`By the people's votes, <@${target.id}> couldn't be determined to have won or lost the bet that <@${author.id}> had sent them.`); } }); }, duration); } - // IF NO - // DROP + // If target refuses: notify and stop. + else if (user.id === target.id && reaction.emoji.name === "⛔") + await channel.send(`<@${target.id}> has rejected your bet, <@${author.id}>`); + return false; }, - // waiting for a minute + // [Lesser pertinence to make configurable on the fly.] + // Wait for a minute, and delete the asking message after that. { time: 60000 } - ); - // ON ACCEPT TIMEOUT - // DROP + ).then(() => msg.delete()); } } }) @@ -111,9 +151,10 @@ export const BetCommand = new Command({ /** * Parses a duration string into milliseconds * Examples: - * - 3d -> 3 days -> 259200000ms + * - 3d -> 3 days -> 259200000ms + * - 2h -> 2 hours -> 7200000ms + * - 7m -> 7 minutes -> 420000ms * - 3s -> 3 seconds -> 3000ms - * - 2h -> 2 hours -> 7200000ms */ function parseDuration(duration: string): number { // extract last char as unit From 30697e502025098c518977674b61d4566fa11c0e Mon Sep 17 00:00:00 2001 From: WatDuhHekBro <44940783+WatDuhHekBro@users.noreply.github.com> Date: Tue, 26 Jan 2021 09:57:05 -0600 Subject: [PATCH 089/178] First attempt at getting husky to work --- .husky/.gitignore | 1 + .husky/pre-commit | 6 ++ .prettierignore | 1 + package-lock.json | 162 ++-------------------------------------------- package.json | 10 +-- 5 files changed, 16 insertions(+), 164 deletions(-) create mode 100644 .husky/.gitignore create mode 100644 .husky/pre-commit diff --git a/.husky/.gitignore b/.husky/.gitignore new file mode 100644 index 0000000..31354ec --- /dev/null +++ b/.husky/.gitignore @@ -0,0 +1 @@ +_ diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100644 index 0000000..07eda3c --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,6 @@ +#!/bin/sh +. "$(dirname $0)/_/husky.sh" + +npm test +npm run format +git add -A diff --git a/.prettierignore b/.prettierignore index 960cb07..40343cc 100644 --- a/.prettierignore +++ b/.prettierignore @@ -2,6 +2,7 @@ .dockerignore .gitignore .prettierignore +.husky/ Dockerfile LICENSE diff --git a/package-lock.json b/package-lock.json index dcccab1..a12762e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1419,18 +1419,6 @@ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, - "concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" - } - }, "convert-source-map": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", @@ -2306,6 +2294,12 @@ "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", "dev": true }, + "husky": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/husky/-/husky-5.0.6.tgz", + "integrity": "sha512-SM+evfvcHT3rAYJKvPlatz3L5RqzgeM6xIvDjhs8VuhKj6iKqFDOt/Ov8sPjvWuE4FDB385gJBwWXRj7G3c1hg==", + "dev": true + }, "iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -3812,12 +3806,6 @@ "resolved": "https://registry.npmjs.org/os/-/os-0.1.1.tgz", "integrity": "sha1-IIhF6J4ZOtTZcUdLk5R3NqVtE/M=" }, - "os-shim": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/os-shim/-/os-shim-0.1.3.tgz", - "integrity": "sha1-a2LDeRz3kJ6jXtRuF2WLtBfLORc=", - "dev": true - }, "os-tmpdir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", @@ -3972,70 +3960,6 @@ "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", "dev": true }, - "pre-commit": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/pre-commit/-/pre-commit-1.2.2.tgz", - "integrity": "sha1-287g7p3nI15X95xW186UZBpp7sY=", - "dev": true, - "requires": { - "cross-spawn": "^5.0.1", - "spawn-sync": "^1.0.15", - "which": "1.2.x" - }, - "dependencies": { - "cross-spawn": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", - "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", - "dev": true, - "requires": { - "lru-cache": "^4.0.1", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, - "lru-cache": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", - "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", - "dev": true, - "requires": { - "pseudomap": "^1.0.2", - "yallist": "^2.1.2" - } - }, - "shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", - "dev": true, - "requires": { - "shebang-regex": "^1.0.0" - } - }, - "shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", - "dev": true - }, - "which": { - "version": "1.2.14", - "resolved": "https://registry.npmjs.org/which/-/which-1.2.14.tgz", - "integrity": "sha1-mofEN48D6CfOyvGs31bHNsAcFOU=", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - }, - "yallist": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", - "dev": true - } - } - }, "prelude-ls": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", @@ -4065,12 +3989,6 @@ "resolved": "https://registry.npmjs.org/prism-media/-/prism-media-1.2.3.tgz", "integrity": "sha512-fSrR66n0l6roW9Rx4rSLMyTPTjRTiXy5RVqDOurACQ6si1rKHHKDU5gwBJoCsIV0R3o9gi+K50akl/qyw1C74A==" }, - "process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true - }, "prompts": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.0.tgz", @@ -4090,12 +4008,6 @@ "event-stream": "=3.3.4" } }, - "pseudomap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", - "dev": true - }, "psl": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", @@ -4206,29 +4118,6 @@ } } }, - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - }, - "dependencies": { - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - } - } - }, "regex-not": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", @@ -4833,16 +4722,6 @@ "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", "dev": true }, - "spawn-sync": { - "version": "1.0.15", - "resolved": "https://registry.npmjs.org/spawn-sync/-/spawn-sync-1.0.15.tgz", - "integrity": "sha1-sAeZVX63+wyDdsKdROih6mfldHY=", - "dev": true, - "requires": { - "concat-stream": "^1.4.7", - "os-shim": "^0.1.2" - } - }, "spdx-correct": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", @@ -5003,23 +4882,6 @@ "strip-ansi": "^6.0.0" } }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - }, - "dependencies": { - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - } - } - }, "strip-ansi": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", @@ -5271,12 +5133,6 @@ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.11.0.tgz", "integrity": "sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==" }, - "typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", - "dev": true - }, "typedarray-to-buffer": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", @@ -5365,12 +5221,6 @@ "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", "dev": true }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "dev": true - }, "uuid": { "version": "8.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", diff --git a/package.json b/package.json index 8362778..ebbc126 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,6 @@ "version": "0.0.1", "description": "A Discord bot built on Discord.JS v12", "main": "dist/index.js", - "private": true, "dependencies": { "chalk": "^4.1.0", "discord.js": "^12.5.1", @@ -21,8 +20,8 @@ "@types/ms": "^0.7.31", "@types/node": "^14.14.20", "@types/ws": "^7.4.0", + "husky": "^5.0.6", "jest": "^26.6.3", - "pre-commit": "^1.2.2", "prettier": "2.1.2", "ts-jest": "^26.4.4", "tsc-watch": "^4.2.9", @@ -35,13 +34,8 @@ "dev": "tsc-watch --onSuccess \"node . dev\"", "test": "jest", "format": "prettier --write **/*", - "precommit-message": "echo \"Running pre-commit formatting and testing...\"" + "postinstall": "husky install" }, - "pre-commit": [ - "precommit-message", - "test", - "format" - ], "keywords": [ "discord.js", "bot" From 499aea9e66479f6ada569e8c34251daaff0a8ae4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=83=95=E3=82=BA=E3=82=AD?= Date: Tue, 26 Jan 2021 18:11:50 +0100 Subject: [PATCH 090/178] eco-bet: save storage modification --- src/commands/fun/subcommands/eco-bet.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/commands/fun/subcommands/eco-bet.ts b/src/commands/fun/subcommands/eco-bet.ts index 5329b49..12f0191 100644 --- a/src/commands/fun/subcommands/eco-bet.ts +++ b/src/commands/fun/subcommands/eco-bet.ts @@ -90,6 +90,7 @@ export const BetCommand = new Command({ // Remove amount money from both parts to avoid duplication of money. sender.money -= amount; receiver.money -= amount; + Storage.save(); // Notify both users. await channel.send(`<@${target.id}> has taken <@${author.id}>'s bet, the bet amount of ${$(amount).pluralise("Mon", "s")} has been deducted from each of them.`); @@ -127,6 +128,7 @@ export const BetCommand = new Command({ receiver.money += amount; channel.send(`By the people's votes, <@${target.id}> couldn't be determined to have won or lost the bet that <@${author.id}> had sent them.`); } + Storage.save(); }); }, duration); } From 055a57e928630897afc15ab36f5c83e94f4a653a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=83=95=E3=82=BA=E3=82=AD?= Date: Tue, 26 Jan 2021 19:37:31 +0100 Subject: [PATCH 091/178] eco-bet: use askYesOrNo to clean up --- src/commands/fun/subcommands/eco-bet.ts | 116 +++++++++++------------- 1 file changed, 52 insertions(+), 64 deletions(-) diff --git a/src/commands/fun/subcommands/eco-bet.ts b/src/commands/fun/subcommands/eco-bet.ts index 12f0191..492da12 100644 --- a/src/commands/fun/subcommands/eco-bet.ts +++ b/src/commands/fun/subcommands/eco-bet.ts @@ -75,73 +75,61 @@ export const BetCommand = new Command({ // else if (duration >= {threshold}) // return channel.send("Too long idk"); - // [Potential pertinence to use the ask later on?] // Ask target whether or not they want to take the bet. - const msg = await channel.send(`<@${target.id}>, do you want to take this bet of ${$(amount).pluralise("Mon", "s")}`); - await msg.react("✅"); - await msg.react("⛔"); + const takeBet = await askYesOrNo( + await channel.send(`<@${target.id}>, do you want to take this bet of ${$(amount).pluralise("Mon", "s")}`), + target.id + ); - // Wait for a reaction. - msg.awaitReactions( - async (reaction, user) => { - // If target accepts: set bet up. - if (user.id === target.id && reaction.emoji.name === "✅") { - // [ISSUE: volatile storage] - // Remove amount money from both parts to avoid duplication of money. - sender.money -= amount; - receiver.money -= amount; + if (takeBet) { + // [ISSUE: volatile storage] + // Remove amount money from both parts to avoid duplication of money. + sender.money -= amount; + receiver.money -= amount; + Storage.save(); + + // Notify both users. + await channel.send(`<@${target.id}> has taken <@${author.id}>'s bet, the bet amount of ${$(amount).pluralise("Mon", "s")} has been deducted from each of them.`); + + // Wait for the duration of the bet. + client.setTimeout(async () => { + // [Pertinence to reference the invocation message to let people find the bet more easily] + // When bet is over, give a vote to ask people their thoughts. + const voteMsg = await channel.send(`VOTE: do you think that <@${target.id}> has won the bet?`); + await voteMsg.react("✅"); + await voteMsg.react("❌"); + + // Filter reactions to only collect the pertinent ones. + voteMsg.awaitReactions( + (reaction, user) => { + return ["✅", "❌"].includes(reaction.emoji.name); + }, + // [Pertinence to make configurable on the fly.] + { time: parseDuration("2m") } + ).then(reactions => { + // Count votes + const ok = reactions.filter(reaction => reaction.emoji.name === "✅").size; + const no = reactions.filter(reaction => reaction.emoji.name === "❌").size; + + if (ok > no) { + receiver.money += amount * 2; + channel.send(`By the people's votes, <@${target.id}> has won the bet that <@${author.id}> had sent them.`); + } + else if (ok < no) { + sender.money += amount * 2; + channel.send(`By the people's votes, <@${target.id}> has lost the bet that <@${author.id}> had sent them.`); + } + else { + sender.money += amount; + receiver.money += amount; + channel.send(`By the people's votes, <@${target.id}> couldn't be determined to have won or lost the bet that <@${author.id}> had sent them.`); + } Storage.save(); - - // Notify both users. - await channel.send(`<@${target.id}> has taken <@${author.id}>'s bet, the bet amount of ${$(amount).pluralise("Mon", "s")} has been deducted from each of them.`); - - // Wait for the duration of the bet. - client.setTimeout(async () => { - // [Pertinence to reference the invocation message to let people find the bet more easily] - // When bet is over, give a vote to ask people their thoughts. - const voteMsg = await channel.send(`VOTE: do you think that <@${target.id}> has won the bet?`); - await voteMsg.react("✅"); - await voteMsg.react("⛔"); - - // Filter reactions to only collect the pertinent ones. - voteMsg.awaitReactions( - (reaction, user) => { - return ["✅", "⛔"].includes(reaction.emoji.name); - }, - // [Pertinence to make configurable on the fly.] - { time: parseDuration("2m") } - ).then(reactions => { - // Count votes - const ok = reactions.filter(reaction => reaction.emoji.name === "✅").size; - const no = reactions.filter(reaction => reaction.emoji.name === "⛔").size; - - if (ok > no) { - receiver.money += amount * 2; - channel.send(`By the people's votes, <@${target.id}> has won the bet that <@${author.id}> had sent them.`); - } - else if (ok < no) { - sender.money += amount * 2; - channel.send(`By the people's votes, <@${target.id}> has lost the bet that <@${author.id}> had sent them.`); - } - else { - sender.money += amount; - receiver.money += amount; - channel.send(`By the people's votes, <@${target.id}> couldn't be determined to have won or lost the bet that <@${author.id}> had sent them.`); - } - Storage.save(); - }); - }, duration); - } - // If target refuses: notify and stop. - else if (user.id === target.id && reaction.emoji.name === "⛔") - await channel.send(`<@${target.id}> has rejected your bet, <@${author.id}>`); - - return false; - }, - // [Lesser pertinence to make configurable on the fly.] - // Wait for a minute, and delete the asking message after that. - { time: 60000 } - ).then(() => msg.delete()); + }); + }, duration); + } + else + await channel.send(`<@${target.id}> has rejected your bet, <@${author.id}>`); } } }) From baf17e6c7691f2058ee6efab3566243309b1ed2d Mon Sep 17 00:00:00 2001 From: Keanu Date: Fri, 29 Jan 2021 00:57:50 +0100 Subject: [PATCH 092/178] Corrected usage of trimarray in info. --- src/commands/info.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands/info.ts b/src/commands/info.ts index d87044f..e57e242 100644 --- a/src/commands/info.ts +++ b/src/commands/info.ts @@ -186,7 +186,7 @@ export default new Command({ roles.length < 10 ? roles.join(", ") : roles.length > 10 - ? this.client.utils.trimArray(roles) + ? trimArray(roles) : "None" }` ]); From 74d36e36b2ad1585ebb26059567086e8738c57a2 Mon Sep 17 00:00:00 2001 From: Keanu Date: Fri, 29 Jan 2021 01:15:42 +0100 Subject: [PATCH 093/178] Improved logic of role display on info. Co-authored-by: Mijyuoon --- src/commands/info.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/commands/info.ts b/src/commands/info.ts index e57e242..1a6b401 100644 --- a/src/commands/info.ts +++ b/src/commands/info.ts @@ -183,11 +183,10 @@ export default new Command({ `**❯ Server Join Date:** ${moment(member.joinedAt).format("LL LTS")}`, `**❯ Hoist Role:** ${member.roles.hoist ? member.roles.hoist.name : "None"}`, `**❯ Roles:** [${roles.length}]: ${ - roles.length < 10 - ? roles.join(", ") - : roles.length > 10 - ? trimArray(roles) - : "None" + roles.length == 0 ? "None" + : roles.length <= 10 + ? roles.join(", ") + : trimArray(roles).join(", ") }` ]); $.channel.send(embed); From a21ed7a97f909220ba448b0bbb8ec0da9672415a Mon Sep 17 00:00:00 2001 From: Keanu Date: Fri, 29 Jan 2021 14:34:46 +0100 Subject: [PATCH 094/178] Fixed info using `heapTotal` for both values. --- src/commands/info.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands/info.ts b/src/commands/info.ts index 1a6b401..a85a2e5 100644 --- a/src/commands/info.ts +++ b/src/commands/info.ts @@ -68,7 +68,7 @@ export default new Command({ `\u3000 • Speed: ${core.speed}MHz`, `**❯ Memory:**`, `\u3000 • Total: ${formatBytes(process.memoryUsage().heapTotal)}`, - `\u3000 • Used: ${formatBytes(process.memoryUsage().heapTotal)}` + `\u3000 • Used: ${formatBytes(process.memoryUsage().heapUsed)}` ]) .setTimestamp(); $.channel.send(embed); From 417bfc8a18745efdd9e2890cc944c02a50a244a1 Mon Sep 17 00:00:00 2001 From: Dmytro Meleshko Date: Fri, 29 Jan 2021 18:21:06 +0200 Subject: [PATCH 095/178] get rid of the remaining calls to BaseManager#resolve to make all cache access explicit --- src/commands/info.ts | 2 +- src/commands/utilities/desc.ts | 11 +++++------ src/core/lib.ts | 2 +- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/commands/info.ts b/src/commands/info.ts index a85a2e5..2de35d0 100644 --- a/src/commands/info.ts +++ b/src/commands/info.ts @@ -145,7 +145,7 @@ export default new Command({ description: "Displays info about mentioned user.", async run($: CommonLibrary): Promise { // Transforms the User object into a GuildMember object of the current guild. - const member = $.guild?.members.resolve($.args[0]) ?? (await $.guild?.members.fetch($.args[0])); + const member = await $.guild?.members.fetch($.args[0]); if (!member) return $.channel.send( diff --git a/src/commands/utilities/desc.ts b/src/commands/utilities/desc.ts index 467053c..c5e75f3 100644 --- a/src/commands/utilities/desc.ts +++ b/src/commands/utilities/desc.ts @@ -9,15 +9,14 @@ export default new Command({ if (!voiceChannel) return $.channel.send("You are not in a voice channel."); - if (!$.guild?.me?.hasPermission("MANAGE_CHANNELS")) + if (!voiceChannel.guild.me?.hasPermission("MANAGE_CHANNELS")) return $.channel.send("I am lacking the required permissions to perform this action."); if ($.args.length === 0) return $.channel.send("Please provide a new voice channel name."); - const changeVC = $.guild.channels.resolve(voiceChannel.id); - $.channel - .send(`Changed channel name from "${voiceChannel}" to "${$.args.join(" ")}".`) - /// @ts-ignore - .then(changeVC?.setName($.args.join(" "))); + const prevName = voiceChannel.name; + const newName = $.args.join(" "); + await voiceChannel.setName(newName); + await $.channel.send(`Changed channel name from "${prevName}" to "${newName}".`); } }); diff --git a/src/core/lib.ts b/src/core/lib.ts index 42852d9..73231b4 100644 --- a/src/core/lib.ts +++ b/src/core/lib.ts @@ -172,7 +172,7 @@ export function formatUTCTimestamp(now = new Date()) { } export function botHasPermission(guild: Guild | null, permission: number): boolean { - return !!(client.user && guild?.members.resolve(client.user)?.hasPermission(permission)); + return !!(guild?.me?.hasPermission(permission)); } export function updateGlobalEmoteRegistry(): void { From 593efb3602d5abcf201bf59b039f333b1f3f13ff Mon Sep 17 00:00:00 2001 From: Dmytro Meleshko Date: Fri, 29 Jan 2021 19:07:39 +0200 Subject: [PATCH 096/178] raise the ES target level and enable sourcemaps in tsconfig.json --- src/core/command.ts | 18 +++++++++++------- tsconfig.json | 5 +++-- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/core/command.ts b/src/core/command.ts index 7ba0f02..b5fa826 100644 --- a/src/core/command.ts +++ b/src/core/command.ts @@ -182,12 +182,16 @@ export async function loadCommands(): Promise> { if (cmd.isDirectory()) { if (cmd.name === "subcommands") continue; else $.warn(`You can't have multiple levels of directories! From: "dist/commands/${cmd.name}"`); - } else loadCommand(cmd.name, list, selected.name); + } else if (cmd.name.endsWith(".js")) { + loadCommand(cmd.name, list, selected.name); + } } subdir.close(); categories.set(category, list); - } else loadCommand(selected.name, listMisc); + } else if (selected.name.endsWith(".js")) { + loadCommand(selected.name, listMisc); + } } dir.close(); @@ -236,7 +240,7 @@ export default new Command({ permission: null, aliases: [], async run($: CommonLibrary): Promise { - + }, subcommands: { layer: new Command({ @@ -246,7 +250,7 @@ export default new Command({ permission: null, aliases: [], async run($: CommonLibrary): Promise { - + } }) }, @@ -256,7 +260,7 @@ export default new Command({ usage: '', permission: null, async run($: CommonLibrary): Promise { - + } }), number: new Command({ @@ -265,7 +269,7 @@ export default new Command({ usage: '', permission: null, async run($: CommonLibrary): Promise { - + } }), any: new Command({ @@ -274,7 +278,7 @@ export default new Command({ usage: '', permission: null, async run($: CommonLibrary): Promise { - + } }) });`; diff --git a/tsconfig.json b/tsconfig.json index ba4dd11..34245ef 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,7 +2,7 @@ "compilerOptions": { "rootDir": "src", "outDir": "dist", - "target": "ES6", + "target": "es2019", "module": "CommonJS", "moduleResolution": "node", "esModuleInterop": true, @@ -11,7 +11,8 @@ "strictNullChecks": true, "strictFunctionTypes": true, "strictPropertyInitialization": true, - "removeComments": true + "removeComments": true, + "sourceMap": true }, "exclude": ["test"] } From 0cba164f3d33b646ea118c684969a0d6c2b086ff Mon Sep 17 00:00:00 2001 From: Dmytro Meleshko Date: Fri, 29 Jan 2021 19:15:18 +0200 Subject: [PATCH 097/178] get rid of @ts-ignore or something --- src/commands/admin.ts | 9 +++++---- src/commands/info.ts | 24 +++++++++--------------- src/core/lib.ts | 1 - 3 files changed, 14 insertions(+), 20 deletions(-) diff --git a/src/commands/admin.ts b/src/commands/admin.ts index 47279f9..35c3ff2 100644 --- a/src/commands/admin.ts +++ b/src/commands/admin.ts @@ -124,14 +124,15 @@ export default new Command({ number: new Command({ description: "Amount of messages to delete.", async run($: CommonLibrary): Promise { + if ($.channel.type === "dm") { + await $.channel.send("Can't clear messages in the DMs!"); + return; + } $.message.delete(); const fetched = await $.channel.messages.fetch({ limit: $.args[0] }); - $.channel - /// @ts-ignore - .bulkDelete(fetched) - .catch((error: any) => $.channel.send(`Error: ${error}`)); + await $.channel.bulkDelete(fetched); } }) }), diff --git a/src/commands/info.ts b/src/commands/info.ts index 2de35d0..56b5277 100644 --- a/src/commands/info.ts +++ b/src/commands/info.ts @@ -1,6 +1,4 @@ import {MessageEmbed, version as djsversion} from "discord.js"; -/// @ts-ignore -import {version} from "../../package.json"; import ms from "ms"; import os from "os"; import Command from "../core/command"; @@ -9,6 +7,8 @@ import {verificationLevels, filterLevels, regions, flags} from "../defs/info"; import moment from "moment"; import utc from "moment"; +const {version} = require("../../package.json"); + export default new Command({ description: "Command to provide all sorts of info about the current server, a user, etc.", run: "Please provide an argument.\nFor help, run `%prefix%help info`.", @@ -36,13 +36,6 @@ export default new Command({ async run($: CommonLibrary): Promise { const core = os.cpus()[0]; const embed = new MessageEmbed() - .setThumbnail( - /// @ts-ignore - $.client.user?.displayAvatarURL({ - dynamic: true, - size: 2048 - }) - ) .setColor($.guild?.me?.displayHexColor || "BLUE") .addField("General", [ `**❯ Client:** ${$.client.user?.tag} (${$.client.user?.id})`, @@ -71,6 +64,11 @@ export default new Command({ `\u3000 • Used: ${formatBytes(process.memoryUsage().heapUsed)}` ]) .setTimestamp(); + const avatarURL = $.client.user?.displayAvatarURL({ + dynamic: true, + size: 2048 + }); + if (avatarURL) embed.setThumbnail(avatarURL); $.channel.send(embed); } }), @@ -156,8 +154,7 @@ export default new Command({ .sort((a: {position: number}, b: {position: number}) => b.position - a.position) .map((role: {toString: () => any}) => role.toString()) .slice(0, -1); - // @ts-ignore - Discord.js' typings seem to be outdated here. According to their v12 docs, it's User.fetchFlags() instead of User.flags. - const userFlags = ((await member.user.fetchFlags()) as UserFlags).toArray(); + const userFlags = (await member.user.fetchFlags()).toArray(); const embed = new MessageEmbed() .setThumbnail(member.user.displayAvatarURL({dynamic: true, size: 512})) @@ -183,10 +180,7 @@ export default new Command({ `**❯ Server Join Date:** ${moment(member.joinedAt).format("LL LTS")}`, `**❯ Hoist Role:** ${member.roles.hoist ? member.roles.hoist.name : "None"}`, `**❯ Roles:** [${roles.length}]: ${ - roles.length == 0 ? "None" - : roles.length <= 10 - ? roles.join(", ") - : trimArray(roles).join(", ") + roles.length == 0 ? "None" : roles.length <= 10 ? roles.join(", ") : trimArray(roles).join(", ") }` ]); $.channel.send(embed); diff --git a/src/core/lib.ts b/src/core/lib.ts index 73231b4..0f5f193 100644 --- a/src/core/lib.ts +++ b/src/core/lib.ts @@ -554,7 +554,6 @@ export abstract class GenericStructure { public save(asynchronous = true) { const tag = this.__meta__; - /// @ts-ignore delete this.__meta__; FileManager.write(tag, this, asynchronous); this.__meta__ = tag; From 303e81cc3708c2e65c800d94f78d1a1a86ee1a06 Mon Sep 17 00:00:00 2001 From: Dmytro Meleshko Date: Fri, 29 Jan 2021 21:12:53 +0200 Subject: [PATCH 098/178] reduce the amount of cache lookups --- src/commands/admin.ts | 3 +-- src/commands/info.ts | 5 ++++- src/core/lib.ts | 16 +++++++++------- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/commands/admin.ts b/src/commands/admin.ts index 35c3ff2..f174750 100644 --- a/src/commands/admin.ts +++ b/src/commands/admin.ts @@ -158,8 +158,7 @@ export default new Command({ permission: Command.PERMISSIONS.BOT_SUPPORT, async run($: CommonLibrary): Promise { const nickName = $.args.join(" "); - const trav = $.guild?.members.cache.find((member) => member.id === $.client.user?.id); - await trav?.setNickname(nickName); + await $.guild?.me?.setNickname(nickName); if (botHasPermission($.guild, Permissions.FLAGS.MANAGE_MESSAGES)) $.message.delete({timeout: 5000}).catch($.handler.bind($)); $.channel.send(`Nickname set to \`${nickName}\``).then((m) => m.delete({timeout: 5000})); diff --git a/src/commands/info.ts b/src/commands/info.ts index 56b5277..9b7b71a 100644 --- a/src/commands/info.ts +++ b/src/commands/info.ts @@ -76,10 +76,13 @@ export default new Command({ description: "Displays info about the current guild.", async run($: CommonLibrary): Promise { if ($.guild) { + const members = await $.guild.members.fetch({ + withPresences: true, + force: true + }); const roles = $.guild.roles.cache .sort((a, b) => b.position - a.position) .map((role) => role.toString()); - const members = $.guild.members.cache; const channels = $.guild.channels.cache; const emojis = $.guild.emojis.cache; const iconURL = $.guild.iconURL({dynamic: true}); diff --git a/src/core/lib.ts b/src/core/lib.ts index 0f5f193..4cadf4d 100644 --- a/src/core/lib.ts +++ b/src/core/lib.ts @@ -172,7 +172,7 @@ export function formatUTCTimestamp(now = new Date()) { } export function botHasPermission(guild: Guild | null, permission: number): boolean { - return !!(guild?.me?.hasPermission(permission)); + return !!guild?.me?.hasPermission(permission); } export function updateGlobalEmoteRegistry(): void { @@ -216,20 +216,22 @@ $.paginate = async ( callback(page); }; + const BACKWARDS_EMOJI = "⬅️"; + const FORWARDS_EMOJI = "➡️"; const handle = (emote: string, reacterID: string) => { switch (emote) { - case "⬅️": + case BACKWARDS_EMOJI: turn(-1); break; - case "➡️": + case FORWARDS_EMOJI: turn(1); break; } }; // Listen for reactions and call the handler. - await message.react("⬅️"); - await message.react("➡️"); + let backwardsReaction = await message.react(BACKWARDS_EMOJI); + let forwardsReaction = await message.react(FORWARDS_EMOJI); eventListeners.set(message.id, handle); await message.awaitReactions( (reaction, user) => { @@ -248,8 +250,8 @@ $.paginate = async ( ); // When time's up, remove the bot's own reactions. eventListeners.delete(message.id); - message.reactions.cache.get("⬅️")?.users.remove(message.author); - message.reactions.cache.get("➡️")?.users.remove(message.author); + backwardsReaction.users.remove(message.author); + forwardsReaction.users.remove(message.author); }; // Waits for the sender to either confirm an action or let it pass (and delete the message). From e22250b3f159e444f175c7e548bd50c31e4c04fe Mon Sep 17 00:00:00 2001 From: Dmytro Meleshko Date: Fri, 29 Jan 2021 21:14:31 +0200 Subject: [PATCH 099/178] introduce the terrible hack for reducing memory usage --- src/index.ts | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/src/index.ts b/src/index.ts index 9721784..fa72c21 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,4 @@ -import {Client} from "discord.js"; +import * as discord from "discord.js"; import setup from "./setup"; import {Config} from "./core/structures"; import {loadCommands} from "./core/command"; @@ -6,9 +6,37 @@ import {loadEvents} from "./core/event"; import "discord.js-lavalink-lib"; import LavalinkMusic from "discord.js-lavalink-lib"; +declare module "discord.js" { + interface Presence { + patch(data: any): void; + } +} + +// The terrible hacks were written by none other than The Noble Programmer On The White PC. + +// NOTE: Terrible hack ahead!!! In order to reduce the memory usage of the bot +// we only store the information from presences that we actually end up using, +// which currently is only the (online/idle/dnd/offline/...) status (see +// `src/commands/info.ts`). What data is retrieved from the `data` object +// (which contains the data received from the Gateway) and how can be seen +// here: +// . +const oldPresencePatch = discord.Presence.prototype.patch; +discord.Presence.prototype.patch = function patch(data: any) { + oldPresencePatch.call(this, {status: data.status}); +}; + // This is here in order to make it much less of a headache to access the client from other files. // This of course won't actually do anything until the setup process is complete and it logs in. -export const client = new Client(); +export const client = new discord.Client(); + +// NOTE: Terrible hack continued!!! Unfortunately we can't receive the presence +// data at all when the GUILD_PRESENCES intent is disabled, so while we do +// waste network bandwidth and the CPU time for decoding the incoming packets, +// the function which handles those packets is NOP-ed out, which, among other +// things, skips the code which caches the referenced users in the packet. See +// . +(client["actions"] as any)["PresenceUpdate"].handle = () => {}; (client as any).music = LavalinkMusic(client, { lavalink: { From 22bd5302c5ee66ebcc8e73967656312152dd039c Mon Sep 17 00:00:00 2001 From: Dmytro Meleshko Date: Sun, 21 Feb 2021 12:24:37 +0200 Subject: [PATCH 100/178] don't install the module `os` from npm, it's a built-in one (#24) --- package-lock.json | 5 ----- package.json | 3 +-- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index 67cb5d2..31bcc46 100644 --- a/package-lock.json +++ b/package-lock.json @@ -854,11 +854,6 @@ "mimic-fn": "^2.1.0" } }, - "os": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/os/-/os-0.1.1.tgz", - "integrity": "sha1-IIhF6J4ZOtTZcUdLk5R3NqVtE/M=" - }, "os-tmpdir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", diff --git a/package.json b/package.json index b76715e..49eaccf 100644 --- a/package.json +++ b/package.json @@ -10,8 +10,7 @@ "discord.js-lavalink-lib": "^0.1.8", "inquirer": "^7.3.3", "moment": "^2.29.1", - "ms": "^2.1.3", - "os": "^0.1.1" + "ms": "^2.1.3" }, "devDependencies": { "@types/inquirer": "^6.5.0", From d6548c53dbb9c8232422ab960578f6eb59661ea9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=83=95=E3=82=BA=E3=82=AD?= Date: Sun, 21 Feb 2021 14:11:25 +0100 Subject: [PATCH 101/178] eco-bet: improvements - duration bounds - link to calling message --- src/commands/fun/subcommands/eco-bet.ts | 26 ++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/src/commands/fun/subcommands/eco-bet.ts b/src/commands/fun/subcommands/eco-bet.ts index 492da12..556054c 100644 --- a/src/commands/fun/subcommands/eco-bet.ts +++ b/src/commands/fun/subcommands/eco-bet.ts @@ -47,8 +47,12 @@ export const BetCommand = new Command({ } }, any: new Command({ - async run({client, args, author, channel, guild}): Promise { + async run({client, args, author, message, channel, guild}): Promise { if (isAuthorized(guild, channel)) { + // [Pertinence to make configurable on the fly.] + // Lower and upper bounds for bet + const durationBounds = { min:"1m", max:"1d" }; + const sender = Storage.getUser(author.id); const target = args[0]; const receiver = Storage.getUser(target); @@ -69,11 +73,11 @@ export const BetCommand = new Command({ // handle invalid duration if (duration <= 0) - return channel.send("Invalid duration"); - // else if (duration <= {threshold}) - // return channel.send("Too short idk"); - // else if (duration >= {threshold}) - // return channel.send("Too long idk"); + return channel.send("Invalid bet duration"); + else if (duration <= parseDuration(durationBounds.min)) + return channel.send(`Bet duration is too short, maximum duration is ${durationBounds.min}`); + else if (duration >= parseDuration(durationBounds.max)) + return channel.send(`Bet duration is too long, maximum duration is ${durationBounds.max}`); // Ask target whether or not they want to take the bet. const takeBet = await askYesOrNo( @@ -82,8 +86,8 @@ export const BetCommand = new Command({ ); if (takeBet) { - // [ISSUE: volatile storage] - // Remove amount money from both parts to avoid duplication of money. + // [MEDIUM PRIORITY: bet persistence to prevent losses in case of shutdown.] + // Remove amount money from both parts at the start to avoid duplication of money. sender.money -= amount; receiver.money -= amount; Storage.save(); @@ -93,9 +97,9 @@ export const BetCommand = new Command({ // Wait for the duration of the bet. client.setTimeout(async () => { - // [Pertinence to reference the invocation message to let people find the bet more easily] + // [TODO: when D.JSv13 comes out, inline reply to clean up.] // When bet is over, give a vote to ask people their thoughts. - const voteMsg = await channel.send(`VOTE: do you think that <@${target.id}> has won the bet?`); + const voteMsg = await channel.send(`VOTE: do you think that <@${target.id}> has won the bet?\nhttps://discord.com/channels/${guild.id}/${channel.id}/${message.id}`); await voteMsg.react("✅"); await voteMsg.react("❌"); @@ -165,4 +169,4 @@ function parseDuration(duration: string): number { value *= 1000; // 1000ms return value; -} \ No newline at end of file +} From c71406a8d081965667702d73032f72af98a268a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=83=95=E3=82=BA=E3=82=AD?= Date: Tue, 23 Feb 2021 23:25:28 +0100 Subject: [PATCH 102/178] eco-bet: added a check for bet target's money --- src/commands/fun/subcommands/eco-bet.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/commands/fun/subcommands/eco-bet.ts b/src/commands/fun/subcommands/eco-bet.ts index 556054c..edce702 100644 --- a/src/commands/fun/subcommands/eco-bet.ts +++ b/src/commands/fun/subcommands/eco-bet.ts @@ -42,6 +42,8 @@ export const BetCommand = new Command({ return channel.send("You must bet at least one Mon!"); else if (sender.money < amount) return channel.send("You don't have enough Mons for that.", getMoneyEmbed(author)); + else if (receiver.money < amount) + return channel.send("They don't have enough Mons for that.", getMoneyEmbed(target)); return channel.send("How long until the bet ends?"); } @@ -70,6 +72,8 @@ export const BetCommand = new Command({ return channel.send("You must bet at least one Mon!"); else if (sender.money < amount) return channel.send("You don't have enough Mons for that.", getMoneyEmbed(author)); + else if (receiver.money < amount) + return channel.send("They don't have enough Mons for that.", getMoneyEmbed(target)); // handle invalid duration if (duration <= 0) From cec38cf4bd4e33ebc2650fca5f883a3229b6df1f Mon Sep 17 00:00:00 2001 From: Keanu Date: Sun, 7 Mar 2021 22:07:59 +0100 Subject: [PATCH 103/178] Stop deleting the emote invocation. --- src/commands/utilities/emote.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/commands/utilities/emote.ts b/src/commands/utilities/emote.ts index 7b841e5..912774c 100644 --- a/src/commands/utilities/emote.ts +++ b/src/commands/utilities/emote.ts @@ -12,7 +12,6 @@ export default new Command({ async run({guild, channel, message, args}) { let output = ""; for (const query of args) output += queryClosestEmoteByName(query).toString(); - if (botHasPermission(guild, Permissions.FLAGS.MANAGE_MESSAGES)) message.delete(); channel.send(output); } }) From 3f4ee9315f812e5897f988dbde428c18f9a067ec Mon Sep 17 00:00:00 2001 From: Keanu Date: Sat, 13 Mar 2021 15:09:18 +0100 Subject: [PATCH 104/178] Added channel lock for eco. --- src/commands/fun/subcommands/eco-utils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/commands/fun/subcommands/eco-utils.ts b/src/commands/fun/subcommands/eco-utils.ts index 4ad0694..8ea5176 100644 --- a/src/commands/fun/subcommands/eco-utils.ts +++ b/src/commands/fun/subcommands/eco-utils.ts @@ -62,9 +62,9 @@ export function getSendEmbed(sender: User, receiver: User, amount: number): obje } export function isAuthorized(guild: Guild | null, channel: TextChannel | DMChannel | NewsChannel): boolean { - if (guild?.id === "637512823676600330" || process.argv[2] === "dev") return true; + if (guild?.id === "637512823676600330" && channel?.id === "669464416420364288" || process.argv[2] === "dev") return true; else { - channel.send("Sorry, this command can only be used in Monika's emote server."); + channel.send("Sorry, this command can only be used in Monika's emote server. (#mon-stocks)"); return false; } } From 38e03a85bb650568cf1df6fe585c11d0d02828ae Mon Sep 17 00:00:00 2001 From: Keanu Date: Tue, 16 Mar 2021 19:01:44 +0100 Subject: [PATCH 105/178] =?UTF-8?q?Too=20lazy=20to=20add=20interceptor;=20?= =?UTF-8?q?get=20content=20instead.=20This=20is=20for=20my=20running=20jok?= =?UTF-8?q?e=20of=20reacting=20with=20=F0=9F=9A=B1=20to=20CheeseBot's=20"R?= =?UTF-8?q?emember=20to=20drink=20water!"=20message.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/events/message.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/events/message.ts b/src/events/message.ts index 107c8cf..f793971 100644 --- a/src/events/message.ts +++ b/src/events/message.ts @@ -10,6 +10,9 @@ let commands: Collection | null = null; export default new Event<"message">({ async on(message) { + if (message.content.toLowerCase().includes("remember to drink water")) { + message.react("🚱"); + } // Load commands if it hasn't already done so. Luckily, it's called once at most. if (!commands) commands = await loadCommands(); From 705e0939994e511624347866dcee6a10c9f20d0f Mon Sep 17 00:00:00 2001 From: Keanu Date: Sat, 20 Mar 2021 13:27:57 +0100 Subject: [PATCH 106/178] Added message quoting. See https://is.gd/lpGqxj --- package-lock.json | 6 ++-- src/core/lib.ts | 1 + src/events/message.ts | 5 +++ src/modules/message_embed.ts | 60 ++++++++++++++++++++++++++++++++++++ 4 files changed, 69 insertions(+), 3 deletions(-) create mode 100644 src/modules/message_embed.ts diff --git a/package-lock.json b/package-lock.json index 31bcc46..3eb82ca 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1182,9 +1182,9 @@ "integrity": "sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==" }, "typescript": { - "version": "3.9.7", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.7.tgz", - "integrity": "sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw==", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.2.3.tgz", + "integrity": "sha512-qOcYwxaByStAWrBf4x0fibwZvMRG+r4cQoTjbPtUlrWjBHbmCAww1i448U0GJ+3cNNEtebDteo/cHOR3xJ4wEw==", "dev": true }, "which": { diff --git a/src/core/lib.ts b/src/core/lib.ts index 4cadf4d..b35577f 100644 --- a/src/core/lib.ts +++ b/src/core/lib.ts @@ -556,6 +556,7 @@ export abstract class GenericStructure { public save(asynchronous = true) { const tag = this.__meta__; + /// @ts-ignore delete this.__meta__; FileManager.write(tag, this, asynchronous); this.__meta__ = tag; diff --git a/src/events/message.ts b/src/events/message.ts index f793971..bcb1343 100644 --- a/src/events/message.ts +++ b/src/events/message.ts @@ -4,6 +4,7 @@ import {hasPermission, getPermissionLevel, PermissionNames} from "../core/permis import {Permissions, Collection} from "discord.js"; import {getPrefix} from "../core/structures"; import $, {replyEventListeners} from "../core/lib"; +import quote from "../modules/message_embed"; // It's a rather hacky solution, but since there's no top-level await, I just have to make the loading conditional. let commands: Collection | null = null; @@ -31,6 +32,10 @@ export default new Event<"message">({ const clientUser = message.client.user; let usesBotSpecificPrefix = false; + if (!message.content.startsWith(prefix)) { + return quote(message); + } + // If the client user exists, check if it starts with the bot-specific prefix. if (clientUser) { // If the prefix starts with the bot-specific prefix, go off that instead (these two options must mutually exclude each other). diff --git a/src/modules/message_embed.ts b/src/modules/message_embed.ts new file mode 100644 index 0000000..b69e76e --- /dev/null +++ b/src/modules/message_embed.ts @@ -0,0 +1,60 @@ +import { client } from '..' +import { Message, TextChannel, APIMessage, MessageEmbed } from 'discord.js' +import { getPrefix } from '../core/structures' +import { DiscordAPIError } from 'discord.js' + +export default async function quote(message: Message) { + if (message.author.bot) return + // const message_link_regex = message.content.match(/(!)?https?:\/\/\w+\.com\/channels\/(\d+)\/(\d+)\/(\d+)/) + const message_link_regex = message.content.match(/([?)/) + + if (message_link_regex == null) return + const [, char, guildID, channelID, messageID] = message_link_regex + + if (char || message.content.startsWith(getPrefix(message.guild))) return + + try { + const channel = client.guilds.cache.get(guildID)?.channels.cache.get(channelID) as TextChannel + const link_message = await channel.messages.fetch(messageID) + + let rtmsg: string | APIMessage = '' + if (link_message.cleanContent) { + rtmsg = new APIMessage(message.channel as TextChannel, { + content: link_message.cleanContent, + disableMentions: 'all', + files: link_message.attachments.array() + }) + } + + const embeds = [ + ...link_message.embeds.filter(v => v.type == 'rich'), + ...link_message.attachments.values() + ] + + /// @ts-ignore + if (!link_message.cleanContent && embeds.empty) { + const Embed = new MessageEmbed() + .setDescription('🚫 The message is empty.') + return message.channel.send(Embed) + } + + const infoEmbed = new MessageEmbed() + .setAuthor( + link_message.author.username, + link_message.author.displayAvatarURL({format: 'png', dynamic: true, size: 4096})) + .setTimestamp(link_message.createdTimestamp) + .setDescription(`${link_message.cleanContent}\n\nSent in **${link_message.guild?.name}** | <#${link_message.channel.id}> ([link](https://discord.com/channels/${guildID}/${channelID}/${messageID}))`); + if (link_message.attachments.size !== 0) { + const image = link_message.attachments.first(); + /// @ts-ignore + infoEmbed.setImage(image.url); + } + + await message.channel.send(infoEmbed) + } catch (error) { + if (error instanceof DiscordAPIError) { + message.channel.send("I don't have access to this channel, or something else went wrong.") + } + return console.error(error) + } +} \ No newline at end of file From 90c41c8df4eca0bf242758f6a490e8a0d4c0fe23 Mon Sep 17 00:00:00 2001 From: WatDuhHekBro <44940783+WatDuhHekBro@users.noreply.github.com> Date: Sun, 28 Mar 2021 09:34:31 -0500 Subject: [PATCH 107/178] Added query to lsemotes and searching other guilds --- package-lock.json | 1919 +++++++++++++++++++++++++++- src/commands/info.ts | 153 ++- src/commands/utilities/lsemotes.ts | 87 +- 3 files changed, 2074 insertions(+), 85 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3eb82ca..20330c8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,8 +1,1919 @@ { "name": "d.js-v12-bot", "version": "0.0.1", - "lockfileVersion": 1, + "lockfileVersion": 2, "requires": true, + "packages": { + "": { + "name": "d.js-v12-bot", + "version": "0.0.1", + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "discord.js": "^12.5.1", + "discord.js-lavalink-lib": "^0.1.8", + "inquirer": "^7.3.3", + "moment": "^2.29.1", + "ms": "^2.1.3" + }, + "devDependencies": { + "@types/inquirer": "^6.5.0", + "@types/mocha": "^8.2.0", + "@types/ms": "^0.7.31", + "@types/node": "^14.14.20", + "@types/ws": "^7.4.0", + "mocha": "^8.2.1", + "prettier": "2.1.2", + "ts-node": "^9.1.1", + "tsc-watch": "^4.2.9", + "typescript": "^3.9.7" + } + }, + "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/@lavacord/discord.js": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/@lavacord/discord.js/-/discord.js-0.0.5.tgz", + "integrity": "sha512-qc2lw0zB48fq4SrSlpMJOmogUXIeM5YvufQfPg0ubjp7jqm20JnOXF9fliy1MPdcKPnZ8LwIZ222H0+ghGzP/Q==", + "dependencies": { + "lavacord": "^1.1.9" + }, + "engines": { + "node": ">=10", + "npm": ">=5" + } + }, + "node_modules/@lavacord/discord.js/node_modules/lavacord": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/lavacord/-/lavacord-1.1.9.tgz", + "integrity": "sha512-haZghbblO1w3Hodc9q63ZWgV5zA/jB6xFKS17fImK5aIdn0PkKuZ6AsJBxMFpR275v8GNYOxg6cTQBYBQ+batQ==", + "dependencies": { + "node-fetch": "^2.6.0", + "ws": "^7.3.0" + }, + "engines": { + "node": ">=10", + "npm": ">=5" + } + }, + "node_modules/@types/color-name": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", + "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==" + }, + "node_modules/@types/inquirer": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@types/inquirer/-/inquirer-6.5.0.tgz", + "integrity": "sha512-rjaYQ9b9y/VFGOpqBEXRavc3jh0a+e6evAbI31tMda8VlPaSy0AZJfXsvmIe3wklc7W6C3zCSfleuMXR7NOyXw==", + "dev": true, + "dependencies": { + "@types/through": "*", + "rxjs": "^6.4.0" + } + }, + "node_modules/@types/mocha": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-8.2.0.tgz", + "integrity": "sha512-/Sge3BymXo4lKc31C8OINJgXLaw+7vL1/L1pGiBNpGrBiT8FQiaFpSYV0uhTaG4y78vcMBTMFsWaHDvuD+xGzQ==", + "dev": true + }, + "node_modules/@types/ms": { + "version": "0.7.31", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz", + "integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==", + "dev": true + }, + "node_modules/@types/node": { + "version": "14.14.20", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.20.tgz", + "integrity": "sha512-Y93R97Ouif9JEOWPIUyU+eyIdyRqQR0I8Ez1dzku4hDx34NWh4HbtIc3WNzwB1Y9ULvNGeu5B8h8bVL5cAk4/A==", + "dev": true + }, + "node_modules/@types/through": { + "version": "0.0.30", + "resolved": "https://registry.npmjs.org/@types/through/-/through-0.0.30.tgz", + "integrity": "sha512-FvnCJljyxhPM3gkRgWmxmDZyAQSiBQQWLI0A0VFL0K7W1oRUrPJSqNO0NvTnLkBcotdlp3lKvaT0JrnyRDkzOg==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/ws": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-7.4.0.tgz", + "integrity": "sha512-Y29uQ3Uy+58bZrFLhX36hcI3Np37nqWE7ky5tjiDoy1GDZnIwVxS0CgF+s+1bXMzjKBFy+fqaRfb708iNzdinw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@ungap/promise-all-settled": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", + "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", + "dev": true + }, + "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/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-escapes": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.1.tgz", + "integrity": "sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA==", + "dependencies": { + "type-fest": "^0.11.0" + }, + "engines": { + "node": ">=8" + } + }, + "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==", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dependencies": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/anymatch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", + "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "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/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, + "node_modules/axios": { + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz", + "integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==", + "dependencies": { + "follow-redirects": "^1.10.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "node_modules/binary-extensions": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz", + "integrity": "sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, + "node_modules/buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==" + }, + "node_modules/chokidar": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.3.tgz", + "integrity": "sha512-DtM3g7juCXQxFVSNPNByEC2+NImtBuxQQvWlHunpJIS5Ocr0lG306cC7FCi7cEA0fzmybPUIl4txBIobk1gGOQ==", + "dev": true, + "dependencies": { + "anymatch": "~3.1.1", + "braces": "~3.0.2", + "glob-parent": "~5.1.0", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.5.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.1.2" + } + }, + "node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dependencies": { + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-width": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", + "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", + "engines": { + "node": ">= 10" + } + }, + "node_modules/cliui": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "dev": true, + "dependencies": { + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + } + }, + "node_modules/cliui/node_modules/ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/cliui/node_modules/emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "node_modules/cliui/node_modules/is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/cliui/node_modules/string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "dependencies": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "dependencies": { + "ansi-regex": "^4.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/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/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, + "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.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", + "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/debug/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "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/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/discord.js": { + "version": "12.5.1", + "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-12.5.1.tgz", + "integrity": "sha512-VwZkVaUAIOB9mKdca0I5MefPMTQJTNg0qdgi1huF3iwsFwJ0L5s/Y69AQe+iPmjuV6j9rtKoG0Ta0n9vgEIL6w==", + "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.2", + "setimmediate": "^1.0.5", + "tweetnacl": "^1.0.3", + "ws": "^7.3.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/discord.js-lavalink-lib": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/discord.js-lavalink-lib/-/discord.js-lavalink-lib-0.1.8.tgz", + "integrity": "sha512-Kv4ps5AW4xYxMuJypHJrPL1occlV817CjHiIL7J09EdgzNRcKRIif0U6XMxWxi45Pg3DOl41Lw1V+J2GRsMgcw==", + "dependencies": { + "@lavacord/discord.js": "0.0.5", + "axios": ">=0.21.1", + "discord.js": "^12.2.0", + "lavacord": "^1.1.7" + } + }, + "node_modules/duplexer": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", + "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=", + "dev": true + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/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=", + "engines": { + "node": ">=0.8.0" + } + }, + "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/event-stream": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz", + "integrity": "sha1-SrTJoPWlTbkzi0w02Gv86PSzVXE=", + "dev": true, + "dependencies": { + "duplexer": "~0.1.1", + "from": "~0", + "map-stream": "~0.1.0", + "pause-stream": "0.0.11", + "split": "0.3", + "stream-combiner": "~0.0.4", + "through": "~2.3.1" + } + }, + "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/external-editor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", + "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", + "dependencies": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/figures": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "dependencies": { + "escape-string-regexp": "^1.0.5" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "bin": { + "flat": "cli.js" + } + }, + "node_modules/follow-redirects": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.1.tgz", + "integrity": "sha512-SSG5xmZh1mkPGyKzjZP8zLjltIfpW32Y5QpdNJyjcfGxK3qo3NDDkZOZSFiGn1A6SclQxY9GzEwAHQ3dmYRWpg==", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/from": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz", + "integrity": "sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4=", + "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/fsevents": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", + "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "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": "*" + } + }, + "node_modules/glob-parent": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", + "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "dev": true, + "engines": { + "node": ">=4.x" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "bin": { + "he": "bin/he" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "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/inquirer": { + "version": "7.3.3", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.3.3.tgz", + "integrity": "sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==", + "dependencies": { + "ansi-escapes": "^4.2.1", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-width": "^3.0.0", + "external-editor": "^3.0.3", + "figures": "^3.0.0", + "lodash": "^4.17.19", + "mute-stream": "0.0.8", + "run-async": "^2.4.0", + "rxjs": "^6.6.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0", + "through": "^2.3.6" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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==", + "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/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "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-yaml": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz", + "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/lavacord": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/lavacord/-/lavacord-1.1.9.tgz", + "integrity": "sha512-haZghbblO1w3Hodc9q63ZWgV5zA/jB6xFKS17fImK5aIdn0PkKuZ6AsJBxMFpR275v8GNYOxg6cTQBYBQ+batQ==", + "dependencies": { + "node-fetch": "^2.6.0", + "ws": "^7.3.0" + }, + "engines": { + "node": ">=10", + "npm": ">=5" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/lodash": { + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==" + }, + "node_modules/log-symbols": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.0.0.tgz", + "integrity": "sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "node_modules/map-stream": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz", + "integrity": "sha1-5WqpTEyAVaFkBKBnS3jyFffI4ZQ=", + "dev": true + }, + "node_modules/mime-db": { + "version": "1.45.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.45.0.tgz", + "integrity": "sha512-CkqLUxUk15hofLoLyljJSrukZi8mAtgd+yE5uO4tqRZsdsAJKv0O+rFMhVDRJgozy+yG6md5KwuXhD4ocIoP+w==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.28", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.28.tgz", + "integrity": "sha512-0TO2yJ5YHYr7M2zzT7gDU1tbwHxEUWBCLt0lscSNpcdAfFyJOVEpRYNS7EXVcTLNj/25QO8gulHC5JtTzSE2UQ==", + "dependencies": { + "mime-db": "1.45.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "engines": { + "node": ">=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/mocha": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-8.2.1.tgz", + "integrity": "sha512-cuLBVfyFfFqbNR0uUKbDGXKGk+UDFe6aR4os78XIrMQpZl/nv7JYHcvP5MFIAb374b2zFXsdgEGwmzMtP0Xg8w==", + "dev": true, + "dependencies": { + "@ungap/promise-all-settled": "1.1.2", + "ansi-colors": "4.1.1", + "browser-stdout": "1.3.1", + "chokidar": "3.4.3", + "debug": "4.2.0", + "diff": "4.0.2", + "escape-string-regexp": "4.0.0", + "find-up": "5.0.0", + "glob": "7.1.6", + "growl": "1.10.5", + "he": "1.2.0", + "js-yaml": "3.14.0", + "log-symbols": "4.0.0", + "minimatch": "3.0.4", + "ms": "2.1.2", + "nanoid": "3.1.12", + "serialize-javascript": "5.0.1", + "strip-json-comments": "3.1.1", + "supports-color": "7.2.0", + "which": "2.0.2", + "wide-align": "1.1.3", + "workerpool": "6.0.2", + "yargs": "13.3.2", + "yargs-parser": "13.1.2", + "yargs-unparser": "2.0.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha" + }, + "engines": { + "node": ">= 10.12.0" + } + }, + "node_modules/mocha/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/mocha/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/mocha/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/moment": { + "version": "2.29.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz", + "integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==", + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/mute-stream": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==" + }, + "node_modules/nanoid": { + "version": "3.1.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.12.tgz", + "integrity": "sha512-1qstj9z5+x491jfiC4Nelk+f8XBad7LN20PmyWINJEMRSf3wcAjAWysw1qaA8z6NSKe2sjq1hRSDpBH5paCb6A==", + "dev": true, + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || >=13.7" + } + }, + "node_modules/node-cleanup": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/node-cleanup/-/node-cleanup-2.1.2.tgz", + "integrity": "sha1-esGavSl+Caf3KnFUXZUbUX5N3iw=", + "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/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/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/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "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/pause-stream": { + "version": "0.0.11", + "resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", + "integrity": "sha1-/lo0sMvOErWqaitAPuLnO2AvFEU=", + "dev": true, + "dependencies": { + "through": "~2.3" + } + }, + "node_modules/picomatch": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", + "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", + "dev": true, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/prettier": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.1.2.tgz", + "integrity": "sha512-16c7K+x4qVlJg9rEbXl7HEGmQyZlG4R9AgP+oHKRMsMsuk8s+ATStlf1NpDqyBI1HpVyfjLOeMhH2LvuNvV5Vg==", + "dev": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/prism-media": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/prism-media/-/prism-media-1.2.3.tgz", + "integrity": "sha512-fSrR66n0l6roW9Rx4rSLMyTPTjRTiXy5RVqDOurACQ6si1rKHHKDU5gwBJoCsIV0R3o9gi+K50akl/qyw1C74A==" + }, + "node_modules/ps-tree": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/ps-tree/-/ps-tree-1.2.0.tgz", + "integrity": "sha512-0VnamPPYHl4uaU/nSFeZZpR21QAWRz+sRv4iW9+v/GS/J5U5iZB5BNN6J0RMoOvdx2gWM2+ZFMIm58q24e4UYA==", + "dev": true, + "dependencies": { + "event-stream": "=3.3.4" + }, + "bin": { + "ps-tree": "bin/ps-tree.js" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/readdirp": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", + "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true + }, + "node_modules/restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/run-async": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", + "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/rxjs": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.0.tgz", + "integrity": "sha512-3HMA8z/Oz61DUHe+SdOiQyzIf4tOx5oQHmMir7IZEu6TMqCLHT4LRcmNaUS0NwOz8VLvmmBduMsoaUvMaIiqzg==", + "dependencies": { + "tslib": "^1.9.0" + }, + "engines": { + "npm": ">=2.0.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/serialize-javascript": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-5.0.1.tgz", + "integrity": "sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==", + "dev": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true + }, + "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/signal-exit": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", + "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==" + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.19", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", + "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/split": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/split/-/split-0.3.3.tgz", + "integrity": "sha1-zQ7qXmOiEd//frDwkcQTPi0N0o8=", + "dev": true, + "dependencies": { + "through": "2" + }, + "engines": { + "node": "*" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "node_modules/stream-combiner": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz", + "integrity": "sha1-TV5DPBhSYd3mI8o/RMWGvPXErRQ=", + "dev": true, + "dependencies": { + "duplexer": "~0.1.1" + } + }, + "node_modules/string-argv": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.1.2.tgz", + "integrity": "sha512-mBqPGEOMNJKXRo7z0keX0wlAhbBAjilUdPW13nN0PecVryZxdHIeM7TqbsSUA7VYuS00HGC6mojP7DlQzfa9ZA==", + "dev": true, + "engines": { + "node": ">=0.6.19" + } + }, + "node_modules/string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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==", + "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" + } + }, + "node_modules/supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" + }, + "node_modules/tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dependencies": { + "os-tmpdir": "~1.0.2" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-node": { + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-9.1.1.tgz", + "integrity": "sha512-hPlt7ZACERQGf03M253ytLY3dHbGNGrAq9qIHWUY9XHYl1z7wYngSr3OQ5xmui8o2AaxsONxIzjafLUiWBo1Fg==", + "dev": true, + "dependencies": { + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "source-map-support": "^0.5.17", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/tsc-watch": { + "version": "4.2.9", + "resolved": "https://registry.npmjs.org/tsc-watch/-/tsc-watch-4.2.9.tgz", + "integrity": "sha512-DlTaoDs74+KUpyWr7dCGhuscAUKCz6CiFduBN7R9RbLJSSN1moWdwoCLASE7+zLgGvV5AwXfYDiEMAsPGaO+Vw==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "node-cleanup": "^2.1.2", + "ps-tree": "^1.2.0", + "string-argv": "^0.1.1", + "strip-ansi": "^6.0.0" + }, + "bin": { + "tsc-watch": "index.js" + }, + "engines": { + "node": ">=8.17.0" + } + }, + "node_modules/tslib": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz", + "integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==" + }, + "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-fest": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.11.0.tgz", + "integrity": "sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/typescript": { + "version": "3.9.9", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.9.tgz", + "integrity": "sha512-kdMjTiekY+z/ubJCATUPlRDl39vXYiMV9iyeMuEuXZh2we6zz80uovNN2WlAxmmdE/Z/YQe+EbOEXB5RHEED3w==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "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/which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true + }, + "node_modules/wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "dev": true, + "dependencies": { + "string-width": "^1.0.2 || 2" + } + }, + "node_modules/wide-align/node_modules/ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/wide-align/node_modules/is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/wide-align/node_modules/string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "dependencies": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/wide-align/node_modules/strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "dependencies": { + "ansi-regex": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/workerpool": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.0.2.tgz", + "integrity": "sha512-DSNyvOpFKrNusaaUwk+ej6cBj1bmhLcBfj80elGk+ZIo5JSkq+unB1dLKEOcNfJDZgjGICfhQ0Q5TbP0PvF4+Q==", + "dev": true + }, + "node_modules/wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/wrap-ansi/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/wrap-ansi/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/wrap-ansi/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/wrap-ansi/node_modules/emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "node_modules/wrap-ansi/node_modules/is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "dependencies": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "dependencies": { + "ansi-regex": "^4.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "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.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.2.tgz", + "integrity": "sha512-T4tewALS3+qsrpGI/8dqNMLIVdq/g/85U98HPMa6F0m6xTbvhXU6RCQLqPH3+SlomNV/LdY6RXEbBpMH6EOJnA==", + "engines": { + "node": ">=8.3.0" + } + }, + "node_modules/y18n": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.1.tgz", + "integrity": "sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ==", + "dev": true + }, + "node_modules/yargs": { + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", + "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", + "dev": true, + "dependencies": { + "cliui": "^5.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.1.2" + } + }, + "node_modules/yargs-parser": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", + "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", + "dev": true, + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + }, + "node_modules/yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "dev": true, + "dependencies": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-unparser/node_modules/camelcase": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", + "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-unparser/node_modules/decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs/node_modules/ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/yargs/node_modules/emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "node_modules/yargs/node_modules/find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "dependencies": { + "locate-path": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/yargs/node_modules/is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/yargs/node_modules/locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "dependencies": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/yargs/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/yargs/node_modules/p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "dependencies": { + "p-limit": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/yargs/node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/yargs/node_modules/string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "dependencies": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/yargs/node_modules/strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "dependencies": { + "ansi-regex": "^4.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + } + } + }, "dependencies": { "@discordjs/collection": { "version": "0.1.6", @@ -1182,9 +3093,9 @@ "integrity": "sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==" }, "typescript": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.2.3.tgz", - "integrity": "sha512-qOcYwxaByStAWrBf4x0fibwZvMRG+r4cQoTjbPtUlrWjBHbmCAww1i448U0GJ+3cNNEtebDteo/cHOR3xJ4wEw==", + "version": "3.9.9", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.9.tgz", + "integrity": "sha512-kdMjTiekY+z/ubJCATUPlRDl39vXYiMV9iyeMuEuXZh2we6zz80uovNN2WlAxmmdE/Z/YQe+EbOEXB5RHEED3w==", "dev": true }, "which": { diff --git a/src/commands/info.ts b/src/commands/info.ts index 9b7b71a..16dd854 100644 --- a/src/commands/info.ts +++ b/src/commands/info.ts @@ -6,6 +6,7 @@ import {CommonLibrary, formatBytes, trimArray} from "../core/lib"; import {verificationLevels, filterLevels, regions, flags} from "../defs/info"; import moment from "moment"; import utc from "moment"; +import {Guild} from "discord.js"; const {version} = require("../../package.json"); @@ -73,73 +74,40 @@ export default new Command({ } }), guild: new Command({ - description: "Displays info about the current guild.", + description: "Displays info about the current guild or another guild.", + usage: "(/)", async run($: CommonLibrary): Promise { if ($.guild) { - const members = await $.guild.members.fetch({ - withPresences: true, - force: true - }); - const roles = $.guild.roles.cache - .sort((a, b) => b.position - a.position) - .map((role) => role.toString()); - const channels = $.guild.channels.cache; - const emojis = $.guild.emojis.cache; - const iconURL = $.guild.iconURL({dynamic: true}); - const embed = new MessageEmbed() - .setDescription(`**Guild information for __${$.guild.name}__**`) - .setColor("BLUE"); - if (iconURL) - embed - .setThumbnail(iconURL) - .addField("General", [ - `**❯ Name:** ${$.guild.name}`, - `**❯ ID:** ${$.guild.id}`, - `**❯ Owner:** ${$.guild.owner?.user.tag} (${$.guild.ownerID})`, - `**❯ Region:** ${regions[$.guild.region]}`, - `**❯ Boost Tier:** ${$.guild.premiumTier ? `Tier ${$.guild.premiumTier}` : "None"}`, - `**❯ Explicit Filter:** ${filterLevels[$.guild.explicitContentFilter]}`, - `**❯ Verification Level:** ${verificationLevels[$.guild.verificationLevel]}`, - `**❯ Time Created:** ${moment($.guild.createdTimestamp).format("LT")} ${moment( - $.guild.createdTimestamp - ).format("LL")} ${moment($.guild.createdTimestamp).fromNow()})`, - "\u200b" - ]) - .addField("Statistics", [ - `**❯ Role Count:** ${roles.length}`, - `**❯ Emoji Count:** ${emojis.size}`, - `**❯ Regular Emoji Count:** ${emojis.filter((emoji) => !emoji.animated).size}`, - `**❯ Animated Emoji Count:** ${emojis.filter((emoji) => emoji.animated).size}`, - `**❯ Member Count:** ${$.guild.memberCount}`, - `**❯ Humans:** ${members.filter((member) => !member.user.bot).size}`, - `**❯ Bots:** ${members.filter((member) => member.user.bot).size}`, - `**❯ Text Channels:** ${channels.filter((channel) => channel.type === "text").size}`, - `**❯ Voice Channels:** ${channels.filter((channel) => channel.type === "voice").size}`, - `**❯ Boost Count:** ${$.guild.premiumSubscriptionCount || "0"}`, - `\u200b` - ]) - .addField("Presence", [ - `**❯ Online:** ${members.filter((member) => member.presence.status === "online").size}`, - `**❯ Idle:** ${members.filter((member) => member.presence.status === "idle").size}`, - `**❯ Do Not Disturb:** ${ - members.filter((member) => member.presence.status === "dnd").size - }`, - `**❯ Offline:** ${ - members.filter((member) => member.presence.status === "offline").size - }`, - "\u200b" - ]) - .addField( - `Roles [${roles.length - 1}]`, - roles.length < 10 ? roles.join(", ") : roles.length > 10 ? trimArray(roles) : "None" - ) - .setTimestamp(); - - $.channel.send(embed); + $.channel.send(await getGuildInfo($.guild, $.guild)); } else { $.channel.send("Please execute this command in a guild."); } - } + }, + any: new Command({ + description: "Display info about a guild by finding its name or ID.", + async run($: CommonLibrary): Promise { + // If a guild ID is provided (avoid the "number" subcommand because of inaccuracies), search for that guild + if ($.args.length === 1 && /^\d{17,19}$/.test($.args[0])) { + const id = $.args[0]; + const guild = $.client.guilds.cache.get(id); + + if (guild) { + $.channel.send(await getGuildInfo(guild, $.guild)); + } else { + $.channel.send(`None of the servers I'm in matches the guild ID \`${id}\`!`); + } + } else { + const query: string = $.args.join(" ").toLowerCase(); + const guild = $.client.guilds.cache.find((guild) => guild.name.toLowerCase().includes(query)); + + if (guild) { + $.channel.send(await getGuildInfo(guild, $.guild)); + } else { + $.channel.send(`None of the servers I'm in matches the query \`${query}\`!`); + } + } + } + }) }) }, user: new Command({ @@ -190,3 +158,64 @@ export default new Command({ } }) }); + +async function getGuildInfo(guild: Guild, currentGuild: Guild | null) { + const members = await guild.members.fetch({ + withPresences: true, + force: true + }); + const roles = guild.roles.cache.sort((a, b) => b.position - a.position).map((role) => role.toString()); + const channels = guild.channels.cache; + const emojis = guild.emojis.cache; + const iconURL = guild.iconURL({dynamic: true}); + const embed = new MessageEmbed().setDescription(`**Guild information for __${guild.name}__**`).setColor("BLUE"); + const displayRoles = !!(currentGuild && guild.id === currentGuild.id); + if (iconURL) { + embed + .setThumbnail(iconURL) + .addField("General", [ + `**❯ Name:** ${guild.name}`, + `**❯ ID:** ${guild.id}`, + `**❯ Owner:** ${guild.owner?.user.tag} (${guild.ownerID})`, + `**❯ Region:** ${regions[guild.region]}`, + `**❯ Boost Tier:** ${guild.premiumTier ? `Tier ${guild.premiumTier}` : "None"}`, + `**❯ Explicit Filter:** ${filterLevels[guild.explicitContentFilter]}`, + `**❯ Verification Level:** ${verificationLevels[guild.verificationLevel]}`, + `**❯ Time Created:** ${moment(guild.createdTimestamp).format("LT")} ${moment( + guild.createdTimestamp + ).format("LL")} ${moment(guild.createdTimestamp).fromNow()}`, + "\u200b" + ]) + .addField("Statistics", [ + `**❯ Role Count:** ${roles.length}`, + `**❯ Emoji Count:** ${emojis.size}`, + `**❯ Regular Emoji Count:** ${emojis.filter((emoji) => !emoji.animated).size}`, + `**❯ Animated Emoji Count:** ${emojis.filter((emoji) => emoji.animated).size}`, + `**❯ Member Count:** ${guild.memberCount}`, + `**❯ Humans:** ${members.filter((member) => !member.user.bot).size}`, + `**❯ Bots:** ${members.filter((member) => member.user.bot).size}`, + `**❯ Text Channels:** ${channels.filter((channel) => channel.type === "text").size}`, + `**❯ Voice Channels:** ${channels.filter((channel) => channel.type === "voice").size}`, + `**❯ Boost Count:** ${guild.premiumSubscriptionCount || "0"}`, + `\u200b` + ]) + .addField("Presence", [ + `**❯ Online:** ${members.filter((member) => member.presence.status === "online").size}`, + `**❯ Idle:** ${members.filter((member) => member.presence.status === "idle").size}`, + `**❯ Do Not Disturb:** ${members.filter((member) => member.presence.status === "dnd").size}`, + `**❯ Offline:** ${members.filter((member) => member.presence.status === "offline").size}`, + displayRoles ? "\u200b" : "" + ]) + .setTimestamp(); + + // Only add the roles if the guild the bot is sending the message to is the same one that's being requested. + if (displayRoles) { + embed.addField( + `Roles [${roles.length - 1}]`, + roles.length < 10 ? roles.join(", ") : roles.length > 10 ? trimArray(roles) : "None" + ); + } + } + + return embed; +} diff --git a/src/commands/utilities/lsemotes.ts b/src/commands/utilities/lsemotes.ts index b84fbb8..0711a9f 100644 --- a/src/commands/utilities/lsemotes.ts +++ b/src/commands/utilities/lsemotes.ts @@ -1,32 +1,81 @@ +import {GuildEmoji} from "discord.js"; import {MessageEmbed} from "discord.js"; import Command from "../../core/command"; import {CommonLibrary} from "../../core/lib"; export default new Command({ description: "Lists all emotes the bot has in it's registry,", - endpoint: true, + usage: " (-flags)", async run($: CommonLibrary): Promise { - const nsfw: string | string[] = []; - const pages = $.client.emojis.cache.filter((x) => !nsfw.includes(x.guild.id), this).array(); - const pagesSplit = $(pages).split(20); - $.log(pagesSplit); - var embed = new MessageEmbed().setTitle("**Emoji list!**").setColor("AQUA"); - let desc = ""; + displayEmoteList($, $.client.emojis.cache.array()); + }, + any: new Command({ + description: + "Filters emotes by via a regular expression. Flags can be added by adding a dash at the end. For example, to do a case-insensitive search, do %prefix%lsemotes somepattern -i", + async run($: CommonLibrary): Promise { + // If a guild ID is provided, filter all emotes by that guild (but only if there aren't any arguments afterward) + if ($.args.length === 1 && /^\d{17,19}$/.test($.args[0])) { + const guildID: string = $.args[0]; - for (const emote of pagesSplit[0]) { - desc += `${emote} | ${emote.name}\n`; + displayEmoteList($, $.client.emojis.cache.filter((emote) => emote.guild.id === guildID).array()); + } else { + // Otherwise, search via a regex pattern + let flags: string | undefined = undefined; + + if (/^-[dgimsuy]{1,7}$/.test($.args[$.args.length - 1])) { + flags = $.args.pop().substring(1); + } + + displayEmoteList( + $, + $.client.emojis.cache + .filter((emote) => new RegExp($.args.join(" "), flags).test(emote.name)) + .array() + ); + } + } + }) +}); + +async function displayEmoteList($: CommonLibrary, emotes: GuildEmoji[]) { + emotes.sort((a, b) => { + const first = a.name.toLowerCase(); + const second = b.name.toLowerCase(); + + if (first > second) return 1; + else if (first < second) return -1; + else return 0; + }); + const sections = $(emotes).split(20); + const pages = sections.length; + const embed = new MessageEmbed().setTitle("**Emotes**").setColor("AQUA"); + let desc = ""; + + // Gather the first page (if it even exists, which it might not if there no valid emotes appear) + if (pages > 0) { + for (const emote of sections[0]) { + desc += `${emote} ${emote.name} (**${emote.guild.name}**)\n`; } embed.setDescription(desc); - const msg = await $.channel.send({embed}); - $.paginate(msg, $.author.id, pages.length, (page) => { - let desc = ""; - for (const emote of pagesSplit[page]) { - desc += `${emote} | ${emote.name}\n`; - } - embed.setDescription(desc); - msg.edit(embed); - }); + if (pages > 1) { + embed.setTitle(`**Emotes** (Page 1 of ${pages})`); + const msg = await $.channel.send({embed}); + + $.paginate(msg, $.author.id, pages, (page) => { + let desc = ""; + for (const emote of sections[page]) { + desc += `${emote} ${emote.name} (**${emote.guild.name}**)\n`; + } + embed.setTitle(`**Emotes** (Page ${page + 1} of ${pages})`); + embed.setDescription(desc); + msg.edit(embed); + }); + } else { + await $.channel.send({embed}); + } + } else { + $.channel.send("No valid emotes found by that query."); } -}); +} From b3e1b5e140323279f71df84b861a51ffc688fea4 Mon Sep 17 00:00:00 2001 From: WatDuhHekBro <44940783+WatDuhHekBro@users.noreply.github.com> Date: Sun, 28 Mar 2021 11:01:07 -0500 Subject: [PATCH 108/178] Removed problematic feature --- src/commands/utilities/lsemotes.ts | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/commands/utilities/lsemotes.ts b/src/commands/utilities/lsemotes.ts index 0711a9f..6a912de 100644 --- a/src/commands/utilities/lsemotes.ts +++ b/src/commands/utilities/lsemotes.ts @@ -19,18 +19,12 @@ export default new Command({ displayEmoteList($, $.client.emojis.cache.filter((emote) => emote.guild.id === guildID).array()); } else { - // Otherwise, search via a regex pattern - let flags: string | undefined = undefined; - - if (/^-[dgimsuy]{1,7}$/.test($.args[$.args.length - 1])) { - flags = $.args.pop().substring(1); - } + // Otherwise, by comparing inputs + const query = $.args.join(" ").toLowerCase(); displayEmoteList( $, - $.client.emojis.cache - .filter((emote) => new RegExp($.args.join(" "), flags).test(emote.name)) - .array() + $.client.emojis.cache.filter((emote) => emote.name.toLowerCase().includes(query)).array() ); } } From 3b1b8ec91425ec2a97b9530a637f5bb2536004b5 Mon Sep 17 00:00:00 2001 From: WatDuhHekBro <44940783+WatDuhHekBro@users.noreply.github.com> Date: Sun, 28 Mar 2021 12:49:45 -0500 Subject: [PATCH 109/178] Added a timeout to regex search --- src/commands/utilities/lsemotes.ts | 54 ++++++++++++++++++++++++++---- 1 file changed, 48 insertions(+), 6 deletions(-) diff --git a/src/commands/utilities/lsemotes.ts b/src/commands/utilities/lsemotes.ts index 6a912de..badd960 100644 --- a/src/commands/utilities/lsemotes.ts +++ b/src/commands/utilities/lsemotes.ts @@ -2,6 +2,9 @@ import {GuildEmoji} from "discord.js"; import {MessageEmbed} from "discord.js"; import Command from "../../core/command"; import {CommonLibrary} from "../../core/lib"; +import vm from "vm"; + +const REGEX_TIMEOUT_MS = 1000; export default new Command({ description: "Lists all emotes the bot has in it's registry,", @@ -19,13 +22,52 @@ export default new Command({ displayEmoteList($, $.client.emojis.cache.filter((emote) => emote.guild.id === guildID).array()); } else { - // Otherwise, by comparing inputs - const query = $.args.join(" ").toLowerCase(); + // Otherwise, search via a regex pattern + let flags: string | undefined = undefined; - displayEmoteList( - $, - $.client.emojis.cache.filter((emote) => emote.name.toLowerCase().includes(query)).array() - ); + if (/^-[dgimsuy]{1,7}$/.test($.args[$.args.length - 1])) { + flags = $.args.pop().substring(1); + } + + let emoteCollection = $.client.emojis.cache.array(); + // Creates a sandbox to stop a regular expression if it takes too much time to search. + // To avoid passing in a giant data structure, I'll just pass in the structure {[id: string]: [name: string]}. + //let emotes: {[id: string]: string} = {}; + let emotes = new Map(); + + for (const emote of emoteCollection) { + emotes.set(emote.id, emote.name); + } + + // The result will be sandbox.emotes because it'll be modified in-place. + const sandbox = { + regex: new RegExp($.args.join(" "), flags), + emotes + }; + const context = vm.createContext(sandbox); + + if (vm.isContext(sandbox)) { + // Restrict an entire query to the timeout specified. + try { + const script = new vm.Script( + "for(const [id, name] of emotes.entries()) if(!regex.test(name)) emotes.delete(id);" + ); + script.runInContext(context, {timeout: REGEX_TIMEOUT_MS}); + emotes = sandbox.emotes; + emoteCollection = emoteCollection.filter((emote) => emote.id in emotes); // Only allow emotes that haven't been deleted. + displayEmoteList($, emoteCollection); + } catch (error) { + if (error.code === "ERR_SCRIPT_EXECUTION_TIMEOUT") { + $.channel.send( + `The regular expression you entered exceeded the time limit of ${REGEX_TIMEOUT_MS} milliseconds.` + ); + } else { + throw new Error(error); + } + } + } else { + $.channel.send("Failed to initialize sandbox."); + } } } }) From 1954b2d999f729131935e7cb8cda990fccc8d86b Mon Sep 17 00:00:00 2001 From: WatDuhHekBro <44940783+WatDuhHekBro@users.noreply.github.com> Date: Sun, 28 Mar 2021 13:58:10 -0500 Subject: [PATCH 110/178] Fixed small error --- src/commands/utilities/lsemotes.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands/utilities/lsemotes.ts b/src/commands/utilities/lsemotes.ts index badd960..23311cb 100644 --- a/src/commands/utilities/lsemotes.ts +++ b/src/commands/utilities/lsemotes.ts @@ -54,7 +54,7 @@ export default new Command({ ); script.runInContext(context, {timeout: REGEX_TIMEOUT_MS}); emotes = sandbox.emotes; - emoteCollection = emoteCollection.filter((emote) => emote.id in emotes); // Only allow emotes that haven't been deleted. + emoteCollection = emoteCollection.filter((emote) => emotes.has(emote.id)); // Only allow emotes that haven't been deleted. displayEmoteList($, emoteCollection); } catch (error) { if (error.code === "ERR_SCRIPT_EXECUTION_TIMEOUT") { From 10c1cd9cff0e184ac037be27de8b034749119050 Mon Sep 17 00:00:00 2001 From: WatDuhHekBro <44940783+WatDuhHekBro@users.noreply.github.com> Date: Tue, 30 Mar 2021 03:58:21 -0500 Subject: [PATCH 111/178] Separated custom logger from command menu --- package-lock.json | 7794 +++++++++++++++++---- src/commands/admin.ts | 3 +- src/commands/fun/subcommands/eco-utils.ts | 2 +- src/commands/help.ts | 8 +- src/commands/scanemotes.ts | 4 +- src/core/command.ts | 20 +- src/core/event.ts | 5 +- src/core/lib.ts | 91 +- src/core/permissions.ts | 3 +- src/core/storage.ts | 9 +- src/core/structures.ts | 10 +- src/events/channelCreate.ts | 3 +- src/events/channelDelete.ts | 3 +- src/events/emojiCreate.ts | 3 +- src/events/emojiDelete.ts | 3 +- src/events/emojiUpdate.ts | 3 +- src/events/guildCreate.ts | 3 +- src/events/guildDelete.ts | 3 +- src/events/message.ts | 8 +- src/events/ready.ts | 3 +- src/globals.ts | 86 + src/index.ts | 1 + src/modules/message_embed.ts | 65 +- src/setup.ts | 18 +- 24 files changed, 6763 insertions(+), 1388 deletions(-) create mode 100644 src/globals.ts diff --git a/package-lock.json b/package-lock.json index 8d7b25b..aab9d3b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,28 +7,532 @@ "": { "name": "d.js-v12-bot", "version": "0.0.1", + "hasInstallScript": true, "license": "MIT", "dependencies": { "chalk": "^4.1.0", "discord.js": "^12.5.1", "discord.js-lavalink-lib": "^0.1.8", + "glob": "^7.1.6", "inquirer": "^7.3.3", "moment": "^2.29.1", "ms": "^2.1.3" }, "devDependencies": { + "@types/glob": "^7.1.3", "@types/inquirer": "^6.5.0", - "@types/mocha": "^8.2.0", + "@types/jest": "^26.0.20", "@types/ms": "^0.7.31", "@types/node": "^14.14.20", "@types/ws": "^7.4.0", - "mocha": "^8.2.1", + "husky": "^5.0.6", + "jest": "^26.6.3", "prettier": "2.1.2", - "ts-node": "^9.1.1", + "ts-jest": "^26.4.4", "tsc-watch": "^4.2.9", "typescript": "^3.9.7" } }, + "node_modules/@babel/code-frame": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz", + "integrity": "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.12.13" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.13.12", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.13.12.tgz", + "integrity": "sha512-3eJJ841uKxeV8dcN/2yGEUy+RfgQspPEgQat85umsE1rotuquQ2AbIub4S6j7c50a2d+4myc+zSlnXeIHrOnhQ==", + "dev": true + }, + "node_modules/@babel/core": { + "version": "7.13.14", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.13.14.tgz", + "integrity": "sha512-wZso/vyF4ki0l0znlgM4inxbdrUvCb+cVz8grxDq+6C9k6qbqoIJteQOKicaKjCipU3ISV+XedCqpL2RJJVehA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@babel/generator": "^7.13.9", + "@babel/helper-compilation-targets": "^7.13.13", + "@babel/helper-module-transforms": "^7.13.14", + "@babel/helpers": "^7.13.10", + "@babel/parser": "^7.13.13", + "@babel/template": "^7.12.13", + "@babel/traverse": "^7.13.13", + "@babel/types": "^7.13.14", + "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" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@babel/generator": { + "version": "7.13.9", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.13.9.tgz", + "integrity": "sha512-mHOOmY0Axl/JCTkxTU6Lf5sWOg/v8nUa+Xkt4zMTftX0wqmb6Sh7J8gvcehBw7q0AhrhAR+FDacKjCZ2X8K+Sw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.13.0", + "jsesc": "^2.5.1", + "source-map": "^0.5.0" + } + }, + "node_modules/@babel/generator/node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.13.13", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.13.13.tgz", + "integrity": "sha512-q1kcdHNZehBwD9jYPh3WyXcsFERi39X4I59I3NadciWtNDyZ6x+GboOxncFK0kXlKIv6BJm5acncehXWUjWQMQ==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.13.12", + "@babel/helper-validator-option": "^7.12.17", + "browserslist": "^4.14.5", + "semver": "^6.3.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.12.13.tgz", + "integrity": "sha512-TZvmPn0UOqmvi5G4vvw0qZTpVptGkB1GL61R6lKvrSdIxGm5Pky7Q3fpKiIkQCAtRCBUwB0PaThlx9vebCDSwA==", + "dev": true, + "dependencies": { + "@babel/helper-get-function-arity": "^7.12.13", + "@babel/template": "^7.12.13", + "@babel/types": "^7.12.13" + } + }, + "node_modules/@babel/helper-get-function-arity": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.13.tgz", + "integrity": "sha512-DjEVzQNz5LICkzN0REdpD5prGoidvbdYk1BVgRUOINaWJP2t6avB27X1guXK1kXNrX0WMfsrm1A/ZBthYuIMQg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.12.13" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.13.12", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.13.12.tgz", + "integrity": "sha512-48ql1CLL59aKbU94Y88Xgb2VFy7a95ykGRbJJaaVv+LX5U8wFpLfiGXJJGUozsmA1oEh/o5Bp60Voq7ACyA/Sw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.13.12" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.13.12", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.13.12.tgz", + "integrity": "sha512-4cVvR2/1B693IuOvSI20xqqa/+bl7lqAMR59R4iu39R9aOX8/JoYY1sFaNvUMyMBGnHdwvJgUrzNLoUZxXypxA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.13.12" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.13.14", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.13.14.tgz", + "integrity": "sha512-QuU/OJ0iAOSIatyVZmfqB0lbkVP0kDRiKj34xy+QNsnVZi/PA6BoSoreeqnxxa9EHFAIL0R9XOaAR/G9WlIy5g==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.13.12", + "@babel/helper-replace-supers": "^7.13.12", + "@babel/helper-simple-access": "^7.13.12", + "@babel/helper-split-export-declaration": "^7.12.13", + "@babel/helper-validator-identifier": "^7.12.11", + "@babel/template": "^7.12.13", + "@babel/traverse": "^7.13.13", + "@babel/types": "^7.13.14" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.12.13.tgz", + "integrity": "sha512-BdWQhoVJkp6nVjB7nkFWcn43dkprYauqtk++Py2eaf/GRDFm5BxRqEIZCiHlZUGAVmtwKcsVL1dC68WmzeFmiA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.12.13" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.13.0.tgz", + "integrity": "sha512-ZPafIPSwzUlAoWT8DKs1W2VyF2gOWthGd5NGFMsBcMMol+ZhK+EQY/e6V96poa6PA/Bh+C9plWN0hXO1uB8AfQ==", + "dev": true + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.13.12", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.13.12.tgz", + "integrity": "sha512-Gz1eiX+4yDO8mT+heB94aLVNCL+rbuT2xy4YfyNqu8F+OI6vMvJK891qGBTqL9Uc8wxEvRW92Id6G7sDen3fFw==", + "dev": true, + "dependencies": { + "@babel/helper-member-expression-to-functions": "^7.13.12", + "@babel/helper-optimise-call-expression": "^7.12.13", + "@babel/traverse": "^7.13.0", + "@babel/types": "^7.13.12" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.13.12", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.13.12.tgz", + "integrity": "sha512-7FEjbrx5SL9cWvXioDbnlYTppcZGuCY6ow3/D5vMggb2Ywgu4dMrpTJX0JdQAIcRRUElOIxF3yEooa9gUb9ZbA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.13.12" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.13.tgz", + "integrity": "sha512-tCJDltF83htUtXx5NLcaDqRmknv652ZWCHyoTETf1CXYJdPC7nohZohjUgieXhv0hTJdRf2FjDueFehdNucpzg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.12.13" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz", + "integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==", + "dev": true + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.12.17", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.12.17.tgz", + "integrity": "sha512-TopkMDmLzq8ngChwRlyjR6raKD6gMSae4JdYDB8bByKreQgG0RBTuKe9LRxW3wFtUnjxOPRKBDwEH6Mg5KeDfw==", + "dev": true + }, + "node_modules/@babel/helpers": { + "version": "7.13.10", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.13.10.tgz", + "integrity": "sha512-4VO883+MWPDUVRF3PhiLBUFHoX/bsLTGFpFK/HqvvfBZz2D57u9XzPVNFVBTc0PW/CWR9BXTOKt8NF4DInUHcQ==", + "dev": true, + "dependencies": { + "@babel/template": "^7.12.13", + "@babel/traverse": "^7.13.0", + "@babel/types": "^7.13.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.13.10", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.13.10.tgz", + "integrity": "sha512-5aPpe5XQPzflQrFwL1/QoeHkP2MsA4JCntcXHRhEsdsfPVkvPi2w7Qix4iV7t5S/oC9OodGrggd8aco1g3SZFg==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.12.11", + "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/@babel/parser": { + "version": "7.13.13", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.13.13.tgz", + "integrity": "sha512-OhsyMrqygfk5v8HmWwOzlYjJrtLaFhF34MrfG/Z73DgYCI6ojNUTUp2TYbtnjo8PegeJp12eamsNettCQjKjVw==", + "dev": true, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.12.13.tgz", + "integrity": "sha512-A81F9pDwyS7yM//KwbCSDqy3Uj4NMIurtplxphWxoYtNPov7cJsDkAFNNyVlIZ3jwGycVsurZ+LtOA8gZ376iQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.13.tgz", + "integrity": "sha512-/7xxiGA57xMo/P2GVvdEumr8ONhFOhfgq2ihK3h1e6THqzTAkHbkXgB0xI9yeTfIUoH3+oAeHhqm/I43OTbbjA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@babel/parser": "^7.12.13", + "@babel/types": "^7.12.13" + } + }, + "node_modules/@babel/traverse": { + "version": "7.13.13", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.13.13.tgz", + "integrity": "sha512-CblEcwmXKR6eP43oQGG++0QMTtCjAsa3frUuzHoiIJWpaIIi8dwMyEFUJoXRLxagGqCK+jALRwIO+o3R9p/uUg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@babel/generator": "^7.13.9", + "@babel/helper-function-name": "^7.12.13", + "@babel/helper-split-export-declaration": "^7.12.13", + "@babel/parser": "^7.13.13", + "@babel/types": "^7.13.13", + "debug": "^4.1.0", + "globals": "^11.1.0" + } + }, + "node_modules/@babel/types": { + "version": "7.13.14", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.13.14.tgz", + "integrity": "sha512-A2aa3QTkWoyqsZZFl56MLUsfmh7O0gN41IPvXAE/++8ojpbz12SszD7JEGYVdn4f9Kt4amIei07swF1h4AqmmQ==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.12.11", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true + }, + "node_modules/@cnakazawa/watch": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@cnakazawa/watch/-/watch-1.0.4.tgz", + "integrity": "sha512-v9kIhKwjeZThiWrLmj0y17CWoyddASLj9O2yvbZkbvw/N3rWOYy9zkV66ursAoVr0mV15bL8g0c4QZUE6cdDoQ==", + "dev": true, + "dependencies": { + "exec-sh": "^0.3.2", + "minimist": "^1.2.0" + }, + "bin": { + "watch": "cli.js" + }, + "engines": { + "node": ">=0.1.95" + } + }, "node_modules/@discordjs/collection": { "version": "0.1.6", "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-0.1.6.tgz", @@ -47,6 +551,310 @@ "node": ">= 6" } }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-26.6.2.tgz", + "integrity": "sha512-IY1R2i2aLsLr7Id3S6p2BA82GNWryt4oSvEXLAKc+L2zdi89dSkE8xC1C+0kpATG4JhBJREnQOH7/zmccM2B0g==", + "dev": true, + "dependencies": { + "@jest/types": "^26.6.2", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^26.6.2", + "jest-util": "^26.6.2", + "slash": "^3.0.0" + }, + "engines": { + "node": ">= 10.14.2" + } + }, + "node_modules/@jest/core": { + "version": "26.6.3", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-26.6.3.tgz", + "integrity": "sha512-xvV1kKbhfUqFVuZ8Cyo+JPpipAHHAV3kcDBftiduK8EICXmTFddryy3P7NfZt8Pv37rA9nEJBKCCkglCPt/Xjw==", + "dev": true, + "dependencies": { + "@jest/console": "^26.6.2", + "@jest/reporters": "^26.6.2", + "@jest/test-result": "^26.6.2", + "@jest/transform": "^26.6.2", + "@jest/types": "^26.6.2", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.4", + "jest-changed-files": "^26.6.2", + "jest-config": "^26.6.3", + "jest-haste-map": "^26.6.2", + "jest-message-util": "^26.6.2", + "jest-regex-util": "^26.0.0", + "jest-resolve": "^26.6.2", + "jest-resolve-dependencies": "^26.6.3", + "jest-runner": "^26.6.3", + "jest-runtime": "^26.6.3", + "jest-snapshot": "^26.6.2", + "jest-util": "^26.6.2", + "jest-validate": "^26.6.2", + "jest-watcher": "^26.6.2", + "micromatch": "^4.0.2", + "p-each-series": "^2.1.0", + "rimraf": "^3.0.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">= 10.14.2" + } + }, + "node_modules/@jest/environment": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-26.6.2.tgz", + "integrity": "sha512-nFy+fHl28zUrRsCeMB61VDThV1pVTtlEokBRgqPrcT1JNq4yRNIyTHfyht6PqtUvY9IsuLGTrbG8kPXjSZIZwA==", + "dev": true, + "dependencies": { + "@jest/fake-timers": "^26.6.2", + "@jest/types": "^26.6.2", + "@types/node": "*", + "jest-mock": "^26.6.2" + }, + "engines": { + "node": ">= 10.14.2" + } + }, + "node_modules/@jest/fake-timers": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-26.6.2.tgz", + "integrity": "sha512-14Uleatt7jdzefLPYM3KLcnUl1ZNikaKq34enpb5XG9i81JpppDb5muZvonvKyrl7ftEHkKS5L5/eB/kxJ+bvA==", + "dev": true, + "dependencies": { + "@jest/types": "^26.6.2", + "@sinonjs/fake-timers": "^6.0.1", + "@types/node": "*", + "jest-message-util": "^26.6.2", + "jest-mock": "^26.6.2", + "jest-util": "^26.6.2" + }, + "engines": { + "node": ">= 10.14.2" + } + }, + "node_modules/@jest/globals": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-26.6.2.tgz", + "integrity": "sha512-85Ltnm7HlB/KesBUuALwQ68YTU72w9H2xW9FjZ1eL1U3lhtefjjl5c2MiUbpXt/i6LaPRvoOFJ22yCBSfQ0JIA==", + "dev": true, + "dependencies": { + "@jest/environment": "^26.6.2", + "@jest/types": "^26.6.2", + "expect": "^26.6.2" + }, + "engines": { + "node": ">= 10.14.2" + } + }, + "node_modules/@jest/reporters": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-26.6.2.tgz", + "integrity": "sha512-h2bW53APG4HvkOnVMo8q3QXa6pcaNt1HkwVsOPMBV6LD/q9oSpxNSYZQYkAnjdMjrJ86UuYeLo+aEZClV6opnw==", + "dev": true, + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^26.6.2", + "@jest/test-result": "^26.6.2", + "@jest/transform": "^26.6.2", + "@jest/types": "^26.6.2", + "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": "^26.6.2", + "jest-resolve": "^26.6.2", + "jest-util": "^26.6.2", + "jest-worker": "^26.6.2", + "slash": "^3.0.0", + "source-map": "^0.6.0", + "string-length": "^4.0.1", + "terminal-link": "^2.0.0", + "v8-to-istanbul": "^7.0.0" + }, + "engines": { + "node": ">= 10.14.2" + }, + "optionalDependencies": { + "node-notifier": "^8.0.0" + } + }, + "node_modules/@jest/source-map": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-26.6.2.tgz", + "integrity": "sha512-YwYcCwAnNmOVsZ8mr3GfnzdXDAl4LaenZP5z+G0c8bzC9/dugL8zRmxZzdoTl4IaS3CryS1uWnROLPFmb6lVvA==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0", + "graceful-fs": "^4.2.4", + "source-map": "^0.6.0" + }, + "engines": { + "node": ">= 10.14.2" + } + }, + "node_modules/@jest/test-result": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-26.6.2.tgz", + "integrity": "sha512-5O7H5c/7YlojphYNrK02LlDIV2GNPYisKwHm2QTKjNZeEzezCbwYs9swJySv2UfPMyZ0VdsmMv7jIlD/IKYQpQ==", + "dev": true, + "dependencies": { + "@jest/console": "^26.6.2", + "@jest/types": "^26.6.2", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": ">= 10.14.2" + } + }, + "node_modules/@jest/test-sequencer": { + "version": "26.6.3", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-26.6.3.tgz", + "integrity": "sha512-YHlVIjP5nfEyjlrSr8t/YdNfU/1XEt7c5b4OxcXCjyRhjzLYu/rO69/WHPuYcbCWkz8kAeZVZp2N2+IOLLEPGw==", + "dev": true, + "dependencies": { + "@jest/test-result": "^26.6.2", + "graceful-fs": "^4.2.4", + "jest-haste-map": "^26.6.2", + "jest-runner": "^26.6.3", + "jest-runtime": "^26.6.3" + }, + "engines": { + "node": ">= 10.14.2" + } + }, + "node_modules/@jest/transform": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-26.6.2.tgz", + "integrity": "sha512-E9JjhUgNzvuQ+vVAL21vlyfy12gP0GhazGgJC4h6qUt1jSdUXGWJ1wfu/X7Sd8etSgxV4ovT1pb9v5D6QW4XgA==", + "dev": true, + "dependencies": { + "@babel/core": "^7.1.0", + "@jest/types": "^26.6.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": "^26.6.2", + "jest-regex-util": "^26.0.0", + "jest-util": "^26.6.2", + "micromatch": "^4.0.2", + "pirates": "^4.0.1", + "slash": "^3.0.0", + "source-map": "^0.6.1", + "write-file-atomic": "^3.0.0" + }, + "engines": { + "node": ">= 10.14.2" + } + }, + "node_modules/@jest/types": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", + "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^15.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": ">= 10.14.2" + } + }, "node_modules/@lavacord/discord.js": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/@lavacord/discord.js/-/discord.js-0.0.5.tgz", @@ -72,11 +880,89 @@ "npm": ">=5" } }, + "node_modules/@sinonjs/commons": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.2.tgz", + "integrity": "sha512-sruwd86RJHdsVf/AtBoijDmUqJp3B6hF/DGC23C+JaegnDHaZyewCjoVGTdg3J0uz3Zs7NnIT05OBOmML72lQw==", + "dev": true, + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-6.0.1.tgz", + "integrity": "sha512-MZPUxrmFubI36XS1DI3qmI0YdN1gks62JtFZvxR67ljjSNCeK6U08Zx4msEWOXuofgqUt6zPHSi1H9fbjR/NRA==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^1.7.0" + } + }, + "node_modules/@types/babel__core": { + "version": "7.1.14", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.14.tgz", + "integrity": "sha512-zGZJzzBUVDo/eV6KgbE0f0ZI7dInEYvo12Rb70uNQDshC3SkRMb67ja0GgRHZgAX3Za6rhaWlvbDO8rrGyAb1g==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.2.tgz", + "integrity": "sha512-MdSJnBjl+bdwkLskZ3NGFp9YcXGx5ggLpQQPqtgakVhsWK0hTtNYhjpZLlWQTviGTvF8at+Bvli3jV7faPdgeQ==", + "dev": true, + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.0.tgz", + "integrity": "sha512-NTPErx4/FiPCGScH7foPyr+/1Dkzkni+rHiYHHoTjvwou7AQzJkNeD60A9CXRy+ZEN2B1bggmkTMCDb+Mv5k+A==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.11.1", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.11.1.tgz", + "integrity": "sha512-Vs0hm0vPahPMYi9tDjtP66llufgO3ST16WXaSTtDGEl9cewAl3AibmxWw6TINOqHPT9z0uABKAYjT9jNSg4npw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.3.0" + } + }, "node_modules/@types/color-name": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==" }, + "node_modules/@types/glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-SEYeGAIQIQX8NN6LDKprLjbrd5dARM5EXsd8GI/A5l0apYI1fGMWgPHSe4ZKL4eozlAyI+doUE9XbYS4xCkQ1w==", + "dev": true, + "dependencies": { + "@types/minimatch": "*", + "@types/node": "*" + } + }, + "node_modules/@types/graceful-fs": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz", + "integrity": "sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/inquirer": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/@types/inquirer/-/inquirer-6.5.0.tgz", @@ -87,10 +973,44 @@ "rxjs": "^6.4.0" } }, - "node_modules/@types/mocha": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-8.2.0.tgz", - "integrity": "sha512-/Sge3BymXo4lKc31C8OINJgXLaw+7vL1/L1pGiBNpGrBiT8FQiaFpSYV0uhTaG4y78vcMBTMFsWaHDvuD+xGzQ==", + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz", + "integrity": "sha512-sz7iLqvVUg1gIedBOvlkxPlc8/uVzyS5OwGz1cKjXzkl3FpL3al0crU8YGU1WoHkxn0Wxbw5tyi6hvzJKNzFsw==", + "dev": true + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", + "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/jest": { + "version": "26.0.22", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-26.0.22.tgz", + "integrity": "sha512-eeWwWjlqxvBxc4oQdkueW5OF/gtfSceKk4OnOAGlUSwS/liBRtZppbJuz1YkgbrbfGOoeBHun9fOvXnjNwrSOw==", + "dev": true, + "dependencies": { + "jest-diff": "^26.0.0", + "pretty-format": "^26.0.0" + } + }, + "node_modules/@types/minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-1z8k4wzFnNjVK/tlxvrWuK5WMt6mydWWP7+zvH5eFep4oj+UkrfiJTRtjCeBXNpwaA/FYqqtb4/QS4ianFpIRA==", "dev": true }, "node_modules/@types/ms": { @@ -105,6 +1025,24 @@ "integrity": "sha512-Y93R97Ouif9JEOWPIUyU+eyIdyRqQR0I8Ez1dzku4hDx34NWh4HbtIc3WNzwB1Y9ULvNGeu5B8h8bVL5cAk4/A==", "dev": true }, + "node_modules/@types/normalize-package-data": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz", + "integrity": "sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==", + "dev": true + }, + "node_modules/@types/prettier": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.2.3.tgz", + "integrity": "sha512-PijRCG/K3s3w1We6ynUKdxEc5AcuuH3NBmMDP8uvKVp6X43UY7NQlTzczakXP3DJR0F4dfNQIGjU2cUeRYs2AA==", + "dev": true + }, + "node_modules/@types/stack-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.0.tgz", + "integrity": "sha512-RJJrrySY7A8havqpGObOB4W92QXKJo63/jFLLgpvOtsGUqbQZ9Sbgl35KMm1DjC6j7AvmmU2bIno+3IyEaemaw==", + "dev": true + }, "node_modules/@types/through": { "version": "0.0.30", "resolved": "https://registry.npmjs.org/@types/through/-/through-0.0.30.tgz", @@ -123,10 +1061,25 @@ "@types/node": "*" } }, - "node_modules/@ungap/promise-all-settled": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", - "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", + "node_modules/@types/yargs": { + "version": "15.0.13", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.13.tgz", + "integrity": "sha512-kQ5JNTrbDv3Rp5X2n/iUu37IJBDU2gsZ5R/g1/KHOOEc5IKfUFjXT6DENPGduh08I/pamwtEq4oul7gUqKTQDQ==", + "dev": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "20.2.0", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-20.2.0.tgz", + "integrity": "sha512-37RSHht+gzzgYeobbG+KWryeAW8J33Nhr69cjTqSYymXVZEN9NbRYWoYlRtDhHKPVT1FyNKwaTPC1NynKZpzRA==", + "dev": true + }, + "node_modules/abab": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.5.tgz", + "integrity": "sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q==", "dev": true }, "node_modules/abort-controller": { @@ -140,13 +1093,63 @@ "node": ">=6.5" } }, - "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==", + "node_modules/acorn": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.1.0.tgz", + "integrity": "sha512-LWCF/Wn0nfHOmJ9rzQApGnxnvgfROzGilS8936rqN/lfcYkY9MYZzdMqN+2NJ4SlTc+m5HiSa+kNfDtI64dwUA==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-globals": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz", + "integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==", + "dev": true, + "dependencies": { + "acorn": "^7.1.1", + "acorn-walk": "^7.1.1" + } + }, + "node_modules/acorn-globals/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-walk": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", + "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", "dev": true, "engines": { - "node": ">=6" + "node": ">=0.4.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-escapes": { @@ -197,7 +1200,9 @@ "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true + "dev": true, + "optional": true, + "peer": true }, "node_modules/argparse": { "version": "1.0.10", @@ -208,11 +1213,101 @@ "sprintf-js": "~1.0.2" } }, + "node_modules/arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/asn1": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "dev": true, + "dependencies": { + "safer-buffer": "~2.1.0" + } + }, + "node_modules/assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" }, + "node_modules/atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", + "dev": true, + "bin": { + "atob": "bin/atob.js" + }, + "engines": { + "node": ">= 4.5.0" + } + }, + "node_modules/aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/aws4": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", + "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==", + "dev": true + }, "node_modules/axios": { "version": "0.21.1", "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz", @@ -221,26 +1316,152 @@ "follow-redirects": "^1.10.0" } }, - "node_modules/balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true - }, - "node_modules/binary-extensions": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz", - "integrity": "sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==", + "node_modules/babel-jest": { + "version": "26.6.3", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-26.6.3.tgz", + "integrity": "sha512-pl4Q+GAVOHwvjrck6jKjvmGhnO3jHX/xuB9d27f+EJZ/6k+6nMuPjorrYp7s++bKKdANwzElBWnLWaObvTnaZA==", "dev": true, + "dependencies": { + "@jest/transform": "^26.6.2", + "@jest/types": "^26.6.2", + "@types/babel__core": "^7.1.7", + "babel-plugin-istanbul": "^6.0.0", + "babel-preset-jest": "^26.6.2", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.4", + "slash": "^3.0.0" + }, + "engines": { + "node": ">= 10.14.2" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.0.0.tgz", + "integrity": "sha512-AF55rZXpe7trmEylbaE1Gv54wn6rwU03aptvRoVIGP8YykoSxqdVLV1TfwflBCE/QtHmqtP8SWlTENqbK8GCSQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^4.0.0", + "test-exclude": "^6.0.0" + }, "engines": { "node": ">=8" } }, + "node_modules/babel-plugin-jest-hoist": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-26.6.2.tgz", + "integrity": "sha512-PO9t0697lNTmcEHH69mdtYiOIkkOlj9fySqfO3K1eCcdISevLAE0xY59VLLUj0SoiPiTX/JU2CYFpILydUa5Lw==", + "dev": true, + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.0.0", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": ">= 10.14.2" + } + }, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", + "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", + "dev": true, + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.8.3", + "@babel/plugin-syntax-import-meta": "^7.8.3", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.8.3", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-top-level-await": "^7.8.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/babel-preset-jest": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-26.6.2.tgz", + "integrity": "sha512-YvdtlVm9t3k777c5NPQIv6cxFFFapys25HiUmuSgHwIZhfifweR5c5Sf5nwE3MAbfu327CYSvps8Yx6ANLyleQ==", + "dev": true, + "dependencies": { + "babel-plugin-jest-hoist": "^26.6.2", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": ">= 10.14.2" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + }, + "node_modules/base": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", + "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", + "dev": true, + "dependencies": { + "cache-base": "^1.0.1", + "class-utils": "^0.3.5", + "component-emitter": "^1.2.1", + "define-property": "^1.0.0", + "isobject": "^3.0.1", + "mixin-deep": "^1.2.0", + "pascalcase": "^0.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/base/node_modules/define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "dependencies": { + "is-descriptor": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "dev": true, + "dependencies": { + "tweetnacl": "^0.14.3" + } + }, + "node_modules/bcrypt-pbkdf/node_modules/tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "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" @@ -258,18 +1479,91 @@ "node": ">=8" } }, - "node_modules/browser-stdout": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", - "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "node_modules/browser-process-hrtime": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", + "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==", "dev": true }, + "node_modules/browserslist": { + "version": "4.16.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.3.tgz", + "integrity": "sha512-vIyhWmIkULaq04Gt93txdh+j02yX/JzlyhLYbV3YQCn/zvES3JnY7TifHHvvr1w5hTDluNKMkV05cs4vy8Q7sw==", + "dev": true, + "dependencies": { + "caniuse-lite": "^1.0.30001181", + "colorette": "^1.2.1", + "electron-to-chromium": "^1.3.649", + "escalade": "^3.1.1", + "node-releases": "^1.1.70" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + } + }, + "node_modules/bs-logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", + "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", + "dev": true, + "dependencies": { + "fast-json-stable-stringify": "2.x" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "dependencies": { + "node-int64": "^0.4.0" + } + }, "node_modules/buffer-from": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", "dev": true }, + "node_modules/cache-base": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", + "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "dev": true, + "dependencies": { + "collection-visit": "^1.0.0", + "component-emitter": "^1.2.1", + "get-value": "^2.0.6", + "has-value": "^1.0.0", + "isobject": "^3.0.1", + "set-value": "^2.0.0", + "to-object-path": "^0.3.0", + "union-value": "^1.0.0", + "unset-value": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/camelcase": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", @@ -279,6 +1573,30 @@ "node": ">=6" } }, + "node_modules/caniuse-lite": { + "version": "1.0.30001204", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001204.tgz", + "integrity": "sha512-JUdjWpcxfJ9IPamy2f5JaRDCaqJOxDzOSKtbdx4rH9VivMd1vIzoPumsJa9LoMIi4Fx2BV2KZOxWhNkBjaYivQ==", + "dev": true + }, + "node_modules/capture-exit": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/capture-exit/-/capture-exit-2.0.0.tgz", + "integrity": "sha512-PiT/hQmTonHhl/HFGN+Lx3JJUznrVYJ3+AQsnthneZbvW7x+f08Tk7yLJTLEOUvBTbduLeeBkxEaYXUOUrRq6g==", + "dev": true, + "dependencies": { + "rsvp": "^4.8.4" + }, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", + "dev": true + }, "node_modules/chalk": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", @@ -291,30 +1609,128 @@ "node": ">=10" } }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, "node_modules/chardet": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==" }, - "node_modules/chokidar": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.3.tgz", - "integrity": "sha512-DtM3g7juCXQxFVSNPNByEC2+NImtBuxQQvWlHunpJIS5Ocr0lG306cC7FCi7cEA0fzmybPUIl4txBIobk1gGOQ==", + "node_modules/ci-info": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", + "dev": true + }, + "node_modules/cjs-module-lexer": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-0.6.0.tgz", + "integrity": "sha512-uc2Vix1frTfnuzxxu1Hp4ktSvM3QaI4oXl4ZUqL1wjTu/BGki9TrCWoqLTg/drR1KwAEarXuRFCG2Svr1GxPFw==", + "dev": true + }, + "node_modules/class-utils": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", + "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", "dev": true, "dependencies": { - "anymatch": "~3.1.1", - "braces": "~3.0.2", - "glob-parent": "~5.1.0", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.5.0" + "arr-union": "^3.1.0", + "define-property": "^0.2.5", + "isobject": "^3.0.0", + "static-extend": "^0.1.1" }, "engines": { - "node": ">= 8.10.0" + "node": ">=0.10.0" + } + }, + "node_modules/class-utils/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "dependencies": { + "is-descriptor": "^0.1.0" }, - "optionalDependencies": { - "fsevents": "~2.1.2" + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/class-utils/node_modules/is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/class-utils/node_modules/is-accessor-descriptor/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/class-utils/node_modules/is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/class-utils/node_modules/is-data-descriptor/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/class-utils/node_modules/is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/class-utils/node_modules/kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true, + "engines": { + "node": ">=0.10.0" } }, "node_modules/cli-cursor": { @@ -336,65 +1752,33 @@ "node": ">= 10" } }, - "node_modules/cliui": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", - "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", - "dev": true, - "dependencies": { - "string-width": "^3.1.0", - "strip-ansi": "^5.2.0", - "wrap-ansi": "^5.1.0" - } - }, - "node_modules/cliui/node_modules/ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", "dev": true, "engines": { - "node": ">=6" + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" } }, - "node_modules/cliui/node_modules/emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "node_modules/collect-v8-coverage": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", + "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==", "dev": true }, - "node_modules/cliui/node_modules/is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/cliui/node_modules/string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "node_modules/collection-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", + "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", "dev": true, "dependencies": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" + "map-visit": "^1.0.0", + "object-visit": "^1.0.0" }, "engines": { - "node": ">=6" - } - }, - "node_modules/cliui/node_modules/strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "dependencies": { - "ansi-regex": "^4.1.0" - }, - "engines": { - "node": ">=6" + "node": ">=0.10.0" } }, "node_modules/color-convert": { @@ -413,6 +1797,12 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, + "node_modules/colorette": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.2.tgz", + "integrity": "sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==", + "dev": true + }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -424,17 +1814,54 @@ "node": ">= 0.8" } }, + "node_modules/component-emitter": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", + "dev": true + }, "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=", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "node_modules/convert-source-map": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", + "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.1" + } + }, + "node_modules/convert-source-map/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/copy-descriptor": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", + "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", "dev": true }, "node_modules/create-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true + "dev": true, + "optional": true, + "peer": true }, "node_modules/cross-spawn": { "version": "7.0.3", @@ -450,6 +1877,56 @@ "node": ">= 8" } }, + "node_modules/cssom": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz", + "integrity": "sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw==", + "dev": true + }, + "node_modules/cssstyle": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", + "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", + "dev": true, + "dependencies": { + "cssom": "~0.3.6" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cssstyle/node_modules/cssom": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", + "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", + "dev": true + }, + "node_modules/dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "dev": true, + "dependencies": { + "assert-plus": "^1.0.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/data-urls": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz", + "integrity": "sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ==", + "dev": true, + "dependencies": { + "abab": "^2.0.3", + "whatwg-mimetype": "^2.3.0", + "whatwg-url": "^8.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/debug": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", @@ -477,6 +1954,49 @@ "node": ">=0.10.0" } }, + "node_modules/decimal.js": { + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.2.1.tgz", + "integrity": "sha512-KaL7+6Fw6i5A2XSnsbhm/6B+NuEA7TZ4vqxnd5tXz9sbKtrN9Srj8ab4vKVdK8YAqZO9P1kg45Y6YLoduPf+kw==", + "dev": true + }, + "node_modules/decode-uri-component": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", + "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", + "dev": true, + "engines": { + "node": ">=0.10" + } + }, + "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/deepmerge": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", + "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/define-property": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "dev": true, + "dependencies": { + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -485,15 +2005,35 @@ "node": ">=0.4.0" } }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/diff": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", "dev": true, + "optional": true, + "peer": true, "engines": { "node": ">=0.3.1" } }, + "node_modules/diff-sequences": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-26.6.2.tgz", + "integrity": "sha512-Mv/TDa3nZ9sbc5soK+OoA74BsS3mL37yixCvUAQkiuA4Wz6YtwP/K47n2rv2ovzHZvoiQeA5FTQOschKkEwB0Q==", + "dev": true, + "engines": { + "node": ">= 10.14.2" + } + }, "node_modules/discord.js": { "version": "12.5.1", "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-12.5.1.tgz", @@ -523,17 +2063,93 @@ "lavacord": "^1.1.7" } }, + "node_modules/domexception": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz", + "integrity": "sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg==", + "dev": true, + "dependencies": { + "webidl-conversions": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/domexception/node_modules/webidl-conversions": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz", + "integrity": "sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/duplexer": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=", "dev": true }, + "node_modules/ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "dev": true, + "dependencies": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.3.702", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.702.tgz", + "integrity": "sha512-qJVUKFWQnF6wP7MmTngDkmm8/KPzaiTXNFOAg5j7DSa6J7kPou7mTBqC8jpUOxauQWwHR3pn4dMRdV8IE1xdtA==", + "dev": true + }, + "node_modules/emittery": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.7.2.tgz", + "integrity": "sha512-A8OG5SR/ij3SsJdWDJdkkSYUjQdCUx6APQXem0SaEePBSRg4eymGYwBkKo1Y6DU+af/Jn2dBQqDBvjnr9Vi8nQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", @@ -542,6 +2158,28 @@ "node": ">=0.8.0" } }, + "node_modules/escodegen": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.0.0.tgz", + "integrity": "sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw==", + "dev": true, + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2", + "optionator": "^0.8.1" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, "node_modules/esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", @@ -555,6 +2193,24 @@ "node": ">=4" } }, + "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/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-stream": { "version": "3.3.4", "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz", @@ -578,6 +2234,217 @@ "node": ">=6" } }, + "node_modules/exec-sh": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/exec-sh/-/exec-sh-0.3.6.tgz", + "integrity": "sha512-nQn+hI3yp+oD0huYhKwvYI32+JFeq+XkNcD1GAo3Y/MjxsfVGmrrzrnzjWiNY6f+pUCP440fThsFh5gZrRAU/w==", + "dev": true + }, + "node_modules/execa": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz", + "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.0", + "get-stream": "^5.0.0", + "human-signals": "^1.1.1", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.0", + "onetime": "^5.1.0", + "signal-exit": "^3.0.2", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "dev": true, + "dependencies": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/expand-brackets/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/is-accessor-descriptor/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/is-data-descriptor/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "node_modules/expect": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/expect/-/expect-26.6.2.tgz", + "integrity": "sha512-9/hlOBkQl2l/PLHJx6JjoDF6xPKcJEsUlWKb23rKE7KzeDqUZKXKNMW27KIue5JMdBV9HgmoJPcc8HtO85t9IA==", + "dev": true, + "dependencies": { + "@jest/types": "^26.6.2", + "ansi-styles": "^4.0.0", + "jest-get-type": "^26.3.0", + "jest-matcher-utils": "^26.6.2", + "jest-message-util": "^26.6.2", + "jest-regex-util": "^26.0.0" + }, + "engines": { + "node": ">= 10.14.2" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + }, + "node_modules/extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "dev": true, + "dependencies": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/external-editor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", @@ -591,6 +2458,94 @@ "node": ">=4" } }, + "node_modules/extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "dev": true, + "dependencies": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extglob/node_modules/define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "dependencies": { + "is-descriptor": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extglob/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extglob/node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", + "dev": true, + "engines": [ + "node >=0.6.0" + ] + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "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/fb-watchman": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.1.tgz", + "integrity": "sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg==", + "dev": true, + "dependencies": { + "bser": "2.1.1" + } + }, "node_modules/figures": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", @@ -614,28 +2569,6 @@ "node": ">=8" } }, - "node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/flat": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", - "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", - "dev": true, - "bin": { - "flat": "cli.js" - } - }, "node_modules/follow-redirects": { "version": "1.13.1", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.1.tgz", @@ -644,6 +2577,50 @@ "node": ">=4.0" } }, + "node_modules/for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "dev": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 0.12" + } + }, + "node_modules/fragment-cache": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", + "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", + "dev": true, + "dependencies": { + "map-cache": "^0.2.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/from": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz", @@ -653,8 +2630,7 @@ "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 + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, "node_modules/fsevents": { "version": "2.1.3", @@ -669,6 +2645,21 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", @@ -678,11 +2669,52 @@ "node": "6.* || 8.* || >= 10.*" } }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "dev": true, + "dependencies": { + "assert-plus": "^1.0.0" + } + }, "node_modules/glob": { "version": "7.1.6", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -695,25 +2727,61 @@ "node": "*" } }, - "node_modules/glob-parent": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", - "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", "dev": true, - "dependencies": { - "is-glob": "^4.0.1" - }, "engines": { - "node": ">= 6" + "node": ">=4" } }, - "node_modules/growl": { - "version": "1.10.5", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", - "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "node_modules/graceful-fs": { + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", + "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==", + "dev": true + }, + "node_modules/growly": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz", + "integrity": "sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=", + "dev": true, + "optional": true + }, + "node_modules/har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", "dev": true, "engines": { - "node": ">=4.x" + "node": ">=4" + } + }, + "node_modules/har-validator": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", + "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", + "deprecated": "this library is no longer supported", + "dev": true, + "dependencies": { + "ajv": "^6.12.3", + "har-schema": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" } }, "node_modules/has-flag": { @@ -724,13 +2792,137 @@ "node": ">=8" } }, - "node_modules/he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "node_modules/has-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", + "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", "dev": true, + "dependencies": { + "get-value": "^2.0.6", + "has-values": "^1.0.0", + "isobject": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-values": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", + "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", + "dev": true, + "dependencies": { + "is-number": "^3.0.0", + "kind-of": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-values/node_modules/is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-values/node_modules/is-number/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-values/node_modules/kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/hosted-git-info": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz", + "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==", + "dev": true + }, + "node_modules/html-encoding-sniffer": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz", + "integrity": "sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ==", + "dev": true, + "dependencies": { + "whatwg-encoding": "^1.0.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, + "node_modules/http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "dev": true, + "dependencies": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + }, + "engines": { + "node": ">=0.8", + "npm": ">=1.3.7" + } + }, + "node_modules/human-signals": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", + "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", + "dev": true, + "engines": { + "node": ">=8.12.0" + } + }, + "node_modules/husky": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/husky/-/husky-5.2.0.tgz", + "integrity": "sha512-AM8T/auHXRBxlrfPVLKP6jt49GCM2Zz47m8G3FOMsLmTv8Dj/fKVWE0Rh2d4Qrvmy131xEsdQnb3OXRib67PGg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/typicode" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/husky" + } + ], "bin": { - "he": "bin/he" + "husky": "lib/bin.js" + }, + "engines": { + "node": ">= 10" } }, "node_modules/iconv-lite": { @@ -744,11 +2936,35 @@ "node": ">=0.10.0" } }, + "node_modules/import-local": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.0.2.tgz", + "integrity": "sha512-vjL3+w0oulAVZ0hBHnxa/Nm5TAurf9YLQJDhqRZyqb+VKGOB6LU8t9H1Nr5CIo16vh9XfJTOoHwU0B71S557gA==", + "dev": true, + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "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" @@ -757,8 +2973,7 @@ "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 + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "node_modules/inquirer": { "version": "7.3.3", @@ -783,23 +2998,104 @@ "node": ">=8.0.0" } }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "node_modules/is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", "dev": true, "dependencies": { - "binary-extensions": "^2.0.0" + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, + "node_modules/is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, + "node_modules/is-ci": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", + "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", + "dev": true, + "dependencies": { + "ci-info": "^2.0.0" + }, + "bin": { + "is-ci": "bin.js" + } + }, + "node_modules/is-core-module": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.2.0.tgz", + "integrity": "sha512-XRAfAdyyY5F5cOXn7hYQDqh2Xmii+DEfIcQGxK/uNwMHhIkPWO0g8msXcbzLe+MpGoR951MlqM/2iIlU4vKDdQ==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-docker": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.1.1.tgz", + "integrity": "sha512-ZOoqiXfEwtGknTiuDEy8pN2CfE3TxMHprvNer1mXiqwkOT77Rw3YVrUQ52EqAOU3QAWDQ+bQdx7HJzrv7LS2Hw==", + "dev": true, + "optional": true, + "bin": { + "is-docker": "cli.js" }, "engines": { "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "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=", + "node_modules/is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", "dev": true, + "dependencies": { + "is-plain-object": "^2.0.4" + }, "engines": { "node": ">=0.10.0" } @@ -812,16 +3108,13 @@ "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==", + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", "dev": true, - "dependencies": { - "is-extglob": "^2.1.1" - }, "engines": { - "node": ">=0.10.0" + "node": ">=6" } }, "node_modules/is-number": { @@ -833,21 +3126,946 @@ "node": ">=0.12.0" } }, - "node_modules/is-plain-obj": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", - "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.0.tgz", + "integrity": "sha1-DFLlS8yjkbssSUsh6GJtczbG45c=", + "dev": true + }, + "node_modules/is-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", + "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", "dev": true, "engines": { "node": ">=8" } }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "dev": true + }, + "node_modules/is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "optional": true, + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, "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/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", + "dev": true + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz", + "integrity": "sha512-UiUIqxMgRDET6eR+o5HbfRYP1l0hqkWOs7vNxC/mggutCMUIhWMm8gAHb8tHlyfD3/l6rlgNA5cKdDzEAf6hEg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", + "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", + "dev": true, + "dependencies": { + "@babel/core": "^7.7.5", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.0.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", + "dev": true, + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^3.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.0.tgz", + "integrity": "sha512-c16LpFRkR8vQXyHZ5nLpY35JZtzj1PQY1iZmesUbf1FZHbIupcWfjgOXBY9YHkLEQ6puz1u4Dgj6qmU/DisrZg==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-reports": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.0.2.tgz", + "integrity": "sha512-9tZvz7AiR3PEDNGiV9vIouQ/EAcqMXFmkcA1CDFTwOB98OZVDL0PH9glHotf5Ugp6GCOTypfzGWI/OqjWNCRUw==", + "dev": true, + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest": { + "version": "26.6.3", + "resolved": "https://registry.npmjs.org/jest/-/jest-26.6.3.tgz", + "integrity": "sha512-lGS5PXGAzR4RF7V5+XObhqz2KZIDUA1yD0DG6pBVmy10eh0ZIXQImRuzocsI/N2XZ1GrLFwTS27In2i2jlpq1Q==", + "dev": true, + "dependencies": { + "@jest/core": "^26.6.3", + "import-local": "^3.0.2", + "jest-cli": "^26.6.3" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": ">= 10.14.2" + } + }, + "node_modules/jest-changed-files": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-26.6.2.tgz", + "integrity": "sha512-fDS7szLcY9sCtIip8Fjry9oGf3I2ht/QT21bAHm5Dmf0mD4X3ReNUf17y+bO6fR8WgbIZTlbyG1ak/53cbRzKQ==", + "dev": true, + "dependencies": { + "@jest/types": "^26.6.2", + "execa": "^4.0.0", + "throat": "^5.0.0" + }, + "engines": { + "node": ">= 10.14.2" + } + }, + "node_modules/jest-cli": { + "version": "26.6.3", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-26.6.3.tgz", + "integrity": "sha512-GF9noBSa9t08pSyl3CY4frMrqp+aQXFGFkf5hEPbh/pIUFYWMK6ZLTfbmadxJVcJrdRoChlWQsA2VkJcDFK8hg==", + "dev": true, + "dependencies": { + "@jest/core": "^26.6.3", + "@jest/test-result": "^26.6.2", + "@jest/types": "^26.6.2", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.4", + "import-local": "^3.0.2", + "is-ci": "^2.0.0", + "jest-config": "^26.6.3", + "jest-util": "^26.6.2", + "jest-validate": "^26.6.2", + "prompts": "^2.0.1", + "yargs": "^15.4.1" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": ">= 10.14.2" + } + }, + "node_modules/jest-cli/node_modules/cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "node_modules/jest-cli/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-cli/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-cli/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-cli/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-cli/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-cli/node_modules/yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "dev": true, + "dependencies": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-cli/node_modules/yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "dev": true, + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jest-config": { + "version": "26.6.3", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-26.6.3.tgz", + "integrity": "sha512-t5qdIj/bCj2j7NFVHb2nFB4aUdfucDn3JRKgrZnplb8nieAirAzRSHP8uDEd+qV6ygzg9Pz4YG7UTJf94LPSyg==", + "dev": true, + "dependencies": { + "@babel/core": "^7.1.0", + "@jest/test-sequencer": "^26.6.3", + "@jest/types": "^26.6.2", + "babel-jest": "^26.6.3", + "chalk": "^4.0.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.1", + "graceful-fs": "^4.2.4", + "jest-environment-jsdom": "^26.6.2", + "jest-environment-node": "^26.6.2", + "jest-get-type": "^26.3.0", + "jest-jasmine2": "^26.6.3", + "jest-regex-util": "^26.0.0", + "jest-resolve": "^26.6.2", + "jest-util": "^26.6.2", + "jest-validate": "^26.6.2", + "micromatch": "^4.0.2", + "pretty-format": "^26.6.2" + }, + "engines": { + "node": ">= 10.14.2" + }, + "peerDependencies": { + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "ts-node": { + "optional": true + } + } + }, + "node_modules/jest-diff": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-26.6.2.tgz", + "integrity": "sha512-6m+9Z3Gv9wN0WFVasqjCL/06+EFCMTqDEUl/b87HYK2rAPTyfz4ZIuSlPhY51PIQRWx5TaxeF1qmXKe9gfN3sA==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^26.6.2", + "jest-get-type": "^26.3.0", + "pretty-format": "^26.6.2" + }, + "engines": { + "node": ">= 10.14.2" + } + }, + "node_modules/jest-docblock": { + "version": "26.0.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-26.0.0.tgz", + "integrity": "sha512-RDZ4Iz3QbtRWycd8bUEPxQsTlYazfYn/h5R65Fc6gOfwozFhoImx+affzky/FFBuqISPTqjXomoIGJVKBWoo0w==", + "dev": true, + "dependencies": { + "detect-newline": "^3.0.0" + }, + "engines": { + "node": ">= 10.14.2" + } + }, + "node_modules/jest-each": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-26.6.2.tgz", + "integrity": "sha512-Mer/f0KaATbjl8MCJ+0GEpNdqmnVmDYqCTJYTvoo7rqmRiDllmp2AYN+06F93nXcY3ur9ShIjS+CO/uD+BbH4A==", + "dev": true, + "dependencies": { + "@jest/types": "^26.6.2", + "chalk": "^4.0.0", + "jest-get-type": "^26.3.0", + "jest-util": "^26.6.2", + "pretty-format": "^26.6.2" + }, + "engines": { + "node": ">= 10.14.2" + } + }, + "node_modules/jest-environment-jsdom": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-26.6.2.tgz", + "integrity": "sha512-jgPqCruTlt3Kwqg5/WVFyHIOJHsiAvhcp2qiR2QQstuG9yWox5+iHpU3ZrcBxW14T4fe5Z68jAfLRh7joCSP2Q==", + "dev": true, + "dependencies": { + "@jest/environment": "^26.6.2", + "@jest/fake-timers": "^26.6.2", + "@jest/types": "^26.6.2", + "@types/node": "*", + "jest-mock": "^26.6.2", + "jest-util": "^26.6.2", + "jsdom": "^16.4.0" + }, + "engines": { + "node": ">= 10.14.2" + } + }, + "node_modules/jest-environment-node": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-26.6.2.tgz", + "integrity": "sha512-zhtMio3Exty18dy8ee8eJ9kjnRyZC1N4C1Nt/VShN1apyXc8rWGtJ9lI7vqiWcyyXS4BVSEn9lxAM2D+07/Tag==", + "dev": true, + "dependencies": { + "@jest/environment": "^26.6.2", + "@jest/fake-timers": "^26.6.2", + "@jest/types": "^26.6.2", + "@types/node": "*", + "jest-mock": "^26.6.2", + "jest-util": "^26.6.2" + }, + "engines": { + "node": ">= 10.14.2" + } + }, + "node_modules/jest-get-type": { + "version": "26.3.0", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-26.3.0.tgz", + "integrity": "sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig==", + "dev": true, + "engines": { + "node": ">= 10.14.2" + } + }, + "node_modules/jest-haste-map": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-26.6.2.tgz", + "integrity": "sha512-easWIJXIw71B2RdR8kgqpjQrbMRWQBgiBwXYEhtGUTaX+doCjBheluShdDMeR8IMfJiTqH4+zfhtg29apJf/8w==", + "dev": true, + "dependencies": { + "@jest/types": "^26.6.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": "^26.0.0", + "jest-serializer": "^26.6.2", + "jest-util": "^26.6.2", + "jest-worker": "^26.6.2", + "micromatch": "^4.0.2", + "sane": "^4.0.3", + "walker": "^1.0.7" + }, + "engines": { + "node": ">= 10.14.2" + }, + "optionalDependencies": { + "fsevents": "^2.1.2" + } + }, + "node_modules/jest-jasmine2": { + "version": "26.6.3", + "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-26.6.3.tgz", + "integrity": "sha512-kPKUrQtc8aYwBV7CqBg5pu+tmYXlvFlSFYn18ev4gPFtrRzB15N2gW/Roew3187q2w2eHuu0MU9TJz6w0/nPEg==", + "dev": true, + "dependencies": { + "@babel/traverse": "^7.1.0", + "@jest/environment": "^26.6.2", + "@jest/source-map": "^26.6.2", + "@jest/test-result": "^26.6.2", + "@jest/types": "^26.6.2", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "expect": "^26.6.2", + "is-generator-fn": "^2.0.0", + "jest-each": "^26.6.2", + "jest-matcher-utils": "^26.6.2", + "jest-message-util": "^26.6.2", + "jest-runtime": "^26.6.3", + "jest-snapshot": "^26.6.2", + "jest-util": "^26.6.2", + "pretty-format": "^26.6.2", + "throat": "^5.0.0" + }, + "engines": { + "node": ">= 10.14.2" + } + }, + "node_modules/jest-leak-detector": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-26.6.2.tgz", + "integrity": "sha512-i4xlXpsVSMeKvg2cEKdfhh0H39qlJlP5Ex1yQxwF9ubahboQYMgTtz5oML35AVA3B4Eu+YsmwaiKVev9KCvLxg==", + "dev": true, + "dependencies": { + "jest-get-type": "^26.3.0", + "pretty-format": "^26.6.2" + }, + "engines": { + "node": ">= 10.14.2" + } + }, + "node_modules/jest-matcher-utils": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-26.6.2.tgz", + "integrity": "sha512-llnc8vQgYcNqDrqRDXWwMr9i7rS5XFiCwvh6DTP7Jqa2mqpcCBBlpCbn+trkG0KNhPu/h8rzyBkriOtBstvWhw==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^26.6.2", + "jest-get-type": "^26.3.0", + "pretty-format": "^26.6.2" + }, + "engines": { + "node": ">= 10.14.2" + } + }, + "node_modules/jest-message-util": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-26.6.2.tgz", + "integrity": "sha512-rGiLePzQ3AzwUshu2+Rn+UMFk0pHN58sOG+IaJbk5Jxuqo3NYO1U2/MIR4S1sKgsoYSXSzdtSa0TgrmtUwEbmA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "@jest/types": "^26.6.2", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.4", + "micromatch": "^4.0.2", + "pretty-format": "^26.6.2", + "slash": "^3.0.0", + "stack-utils": "^2.0.2" + }, + "engines": { + "node": ">= 10.14.2" + } + }, + "node_modules/jest-mock": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-26.6.2.tgz", + "integrity": "sha512-YyFjePHHp1LzpzYcmgqkJ0nm0gg/lJx2aZFzFy1S6eUqNjXsOqTK10zNRff2dNfssgokjkG65OlWNcIlgd3zew==", + "dev": true, + "dependencies": { + "@jest/types": "^26.6.2", + "@types/node": "*" + }, + "engines": { + "node": ">= 10.14.2" + } + }, + "node_modules/jest-pnp-resolver": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz", + "integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==", + "dev": true, + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } + } + }, + "node_modules/jest-regex-util": { + "version": "26.0.0", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-26.0.0.tgz", + "integrity": "sha512-Gv3ZIs/nA48/Zvjrl34bf+oD76JHiGDUxNOVgUjh3j890sblXryjY4rss71fPtD/njchl6PSE2hIhvyWa1eT0A==", + "dev": true, + "engines": { + "node": ">= 10.14.2" + } + }, + "node_modules/jest-resolve": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-26.6.2.tgz", + "integrity": "sha512-sOxsZOq25mT1wRsfHcbtkInS+Ek7Q8jCHUB0ZUTP0tc/c41QHriU/NunqMfCUWsL4H3MHpvQD4QR9kSYhS7UvQ==", + "dev": true, + "dependencies": { + "@jest/types": "^26.6.2", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.4", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^26.6.2", + "read-pkg-up": "^7.0.1", + "resolve": "^1.18.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">= 10.14.2" + } + }, + "node_modules/jest-resolve-dependencies": { + "version": "26.6.3", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-26.6.3.tgz", + "integrity": "sha512-pVwUjJkxbhe4RY8QEWzN3vns2kqyuldKpxlxJlzEYfKSvY6/bMvxoFrYYzUO1Gx28yKWN37qyV7rIoIp2h8fTg==", + "dev": true, + "dependencies": { + "@jest/types": "^26.6.2", + "jest-regex-util": "^26.0.0", + "jest-snapshot": "^26.6.2" + }, + "engines": { + "node": ">= 10.14.2" + } + }, + "node_modules/jest-runner": { + "version": "26.6.3", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-26.6.3.tgz", + "integrity": "sha512-atgKpRHnaA2OvByG/HpGA4g6CSPS/1LK0jK3gATJAoptC1ojltpmVlYC3TYgdmGp+GLuhzpH30Gvs36szSL2JQ==", + "dev": true, + "dependencies": { + "@jest/console": "^26.6.2", + "@jest/environment": "^26.6.2", + "@jest/test-result": "^26.6.2", + "@jest/types": "^26.6.2", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.7.1", + "exit": "^0.1.2", + "graceful-fs": "^4.2.4", + "jest-config": "^26.6.3", + "jest-docblock": "^26.0.0", + "jest-haste-map": "^26.6.2", + "jest-leak-detector": "^26.6.2", + "jest-message-util": "^26.6.2", + "jest-resolve": "^26.6.2", + "jest-runtime": "^26.6.3", + "jest-util": "^26.6.2", + "jest-worker": "^26.6.2", + "source-map-support": "^0.5.6", + "throat": "^5.0.0" + }, + "engines": { + "node": ">= 10.14.2" + } + }, + "node_modules/jest-runtime": { + "version": "26.6.3", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-26.6.3.tgz", + "integrity": "sha512-lrzyR3N8sacTAMeonbqpnSka1dHNux2uk0qqDXVkMv2c/A3wYnvQ4EXuI013Y6+gSKSCxdaczvf4HF0mVXHRdw==", + "dev": true, + "dependencies": { + "@jest/console": "^26.6.2", + "@jest/environment": "^26.6.2", + "@jest/fake-timers": "^26.6.2", + "@jest/globals": "^26.6.2", + "@jest/source-map": "^26.6.2", + "@jest/test-result": "^26.6.2", + "@jest/transform": "^26.6.2", + "@jest/types": "^26.6.2", + "@types/yargs": "^15.0.0", + "chalk": "^4.0.0", + "cjs-module-lexer": "^0.6.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.4", + "jest-config": "^26.6.3", + "jest-haste-map": "^26.6.2", + "jest-message-util": "^26.6.2", + "jest-mock": "^26.6.2", + "jest-regex-util": "^26.0.0", + "jest-resolve": "^26.6.2", + "jest-snapshot": "^26.6.2", + "jest-util": "^26.6.2", + "jest-validate": "^26.6.2", + "slash": "^3.0.0", + "strip-bom": "^4.0.0", + "yargs": "^15.4.1" + }, + "bin": { + "jest-runtime": "bin/jest-runtime.js" + }, + "engines": { + "node": ">= 10.14.2" + } + }, + "node_modules/jest-runtime/node_modules/cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "node_modules/jest-runtime/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-runtime/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-runtime/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-runtime/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-runtime/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-runtime/node_modules/yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "dev": true, + "dependencies": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-runtime/node_modules/yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "dev": true, + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jest-serializer": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-26.6.2.tgz", + "integrity": "sha512-S5wqyz0DXnNJPd/xfIzZ5Xnp1HrJWBczg8mMfMpN78OJ5eDxXyf+Ygld9wX1DnUWbIbhM1YDY95NjR4CBXkb2g==", + "dev": true, + "dependencies": { + "@types/node": "*", + "graceful-fs": "^4.2.4" + }, + "engines": { + "node": ">= 10.14.2" + } + }, + "node_modules/jest-snapshot": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-26.6.2.tgz", + "integrity": "sha512-OLhxz05EzUtsAmOMzuupt1lHYXCNib0ECyuZ/PZOx9TrZcC8vL0x+DUG3TL+GLX3yHG45e6YGjIm0XwDc3q3og==", + "dev": true, + "dependencies": { + "@babel/types": "^7.0.0", + "@jest/types": "^26.6.2", + "@types/babel__traverse": "^7.0.4", + "@types/prettier": "^2.0.0", + "chalk": "^4.0.0", + "expect": "^26.6.2", + "graceful-fs": "^4.2.4", + "jest-diff": "^26.6.2", + "jest-get-type": "^26.3.0", + "jest-haste-map": "^26.6.2", + "jest-matcher-utils": "^26.6.2", + "jest-message-util": "^26.6.2", + "jest-resolve": "^26.6.2", + "natural-compare": "^1.4.0", + "pretty-format": "^26.6.2", + "semver": "^7.3.2" + }, + "engines": { + "node": ">= 10.14.2" + } + }, + "node_modules/jest-snapshot/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/jest-util": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-26.6.2.tgz", + "integrity": "sha512-MDW0fKfsn0OI7MS7Euz6h8HNDXVQ0gaM9uW6RjfDmd1DAFcaxX9OqIakHIqhbnmF08Cf2DLDG+ulq8YQQ0Lp0Q==", + "dev": true, + "dependencies": { + "@jest/types": "^26.6.2", + "@types/node": "*", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.4", + "is-ci": "^2.0.0", + "micromatch": "^4.0.2" + }, + "engines": { + "node": ">= 10.14.2" + } + }, + "node_modules/jest-validate": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-26.6.2.tgz", + "integrity": "sha512-NEYZ9Aeyj0i5rQqbq+tpIOom0YS1u2MVu6+euBsvpgIme+FOfRmoC4R5p0JiAUpaFvFy24xgrpMknarR/93XjQ==", + "dev": true, + "dependencies": { + "@jest/types": "^26.6.2", + "camelcase": "^6.0.0", + "chalk": "^4.0.0", + "jest-get-type": "^26.3.0", + "leven": "^3.1.0", + "pretty-format": "^26.6.2" + }, + "engines": { + "node": ">= 10.14.2" + } + }, + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", + "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-watcher": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-26.6.2.tgz", + "integrity": "sha512-WKJob0P/Em2csiVthsI68p6aGKTIcsfjH9Gsx1f0A3Italz43e3ho0geSAVsmj09RWOELP1AZ/DXyJgOgDKxXQ==", + "dev": true, + "dependencies": { + "@jest/test-result": "^26.6.2", + "@jest/types": "^26.6.2", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "jest-util": "^26.6.2", + "string-length": "^4.0.1" + }, + "engines": { + "node": ">= 10.14.2" + } + }, + "node_modules/jest-worker": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.6.2.tgz", + "integrity": "sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==", + "dev": true, + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^7.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "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.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz", @@ -861,6 +4079,141 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "dev": true + }, + "node_modules/jsdom": { + "version": "16.5.2", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.5.2.tgz", + "integrity": "sha512-JxNtPt9C1ut85boCbJmffaQ06NBnzkQY/MWO3YxPW8IWS38A26z+B1oBvA9LwKrytewdfymnhi4UNH3/RAgZrg==", + "dev": true, + "dependencies": { + "abab": "^2.0.5", + "acorn": "^8.1.0", + "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", + "html-encoding-sniffer": "^2.0.1", + "is-potential-custom-element-name": "^1.0.0", + "nwsapi": "^2.2.0", + "parse5": "6.0.1", + "request": "^2.88.2", + "request-promise-native": "^1.0.9", + "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.4", + "xml-name-validator": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "canvas": "^2.5.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "node_modules/json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", + "dev": true + }, + "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-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "dev": true + }, + "node_modules/json5": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", + "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", + "dev": true, + "dependencies": { + "minimist": "^1.2.5" + }, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "dev": true, + "engines": [ + "node >=0.6.0" + ], + "dependencies": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/lavacord": { "version": "1.1.9", "resolved": "https://registry.npmjs.org/lavacord/-/lavacord-1.1.9.tgz", @@ -874,47 +4227,127 @@ "npm": ">=5" } }, - "node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", "dev": true, "dependencies": { - "p-locate": "^5.0.0" + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" }, "engines": { - "node": ">=10" + "node": ">= 0.8.0" } }, + "node_modules/lines-and-columns": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", + "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=", + "dev": true + }, "node_modules/lodash": { "version": "4.17.20", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==" }, - "node_modules/log-symbols": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.0.0.tgz", - "integrity": "sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA==", + "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": { - "chalk": "^4.0.0" + "yallist": "^4.0.0" }, "engines": { "node": ">=10" } }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", "dev": true }, + "node_modules/makeerror": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.11.tgz", + "integrity": "sha1-4BpckQnyr3lmDk6LlYd5AYT1qWw=", + "dev": true, + "dependencies": { + "tmpl": "1.0.x" + } + }, + "node_modules/map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/map-stream": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz", "integrity": "sha1-5WqpTEyAVaFkBKBnS3jyFffI4ZQ=", "dev": true }, + "node_modules/map-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", + "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", + "dev": true, + "dependencies": { + "object-visit": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "node_modules/micromatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", + "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", + "dev": true, + "dependencies": { + "braces": "^3.0.1", + "picomatch": "^2.0.5" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/mime-db": { "version": "1.45.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.45.0.tgz", @@ -946,7 +4379,6 @@ "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" }, @@ -954,71 +4386,35 @@ "node": "*" } }, - "node_modules/mocha": { - "version": "8.2.1", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-8.2.1.tgz", - "integrity": "sha512-cuLBVfyFfFqbNR0uUKbDGXKGk+UDFe6aR4os78XIrMQpZl/nv7JYHcvP5MFIAb374b2zFXsdgEGwmzMtP0Xg8w==", - "dev": true, - "dependencies": { - "@ungap/promise-all-settled": "1.1.2", - "ansi-colors": "4.1.1", - "browser-stdout": "1.3.1", - "chokidar": "3.4.3", - "debug": "4.2.0", - "diff": "4.0.2", - "escape-string-regexp": "4.0.0", - "find-up": "5.0.0", - "glob": "7.1.6", - "growl": "1.10.5", - "he": "1.2.0", - "js-yaml": "3.14.0", - "log-symbols": "4.0.0", - "minimatch": "3.0.4", - "ms": "2.1.2", - "nanoid": "3.1.12", - "serialize-javascript": "5.0.1", - "strip-json-comments": "3.1.1", - "supports-color": "7.2.0", - "which": "2.0.2", - "wide-align": "1.1.3", - "workerpool": "6.0.2", - "yargs": "13.3.2", - "yargs-parser": "13.1.2", - "yargs-unparser": "2.0.0" - }, - "bin": { - "_mocha": "bin/_mocha", - "mocha": "bin/mocha" - }, - "engines": { - "node": ">= 10.12.0" - } - }, - "node_modules/mocha/node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/mocha/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "node_modules/minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", "dev": true }, - "node_modules/mocha/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/mixin-deep": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", + "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", "dev": true, "dependencies": { - "has-flag": "^4.0.0" + "for-in": "^1.0.2", + "is-extendable": "^1.0.1" }, "engines": { - "node": ">=8" + "node": ">=0.10.0" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" } }, "node_modules/moment": { @@ -1039,18 +4435,40 @@ "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==" }, - "node_modules/nanoid": { - "version": "3.1.12", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.12.tgz", - "integrity": "sha512-1qstj9z5+x491jfiC4Nelk+f8XBad7LN20PmyWINJEMRSf3wcAjAWysw1qaA8z6NSKe2sjq1hRSDpBH5paCb6A==", + "node_modules/nanomatch": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", + "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", "dev": true, - "bin": { - "nanoid": "bin/nanoid.cjs" + "dependencies": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "fragment-cache": "^0.2.1", + "is-windows": "^1.0.2", + "kind-of": "^6.0.2", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" }, "engines": { - "node": "^10 || ^12 || >=13.7" + "node": ">=0.10.0" } }, + "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/nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true + }, "node_modules/node-cleanup": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/node-cleanup/-/node-cleanup-2.1.2.tgz", @@ -1065,6 +4483,79 @@ "node": "4.x || >=6.0.0" } }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs=", + "dev": true + }, + "node_modules/node-modules-regexp": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz", + "integrity": "sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/node-notifier": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-8.0.2.tgz", + "integrity": "sha512-oJP/9NAdd9+x2Q+rfphB2RJCHjod70RcRLjosiPMMu5gjIfwVnOUGq2nbTjTUbmy0DJ/tFIVT30+Qe3nzl4TJg==", + "dev": true, + "optional": true, + "dependencies": { + "growly": "^1.3.0", + "is-wsl": "^2.2.0", + "semver": "^7.3.2", + "shellwords": "^0.1.1", + "uuid": "^8.3.0", + "which": "^2.0.2" + } + }, + "node_modules/node-notifier/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, + "optional": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-releases": { + "version": "1.1.71", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.71.tgz", + "integrity": "sha512-zR6HoT6LrLCRBwukmrVbHv0EpEQjksO6GmFcZQQuCAy139BEsoVKPYnf3jongYW83fAa1torLGYwxxky/p28sg==", + "dev": true + }, + "node_modules/normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "dependencies": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "node_modules/normalize-package-data/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -1074,11 +4565,146 @@ "node": ">=0.10.0" } }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nwsapi": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz", + "integrity": "sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ==", + "dev": true + }, + "node_modules/oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/object-copy": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", + "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", + "dev": true, + "dependencies": { + "copy-descriptor": "^0.1.0", + "define-property": "^0.2.5", + "kind-of": "^3.0.3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-copy/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-copy/node_modules/is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-copy/node_modules/is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-copy/node_modules/is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-copy/node_modules/is-descriptor/node_modules/kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-copy/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-visit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", + "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", + "dev": true, + "dependencies": { + "isobject": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", + "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", + "dev": true, + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.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" } @@ -1094,6 +4720,23 @@ "node": ">=6" } }, + "node_modules/optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "dev": true, + "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" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/os-tmpdir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", @@ -1102,28 +4745,25 @@ "node": ">=0.10.0" } }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "node_modules/p-each-series": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-2.2.0.tgz", + "integrity": "sha512-ycIL2+1V32th+8scbpTvyHNaHe02z0sjgh91XXjAk+ZeXoPN4Z46DVUnzdso0aX4KckKw0FNNFHdjZ2UsZvxiA==", "dev": true, - "dependencies": { - "yocto-queue": "^0.1.0" - }, "engines": { - "node": ">=10" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "node_modules/p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", "dev": true, - "dependencies": { - "p-limit": "^3.0.2" - }, "engines": { - "node": ">=10" + "node": ">=4" } }, "node_modules/p-try": { @@ -1135,6 +4775,39 @@ "node": ">=6" } }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "dev": true + }, + "node_modules/pascalcase": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", + "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -1148,7 +4821,6 @@ "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" } @@ -1162,6 +4834,12 @@ "node": ">=8" } }, + "node_modules/path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "dev": true + }, "node_modules/pause-stream": { "version": "0.0.11", "resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", @@ -1171,6 +4849,12 @@ "through": "~2.3" } }, + "node_modules/performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", + "dev": true + }, "node_modules/picomatch": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", @@ -1180,6 +4864,100 @@ "node": ">=8.6" } }, + "node_modules/pirates": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.1.tgz", + "integrity": "sha512-WuNqLTbMI3tmfef2TKxlQmAiLHKtFhlsCZnPIpuv2Ow0RDVO8lfy1Opf4NUzlMXLjPl+Men7AuVdX6TA+s+uGA==", + "dev": true, + "dependencies": { + "node-modules-regexp": "^1.0.0" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/posix-character-classes": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", + "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/prettier": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.1.2.tgz", @@ -1192,11 +4970,39 @@ "node": ">=10.13.0" } }, + "node_modules/pretty-format": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", + "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", + "dev": true, + "dependencies": { + "@jest/types": "^26.6.2", + "ansi-regex": "^5.0.0", + "ansi-styles": "^4.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": ">= 10" + } + }, "node_modules/prism-media": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/prism-media/-/prism-media-1.2.3.tgz", "integrity": "sha512-fSrR66n0l6roW9Rx4rSLMyTPTjRTiXy5RVqDOurACQ6si1rKHHKDU5gwBJoCsIV0R3o9gi+K50akl/qyw1C74A==" }, + "node_modules/prompts": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.0.tgz", + "integrity": "sha512-awZAKrk3vN6CroQukBL+R9051a4R3zCZBlJm/HBfrSZ8iTpYix3VX1vU4mveiLpiwmOJT4wokTF9m6HUk4KqWQ==", + "dev": true, + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/ps-tree": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/ps-tree/-/ps-tree-1.2.0.tgz", @@ -1212,25 +5018,283 @@ "node": ">= 0.10" } }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "node_modules/psl": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", + "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==", + "dev": true + }, + "node_modules/pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", "dev": true, "dependencies": { - "safe-buffer": "^5.1.0" + "end-of-stream": "^1.1.0", + "once": "^1.3.1" } }, - "node_modules/readdirp": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", - "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", + "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/qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", + "dev": true, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true + }, + "node_modules/read-pkg": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", + "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", "dev": true, "dependencies": { - "picomatch": "^2.2.1" + "@types/normalize-package-data": "^2.4.0", + "normalize-package-data": "^2.5.0", + "parse-json": "^5.0.0", + "type-fest": "^0.6.0" }, "engines": { - "node": ">=8.10.0" + "node": ">=8" + } + }, + "node_modules/read-pkg-up": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", + "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", + "dev": true, + "dependencies": { + "find-up": "^4.1.0", + "read-pkg": "^5.2.0", + "type-fest": "^0.8.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg-up/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg-up/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg-up/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg-up/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg-up/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/read-pkg/node_modules/type-fest": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", + "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/regex-not": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", + "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", + "dev": true, + "dependencies": { + "extend-shallow": "^3.0.2", + "safe-regex": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", + "dev": true + }, + "node_modules/repeat-element": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", + "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", + "dev": true, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/request": { + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", + "deprecated": "request has been deprecated, see https://github.com/request/request/issues/3142", + "dev": true, + "dependencies": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/request-promise-core": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.4.tgz", + "integrity": "sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw==", + "dev": true, + "dependencies": { + "lodash": "^4.17.19" + }, + "engines": { + "node": ">=0.10.0" + }, + "peerDependencies": { + "request": "^2.34" + } + }, + "node_modules/request-promise-native": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.9.tgz", + "integrity": "sha512-wcW+sIUiWnKgNY0dqCpOZkUbF/I+YPi+f09JZIDa39Ec+q82CpSYniDp+ISgTTbKmnpJWASeJBPZmoxH84wt3g==", + "deprecated": "request-promise-native has been deprecated because it extends the now deprecated request package, see https://github.com/request/request/issues/3142", + "dev": true, + "dependencies": { + "request-promise-core": "1.1.4", + "stealthy-require": "^1.1.1", + "tough-cookie": "^2.3.3" + }, + "engines": { + "node": ">=0.12.0" + }, + "peerDependencies": { + "request": "^2.34" + } + }, + "node_modules/request-promise-native/node_modules/tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "dev": true, + "dependencies": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/request/node_modules/tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "dev": true, + "dependencies": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/request/node_modules/uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "dev": true, + "bin": { + "uuid": "bin/uuid" } }, "node_modules/require-directory": { @@ -1248,6 +5312,47 @@ "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", "dev": true }, + "node_modules/resolve": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", + "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", + "dev": true, + "dependencies": { + "is-core-module": "^2.2.0", + "path-parse": "^1.0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-url": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", + "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", + "deprecated": "https://github.com/lydell/resolve-url#deprecated", + "dev": true + }, "node_modules/restore-cursor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", @@ -1260,6 +5365,39 @@ "node": ">=8" } }, + "node_modules/ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", + "dev": true, + "engines": { + "node": ">=0.12" + } + }, + "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/rsvp": { + "version": "4.8.5", + "resolved": "https://registry.npmjs.org/rsvp/-/rsvp-4.8.5.tgz", + "integrity": "sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA==", + "dev": true, + "engines": { + "node": "6.* || >= 7.*" + } + }, "node_modules/run-async": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", @@ -1285,18 +5423,332 @@ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", "dev": true }, + "node_modules/safe-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", + "dev": true, + "dependencies": { + "ret": "~0.1.10" + } + }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, - "node_modules/serialize-javascript": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-5.0.1.tgz", - "integrity": "sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==", + "node_modules/sane": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/sane/-/sane-4.1.0.tgz", + "integrity": "sha512-hhbzAgTIX8O7SHfp2c8/kREfEn4qO/9q8C9beyY6+tvZ87EpoZ3i1RIEvp27YBswnNbY9mWd6paKVmKbAgLfZA==", "dev": true, "dependencies": { - "randombytes": "^2.1.0" + "@cnakazawa/watch": "^1.0.3", + "anymatch": "^2.0.0", + "capture-exit": "^2.0.0", + "exec-sh": "^0.3.2", + "execa": "^1.0.0", + "fb-watchman": "^2.0.0", + "micromatch": "^3.1.4", + "minimist": "^1.1.1", + "walker": "~1.0.5" + }, + "bin": { + "sane": "src/cli.js" + }, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/sane/node_modules/anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "dev": true, + "dependencies": { + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" + } + }, + "node_modules/sane/node_modules/braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "dependencies": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sane/node_modules/braces/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sane/node_modules/cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "dependencies": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + }, + "engines": { + "node": ">=4.8" + } + }, + "node_modules/sane/node_modules/execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "dev": true, + "dependencies": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/sane/node_modules/fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "dependencies": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sane/node_modules/fill-range/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sane/node_modules/get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/sane/node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sane/node_modules/is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sane/node_modules/is-number/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sane/node_modules/is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sane/node_modules/micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "dependencies": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sane/node_modules/normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true, + "dependencies": { + "remove-trailing-separator": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sane/node_modules/npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "dev": true, + "dependencies": { + "path-key": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/sane/node_modules/path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/sane/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/sane/node_modules/shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, + "dependencies": { + "shebang-regex": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sane/node_modules/shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sane/node_modules/to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "dev": true, + "dependencies": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sane/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/saxes": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", + "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", + "dev": true, + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" } }, "node_modules/set-blocking": { @@ -1305,6 +5757,42 @@ "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", "dev": true }, + "node_modules/set-value": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", + "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", + "dev": true, + "dependencies": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/set-value/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/set-value/node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/setimmediate": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", @@ -1331,11 +5819,230 @@ "node": ">=8" } }, + "node_modules/shellwords": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz", + "integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==", + "dev": true, + "optional": true + }, "node_modules/signal-exit": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==" }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/snapdragon": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", + "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", + "dev": true, + "dependencies": { + "base": "^0.11.1", + "debug": "^2.2.0", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "map-cache": "^0.2.2", + "source-map": "^0.5.6", + "source-map-resolve": "^0.5.0", + "use": "^3.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", + "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", + "dev": true, + "dependencies": { + "define-property": "^1.0.0", + "isobject": "^3.0.0", + "snapdragon-util": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-node/node_modules/define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "dependencies": { + "is-descriptor": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-util": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", + "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", + "dev": true, + "dependencies": { + "kind-of": "^3.2.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-util/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/snapdragon/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/is-accessor-descriptor/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/is-data-descriptor/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "node_modules/snapdragon/node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -1345,6 +6052,19 @@ "node": ">=0.10.0" } }, + "node_modules/source-map-resolve": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", + "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", + "dev": true, + "dependencies": { + "atob": "^2.1.2", + "decode-uri-component": "^0.2.0", + "resolve-url": "^0.2.1", + "source-map-url": "^0.4.0", + "urix": "^0.1.0" + } + }, "node_modules/source-map-support": { "version": "0.5.19", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", @@ -1355,6 +6075,44 @@ "source-map": "^0.6.0" } }, + "node_modules/source-map-url": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz", + "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==", + "dev": true + }, + "node_modules/spdx-correct": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", + "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", + "dev": true, + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", + "dev": true + }, + "node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.7.tgz", + "integrity": "sha512-U+MTEOO0AiDzxwFvoa4JVnMV6mZlJKk2sBLt90s7G0Gd0Mlknc7kxEn3nuDPNZRta7O2uy8oLcZLVT+4sqNZHQ==", + "dev": true + }, "node_modules/split": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/split/-/split-0.3.3.tgz", @@ -1367,12 +6125,181 @@ "node": "*" } }, + "node_modules/split-string": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", + "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "dev": true, + "dependencies": { + "extend-shallow": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "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/sshpk": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", + "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", + "dev": true, + "dependencies": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + }, + "bin": { + "sshpk-conv": "bin/sshpk-conv", + "sshpk-sign": "bin/sshpk-sign", + "sshpk-verify": "bin/sshpk-verify" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sshpk/node_modules/tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "dev": true + }, + "node_modules/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-gL//fkxfWUsIlFL2Tl42Cl6+HFALEaB1FU76I/Fy+oZjRreP7OPMXFlGbxM7NQsI0ZpUfw76sHnv0WNYuTb7Iw==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/stack-utils/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/static-extend": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", + "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", + "dev": true, + "dependencies": { + "define-property": "^0.2.5", + "object-copy": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/static-extend/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/static-extend/node_modules/is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/static-extend/node_modules/is-accessor-descriptor/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/static-extend/node_modules/is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/static-extend/node_modules/is-data-descriptor/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/static-extend/node_modules/is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/static-extend/node_modules/kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stealthy-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", + "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/stream-combiner": { "version": "0.0.4", "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz", @@ -1391,6 +6318,19 @@ "node": ">=0.6.19" } }, + "node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/string-width": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", @@ -1415,15 +6355,33 @@ "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==", + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", "dev": true, "engines": { "node": ">=8" } }, + "node_modules/strip-eof": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/supports-color": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", @@ -1435,6 +6393,61 @@ "node": ">=8" } }, + "node_modules/supports-hyperlinks": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.1.0.tgz", + "integrity": "sha512-zoE5/e+dnEijk6ASB6/qrK+oYdm2do1hjoLWrqUC/8WEIW1gbxFcKuBof7sW8ArN6e+AYvsE8HBGiVRWL/F5CA==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0", + "supports-color": "^7.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true + }, + "node_modules/terminal-link": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", + "integrity": "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==", + "dev": true, + "dependencies": { + "ansi-escapes": "^4.2.1", + "supports-hyperlinks": "^2.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/throat": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/throat/-/throat-5.0.0.tgz", + "integrity": "sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==", + "dev": true + }, "node_modules/through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", @@ -1451,6 +6464,60 @@ "node": ">=0.6.0" } }, + "node_modules/tmpl": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.4.tgz", + "integrity": "sha1-I2QN17QtAEM5ERQIIOXPRA5SHdE=", + "dev": true + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/to-object-path": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", + "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-object-path/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-regex": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", + "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "dev": true, + "dependencies": { + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "regex-not": "^1.0.2", + "safe-regex": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -1463,11 +6530,91 @@ "node": ">=8.0" } }, + "node_modules/tough-cookie": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.0.0.tgz", + "integrity": "sha512-tHdtEpQCMrc1YLrMaqXXcj6AxhYi/xgit6mZu1+EDWUn+qhUf8wMQoFIy9NXuq23zAwtcB0t/MjACGR18pcRbg==", + "dev": true, + "dependencies": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.1.2" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tr46": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.0.2.tgz", + "integrity": "sha512-3n1qG+/5kg+jrbTzwAykB5yRYtQCTqOGKq5U5PE3b0a1/mzo6snDhjGS0zJVJunO0NrT3Dg1MLy5TjWP/UJppg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ts-jest": { + "version": "26.5.4", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-26.5.4.tgz", + "integrity": "sha512-I5Qsddo+VTm94SukBJ4cPimOoFZsYTeElR2xy6H2TOVs+NsvgYglW8KuQgKoApOKuaU/Ix/vrF9ebFZlb5D2Pg==", + "dev": true, + "dependencies": { + "bs-logger": "0.x", + "buffer-from": "1.x", + "fast-json-stable-stringify": "2.x", + "jest-util": "^26.1.0", + "json5": "2.x", + "lodash": "4.x", + "make-error": "1.x", + "mkdirp": "1.x", + "semver": "7.x", + "yargs-parser": "20.x" + }, + "bin": { + "ts-jest": "cli.js" + }, + "engines": { + "node": ">= 10" + }, + "peerDependencies": { + "jest": ">=26 <27", + "typescript": ">=3.8 <5.0" + } + }, + "node_modules/ts-jest/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/ts-jest/node_modules/yargs-parser": { + "version": "20.2.7", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.7.tgz", + "integrity": "sha512-FiNkvbeHzB/syOjIUxFDCnhSfzAL8R5vs40MgLFBorXACCOAEaWu0gRZl14vG8MR9AOJIZbmkjhusqBYZ3HTHw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, "node_modules/ts-node": { "version": "9.1.1", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-9.1.1.tgz", "integrity": "sha512-hPlt7ZACERQGf03M253ytLY3dHbGNGrAq9qIHWUY9XHYl1z7wYngSr3OQ5xmui8o2AaxsONxIzjafLUiWBo1Fg==", "dev": true, + "optional": true, + "peer": true, "dependencies": { "arg": "^4.1.0", "create-require": "^1.1.0", @@ -1510,11 +6657,44 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz", "integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==" }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "dev": true, + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, "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.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "dev": true, + "dependencies": { + "prelude-ls": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/type-fest": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.11.0.tgz", @@ -1523,6 +6703,15 @@ "node": ">=8" } }, + "node_modules/typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "dev": true, + "dependencies": { + "is-typedarray": "^1.0.0" + } + }, "node_modules/typescript": { "version": "3.9.9", "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.9.tgz", @@ -1536,6 +6725,237 @@ "node": ">=4.2.0" } }, + "node_modules/union-value": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", + "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", + "dev": true, + "dependencies": { + "arr-union": "^3.1.0", + "get-value": "^2.0.6", + "is-extendable": "^0.1.1", + "set-value": "^2.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/union-value/node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/unset-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", + "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", + "dev": true, + "dependencies": { + "has-value": "^0.3.1", + "isobject": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/unset-value/node_modules/has-value": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", + "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", + "dev": true, + "dependencies": { + "get-value": "^2.0.3", + "has-values": "^0.1.4", + "isobject": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/unset-value/node_modules/has-value/node_modules/isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "dev": true, + "dependencies": { + "isarray": "1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/unset-value/node_modules/has-values": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", + "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/urix": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", + "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", + "deprecated": "Please see https://github.com/lydell/urix#deprecated", + "dev": true + }, + "node_modules/use": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", + "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, + "optional": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/v8-to-istanbul": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-7.1.1.tgz", + "integrity": "sha512-p0BB09E5FRjx0ELN6RgusIPsSPhtgexSRcKETybEs6IGOTXJSZqfwxp7r//55nnu0f1AxltY5VvdVqy2vZf9AA==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^1.6.0", + "source-map": "^0.7.3" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/v8-to-istanbul/node_modules/source-map": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", + "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "dev": true, + "engines": [ + "node >=0.6.0" + ], + "dependencies": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "node_modules/w3c-hr-time": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", + "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", + "dev": true, + "dependencies": { + "browser-process-hrtime": "^1.0.0" + } + }, + "node_modules/w3c-xmlserializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz", + "integrity": "sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA==", + "dev": true, + "dependencies": { + "xml-name-validator": "^3.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/walker": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.7.tgz", + "integrity": "sha1-L3+bj9ENZ3JisYqITijRlhjgKPs=", + "dev": true, + "dependencies": { + "makeerror": "1.0.x" + } + }, + "node_modules/webidl-conversions": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz", + "integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==", + "dev": true, + "engines": { + "node": ">=10.4" + } + }, + "node_modules/whatwg-encoding": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", + "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", + "dev": true, + "dependencies": { + "iconv-lite": "0.4.24" + } + }, + "node_modules/whatwg-mimetype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", + "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==", + "dev": true + }, + "node_modules/whatwg-url": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.5.0.tgz", + "integrity": "sha512-fy+R77xWv0AiqfLl4nuGUlQ3/6b5uNfQ4WAbGQVMYshCTCCPK9psC1nWh3XHuxGVCtlcDDQPQW1csmmIQo+fwg==", + "dev": true, + "dependencies": { + "lodash": "^4.7.0", + "tr46": "^2.0.2", + "webidl-conversions": "^6.1.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -1557,393 +6977,124 @@ "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", "dev": true }, - "node_modules/wide-align": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", - "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", - "dev": true, - "dependencies": { - "string-width": "^1.0.2 || 2" - } - }, - "node_modules/wide-align/node_modules/ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "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": ">=4" - } - }, - "node_modules/wide-align/node_modules/is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/wide-align/node_modules/string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, - "dependencies": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/wide-align/node_modules/strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, - "dependencies": { - "ansi-regex": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/workerpool": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.0.2.tgz", - "integrity": "sha512-DSNyvOpFKrNusaaUwk+ej6cBj1bmhLcBfj80elGk+ZIo5JSkq+unB1dLKEOcNfJDZgjGICfhQ0Q5TbP0PvF4+Q==", - "dev": true - }, - "node_modules/wrap-ansi": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", - "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.0", - "string-width": "^3.0.0", - "strip-ansi": "^5.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/wrap-ansi/node_modules/ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/wrap-ansi/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/wrap-ansi/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/wrap-ansi/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/wrap-ansi/node_modules/emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", - "dev": true - }, - "node_modules/wrap-ansi/node_modules/is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/wrap-ansi/node_modules/string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "dependencies": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/wrap-ansi/node_modules/strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "dependencies": { - "ansi-regex": "^4.1.0" - }, - "engines": { - "node": ">=6" + "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 + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "node_modules/write-file-atomic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" + } }, "node_modules/ws": { - "version": "7.4.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.2.tgz", - "integrity": "sha512-T4tewALS3+qsrpGI/8dqNMLIVdq/g/85U98HPMa6F0m6xTbvhXU6RCQLqPH3+SlomNV/LdY6RXEbBpMH6EOJnA==", + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.4.tgz", + "integrity": "sha512-Qm8k8ojNQIMx7S+Zp8u/uHOx7Qazv3Yv4q68MiWWWOJhiwG5W3x7iqmRtJo8xxrciZUY4vRxUTJCKuRnF28ZZw==", "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/xml-name-validator": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", + "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", + "dev": true + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true + }, "node_modules/y18n": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.1.tgz", "integrity": "sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ==", "dev": true }, - "node_modules/yargs": { - "version": "13.3.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", - "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", - "dev": true, - "dependencies": { - "cliui": "^5.0.0", - "find-up": "^3.0.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^3.0.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^13.1.2" - } - }, - "node_modules/yargs-parser": { - "version": "13.1.2", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", - "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", - "dev": true, - "dependencies": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } - }, - "node_modules/yargs-unparser": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", - "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", - "dev": true, - "dependencies": { - "camelcase": "^6.0.0", - "decamelize": "^4.0.0", - "flat": "^5.0.2", - "is-plain-obj": "^2.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs-unparser/node_modules/camelcase": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", - "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs-unparser/node_modules/decamelize": { + "node_modules/yallist": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", - "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs/node_modules/ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/yargs/node_modules/emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true }, - "node_modules/yargs/node_modules/find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "dependencies": { - "locate-path": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/yargs/node_modules/is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/yargs/node_modules/locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "dependencies": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/yargs/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/yargs/node_modules/p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, - "dependencies": { - "p-limit": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/yargs/node_modules/path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/yargs/node_modules/string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "dependencies": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/yargs/node_modules/strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "dependencies": { - "ansi-regex": "^4.1.0" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/yn": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", "dev": true, + "optional": true, + "peer": true, "engines": { "node": ">=6" } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "engines": { - "node": ">=10" - } } }, "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==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz", + "integrity": "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==", "dev": true, "requires": { - "@babel/highlight": "^7.10.4" + "@babel/highlight": "^7.12.13" } }, + "@babel/compat-data": { + "version": "7.13.12", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.13.12.tgz", + "integrity": "sha512-3eJJ841uKxeV8dcN/2yGEUy+RfgQspPEgQat85umsE1rotuquQ2AbIub4S6j7c50a2d+4myc+zSlnXeIHrOnhQ==", + "dev": true + }, "@babel/core": { - "version": "7.12.10", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.12.10.tgz", - "integrity": "sha512-eTAlQKq65zHfkHZV0sIVODCPGVgoo1HdBlbSLi9CqOzuZanMv2ihzY+4paiKr1mH+XmYESMAmJ/dpZ68eN6d8w==", + "version": "7.13.14", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.13.14.tgz", + "integrity": "sha512-wZso/vyF4ki0l0znlgM4inxbdrUvCb+cVz8grxDq+6C9k6qbqoIJteQOKicaKjCipU3ISV+XedCqpL2RJJVehA==", "dev": true, "requires": { - "@babel/code-frame": "^7.10.4", - "@babel/generator": "^7.12.10", - "@babel/helper-module-transforms": "^7.12.1", - "@babel/helpers": "^7.12.5", - "@babel/parser": "^7.12.10", - "@babel/template": "^7.12.7", - "@babel/traverse": "^7.12.10", - "@babel/types": "^7.12.10", + "@babel/code-frame": "^7.12.13", + "@babel/generator": "^7.13.9", + "@babel/helper-compilation-targets": "^7.13.13", + "@babel/helper-module-transforms": "^7.13.14", + "@babel/helpers": "^7.13.10", + "@babel/parser": "^7.13.13", + "@babel/template": "^7.12.13", + "@babel/traverse": "^7.13.13", + "@babel/types": "^7.13.14", "convert-source-map": "^1.7.0", "debug": "^4.1.0", - "gensync": "^1.0.0-beta.1", + "gensync": "^1.0.0-beta.2", "json5": "^2.1.2", - "lodash": "^4.17.19", - "semver": "^5.4.1", + "semver": "^6.3.0", "source-map": "^0.5.0" }, "dependencies": { @@ -1956,12 +7107,12 @@ } }, "@babel/generator": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.12.11.tgz", - "integrity": "sha512-Ggg6WPOJtSi8yYQvLVjG8F/TlpWDlKx0OpS4Kt+xMQPs5OaGYWy+v1A+1TvxI6sAMGZpKWWoAQ1DaeQbImlItA==", + "version": "7.13.9", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.13.9.tgz", + "integrity": "sha512-mHOOmY0Axl/JCTkxTU6Lf5sWOg/v8nUa+Xkt4zMTftX0wqmb6Sh7J8gvcehBw7q0AhrhAR+FDacKjCZ2X8K+Sw==", "dev": true, "requires": { - "@babel/types": "^7.12.11", + "@babel/types": "^7.13.0", "jsesc": "^2.5.1", "source-map": "^0.5.0" }, @@ -1974,104 +7125,115 @@ } } }, - "@babel/helper-function-name": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.12.11.tgz", - "integrity": "sha512-AtQKjtYNolKNi6nNNVLQ27CP6D9oFR6bq/HPYSizlzbp7uC1M59XJe8L+0uXjbIaZaUJF99ruHqVGiKXU/7ybA==", + "@babel/helper-compilation-targets": { + "version": "7.13.13", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.13.13.tgz", + "integrity": "sha512-q1kcdHNZehBwD9jYPh3WyXcsFERi39X4I59I3NadciWtNDyZ6x+GboOxncFK0kXlKIv6BJm5acncehXWUjWQMQ==", "dev": true, "requires": { - "@babel/helper-get-function-arity": "^7.12.10", - "@babel/template": "^7.12.7", - "@babel/types": "^7.12.11" + "@babel/compat-data": "^7.13.12", + "@babel/helper-validator-option": "^7.12.17", + "browserslist": "^4.14.5", + "semver": "^6.3.0" + } + }, + "@babel/helper-function-name": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.12.13.tgz", + "integrity": "sha512-TZvmPn0UOqmvi5G4vvw0qZTpVptGkB1GL61R6lKvrSdIxGm5Pky7Q3fpKiIkQCAtRCBUwB0PaThlx9vebCDSwA==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.12.13", + "@babel/template": "^7.12.13", + "@babel/types": "^7.12.13" } }, "@babel/helper-get-function-arity": { - "version": "7.12.10", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.10.tgz", - "integrity": "sha512-mm0n5BPjR06wh9mPQaDdXWDoll/j5UpCAPl1x8fS71GHm7HA6Ua2V4ylG1Ju8lvcTOietbPNNPaSilKj+pj+Ag==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.13.tgz", + "integrity": "sha512-DjEVzQNz5LICkzN0REdpD5prGoidvbdYk1BVgRUOINaWJP2t6avB27X1guXK1kXNrX0WMfsrm1A/ZBthYuIMQg==", "dev": true, "requires": { - "@babel/types": "^7.12.10" + "@babel/types": "^7.12.13" } }, "@babel/helper-member-expression-to-functions": { - "version": "7.12.7", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.12.7.tgz", - "integrity": "sha512-DCsuPyeWxeHgh1Dus7APn7iza42i/qXqiFPWyBDdOFtvS581JQePsc1F/nD+fHrcswhLlRc2UpYS1NwERxZhHw==", + "version": "7.13.12", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.13.12.tgz", + "integrity": "sha512-48ql1CLL59aKbU94Y88Xgb2VFy7a95ykGRbJJaaVv+LX5U8wFpLfiGXJJGUozsmA1oEh/o5Bp60Voq7ACyA/Sw==", "dev": true, "requires": { - "@babel/types": "^7.12.7" + "@babel/types": "^7.13.12" } }, "@babel/helper-module-imports": { - "version": "7.12.5", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.12.5.tgz", - "integrity": "sha512-SR713Ogqg6++uexFRORf/+nPXMmWIn80TALu0uaFb+iQIUoR7bOC7zBWyzBs5b3tBBJXuyD0cRu1F15GyzjOWA==", + "version": "7.13.12", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.13.12.tgz", + "integrity": "sha512-4cVvR2/1B693IuOvSI20xqqa/+bl7lqAMR59R4iu39R9aOX8/JoYY1sFaNvUMyMBGnHdwvJgUrzNLoUZxXypxA==", "dev": true, "requires": { - "@babel/types": "^7.12.5" + "@babel/types": "^7.13.12" } }, "@babel/helper-module-transforms": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.12.1.tgz", - "integrity": "sha512-QQzehgFAZ2bbISiCpmVGfiGux8YVFXQ0abBic2Envhej22DVXV9nCFaS5hIQbkyo1AdGb+gNME2TSh3hYJVV/w==", + "version": "7.13.14", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.13.14.tgz", + "integrity": "sha512-QuU/OJ0iAOSIatyVZmfqB0lbkVP0kDRiKj34xy+QNsnVZi/PA6BoSoreeqnxxa9EHFAIL0R9XOaAR/G9WlIy5g==", "dev": true, "requires": { - "@babel/helper-module-imports": "^7.12.1", - "@babel/helper-replace-supers": "^7.12.1", - "@babel/helper-simple-access": "^7.12.1", - "@babel/helper-split-export-declaration": "^7.11.0", - "@babel/helper-validator-identifier": "^7.10.4", - "@babel/template": "^7.10.4", - "@babel/traverse": "^7.12.1", - "@babel/types": "^7.12.1", - "lodash": "^4.17.19" + "@babel/helper-module-imports": "^7.13.12", + "@babel/helper-replace-supers": "^7.13.12", + "@babel/helper-simple-access": "^7.13.12", + "@babel/helper-split-export-declaration": "^7.12.13", + "@babel/helper-validator-identifier": "^7.12.11", + "@babel/template": "^7.12.13", + "@babel/traverse": "^7.13.13", + "@babel/types": "^7.13.14" } }, "@babel/helper-optimise-call-expression": { - "version": "7.12.10", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.12.10.tgz", - "integrity": "sha512-4tpbU0SrSTjjt65UMWSrUOPZTsgvPgGG4S8QSTNHacKzpS51IVWGDj0yCwyeZND/i+LSN2g/O63jEXEWm49sYQ==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.12.13.tgz", + "integrity": "sha512-BdWQhoVJkp6nVjB7nkFWcn43dkprYauqtk++Py2eaf/GRDFm5BxRqEIZCiHlZUGAVmtwKcsVL1dC68WmzeFmiA==", "dev": true, "requires": { - "@babel/types": "^7.12.10" + "@babel/types": "^7.12.13" } }, "@babel/helper-plugin-utils": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz", - "integrity": "sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg==", + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.13.0.tgz", + "integrity": "sha512-ZPafIPSwzUlAoWT8DKs1W2VyF2gOWthGd5NGFMsBcMMol+ZhK+EQY/e6V96poa6PA/Bh+C9plWN0hXO1uB8AfQ==", "dev": true }, "@babel/helper-replace-supers": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.12.11.tgz", - "integrity": "sha512-q+w1cqmhL7R0FNzth/PLLp2N+scXEK/L2AHbXUyydxp828F4FEa5WcVoqui9vFRiHDQErj9Zof8azP32uGVTRA==", + "version": "7.13.12", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.13.12.tgz", + "integrity": "sha512-Gz1eiX+4yDO8mT+heB94aLVNCL+rbuT2xy4YfyNqu8F+OI6vMvJK891qGBTqL9Uc8wxEvRW92Id6G7sDen3fFw==", "dev": true, "requires": { - "@babel/helper-member-expression-to-functions": "^7.12.7", - "@babel/helper-optimise-call-expression": "^7.12.10", - "@babel/traverse": "^7.12.10", - "@babel/types": "^7.12.11" + "@babel/helper-member-expression-to-functions": "^7.13.12", + "@babel/helper-optimise-call-expression": "^7.12.13", + "@babel/traverse": "^7.13.0", + "@babel/types": "^7.13.12" } }, "@babel/helper-simple-access": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.12.1.tgz", - "integrity": "sha512-OxBp7pMrjVewSSC8fXDFrHrBcJATOOFssZwv16F3/6Xtc138GHybBfPbm9kfiqQHKhYQrlamWILwlDCeyMFEaA==", + "version": "7.13.12", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.13.12.tgz", + "integrity": "sha512-7FEjbrx5SL9cWvXioDbnlYTppcZGuCY6ow3/D5vMggb2Ywgu4dMrpTJX0JdQAIcRRUElOIxF3yEooa9gUb9ZbA==", "dev": true, "requires": { - "@babel/types": "^7.12.1" + "@babel/types": "^7.13.12" } }, "@babel/helper-split-export-declaration": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.11.tgz", - "integrity": "sha512-LsIVN8j48gHgwzfocYUSkO/hjYAOJqlpJEc7tGXcIm4cubjVUf8LGW6eWRyxEu7gA25q02p0rQUWoCI33HNS5g==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.13.tgz", + "integrity": "sha512-tCJDltF83htUtXx5NLcaDqRmknv652ZWCHyoTETf1CXYJdPC7nohZohjUgieXhv0hTJdRf2FjDueFehdNucpzg==", "dev": true, "requires": { - "@babel/types": "^7.12.11" + "@babel/types": "^7.12.13" } }, "@babel/helper-validator-identifier": { @@ -2080,24 +7242,30 @@ "integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==", "dev": true }, + "@babel/helper-validator-option": { + "version": "7.12.17", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.12.17.tgz", + "integrity": "sha512-TopkMDmLzq8ngChwRlyjR6raKD6gMSae4JdYDB8bByKreQgG0RBTuKe9LRxW3wFtUnjxOPRKBDwEH6Mg5KeDfw==", + "dev": true + }, "@babel/helpers": { - "version": "7.12.5", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.12.5.tgz", - "integrity": "sha512-lgKGMQlKqA8meJqKsW6rUnc4MdUk35Ln0ATDqdM1a/UpARODdI4j5Y5lVfUScnSNkJcdCRAaWkspykNoFg9sJA==", + "version": "7.13.10", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.13.10.tgz", + "integrity": "sha512-4VO883+MWPDUVRF3PhiLBUFHoX/bsLTGFpFK/HqvvfBZz2D57u9XzPVNFVBTc0PW/CWR9BXTOKt8NF4DInUHcQ==", "dev": true, "requires": { - "@babel/template": "^7.10.4", - "@babel/traverse": "^7.12.5", - "@babel/types": "^7.12.5" + "@babel/template": "^7.12.13", + "@babel/traverse": "^7.13.0", + "@babel/types": "^7.13.0" } }, "@babel/highlight": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", - "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", + "version": "7.13.10", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.13.10.tgz", + "integrity": "sha512-5aPpe5XQPzflQrFwL1/QoeHkP2MsA4JCntcXHRhEsdsfPVkvPi2w7Qix4iV7t5S/oC9OodGrggd8aco1g3SZFg==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.10.4", + "@babel/helper-validator-identifier": "^7.12.11", "chalk": "^2.0.0", "js-tokens": "^4.0.0" }, @@ -2155,9 +7323,9 @@ } }, "@babel/parser": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.12.11.tgz", - "integrity": "sha512-N3UxG+uuF4CMYoNj8AhnbAcJF0PiuJ9KHuy1lQmkYsxTer/MAH9UBNHsBoAX/4s6NvlDD047No8mYVGGzLL4hg==", + "version": "7.13.13", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.13.13.tgz", + "integrity": "sha512-OhsyMrqygfk5v8HmWwOzlYjJrtLaFhF34MrfG/Z73DgYCI6ojNUTUp2TYbtnjo8PegeJp12eamsNettCQjKjVw==", "dev": true }, "@babel/plugin-syntax-async-generators": { @@ -2179,12 +7347,12 @@ } }, "@babel/plugin-syntax-class-properties": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.1.tgz", - "integrity": "sha512-U40A76x5gTwmESz+qiqssqmeEsKvcSyvtgktrm0uzcARAmM9I1jR221f6Oq+GmHrcD+LvZDag1UTOTe2fL3TeA==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-plugin-utils": "^7.12.13" } }, "@babel/plugin-syntax-import-meta": { @@ -2260,46 +7428,45 @@ } }, "@babel/plugin-syntax-top-level-await": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.12.1.tgz", - "integrity": "sha512-i7ooMZFS+a/Om0crxZodrTzNEPJHZrlMVGMTEpFAj6rYY/bKCddB0Dk/YxfPuYXOopuhKk/e1jV6h+WUU9XN3A==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.12.13.tgz", + "integrity": "sha512-A81F9pDwyS7yM//KwbCSDqy3Uj4NMIurtplxphWxoYtNPov7cJsDkAFNNyVlIZ3jwGycVsurZ+LtOA8gZ376iQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-plugin-utils": "^7.12.13" } }, "@babel/template": { - "version": "7.12.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.7.tgz", - "integrity": "sha512-GkDzmHS6GV7ZeXfJZ0tLRBhZcMcY0/Lnb+eEbXDBfCAcZCjrZKe6p3J4we/D24O9Y8enxWAg1cWwof59yLh2ow==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.13.tgz", + "integrity": "sha512-/7xxiGA57xMo/P2GVvdEumr8ONhFOhfgq2ihK3h1e6THqzTAkHbkXgB0xI9yeTfIUoH3+oAeHhqm/I43OTbbjA==", "dev": true, "requires": { - "@babel/code-frame": "^7.10.4", - "@babel/parser": "^7.12.7", - "@babel/types": "^7.12.7" + "@babel/code-frame": "^7.12.13", + "@babel/parser": "^7.12.13", + "@babel/types": "^7.12.13" } }, "@babel/traverse": { - "version": "7.12.12", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.12.12.tgz", - "integrity": "sha512-s88i0X0lPy45RrLM8b9mz8RPH5FqO9G9p7ti59cToE44xFm1Q+Pjh5Gq4SXBbtb88X7Uy7pexeqRIQDDMNkL0w==", + "version": "7.13.13", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.13.13.tgz", + "integrity": "sha512-CblEcwmXKR6eP43oQGG++0QMTtCjAsa3frUuzHoiIJWpaIIi8dwMyEFUJoXRLxagGqCK+jALRwIO+o3R9p/uUg==", "dev": true, "requires": { - "@babel/code-frame": "^7.12.11", - "@babel/generator": "^7.12.11", - "@babel/helper-function-name": "^7.12.11", - "@babel/helper-split-export-declaration": "^7.12.11", - "@babel/parser": "^7.12.11", - "@babel/types": "^7.12.12", + "@babel/code-frame": "^7.12.13", + "@babel/generator": "^7.13.9", + "@babel/helper-function-name": "^7.12.13", + "@babel/helper-split-export-declaration": "^7.12.13", + "@babel/parser": "^7.13.13", + "@babel/types": "^7.13.13", "debug": "^4.1.0", - "globals": "^11.1.0", - "lodash": "^4.17.19" + "globals": "^11.1.0" } }, "@babel/types": { - "version": "7.12.12", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.12.12.tgz", - "integrity": "sha512-lnIX7piTxOH22xE7fDXDbSHg9MM1/6ORnafpJmov5rs0kX5g4BZxeXNJLXsMRiO0U5Rb8/FvMS6xlTnTHvxonQ==", + "version": "7.13.14", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.13.14.tgz", + "integrity": "sha512-A2aa3QTkWoyqsZZFl56MLUsfmh7O0gN41IPvXAE/++8ojpbz12SszD7JEGYVdn4f9Kt4amIei07swF1h4AqmmQ==", "dev": true, "requires": { "@babel/helper-validator-identifier": "^7.12.11", @@ -2391,9 +7558,9 @@ } }, "@istanbuljs/schema": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.2.tgz", - "integrity": "sha512-tsAQNx32a8CoFhjhijUIhI4kccIAgmGhy8LZMZgGfmXcpMbPRUqn5LWmgRttILi6yeGmBJd2xsPkFMs0PzgPCw==", + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", "dev": true }, "@jest/console": { @@ -2626,9 +7793,9 @@ } }, "@types/babel__core": { - "version": "7.1.12", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.12.tgz", - "integrity": "sha512-wMTHiiTiBAAPebqaPiPDLFA4LYPKr6Ph0Xq/6rq1Ur3v66HXyG+clfR9CNETkD7MQS8ZHvpQOtA53DLws5WAEQ==", + "version": "7.1.14", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.14.tgz", + "integrity": "sha512-zGZJzzBUVDo/eV6KgbE0f0ZI7dInEYvo12Rb70uNQDshC3SkRMb67ja0GgRHZgAX3Za6rhaWlvbDO8rrGyAb1g==", "dev": true, "requires": { "@babel/parser": "^7.1.0", @@ -2658,9 +7825,9 @@ } }, "@types/babel__traverse": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.11.0.tgz", - "integrity": "sha512-kSjgDMZONiIfSH1Nxcr5JIRMwUetDki63FSQfpTCz8ogF3Ulqm8+mr5f78dUYs6vMiB6gBusQqfQmBvHZj/lwg==", + "version": "7.11.1", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.11.1.tgz", + "integrity": "sha512-Vs0hm0vPahPMYi9tDjtP66llufgO3ST16WXaSTtDGEl9cewAl3AibmxWw6TINOqHPT9z0uABKAYjT9jNSg4npw==", "dev": true, "requires": { "@babel/types": "^7.3.0" @@ -2682,9 +7849,9 @@ } }, "@types/graceful-fs": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.4.tgz", - "integrity": "sha512-mWA/4zFQhfvOA8zWkXobwJvBD7vzcxgrOQ0J5CH1votGqdq9m7+FwtGaqyCZqC3NyyBkc9z4m+iry4LlqcMWJg==", + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz", + "integrity": "sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw==", "dev": true, "requires": { "@types/node": "*" @@ -2725,9 +7892,9 @@ } }, "@types/jest": { - "version": "26.0.20", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-26.0.20.tgz", - "integrity": "sha512-9zi2Y+5USJRxd0FsahERhBwlcvFh6D2GLQnY2FH2BzK8J9s9omvNHIbvABwIluXa0fD8XVKMLTO0aOEuUfACAA==", + "version": "26.0.22", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-26.0.22.tgz", + "integrity": "sha512-eeWwWjlqxvBxc4oQdkueW5OF/gtfSceKk4OnOAGlUSwS/liBRtZppbJuz1YkgbrbfGOoeBHun9fOvXnjNwrSOw==", "dev": true, "requires": { "jest-diff": "^26.0.0", @@ -2735,9 +7902,9 @@ } }, "@types/minimatch": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", - "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-1z8k4wzFnNjVK/tlxvrWuK5WMt6mydWWP7+zvH5eFep4oj+UkrfiJTRtjCeBXNpwaA/FYqqtb4/QS4ianFpIRA==", "dev": true }, "@types/ms": { @@ -2759,9 +7926,9 @@ "dev": true }, "@types/prettier": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.1.6.tgz", - "integrity": "sha512-6gOkRe7OIioWAXfnO/2lFiv+SJichKVSys1mSsgyrYHSEjk8Ctv4tSR/Odvnu+HWlH2C8j53dahU03XmQdd5fA==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.2.3.tgz", + "integrity": "sha512-PijRCG/K3s3w1We6ynUKdxEc5AcuuH3NBmMDP8uvKVp6X43UY7NQlTzczakXP3DJR0F4dfNQIGjU2cUeRYs2AA==", "dev": true }, "@types/stack-utils": { @@ -2789,9 +7956,9 @@ } }, "@types/yargs": { - "version": "15.0.12", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.12.tgz", - "integrity": "sha512-f+fD/fQAo3BCbCDlrUpznF1A5Zp9rB0noS5vnoormHSIPFKL0Z2DcUJ3Gxp5ytH4uLRNxy7AwYUC9exZzqGMAw==", + "version": "15.0.13", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.13.tgz", + "integrity": "sha512-kQ5JNTrbDv3Rp5X2n/iUu37IJBDU2gsZ5R/g1/KHOOEc5IKfUFjXT6DENPGduh08I/pamwtEq4oul7gUqKTQDQ==", "dev": true, "requires": { "@types/yargs-parser": "*" @@ -2818,9 +7985,9 @@ } }, "acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.1.0.tgz", + "integrity": "sha512-LWCF/Wn0nfHOmJ9rzQApGnxnvgfROzGilS8936rqN/lfcYkY9MYZzdMqN+2NJ4SlTc+m5HiSa+kNfDtI64dwUA==", "dev": true }, "acorn-globals": { @@ -2831,6 +7998,14 @@ "requires": { "acorn": "^7.1.1", "acorn-walk": "^7.1.1" + }, + "dependencies": { + "acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true + } } }, "acorn-walk": { @@ -2883,6 +8058,14 @@ "picomatch": "^2.0.4" } }, + "arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true, + "optional": true, + "peer": true + }, "argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -3067,35 +8250,6 @@ "requires": { "is-descriptor": "^1.0.0" } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } } } }, @@ -3140,6 +8294,19 @@ "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==", "dev": true }, + "browserslist": { + "version": "4.16.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.3.tgz", + "integrity": "sha512-vIyhWmIkULaq04Gt93txdh+j02yX/JzlyhLYbV3YQCn/zvES3JnY7TifHHvvr1w5hTDluNKMkV05cs4vy8Q7sw==", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30001181", + "colorette": "^1.2.1", + "electron-to-chromium": "^1.3.649", + "escalade": "^3.1.1", + "node-releases": "^1.1.70" + } + }, "bs-logger": { "version": "0.2.6", "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", @@ -3193,6 +8360,12 @@ "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", "dev": true }, + "caniuse-lite": { + "version": "1.0.30001204", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001204.tgz", + "integrity": "sha512-JUdjWpcxfJ9IPamy2f5JaRDCaqJOxDzOSKtbdx4rH9VivMd1vIzoPumsJa9LoMIi4Fx2BV2KZOxWhNkBjaYivQ==", + "dev": true + }, "capture-exit": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/capture-exit/-/capture-exit-2.0.0.tgz", @@ -3260,6 +8433,63 @@ "requires": { "is-descriptor": "^0.1.0" } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + } + }, + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true } } }, @@ -3311,6 +8541,12 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, + "colorette": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.2.tgz", + "integrity": "sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==", + "dev": true + }, "combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -3359,6 +8595,14 @@ "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", "dev": true }, + "create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true, + "optional": true, + "peer": true + }, "cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -3468,37 +8712,6 @@ "requires": { "is-descriptor": "^1.0.2", "isobject": "^3.0.1" - }, - "dependencies": { - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - } } }, "delayed-stream": { @@ -3512,6 +8725,14 @@ "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", "dev": true }, + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "optional": true, + "peer": true + }, "diff-sequences": { "version": "26.6.2", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-26.6.2.tgz", @@ -3577,6 +8798,12 @@ "safer-buffer": "^2.1.0" } }, + "electron-to-chromium": { + "version": "1.3.702", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.702.tgz", + "integrity": "sha512-qJVUKFWQnF6wP7MmTngDkmm8/KPzaiTXNFOAg5j7DSa6J7kPou7mTBqC8jpUOxauQWwHR3pn4dMRdV8IE1xdtA==", + "dev": true + }, "emittery": { "version": "0.7.2", "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.7.2.tgz", @@ -3606,19 +8833,25 @@ "is-arrayish": "^0.2.1" } }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true + }, "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=" }, "escodegen": { - "version": "1.14.3", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz", - "integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.0.0.tgz", + "integrity": "sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw==", "dev": true, "requires": { "esprima": "^4.0.1", - "estraverse": "^4.2.0", + "estraverse": "^5.2.0", "esutils": "^2.0.2", "optionator": "^0.8.1", "source-map": "~0.6.1" @@ -3631,9 +8864,9 @@ "dev": true }, "estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", "dev": true }, "esutils": { @@ -3663,69 +8896,26 @@ "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==" }, "exec-sh": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/exec-sh/-/exec-sh-0.3.4.tgz", - "integrity": "sha512-sEFIkc61v75sWeOe72qyrqg2Qg0OuLESziUDk/O/z2qgS15y2gWVFrI6f2Qn/qw/0/NCfCEsmNA4zOjkwEZT1A==", + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/exec-sh/-/exec-sh-0.3.6.tgz", + "integrity": "sha512-nQn+hI3yp+oD0huYhKwvYI32+JFeq+XkNcD1GAo3Y/MjxsfVGmrrzrnzjWiNY6f+pUCP440fThsFh5gZrRAU/w==", "dev": true }, "execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz", + "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==", "dev": true, "requires": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - }, - "dependencies": { - "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "dev": true, - "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, - "path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", - "dev": true - }, - "shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", - "dev": true, - "requires": { - "shebang-regex": "^1.0.0" - } - }, - "shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", - "dev": true - }, - "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - } + "cross-spawn": "^7.0.0", + "get-stream": "^5.0.0", + "human-signals": "^1.1.1", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.0", + "onetime": "^5.1.0", + "signal-exit": "^3.0.2", + "strip-final-newline": "^2.0.0" } }, "exit": { @@ -3776,6 +8966,69 @@ "is-extendable": "^0.1.0" } }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + } + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "dev": true + }, + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -3812,17 +9065,6 @@ "requires": { "assign-symbols": "^1.0.0", "is-extendable": "^1.0.1" - }, - "dependencies": { - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "requires": { - "is-plain-object": "^2.0.4" - } - } } }, "external-editor": { @@ -3869,34 +9111,11 @@ "is-extendable": "^0.1.0" } }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "dev": true } } }, @@ -4030,9 +9249,9 @@ "dev": true }, "get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", "dev": true, "requires": { "pump": "^3.0.0" @@ -4073,9 +9292,9 @@ "dev": true }, "graceful-fs": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", - "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", + "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==", "dev": true }, "growly": { @@ -4206,9 +9425,9 @@ "dev": true }, "husky": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/husky/-/husky-5.0.6.tgz", - "integrity": "sha512-SM+evfvcHT3rAYJKvPlatz3L5RqzgeM6xIvDjhs8VuhKj6iKqFDOt/Ov8sPjvWuE4FDB385gJBwWXRj7G3c1hg==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/husky/-/husky-5.2.0.tgz", + "integrity": "sha512-AM8T/auHXRBxlrfPVLKP6jt49GCM2Zz47m8G3FOMsLmTv8Dj/fKVWE0Rh2d4Qrvmy131xEsdQnb3OXRib67PGg==", "dev": true }, "iconv-lite": { @@ -4269,30 +9488,13 @@ "through": "^2.3.6" } }, - "ip-regex": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz", - "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=", - "dev": true - }, "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", "dev": true, "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } + "kind-of": "^6.0.0" } }, "is-arrayish": { @@ -4326,42 +9528,23 @@ } }, "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", "dev": true, "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } + "kind-of": "^6.0.0" } }, "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", "dev": true, "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "dependencies": { - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true - } + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" } }, "is-docker": { @@ -4372,10 +9555,13 @@ "optional": true }, "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "dev": true + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } }, "is-fullwidth-code-point": { "version": "3.0.0", @@ -4410,9 +9596,9 @@ "dev": true }, "is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", + "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", "dev": true }, "is-typedarray": { @@ -4477,14 +9663,6 @@ "@istanbuljs/schema": "^0.1.2", "istanbul-lib-coverage": "^3.0.0", "semver": "^6.3.0" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } } }, "istanbul-lib-report": { @@ -4528,6 +9706,38 @@ "@jest/core": "^26.6.3", "import-local": "^3.0.2", "jest-cli": "^26.6.3" + } + }, + "jest-changed-files": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-26.6.2.tgz", + "integrity": "sha512-fDS7szLcY9sCtIip8Fjry9oGf3I2ht/QT21bAHm5Dmf0mD4X3ReNUf17y+bO6fR8WgbIZTlbyG1ak/53cbRzKQ==", + "dev": true, + "requires": { + "@jest/types": "^26.6.2", + "execa": "^4.0.0", + "throat": "^5.0.0" + } + }, + "jest-cli": { + "version": "26.6.3", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-26.6.3.tgz", + "integrity": "sha512-GF9noBSa9t08pSyl3CY4frMrqp+aQXFGFkf5hEPbh/pIUFYWMK6ZLTfbmadxJVcJrdRoChlWQsA2VkJcDFK8hg==", + "dev": true, + "requires": { + "@jest/core": "^26.6.3", + "@jest/test-result": "^26.6.2", + "@jest/types": "^26.6.2", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.4", + "import-local": "^3.0.2", + "is-ci": "^2.0.0", + "jest-config": "^26.6.3", + "jest-util": "^26.6.2", + "jest-validate": "^26.6.2", + "prompts": "^2.0.1", + "yargs": "^15.4.1" }, "dependencies": { "cliui": { @@ -4551,27 +9761,6 @@ "path-exists": "^4.0.0" } }, - "jest-cli": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-26.6.3.tgz", - "integrity": "sha512-GF9noBSa9t08pSyl3CY4frMrqp+aQXFGFkf5hEPbh/pIUFYWMK6ZLTfbmadxJVcJrdRoChlWQsA2VkJcDFK8hg==", - "dev": true, - "requires": { - "@jest/core": "^26.6.3", - "@jest/test-result": "^26.6.2", - "@jest/types": "^26.6.2", - "chalk": "^4.0.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.4", - "import-local": "^3.0.2", - "is-ci": "^2.0.0", - "jest-config": "^26.6.3", - "jest-util": "^26.6.2", - "jest-validate": "^26.6.2", - "prompts": "^2.0.1", - "yargs": "^15.4.1" - } - }, "locate-path": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", @@ -4641,60 +9830,6 @@ } } }, - "jest-changed-files": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-26.6.2.tgz", - "integrity": "sha512-fDS7szLcY9sCtIip8Fjry9oGf3I2ht/QT21bAHm5Dmf0mD4X3ReNUf17y+bO6fR8WgbIZTlbyG1ak/53cbRzKQ==", - "dev": true, - "requires": { - "@jest/types": "^26.6.2", - "execa": "^4.0.0", - "throat": "^5.0.0" - }, - "dependencies": { - "execa": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz", - "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==", - "dev": true, - "requires": { - "cross-spawn": "^7.0.0", - "get-stream": "^5.0.0", - "human-signals": "^1.1.1", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.0", - "onetime": "^5.1.0", - "signal-exit": "^3.0.2", - "strip-final-newline": "^2.0.0" - } - }, - "get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "dev": true, - "requires": { - "pump": "^3.0.0" - } - }, - "is-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", - "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", - "dev": true - }, - "npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "requires": { - "path-key": "^3.0.0" - } - } - } - }, "jest-config": { "version": "26.6.3", "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-26.6.3.tgz", @@ -4891,7 +10026,8 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz", "integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==", - "dev": true + "dev": true, + "requires": {} }, "jest-regex-util": { "version": "26.0.0", @@ -5114,9 +10250,9 @@ }, "dependencies": { "semver": { - "version": "7.3.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", - "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", + "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" @@ -5209,36 +10345,36 @@ "dev": true }, "jsdom": { - "version": "16.4.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.4.0.tgz", - "integrity": "sha512-lYMm3wYdgPhrl7pDcRmvzPhhrGVBeVhPIqeHjzeiHN3DFmD1RBpbExbi8vU7BJdH8VAZYovR8DMt0PNNDM7k8w==", + "version": "16.5.2", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.5.2.tgz", + "integrity": "sha512-JxNtPt9C1ut85boCbJmffaQ06NBnzkQY/MWO3YxPW8IWS38A26z+B1oBvA9LwKrytewdfymnhi4UNH3/RAgZrg==", "dev": true, "requires": { - "abab": "^2.0.3", - "acorn": "^7.1.1", + "abab": "^2.0.5", + "acorn": "^8.1.0", "acorn-globals": "^6.0.0", "cssom": "^0.4.4", - "cssstyle": "^2.2.0", + "cssstyle": "^2.3.0", "data-urls": "^2.0.0", - "decimal.js": "^10.2.0", + "decimal.js": "^10.2.1", "domexception": "^2.0.1", - "escodegen": "^1.14.1", + "escodegen": "^2.0.0", "html-encoding-sniffer": "^2.0.1", "is-potential-custom-element-name": "^1.0.0", "nwsapi": "^2.2.0", - "parse5": "5.1.1", + "parse5": "6.0.1", "request": "^2.88.2", - "request-promise-native": "^1.0.8", - "saxes": "^5.0.0", + "request-promise-native": "^1.0.9", + "saxes": "^5.0.1", "symbol-tree": "^3.2.4", - "tough-cookie": "^3.0.1", + "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.0.0", - "ws": "^7.2.3", + "whatwg-url": "^8.5.0", + "ws": "^7.4.4", "xml-name-validator": "^3.0.0" } }, @@ -5273,9 +10409,9 @@ "dev": true }, "json5": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz", - "integrity": "sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", + "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", "dev": true, "requires": { "minimist": "^1.2.5" @@ -5341,18 +10477,6 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==" }, - "lodash.memoize": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", - "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=", - "dev": true - }, - "lodash.sortby": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", - "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=", - "dev": true - }, "lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -5369,14 +10493,6 @@ "dev": true, "requires": { "semver": "^6.0.0" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } } }, "make-error": { @@ -5471,17 +10587,6 @@ "requires": { "for-in": "^1.0.2", "is-extendable": "^1.0.1" - }, - "dependencies": { - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "requires": { - "is-plain-object": "^2.0.4" - } - } } }, "mkdirp": { @@ -5560,9 +10665,9 @@ "dev": true }, "node-notifier": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-8.0.1.tgz", - "integrity": "sha512-BvEXF+UmsnAfYfoapKM9nGxnP+Wn7P91YfXmrKnfcYCx6VBeoN5Ez5Ogck6I8Bi5k4RlpqRYaw75pAwzX9OphA==", + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-8.0.2.tgz", + "integrity": "sha512-oJP/9NAdd9+x2Q+rfphB2RJCHjod70RcRLjosiPMMu5gjIfwVnOUGq2nbTjTUbmy0DJ/tFIVT30+Qe3nzl4TJg==", "dev": true, "optional": true, "requires": { @@ -5575,9 +10680,9 @@ }, "dependencies": { "semver": { - "version": "7.3.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", - "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", "dev": true, "optional": true, "requires": { @@ -5586,6 +10691,12 @@ } } }, + "node-releases": { + "version": "1.1.71", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.71.tgz", + "integrity": "sha512-zR6HoT6LrLCRBwukmrVbHv0EpEQjksO6GmFcZQQuCAy139BEsoVKPYnf3jongYW83fAa1torLGYwxxky/p28sg==", + "dev": true + }, "normalize-package-data": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", @@ -5596,6 +10707,14 @@ "resolve": "^1.10.0", "semver": "2 || 3 || 4 || 5", "validate-npm-package-license": "^3.0.1" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } } }, "normalize-path": { @@ -5605,20 +10724,12 @@ "dev": true }, "npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", "dev": true, "requires": { - "path-key": "^2.0.0" - }, - "dependencies": { - "path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", - "dev": true - } + "path-key": "^3.0.0" } }, "nwsapi": { @@ -5653,6 +10764,43 @@ "is-descriptor": "^0.1.0" } }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, "kind-of": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", @@ -5712,11 +10860,6 @@ "word-wrap": "~1.2.3" } }, - "os": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/os/-/os-0.1.1.tgz", - "integrity": "sha1-IIhF6J4ZOtTZcUdLk5R3NqVtE/M=" - }, "os-tmpdir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", @@ -5753,9 +10896,9 @@ } }, "parse5": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", - "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", "dev": true }, "pascalcase": { @@ -5948,9 +11091,9 @@ "dev": true }, "react-is": { - "version": "17.0.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.1.tgz", - "integrity": "sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA==", + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", "dev": true }, "read-pkg": { @@ -6148,12 +11291,12 @@ "dev": true }, "resolve": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.19.0.tgz", - "integrity": "sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg==", + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", + "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", "dev": true, "requires": { - "is-core-module": "^2.1.0", + "is-core-module": "^2.2.0", "path-parse": "^1.0.6" } }, @@ -6297,6 +11440,34 @@ } } }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "dev": true, + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + }, "fill-range": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", @@ -6320,6 +11491,21 @@ } } }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "dev": true + }, "is-number": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", @@ -6340,6 +11526,12 @@ } } }, + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", + "dev": true + }, "micromatch": { "version": "3.1.10", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", @@ -6370,6 +11562,42 @@ "remove-trailing-separator": "^1.0.1" } }, + "npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "dev": true, + "requires": { + "path-key": "^2.0.0" + } + }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true + }, "to-regex-range": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", @@ -6379,6 +11607,15 @@ "is-number": "^3.0.0", "repeat-string": "^1.6.1" } + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } } } }, @@ -6392,9 +11629,9 @@ } }, "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", "dev": true }, "set-blocking": { @@ -6423,6 +11660,12 @@ "requires": { "is-extendable": "^0.1.0" } + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "dev": true } } }, @@ -6513,6 +11756,69 @@ "is-extendable": "^0.1.0" } }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + } + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "dev": true + }, + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -6546,35 +11852,6 @@ "requires": { "is-descriptor": "^1.0.0" } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } } } }, @@ -6628,9 +11905,9 @@ } }, "source-map-url": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", - "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz", + "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==", "dev": true }, "spdx-correct": { @@ -6749,6 +12026,63 @@ "requires": { "is-descriptor": "^0.1.0" } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + } + }, + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true } } }, @@ -6774,9 +12108,9 @@ "dev": true }, "string-length": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.1.tgz", - "integrity": "sha512-PKyXUd0LK0ePjSOnWn34V2uD6acUWev9uy0Ft05k0E8xRW+SKcA0F7eMr7h5xlzfn+4O3N+55rduYyet3Jk+jw==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", "dev": true, "requires": { "char-regex": "^1.0.2", @@ -6937,14 +12271,14 @@ } }, "tough-cookie": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-3.0.1.tgz", - "integrity": "sha512-yQyJ0u4pZsv9D4clxO69OEjLWYw+jbgspjTue4lTQZLfV0c5l1VmK2y1JK8E9ahdpltPOaAThPcp5nKPUgSnsg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.0.0.tgz", + "integrity": "sha512-tHdtEpQCMrc1YLrMaqXXcj6AxhYi/xgit6mZu1+EDWUn+qhUf8wMQoFIy9NXuq23zAwtcB0t/MjACGR18pcRbg==", "dev": true, "requires": { - "ip-regex": "^2.1.0", - "psl": "^1.1.28", - "punycode": "^2.1.1" + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.1.2" } }, "tr46": { @@ -6957,18 +12291,17 @@ } }, "ts-jest": { - "version": "26.4.4", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-26.4.4.tgz", - "integrity": "sha512-3lFWKbLxJm34QxyVNNCgXX1u4o/RV0myvA2y2Bxm46iGIjKlaY0own9gIckbjZJPn+WaJEnfPPJ20HHGpoq4yg==", + "version": "26.5.4", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-26.5.4.tgz", + "integrity": "sha512-I5Qsddo+VTm94SukBJ4cPimOoFZsYTeElR2xy6H2TOVs+NsvgYglW8KuQgKoApOKuaU/Ix/vrF9ebFZlb5D2Pg==", "dev": true, "requires": { - "@types/jest": "26.x", "bs-logger": "0.x", "buffer-from": "1.x", "fast-json-stable-stringify": "2.x", "jest-util": "^26.1.0", "json5": "2.x", - "lodash.memoize": "4.x", + "lodash": "4.x", "make-error": "1.x", "mkdirp": "1.x", "semver": "7.x", @@ -6976,22 +12309,38 @@ }, "dependencies": { "semver": { - "version": "7.3.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", - "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", + "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" } }, "yargs-parser": { - "version": "20.2.4", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", - "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "version": "20.2.7", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.7.tgz", + "integrity": "sha512-FiNkvbeHzB/syOjIUxFDCnhSfzAL8R5vs40MgLFBorXACCOAEaWu0gRZl14vG8MR9AOJIZbmkjhusqBYZ3HTHw==", "dev": true } } }, + "ts-node": { + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-9.1.1.tgz", + "integrity": "sha512-hPlt7ZACERQGf03M253ytLY3dHbGNGrAq9qIHWUY9XHYl1z7wYngSr3OQ5xmui8o2AaxsONxIzjafLUiWBo1Fg==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "source-map-support": "^0.5.17", + "yn": "3.1.1" + } + }, "tsc-watch": { "version": "4.2.9", "resolved": "https://registry.npmjs.org/tsc-watch/-/tsc-watch-4.2.9.tgz", @@ -7069,8 +12418,22 @@ "get-value": "^2.0.6", "is-extendable": "^0.1.1", "set-value": "^2.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "dev": true + } } }, + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true + }, "unset-value": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", @@ -7140,9 +12503,9 @@ "optional": true }, "v8-to-istanbul": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-7.1.0.tgz", - "integrity": "sha512-uXUVqNUCLa0AH1vuVxzi+MI4RfxEOKt9pBgKwHbgH7st8Kv2P1m+jvWNnektzBh5QShF3ODgKmUFCf38LnVz1g==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-7.1.1.tgz", + "integrity": "sha512-p0BB09E5FRjx0ELN6RgusIPsSPhtgexSRcKETybEs6IGOTXJSZqfwxp7r//55nnu0f1AxltY5VvdVqy2vZf9AA==", "dev": true, "requires": { "@types/istanbul-lib-coverage": "^2.0.1", @@ -7228,12 +12591,12 @@ "dev": true }, "whatwg-url": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.4.0.tgz", - "integrity": "sha512-vwTUFf6V4zhcPkWp/4CQPr1TW9Ml6SF4lVyaIMBdJw5i6qUUJ1QWM4Z6YYVkfka0OUIzVo/0aNtGVGk256IKWw==", + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.5.0.tgz", + "integrity": "sha512-fy+R77xWv0AiqfLl4nuGUlQ3/6b5uNfQ4WAbGQVMYshCTCCPK9psC1nWh3XHuxGVCtlcDDQPQW1csmmIQo+fwg==", "dev": true, "requires": { - "lodash.sortby": "^4.7.0", + "lodash": "^4.7.0", "tr46": "^2.0.2", "webidl-conversions": "^6.1.0" } @@ -7277,9 +12640,10 @@ } }, "ws": { - "version": "7.4.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.2.tgz", - "integrity": "sha512-T4tewALS3+qsrpGI/8dqNMLIVdq/g/85U98HPMa6F0m6xTbvhXU6RCQLqPH3+SlomNV/LdY6RXEbBpMH6EOJnA==" + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.4.tgz", + "integrity": "sha512-Qm8k8ojNQIMx7S+Zp8u/uHOx7Qazv3Yv4q68MiWWWOJhiwG5W3x7iqmRtJo8xxrciZUY4vRxUTJCKuRnF28ZZw==", + "requires": {} }, "xml-name-validator": { "version": "3.0.0", @@ -7304,6 +12668,14 @@ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true + }, + "yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "optional": true, + "peer": true } } } diff --git a/src/commands/admin.ts b/src/commands/admin.ts index 0240dbd..e213204 100644 --- a/src/commands/admin.ts +++ b/src/commands/admin.ts @@ -1,9 +1,10 @@ import Command from "../core/command"; -import {CommonLibrary, logs, botHasPermission, clean} from "../core/lib"; +import {CommonLibrary, botHasPermission, clean} from "../core/lib"; import {Config, Storage} from "../core/structures"; import {PermissionNames, getPermissionLevel} from "../core/permissions"; import {Permissions} from "discord.js"; import * as discord from "discord.js"; +import {logs} from "../globals"; function getLogBuffer(type: string) { return { diff --git a/src/commands/fun/subcommands/eco-utils.ts b/src/commands/fun/subcommands/eco-utils.ts index 8ea5176..554b5bf 100644 --- a/src/commands/fun/subcommands/eco-utils.ts +++ b/src/commands/fun/subcommands/eco-utils.ts @@ -62,7 +62,7 @@ export function getSendEmbed(sender: User, receiver: User, amount: number): obje } export function isAuthorized(guild: Guild | null, channel: TextChannel | DMChannel | NewsChannel): boolean { - if (guild?.id === "637512823676600330" && channel?.id === "669464416420364288" || process.argv[2] === "dev") return true; + if ((guild?.id === "637512823676600330" && channel?.id === "669464416420364288") || IS_DEV_MODE) return true; else { channel.send("Sorry, this command can only be used in Monika's emote server. (#mon-stocks)"); return false; diff --git a/src/commands/help.ts b/src/commands/help.ts index 4a6ff2f..dd74ad4 100644 --- a/src/commands/help.ts +++ b/src/commands/help.ts @@ -19,7 +19,9 @@ export default new Command({ const command = commands.get(header); if (!command) - return $.warn(`Command "${header}" of category "${category}" unexpectedly doesn't exist!`); + return console.warn( + `Command "${header}" of category "${category}" unexpectedly doesn't exist!` + ); output += `\n- \`${header}\`: ${command.description}`; } @@ -37,7 +39,7 @@ export default new Command({ if (!command || header === "test") return $.channel.send(`No command found by the name \`${header}\`!`); if (command.originalCommandName) header = command.originalCommandName; - else $.warn(`originalCommandName isn't defined for ${header}?!`); + else console.warn(`originalCommandName isn't defined for ${header}?!`); let permLevel = command.permission ?? Command.PERMISSIONS.NONE; let usage = command.usage; @@ -48,7 +50,7 @@ export default new Command({ for (const [category, headers] of categories) { if (headers.includes(header)) { if (selectedCategory !== "Unknown") - $.warn( + console.warn( `Command "${header}" is somehow in multiple categories. This means that the command loading stage probably failed in properly adding categories.` ); else selectedCategory = $(category).toTitleCase(); diff --git a/src/commands/scanemotes.ts b/src/commands/scanemotes.ts index 1ad5cd8..f93a949 100644 --- a/src/commands/scanemotes.ts +++ b/src/commands/scanemotes.ts @@ -131,7 +131,7 @@ export default new Command({ continueReactionLoop = false; if (reaction.count !== userReactions + botReactions) { - $.warn( + console.warn( `[Channel: ${channel.id}, Message: ${msg.id}] A reaction count of ${reaction.count} was expected but was given ${userReactions} user reactions and ${botReactions} bot reactions.` ); warnings++; @@ -161,7 +161,7 @@ export default new Command({ "y" )}.` ); - $.log(`Finished operation in ${finishTime - startTime} ms.`); + console.log(`Finished operation in ${finishTime - startTime} ms.`); $.channel.stopTyping(); // Display stats on emote usage. diff --git a/src/core/command.ts b/src/core/command.ts index fd4eb64..ae2e73c 100644 --- a/src/core/command.ts +++ b/src/core/command.ts @@ -1,4 +1,4 @@ -import $, {isType, parseVars, CommonLibrary} from "./lib"; +import {isType, parseVars, CommonLibrary} from "./lib"; import {Collection} from "discord.js"; import {PERMISSIONS} from "./permissions"; import {getPrefix} from "../core/structures"; @@ -68,11 +68,11 @@ export default class Command { for (const alias of aliases) { if (baseSubcommands.includes(alias)) - $.warn( + console.warn( `"${alias}" in subcommand "${name}" was attempted to be declared as an alias but it already exists in the base commands! (Look at the next "Loading Command" line to see which command is affected.)` ); else if (this.subcommands.has(alias)) - $.warn( + console.warn( `Duplicate alias "${alias}" at subcommand "${name}"! (Look at the next "Loading Command" line to see which command is affected.)` ); else this.subcommands.set(alias, subcmd); @@ -81,17 +81,17 @@ export default class Command { } if (this.user && this.user.aliases.length > 0) - $.warn( + console.warn( `There are aliases defined for a "user"-type subcommand, but those aliases won't be used. (Look at the next "Loading Command" line to see which command is affected.)` ); if (this.number && this.number.aliases.length > 0) - $.warn( + console.warn( `There are aliases defined for a "number"-type subcommand, but those aliases won't be used. (Look at the next "Loading Command" line to see which command is affected.)` ); if (this.any && this.any.aliases.length > 0) - $.warn( + console.warn( `There are aliases defined for an "any"-type subcommand, but those aliases won't be used. (Look at the next "Loading Command" line to see which command is affected.)` ); } @@ -179,7 +179,7 @@ export const loadableCommands = (async () => { command.originalCommandName = commandName; if (commands.has(commandName)) { - $.warn( + console.warn( `Command "${commandName}" already exists! Make sure to make each command uniquely identifiable across categories!` ); } else { @@ -188,7 +188,7 @@ export const loadableCommands = (async () => { for (const alias of command.aliases) { if (commands.has(alias)) { - $.warn( + console.warn( `Top-level alias "${alias}" from command "${commandID}" already exists either as a command or alias!` ); } else { @@ -199,9 +199,9 @@ export const loadableCommands = (async () => { if (!(category in lists)) lists[category] = []; lists[category].push(commandName); - $.log(`Loading Command: ${commandID}`); + console.log(`Loading Command: ${commandID}`); } else { - $.warn(`Command "${commandID}" has no default export which is a Command instance!`); + console.warn(`Command "${commandID}" has no default export which is a Command instance!`); } } } diff --git a/src/core/event.ts b/src/core/event.ts index c5b2979..d2b6f9a 100644 --- a/src/core/event.ts +++ b/src/core/event.ts @@ -1,6 +1,5 @@ import {Client, ClientEvents, Constants} from "discord.js"; import Storage from "./storage"; -import $ from "./lib"; interface EventOptions { readonly on?: (...args: ClientEvents[K]) => void; @@ -30,9 +29,9 @@ export async function loadEvents(client: Client) { if ((Object.values(Constants.Events) as string[]).includes(header)) { event.attach(client, header); - $.log(`Loading Event: ${header}`); + console.log(`Loading Event: ${header}`); } else - $.warn( + console.warn( `"${header}" is not a valid event type! Did you misspell it? (Note: If you fixed the issue, delete "dist" because the compiler won't automatically delete any extra files.)` ); } diff --git a/src/core/lib.ts b/src/core/lib.ts index 31d9b37..d2e7d5b 100644 --- a/src/core/lib.ts +++ b/src/core/lib.ts @@ -1,6 +1,5 @@ import {GenericWrapper, NumberWrapper, StringWrapper, ArrayWrapper} from "./wrappers"; import {Client, Message, TextChannel, DMChannel, NewsChannel, Guild, User, GuildMember, Permissions} from "discord.js"; -import chalk from "chalk"; import {get} from "https"; import FileManager from "./storage"; import {eventListeners} from "../events/messageReactionRemove"; @@ -19,11 +18,6 @@ export interface CommonLibrary { // Common Library Functions // /** .catch($.handler.bind($)) or .catch(error => $.handler(error)) */ handler: (error: Error) => void; - log: (...args: any[]) => void; - warn: (...args: any[]) => void; - error: (...args: any[]) => void; - debug: (...args: any[]) => void; - ready: (...args: any[]) => void; paginate: ( message: Message, senderID: string, @@ -79,94 +73,13 @@ $.handler = function (this: CommonLibrary, error: Error) { `There was an error while trying to execute that command!\`\`\`${error.stack ?? error}\`\`\`` ); else - $.warn( + console.warn( "No context was attached to $.handler! Make sure to use .catch($.handler.bind($)) or .catch(error => $.handler(error)) instead!" ); - $.error(error); + console.error(error); }; -// Logs with different levels of verbosity. -export const logs: {[type: string]: string} = { - error: "", - warn: "", - info: "", - verbose: "" -}; - -let enabled = true; - -export function setConsoleActivated(activated: boolean) { - enabled = activated; -} - -// The custom console. In order of verbosity, error, warn, log, and debug. Ready is a variation of log. -// General Purpose Logger -$.log = (...args: any[]) => { - if (enabled) console.log(chalk.white.bgGray(formatTimestamp()), chalk.black.bgWhite("INFO"), ...args); - - const text = `[${formatUTCTimestamp()}] [INFO] ${args.join(" ")}\n`; - logs.info += text; - logs.verbose += text; -}; -// "It'll still work, but you should really check up on this." -$.warn = (...args: any[]) => { - if (enabled) console.warn(chalk.white.bgGray(formatTimestamp()), chalk.black.bgYellow("WARN"), ...args); - - const text = `[${formatUTCTimestamp()}] [WARN] ${args.join(" ")}\n`; - logs.warn += text; - logs.info += text; - logs.verbose += text; -}; -// Used for anything which prevents the program from actually running. -$.error = (...args: any[]) => { - if (enabled) console.error(chalk.white.bgGray(formatTimestamp()), chalk.white.bgRed("ERROR"), ...args); - - const text = `[${formatUTCTimestamp()}] [ERROR] ${args.join(" ")}\n`; - logs.error += text; - logs.warn += text; - logs.info += text; - logs.verbose += text; -}; -// Be as verbose as possible. If anything might help when debugging an error, then include it. This only shows in your console if you run this with "dev", but you can still get it from "logs.verbose". -// $.debug(`core/lib::parseArgs("testing \"in progress\"") = ["testing", "in progress"]`) --> /::(.)() = -// Would probably be more suited for debugging program logic rather than function logic, which can be checked using unit tests. -$.debug = (...args: any[]) => { - if (process.argv[2] === "dev" && enabled) - console.debug(chalk.white.bgGray(formatTimestamp()), chalk.white.bgBlue("DEBUG"), ...args); - - const text = `[${formatUTCTimestamp()}] [DEBUG] ${args.join(" ")}\n`; - logs.verbose += text; -}; -// Used once at the start of the program when the bot loads. -$.ready = (...args: any[]) => { - if (enabled) console.log(chalk.white.bgGray(formatTimestamp()), chalk.black.bgGreen("READY"), ...args); - - const text = `[${formatUTCTimestamp()}] [READY] ${args.join(" ")}\n`; - logs.info += text; - logs.verbose += text; -}; - -export function formatTimestamp(now = new Date()) { - const year = now.getFullYear(); - const month = (now.getMonth() + 1).toString().padStart(2, "0"); - const day = now.getDate().toString().padStart(2, "0"); - const hour = now.getHours().toString().padStart(2, "0"); - const minute = now.getMinutes().toString().padStart(2, "0"); - const second = now.getSeconds().toString().padStart(2, "0"); - return `${year}-${month}-${day} ${hour}:${minute}:${second}`; -} - -export function formatUTCTimestamp(now = new Date()) { - const year = now.getUTCFullYear(); - const month = (now.getUTCMonth() + 1).toString().padStart(2, "0"); - const day = now.getUTCDate().toString().padStart(2, "0"); - const hour = now.getUTCHours().toString().padStart(2, "0"); - const minute = now.getUTCMinutes().toString().padStart(2, "0"); - const second = now.getUTCSeconds().toString().padStart(2, "0"); - return `${year}-${month}-${day} ${hour}:${minute}:${second}`; -} - export function botHasPermission(guild: Guild | null, permission: number): boolean { return !!guild?.me?.hasPermission(permission); } diff --git a/src/core/permissions.ts b/src/core/permissions.ts index 06f9a5b..336ffb5 100644 --- a/src/core/permissions.ts +++ b/src/core/permissions.ts @@ -1,6 +1,5 @@ import {GuildMember, Permissions} from "discord.js"; import {Config} from "./structures"; -import $ from "./lib"; export enum PERMISSIONS { NONE, @@ -70,7 +69,7 @@ export function getPermissionLevel(member: GuildMember): number { // By transitive property, lenNames and lenChecker have to be equal to each other as well. if (length !== lenNames || length !== lenChecker) - $.error( + console.error( `Permission object lengths aren't equal! Enum Length (${length}), Names Length (${lenNames}), and Functions Length (${lenChecker}). This WILL cause problems!` ); })(); diff --git a/src/core/storage.ts b/src/core/storage.ts index a28e665..8553fc6 100644 --- a/src/core/storage.ts +++ b/src/core/storage.ts @@ -1,5 +1,4 @@ import fs from "fs"; -import $ from "./lib"; const Storage = { read(header: string): object { @@ -14,7 +13,7 @@ const Storage = { data = JSON.parse(file); } catch (error) { if (process.argv[2] !== "dev") { - $.warn(`Malformed JSON data (header: ${header}), backing it up.`, file); + console.warn(`Malformed JSON data (header: ${header}), backing it up.`, file); fs.writeFile( `${path}.backup`, file, @@ -30,7 +29,7 @@ const Storage = { this.open("data"); const path = `data/${header}.json`; - if (process.argv[2] === "dev" || header === "config") { + if (IS_DEV_MODE || header === "config") { const result = JSON.stringify(data, null, "\t"); if (asynchronous) @@ -60,8 +59,8 @@ const Storage = { export function generateHandler(message: string) { return (error: Error | null) => { - if (error) $.error(error); - else $.debug(message); + if (error) console.error(error); + else console.debug(message); }; } diff --git a/src/core/structures.ts b/src/core/structures.ts index 37b2194..74e5adf 100644 --- a/src/core/structures.ts +++ b/src/core/structures.ts @@ -1,5 +1,5 @@ import FileManager from "./storage"; -import $, {select, GenericJSON, GenericStructure} from "./lib"; +import {select, GenericJSON, GenericStructure} from "./lib"; import {watch} from "fs"; import {Guild as DiscordGuild, Snowflake} from "discord.js"; @@ -63,7 +63,7 @@ class StorageStructure extends GenericStructure { /** Gets a user's profile if they exist and generate one if not. */ public getUser(id: string): User { if (!/\d{17,19}/g.test(id)) - $.warn(`"${id}" is not a valid user ID! It will be erased when the data loads again.`); + console.warn(`"${id}" is not a valid user ID! It will be erased when the data loads again.`); if (id in this.users) return this.users[id]; else { @@ -76,7 +76,7 @@ class StorageStructure extends GenericStructure { /** Gets a guild's settings if they exist and generate one if not. */ public getGuild(id: string): Guild { if (!/\d{17,19}/g.test(id)) - $.warn(`"${id}" is not a valid guild ID! It will be erased when the data loads again.`); + console.warn(`"${id}" is not a valid guild ID! It will be erased when the data loads again.`); if (id in this.guilds) return this.guilds[id]; else { @@ -93,9 +93,9 @@ export let Storage = new StorageStructure(FileManager.read("storage")); // This part will allow the user to manually edit any JSON files they want while the program is running which'll update the program's cache. // However, fs.watch is a buggy mess that should be avoided in production. While it helps test out stuff for development, it's not a good idea to have it running outside of development as it causes all sorts of issues. -if (process.argv[2] === "dev") { +if (IS_DEV_MODE) { watch("data", (event, filename) => { - $.debug("File Watcher:", event, filename); + console.debug("File Watcher:", event, filename); const header = filename.substring(0, filename.indexOf(".json")); switch (header) { diff --git a/src/events/channelCreate.ts b/src/events/channelCreate.ts index 640cd51..47c2aa3 100644 --- a/src/events/channelCreate.ts +++ b/src/events/channelCreate.ts @@ -1,6 +1,5 @@ import Event from "../core/event"; import {client} from "../index"; -import $ from "../core/lib"; import * as discord from "discord.js"; export default new Event<"channelCreate">({ @@ -8,7 +7,7 @@ export default new Event<"channelCreate">({ const botGuilds = client.guilds; if (channel instanceof discord.GuildChannel) { const createdGuild = await botGuilds.fetch(channel.guild.id); - $.log(`Channel created in '${createdGuild.name}' called '#${channel.name}'`); + console.log(`Channel created in '${createdGuild.name}' called '#${channel.name}'`); } } }); diff --git a/src/events/channelDelete.ts b/src/events/channelDelete.ts index 48fa2df..ac835ed 100644 --- a/src/events/channelDelete.ts +++ b/src/events/channelDelete.ts @@ -1,6 +1,5 @@ import Event from "../core/event"; import {client} from "../index"; -import $ from "../core/lib"; import * as discord from "discord.js"; export default new Event<"channelDelete">({ @@ -8,7 +7,7 @@ export default new Event<"channelDelete">({ const botGuilds = client.guilds; if (channel instanceof discord.GuildChannel) { const createdGuild = await botGuilds.fetch(channel.guild.id); - $.log(`Channel deleted in '${createdGuild.name}' called '#${channel.name}'`); + console.log(`Channel deleted in '${createdGuild.name}' called '#${channel.name}'`); } } }); diff --git a/src/events/emojiCreate.ts b/src/events/emojiCreate.ts index dffd9aa..2af3f82 100644 --- a/src/events/emojiCreate.ts +++ b/src/events/emojiCreate.ts @@ -1,10 +1,9 @@ import Event from "../core/event"; -import $ from "../core/lib"; import {updateGlobalEmoteRegistry} from "../core/lib"; export default new Event<"emojiCreate">({ on(emote) { - $.log(`Updated emote registry. ${emote.name}`); + console.log(`Updated emote registry. ${emote.name}`); updateGlobalEmoteRegistry(); } }); diff --git a/src/events/emojiDelete.ts b/src/events/emojiDelete.ts index a3d5d43..08687c9 100644 --- a/src/events/emojiDelete.ts +++ b/src/events/emojiDelete.ts @@ -1,10 +1,9 @@ import Event from "../core/event"; -import $ from "../core/lib"; import {updateGlobalEmoteRegistry} from "../core/lib"; export default new Event<"emojiDelete">({ on() { - $.log("Updated emote registry."); + console.log("Updated emote registry."); updateGlobalEmoteRegistry(); } }); diff --git a/src/events/emojiUpdate.ts b/src/events/emojiUpdate.ts index a7a8e11..7dbe125 100644 --- a/src/events/emojiUpdate.ts +++ b/src/events/emojiUpdate.ts @@ -1,10 +1,9 @@ import Event from "../core/event"; -import $ from "../core/lib"; import {updateGlobalEmoteRegistry} from "../core/lib"; export default new Event<"emojiUpdate">({ on() { - $.log("Updated emote registry."); + console.log("Updated emote registry."); updateGlobalEmoteRegistry(); } }); diff --git a/src/events/guildCreate.ts b/src/events/guildCreate.ts index 2820db7..61fdec5 100644 --- a/src/events/guildCreate.ts +++ b/src/events/guildCreate.ts @@ -1,10 +1,9 @@ import Event from "../core/event"; -import $ from "../core/lib"; import {updateGlobalEmoteRegistry} from "../core/lib"; export default new Event<"guildCreate">({ on() { - $.log("Updated emote registry."); + console.log("Updated emote registry."); updateGlobalEmoteRegistry(); } }); diff --git a/src/events/guildDelete.ts b/src/events/guildDelete.ts index 8fed108..a8e7eaa 100644 --- a/src/events/guildDelete.ts +++ b/src/events/guildDelete.ts @@ -1,10 +1,9 @@ import Event from "../core/event"; -import $ from "../core/lib"; import {updateGlobalEmoteRegistry} from "../core/lib"; export default new Event<"guildDelete">({ on() { - $.log("Updated emote registry."); + console.log("Updated emote registry."); updateGlobalEmoteRegistry(); } }); diff --git a/src/events/message.ts b/src/events/message.ts index 85a54c0..701db08 100644 --- a/src/events/message.ts +++ b/src/events/message.ts @@ -82,13 +82,13 @@ export default new Event<"message">({ ); } - $.log( + console.log( `${message.author.username}#${message.author.discriminator} executed the command "${header}" with arguments "${args}".` ); // Subcommand Recursion // let command = commands.get(header); - if (!command) return $.warn(`Command "${header}" was called but for some reason it's still undefined!`); + if (!command) return console.warn(`Command "${header}" was called but for some reason it's still undefined!`); const params: any[] = []; let isEndpoint = false; let permLevel = command.permission ?? Command.PERMISSIONS.NONE; @@ -96,7 +96,7 @@ export default new Event<"message">({ for (let param of args) { if (command.endpoint) { if (command.subcommands.size > 0 || command.user || command.number || command.any) - $.warn(`An endpoint cannot have subcommands! Check ${originalPrefix}${header} again.`); + console.warn(`An endpoint cannot have subcommands! Check ${originalPrefix}${header} again.`); isEndpoint = true; break; } @@ -117,7 +117,7 @@ export default new Event<"message">({ } if (!message.member) - return $.warn("This command was likely called from a DM channel meaning the member object is null."); + return console.warn("This command was likely called from a DM channel meaning the member object is null."); if (!hasPermission(message.member, permLevel)) { const userPermLevel = getPermissionLevel(message.member); diff --git a/src/events/ready.ts b/src/events/ready.ts index e3fe021..9e2ec12 100644 --- a/src/events/ready.ts +++ b/src/events/ready.ts @@ -1,13 +1,12 @@ import Event from "../core/event"; import {client} from "../index"; -import $ from "../core/lib"; import {Config} from "../core/structures"; import {updateGlobalEmoteRegistry} from "../core/lib"; export default new Event<"ready">({ once() { if (client.user) { - $.ready(`Logged in as ${client.user.username}#${client.user.discriminator}.`); + console.ready(`Logged in as ${client.user.username}#${client.user.discriminator}.`); client.user.setActivity({ type: "LISTENING", name: `${Config.prefix}help` diff --git a/src/globals.ts b/src/globals.ts new file mode 100644 index 0000000..3c8d4c7 --- /dev/null +++ b/src/globals.ts @@ -0,0 +1,86 @@ +import chalk from "chalk"; + +declare global { + var IS_DEV_MODE: boolean; + + interface Console { + ready: (...data: any[]) => void; + } +} + +global.IS_DEV_MODE = process.argv[2] === "dev"; + +const oldConsole = console; + +export const logs: {[type: string]: string} = { + error: "", + warn: "", + info: "", + verbose: "" +}; + +function formatTimestamp(now = new Date()) { + const year = now.getFullYear(); + const month = (now.getMonth() + 1).toString().padStart(2, "0"); + const day = now.getDate().toString().padStart(2, "0"); + const hour = now.getHours().toString().padStart(2, "0"); + const minute = now.getMinutes().toString().padStart(2, "0"); + const second = now.getSeconds().toString().padStart(2, "0"); + return `${year}-${month}-${day} ${hour}:${minute}:${second}`; +} + +function formatUTCTimestamp(now = new Date()) { + const year = now.getUTCFullYear(); + const month = (now.getUTCMonth() + 1).toString().padStart(2, "0"); + const day = now.getUTCDate().toString().padStart(2, "0"); + const hour = now.getUTCHours().toString().padStart(2, "0"); + const minute = now.getUTCMinutes().toString().padStart(2, "0"); + const second = now.getUTCSeconds().toString().padStart(2, "0"); + return `${year}-${month}-${day} ${hour}:${minute}:${second}`; +} + +// The custom console. In order of verbosity, error, warn, log, and debug. Ready is a variation of log. +console = { + ...oldConsole, + // General Purpose Logger + log(...args: any[]) { + oldConsole.log(chalk.white.bgGray(formatTimestamp()), chalk.black.bgWhite("INFO"), ...args); + const text = `[${formatUTCTimestamp()}] [INFO] ${args.join(" ")}\n`; + logs.info += text; + logs.verbose += text; + }, + // "It'll still work, but you should really check up on this." + warn(...args: any[]) { + oldConsole.warn(chalk.white.bgGray(formatTimestamp()), chalk.black.bgYellow("WARN"), ...args); + const text = `[${formatUTCTimestamp()}] [WARN] ${args.join(" ")}\n`; + logs.warn += text; + logs.info += text; + logs.verbose += text; + }, + // Used for anything which prevents the program from actually running. + error(...args: any[]) { + oldConsole.error(chalk.white.bgGray(formatTimestamp()), chalk.white.bgRed("ERROR"), ...args); + const text = `[${formatUTCTimestamp()}] [ERROR] ${args.join(" ")}\n`; + logs.error += text; + logs.warn += text; + logs.info += text; + logs.verbose += text; + }, + // Be as verbose as possible. If anything might help when debugging an error, then include it. This only shows in your console if you run this with "dev", but you can still get it from "logs.verbose". + // $.debug(`core/lib::parseArgs("testing \"in progress\"") = ["testing", "in progress"]`) --> /::(.)() = + // Would probably be more suited for debugging program logic rather than function logic, which can be checked using unit tests. + debug(...args: any[]) { + if (IS_DEV_MODE) oldConsole.debug(chalk.white.bgGray(formatTimestamp()), chalk.white.bgBlue("DEBUG"), ...args); + const text = `[${formatUTCTimestamp()}] [DEBUG] ${args.join(" ")}\n`; + logs.verbose += text; + }, + // Used once at the start of the program when the bot loads. + ready(...args: any[]) { + oldConsole.log(chalk.white.bgGray(formatTimestamp()), chalk.black.bgGreen("READY"), ...args); + const text = `[${formatUTCTimestamp()}] [READY] ${args.join(" ")}\n`; + logs.info += text; + logs.verbose += text; + } +}; + +console.log("Loading globals..."); diff --git a/src/index.ts b/src/index.ts index aa0d155..426046c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,3 +1,4 @@ +import "./globals"; import * as discord from "discord.js"; import setup from "./setup"; import {Config} from "./core/structures"; diff --git a/src/modules/message_embed.ts b/src/modules/message_embed.ts index b69e76e..e8bdb6d 100644 --- a/src/modules/message_embed.ts +++ b/src/modules/message_embed.ts @@ -1,60 +1,61 @@ -import { client } from '..' -import { Message, TextChannel, APIMessage, MessageEmbed } from 'discord.js' -import { getPrefix } from '../core/structures' -import { DiscordAPIError } from 'discord.js' +import {client} from ".."; +import {Message, TextChannel, APIMessage, MessageEmbed} from "discord.js"; +import {getPrefix} from "../core/structures"; +import {DiscordAPIError} from "discord.js"; export default async function quote(message: Message) { - if (message.author.bot) return + if (message.author.bot) return; // const message_link_regex = message.content.match(/(!)?https?:\/\/\w+\.com\/channels\/(\d+)\/(\d+)\/(\d+)/) - const message_link_regex = message.content.match(/([?)/) + const message_link_regex = message.content.match( + /([?)/ + ); - if (message_link_regex == null) return - const [, char, guildID, channelID, messageID] = message_link_regex + if (message_link_regex == null) return; + const [, char, guildID, channelID, messageID] = message_link_regex; - if (char || message.content.startsWith(getPrefix(message.guild))) return + if (char || message.content.startsWith(getPrefix(message.guild))) return; try { - const channel = client.guilds.cache.get(guildID)?.channels.cache.get(channelID) as TextChannel - const link_message = await channel.messages.fetch(messageID) + const channel = client.guilds.cache.get(guildID)?.channels.cache.get(channelID) as TextChannel; + const link_message = await channel.messages.fetch(messageID); - let rtmsg: string | APIMessage = '' + let rtmsg: string | APIMessage = ""; if (link_message.cleanContent) { rtmsg = new APIMessage(message.channel as TextChannel, { content: link_message.cleanContent, - disableMentions: 'all', + disableMentions: "all", files: link_message.attachments.array() - }) + }); } - const embeds = [ - ...link_message.embeds.filter(v => v.type == 'rich'), - ...link_message.attachments.values() - ] + const embeds = [...link_message.embeds.filter((v) => v.type == "rich"), ...link_message.attachments.values()]; /// @ts-ignore if (!link_message.cleanContent && embeds.empty) { - const Embed = new MessageEmbed() - .setDescription('🚫 The message is empty.') - return message.channel.send(Embed) + const Embed = new MessageEmbed().setDescription("🚫 The message is empty."); + return message.channel.send(Embed); } const infoEmbed = new MessageEmbed() .setAuthor( link_message.author.username, - link_message.author.displayAvatarURL({format: 'png', dynamic: true, size: 4096})) + link_message.author.displayAvatarURL({format: "png", dynamic: true, size: 4096}) + ) .setTimestamp(link_message.createdTimestamp) - .setDescription(`${link_message.cleanContent}\n\nSent in **${link_message.guild?.name}** | <#${link_message.channel.id}> ([link](https://discord.com/channels/${guildID}/${channelID}/${messageID}))`); - if (link_message.attachments.size !== 0) { - const image = link_message.attachments.first(); - /// @ts-ignore - infoEmbed.setImage(image.url); - } + .setDescription( + `${link_message.cleanContent}\n\nSent in **${link_message.guild?.name}** | <#${link_message.channel.id}> ([link](https://discord.com/channels/${guildID}/${channelID}/${messageID}))` + ); + if (link_message.attachments.size !== 0) { + const image = link_message.attachments.first(); + /// @ts-ignore + infoEmbed.setImage(image.url); + } - await message.channel.send(infoEmbed) + await message.channel.send(infoEmbed); } catch (error) { if (error instanceof DiscordAPIError) { - message.channel.send("I don't have access to this channel, or something else went wrong.") + message.channel.send("I don't have access to this channel, or something else went wrong."); } - return console.error(error) + return console.error(error); } -} \ No newline at end of file +} diff --git a/src/setup.ts b/src/setup.ts index 7edde85..97dc6e6 100644 --- a/src/setup.ts +++ b/src/setup.ts @@ -2,12 +2,11 @@ import {existsSync as exists, readFileSync as read, writeFile as write} from "fs import inquirer from "inquirer"; import Storage, {generateHandler} from "./core/storage"; import {Config} from "./core/structures"; -import $, {setConsoleActivated} from "./core/lib"; // The template should be built with a reductionist mentality. // Provide everything the user needs and then let them remove whatever they want. // That way, they aren't focusing on what's missing, but rather what they need for their command. -if (process.argv[2] === "dev" && !exists("src/commands/test.ts")) { +if (IS_DEV_MODE && !exists("src/commands/test.ts")) { write( "src/commands/test.ts", read("src/commands/template.ts"), @@ -64,8 +63,19 @@ export default { }, /** Prompt the user to set their token again. */ async again() { - $.error("It seems that the token you provided is invalid."); - setConsoleActivated(false); + console.error("It seems that the token you provided is invalid."); + + // Deactivate the console // + const oldConsole = console; + console = { + ...oldConsole, + log() {}, + warn() {}, + error() {}, + debug() {}, + ready() {} + }; + const answers = await inquirer.prompt(prompts.slice(0, 1)); Config.token = answers.token as string; Config.save(false); From 51fa9457b4a50ed1390d06b6203e102fae6917e8 Mon Sep 17 00:00:00 2001 From: WatDuhHekBro <44940783+WatDuhHekBro@users.noreply.github.com> Date: Tue, 30 Mar 2021 05:25:07 -0500 Subject: [PATCH 112/178] Fully separated utility functions from command menu --- src/commands/admin.ts | 48 +- src/commands/fun/8ball.ts | 6 +- src/commands/fun/cookie.ts | 12 +- src/commands/fun/eco.ts | 3 +- src/commands/fun/neko.ts | 6 +- src/commands/fun/ok.ts | 3 +- src/commands/fun/owoify.ts | 4 +- src/commands/fun/poll.ts | 3 +- src/commands/fun/subcommands/eco-core.ts | 10 +- .../fun/subcommands/eco-shop-items.ts | 4 +- src/commands/fun/subcommands/eco-shop.ts | 9 +- src/commands/fun/subcommands/eco-utils.ts | 10 +- src/commands/help.ts | 20 +- src/commands/info.ts | 20 +- src/commands/scanemotes.ts | 23 +- src/commands/template.ts | 11 +- src/commands/utilities/desc.ts | 19 +- src/commands/utilities/emote.ts | 2 +- src/commands/utilities/lsemotes.ts | 33 +- src/commands/utilities/react.ts | 5 +- src/commands/utilities/say.ts | 3 +- src/commands/utilities/shorten.ts | 3 +- src/commands/utilities/time.ts | 7 +- src/core/command.ts | 42 +- src/core/{wrappers.test.ts => lib.test.ts} | 26 +- src/core/lib.ts | 510 ++---------------- src/core/libd.ts | 418 ++++++++++++++ src/core/structures.ts | 2 +- src/core/wrappers.ts | 73 --- src/events/emojiCreate.ts | 2 +- src/events/emojiDelete.ts | 2 +- src/events/emojiUpdate.ts | 2 +- src/events/guildCreate.ts | 2 +- src/events/guildDelete.ts | 2 +- src/events/message.ts | 26 +- src/events/messageReactionRemove.ts | 2 +- src/events/ready.ts | 2 +- 37 files changed, 677 insertions(+), 698 deletions(-) rename src/core/{wrappers.test.ts => lib.test.ts} (58%) create mode 100644 src/core/libd.ts delete mode 100644 src/core/wrappers.ts diff --git a/src/commands/admin.ts b/src/commands/admin.ts index e213204..9545799 100644 --- a/src/commands/admin.ts +++ b/src/commands/admin.ts @@ -1,5 +1,5 @@ -import Command from "../core/command"; -import {CommonLibrary, botHasPermission, clean} from "../core/lib"; +import Command, {handler} from "../core/command"; +import {botHasPermission, clean} from "../core/libd"; import {Config, Storage} from "../core/structures"; import {PermissionNames, getPermissionLevel} from "../core/permissions"; import {Permissions} from "discord.js"; @@ -23,11 +23,11 @@ const statuses = ["online", "idle", "dnd", "invisible"]; export default new Command({ description: "An all-in-one command to do admin stuff. You need to be either an admin of the server or one of the bot's mechanics to use this command.", - async run($: CommonLibrary): Promise { - if (!$.member) - return $.channel.send( - "Couldn't find a member object for you! Did you make sure you used this in a server?" - ); + async run($) { + if (!$.member) { + $.channel.send("Couldn't find a member object for you! Did you make sure you used this in a server?"); + return; + } const permLevel = getPermissionLevel($.member); $.channel.send( `${$.author.toString()}, your permission level is \`${PermissionNames[permLevel]}\` (${permLevel}).` @@ -42,7 +42,7 @@ export default new Command({ prefix: new Command({ description: "Set a custom prefix for your guild. Removes your custom prefix if none is provided.", usage: "()", - async run($: CommonLibrary): Promise { + async run($) { Storage.getGuild($.guild?.id || "N/A").prefix = null; Storage.save(); $.channel.send( @@ -50,7 +50,7 @@ export default new Command({ ); }, any: new Command({ - async run($: CommonLibrary): Promise { + async run($) { Storage.getGuild($.guild?.id || "N/A").prefix = $.args[0]; Storage.save(); $.channel.send(`The custom prefix for this guild is now \`${$.args[0]}\`.`); @@ -62,12 +62,12 @@ export default new Command({ diag: new Command({ description: 'Requests a debug log with the "info" verbosity level.', permission: Command.PERMISSIONS.BOT_SUPPORT, - async run($: CommonLibrary): Promise { + async run($) { $.channel.send(getLogBuffer("info")); }, any: new Command({ description: `Select a verbosity to listen to. Available levels: \`[${Object.keys(logs).join(", ")}]\``, - async run($: CommonLibrary): Promise { + async run($) { const type = $.args[0]; if (type in logs) $.channel.send(getLogBuffer(type)); @@ -83,14 +83,16 @@ export default new Command({ status: new Command({ description: "Changes the bot's status.", permission: Command.PERMISSIONS.BOT_SUPPORT, - async run($: CommonLibrary): Promise { + async run($) { $.channel.send("Setting status to `online`..."); }, any: new Command({ description: `Select a status to set to. Available statuses: \`[${statuses.join(", ")}]\`.`, - async run($: CommonLibrary): Promise { - if (!statuses.includes($.args[0])) return $.channel.send("That status doesn't exist!"); - else { + async run($) { + if (!statuses.includes($.args[0])) { + $.channel.send("That status doesn't exist!"); + return; + } else { $.client.user?.setStatus($.args[0]); $.channel.send(`Setting status to \`${$.args[0]}\`...`); } @@ -100,7 +102,7 @@ export default new Command({ purge: new Command({ description: "Purges bot messages.", permission: Command.PERMISSIONS.BOT_SUPPORT, - async run($: CommonLibrary): Promise { + async run($) { if ($.message.channel instanceof discord.DMChannel) { return; } @@ -124,7 +126,7 @@ export default new Command({ run: "A number was not provided.", number: new Command({ description: "Amount of messages to delete.", - async run($: CommonLibrary): Promise { + async run($) { if ($.channel.type === "dm") { await $.channel.send("Can't clear messages in the DMs!"); return; @@ -142,7 +144,7 @@ export default new Command({ usage: "", permission: Command.PERMISSIONS.BOT_OWNER, // You have to bring everything into scope to use them. AFAIK, there isn't a more maintainable way to do this, but at least TS will let you know if anything gets removed. - async run({args, author, channel, client, guild, member, message}): Promise { + async run({args, author, channel, client, guild, member, message}) { try { const code = args.join(" "); let evaled = eval(code); @@ -157,18 +159,18 @@ export default new Command({ nick: new Command({ description: "Change the bot's nickname.", permission: Command.PERMISSIONS.BOT_SUPPORT, - async run($: CommonLibrary): Promise { + async run($) { const nickName = $.args.join(" "); await $.guild?.me?.setNickname(nickName); if (botHasPermission($.guild, Permissions.FLAGS.MANAGE_MESSAGES)) - $.message.delete({timeout: 5000}).catch($.handler.bind($)); + $.message.delete({timeout: 5000}).catch(handler.bind($)); $.channel.send(`Nickname set to \`${nickName}\``).then((m) => m.delete({timeout: 5000})); } }), guilds: new Command({ description: "Shows a list of all guilds the bot is a member of.", permission: Command.PERMISSIONS.BOT_SUPPORT, - async run($: CommonLibrary): Promise { + async run($) { const guildList = $.client.guilds.cache.array().map((e) => e.name); $.channel.send(guildList); } @@ -177,7 +179,7 @@ export default new Command({ description: "Set the activity of the bot.", permission: Command.PERMISSIONS.BOT_SUPPORT, usage: " ", - async run($: CommonLibrary): Promise { + async run($) { $.client.user?.setActivity(".help", { type: "LISTENING" }); @@ -185,7 +187,7 @@ export default new Command({ }, any: new Command({ description: `Select an activity type to set. Available levels: \`[${activities.join(", ")}]\``, - async run($: CommonLibrary): Promise { + async run($) { const type = $.args[0]; if (activities.includes(type)) { diff --git a/src/commands/fun/8ball.ts b/src/commands/fun/8ball.ts index c43ac31..84488a7 100644 --- a/src/commands/fun/8ball.ts +++ b/src/commands/fun/8ball.ts @@ -1,5 +1,5 @@ import Command from "../../core/command"; -import {CommonLibrary} from "../../core/lib"; +import {random} from "../../core/lib"; const responses = [ "Most likely,", @@ -31,9 +31,9 @@ export default new Command({ run: "Please provide a question.", any: new Command({ description: "Question to ask the 8-ball.", - async run($: CommonLibrary): Promise { + async run($) { const sender = $.message.author; - $.channel.send($(responses).random() + ` <@${sender.id}>`); + $.channel.send(`${random(responses)} <@${sender.id}>`); } }) }); diff --git a/src/commands/fun/cookie.ts b/src/commands/fun/cookie.ts index 5c545e9..99ace4b 100644 --- a/src/commands/fun/cookie.ts +++ b/src/commands/fun/cookie.ts @@ -1,18 +1,17 @@ import Command from "../../core/command"; -import {CommonLibrary} from "../../core/lib"; export default new Command({ description: "Gives specified user a cookie.", usage: "['all'/@user]", run: ":cookie: Here's a cookie!", any: new Command({ - async run($: CommonLibrary): Promise { - if ($.args[0] == "all") return $.channel.send(`${$.author} gave everybody a cookie!`); + async run($) { + if ($.args[0] == "all") $.channel.send(`${$.author} gave everybody a cookie!`); } }), user: new Command({ description: "User to give cookie to.", - async run($: CommonLibrary): Promise { + async run($) { const sender = $.author; const mention = $.message.mentions.users.first(); @@ -41,7 +40,10 @@ export default new Command({ `bakes <@${mention.id}> fresh cookies, it smells amazing.` ]; - if (mention.id == sender.id) return $.channel.send("You can't give yourself cookies!"); + if (mention.id == sender.id) { + $.channel.send("You can't give yourself cookies!"); + return; + } $.channel.send(`:cookie: <@${sender.id}> ` + cookies[Math.floor(Math.random() * cookies.length)]); } diff --git a/src/commands/fun/eco.ts b/src/commands/fun/eco.ts index 5be316f..6df36ae 100644 --- a/src/commands/fun/eco.ts +++ b/src/commands/fun/eco.ts @@ -3,6 +3,7 @@ import {isAuthorized, getMoneyEmbed} from "./subcommands/eco-utils"; import {DailyCommand, PayCommand, GuildCommand, LeaderboardCommand} from "./subcommands/eco-core"; import {BuyCommand, ShopCommand} from "./subcommands/eco-shop"; import {MondayCommand} from "./subcommands/eco-extras"; +import {callMemberByUsername} from "../../core/libd"; export default new Command({ description: "Economy command for Monika.", @@ -26,7 +27,7 @@ export default new Command({ }), any: new Command({ description: "See how much money someone else has by using their username.", - async run({guild, channel, args, callMemberByUsername, message}) { + async run({guild, channel, args, message}) { if (isAuthorized(guild, channel)) callMemberByUsername(message, args.join(" "), (member) => { channel.send(getMoneyEmbed(member.user)); diff --git a/src/commands/fun/neko.ts b/src/commands/fun/neko.ts index 63a8073..68ab1bf 100644 --- a/src/commands/fun/neko.ts +++ b/src/commands/fun/neko.ts @@ -2,13 +2,13 @@ import {URL} from "url"; import FileManager from "../../core/storage"; import Command from "../../core/command"; -import {CommonLibrary, getContent} from "../../core/lib"; +import {getContent} from "../../core/libd"; const endpoints = FileManager.read("endpoints"); export default new Command({ description: "Provides you with a random image with the selected argument.", - async run($: CommonLibrary): Promise { + async run($) { console.log(endpoints.sfw); $.channel.send( `Please provide an image type. Available arguments:\n\`[${Object.keys(endpoints.sfw).join(", ")}]\`.` @@ -16,7 +16,7 @@ export default new Command({ }, any: new Command({ description: "Image type to send.", - async run($: CommonLibrary): Promise { + async run($) { if (!($.args[0] in endpoints.sfw)) return $.channel.send("Couldn't find that endpoint!"); let baseURL = "https://nekos.life/api/v2"; diff --git a/src/commands/fun/ok.ts b/src/commands/fun/ok.ts index c7b3570..9c8d641 100644 --- a/src/commands/fun/ok.ts +++ b/src/commands/fun/ok.ts @@ -1,9 +1,8 @@ import Command from "../../core/command"; -import {CommonLibrary} from "../../core/lib"; export default new Command({ description: "Sends random ok message.", - async run($: CommonLibrary): Promise { + async run($) { const responses = [ "boomer", "zoomer", diff --git a/src/commands/fun/owoify.ts b/src/commands/fun/owoify.ts index ca5d18a..789d74a 100644 --- a/src/commands/fun/owoify.ts +++ b/src/commands/fun/owoify.ts @@ -1,10 +1,10 @@ /// @ts-nocheck import Command from "../../core/command"; -import {CommonLibrary, getContent} from "../../core/lib"; +import {getContent} from "../../core/libd"; export default new Command({ description: "OwO-ifies the input.", - async run($: CommonLibrary): Promise { + async run($) { let url = new URL(`https://nekos.life/api/v2/owoify?text=${$.args.join(" ")}`); const content = await getContent(url.toString()); $.channel.send(content.owo); diff --git a/src/commands/fun/poll.ts b/src/commands/fun/poll.ts index e104798..8b6d6e1 100644 --- a/src/commands/fun/poll.ts +++ b/src/commands/fun/poll.ts @@ -1,6 +1,5 @@ import {MessageEmbed} from "discord.js"; import Command from "../../core/command"; -import {CommonLibrary} from "../../core/lib"; export default new Command({ description: "Create a poll.", @@ -8,7 +7,7 @@ export default new Command({ run: "Please provide a question.", any: new Command({ description: "Question for the poll.", - async run($: CommonLibrary): Promise { + async run($) { const embed = new MessageEmbed() .setAuthor( `Poll created by ${$.message.author.username}`, diff --git a/src/commands/fun/subcommands/eco-core.ts b/src/commands/fun/subcommands/eco-core.ts index 5723137..ea174b1 100644 --- a/src/commands/fun/subcommands/eco-core.ts +++ b/src/commands/fun/subcommands/eco-core.ts @@ -1,5 +1,6 @@ import Command from "../../../core/command"; -import $ from "../../../core/lib"; +import {prompt} from "../../../core/libd"; +import {pluralise} from "../../../core/lib"; import {Storage} from "../../../core/structures"; import {isAuthorized, getMoneyEmbed, getSendEmbed, ECO_EMBED_COLOR} from "./eco-utils"; @@ -90,7 +91,7 @@ export const LeaderboardCommand = new Command({ fields.push({ name: `#${i + 1}. ${user.username}#${user.discriminator}`, - value: $(users[id].money).pluralise("Mon", "s") + value: pluralise(users[id].money, "Mon", "s") }); } @@ -141,7 +142,7 @@ export const PayCommand = new Command({ run: "You must use the format `eco pay `!" }), any: new Command({ - async run({args, author, channel, guild, prompt}) { + async run({args, author, channel, guild}) { if (isAuthorized(guild, channel)) { const last = args.pop(); @@ -177,7 +178,8 @@ export const PayCommand = new Command({ return prompt( await channel.send( - `Are you sure you want to send ${$(amount).pluralise( + `Are you sure you want to send ${pluralise( + amount, "Mon", "s" )} to this person?\n*(This message will automatically be deleted after 10 seconds.)*`, diff --git a/src/commands/fun/subcommands/eco-shop-items.ts b/src/commands/fun/subcommands/eco-shop-items.ts index cd0580c..0a90549 100644 --- a/src/commands/fun/subcommands/eco-shop-items.ts +++ b/src/commands/fun/subcommands/eco-shop-items.ts @@ -1,5 +1,5 @@ import {Message} from "discord.js"; -import $ from "../../../core/lib"; +import {random} from "../../../core/lib"; export interface ShopItem { cost: number; @@ -43,7 +43,7 @@ export const ShopItems: ShopItem[] = [ description: "Buys what is technically a laser bridge.", usage: "laser bridge", run(message) { - message.channel.send($(lines).random(), { + message.channel.send(random(lines), { files: [ { attachment: diff --git a/src/commands/fun/subcommands/eco-shop.ts b/src/commands/fun/subcommands/eco-shop.ts index 650cd8f..24ebcd7 100644 --- a/src/commands/fun/subcommands/eco-shop.ts +++ b/src/commands/fun/subcommands/eco-shop.ts @@ -1,5 +1,6 @@ import Command from "../../../core/command"; -import $ from "../../../core/lib"; +import {pluralise, split} from "../../../core/lib"; +import {paginate} from "../../../core/libd"; import {Storage, getPrefix} from "../../../core/structures"; import {isAuthorized, ECO_EMBED_COLOR} from "./eco-utils"; import {ShopItems, ShopItem} from "./eco-shop-items"; @@ -15,7 +16,7 @@ export const ShopCommand = new Command({ for (const item of selection) fields.push({ name: `**${item.title}** (${getPrefix(guild)}eco buy ${item.usage})`, - value: `${item.description} Costs ${$(item.cost).pluralise("Mon", "s")}.`, + value: `${item.description} Costs ${pluralise(item.cost, "Mon", "s")}.`, inline: false }); @@ -34,11 +35,11 @@ export const ShopCommand = new Command({ // In case there's just one page, omit unnecessary details. if (ShopItems.length <= 5) channel.send(getShopEmbed(ShopItems)); else { - const shopPages = $(ShopItems).split(5); + const shopPages = split(ShopItems, 5); const pageAmount = shopPages.length; const msg = await channel.send(getShopEmbed(shopPages[0], `Shop (Page 1 of ${pageAmount})`)); - $.paginate(msg, author.id, pageAmount, (page) => { + paginate(msg, author.id, pageAmount, (page) => { msg.edit(getShopEmbed(shopPages[page], `Shop (Page ${page + 1} of ${pageAmount})`)); }); } diff --git a/src/commands/fun/subcommands/eco-utils.ts b/src/commands/fun/subcommands/eco-utils.ts index 554b5bf..e4ea32f 100644 --- a/src/commands/fun/subcommands/eco-utils.ts +++ b/src/commands/fun/subcommands/eco-utils.ts @@ -1,4 +1,4 @@ -import $ from "../../../core/lib"; +import {pluralise} from "../../../core/lib"; import {Storage} from "../../../core/structures"; import {User, Guild, TextChannel, DMChannel, NewsChannel} from "discord.js"; @@ -20,7 +20,7 @@ export function getMoneyEmbed(user: User): object { fields: [ { name: "Balance", - value: $(profile.money).pluralise("Mon", "s") + value: pluralise(profile.money, "Mon", "s") } ] } @@ -39,15 +39,15 @@ export function getSendEmbed(sender: User, receiver: User, amount: number): obje }) }, title: "Transaction", - description: `${sender.toString()} has sent ${$(amount).pluralise("Mon", "s")} to ${receiver.toString()}!`, + description: `${sender.toString()} has sent ${pluralise(amount, "Mon", "s")} to ${receiver.toString()}!`, fields: [ { name: `Sender: ${sender.username}#${sender.discriminator}`, - value: $(Storage.getUser(sender.id).money).pluralise("Mon", "s") + value: pluralise(Storage.getUser(sender.id).money, "Mon", "s") }, { name: `Receiver: ${receiver.username}#${receiver.discriminator}`, - value: $(Storage.getUser(receiver.id).money).pluralise("Mon", "s") + value: pluralise(Storage.getUser(receiver.id).money, "Mon", "s") } ], footer: { diff --git a/src/commands/help.ts b/src/commands/help.ts index dd74ad4..02b8650 100644 --- a/src/commands/help.ts +++ b/src/commands/help.ts @@ -1,5 +1,5 @@ import Command from "../core/command"; -import {CommonLibrary} from "../core/lib"; +import {toTitleCase} from "../core/lib"; import {loadableCommands, categories} from "../core/command"; import {PermissionNames} from "../core/permissions"; @@ -7,12 +7,12 @@ export default new Command({ description: "Lists all commands. If a command is specified, their arguments are listed as well.", usage: "([command, [subcommand/type], ...])", aliases: ["h"], - async run($: CommonLibrary): Promise { + async run($) { const commands = await loadableCommands; let output = `Legend: \`\`, \`[list/of/stuff]\`, \`(optional)\`, \`()\`, \`([optional/list/...])\``; for (const [category, headers] of categories) { - output += `\n\n===[ ${$(category).toTitleCase()} ]===`; + output += `\n\n===[ ${toTitleCase(category)} ]===`; for (const header of headers) { if (header !== "test") { @@ -31,12 +31,15 @@ export default new Command({ $.channel.send(output, {split: true}); }, any: new Command({ - async run($: CommonLibrary): Promise { + async run($) { const commands = await loadableCommands; let header = $.args.shift() as string; let command = commands.get(header); - if (!command || header === "test") return $.channel.send(`No command found by the name \`${header}\`!`); + if (!command || header === "test") { + $.channel.send(`No command found by the name \`${header}\`!`); + return; + } if (command.originalCommandName) header = command.originalCommandName; else console.warn(`originalCommandName isn't defined for ${header}?!`); @@ -53,7 +56,7 @@ export default new Command({ console.warn( `Command "${header}" is somehow in multiple categories. This means that the command loading stage probably failed in properly adding categories.` ); - else selectedCategory = $(category).toTitleCase(); + else selectedCategory = toTitleCase(category); } } @@ -86,7 +89,10 @@ export default new Command({ } } - if (invalid) return $.channel.send(`No command found by the name \`${header}\`!`); + if (invalid) { + $.channel.send(`No command found by the name \`${header}\`!`); + return; + } let append = ""; diff --git a/src/commands/info.ts b/src/commands/info.ts index 3ee4128..7b4b103 100644 --- a/src/commands/info.ts +++ b/src/commands/info.ts @@ -2,7 +2,7 @@ import {MessageEmbed, version as djsversion} from "discord.js"; import ms from "ms"; import os from "os"; import Command from "../core/command"; -import {CommonLibrary, formatBytes, trimArray} from "../core/lib"; +import {formatBytes, trimArray} from "../core/libd"; import {verificationLevels, filterLevels, regions, flags} from "../defs/info"; import moment from "moment"; import utc from "moment"; @@ -17,12 +17,12 @@ export default new Command({ avatar: new Command({ description: "Shows your own, or another user's avatar.", usage: "()", - async run($: CommonLibrary): Promise { + async run($) { $.channel.send($.author.displayAvatarURL({dynamic: true, size: 2048})); }, user: new Command({ description: "Shows your own, or another user's avatar.", - async run($: CommonLibrary): Promise { + async run($) { $.channel.send( $.args[0].displayAvatarURL({ dynamic: true, @@ -34,7 +34,7 @@ export default new Command({ }), bot: new Command({ description: "Displays info about the bot.", - async run($: CommonLibrary): Promise { + async run($) { const core = os.cpus()[0]; const embed = new MessageEmbed() .setColor($.guild?.me?.displayHexColor || "BLUE") @@ -76,7 +76,7 @@ export default new Command({ guild: new Command({ description: "Displays info about the current guild or another guild.", usage: "(/)", - async run($: CommonLibrary): Promise { + async run($) { if ($.guild) { $.channel.send(await getGuildInfo($.guild, $.guild)); } else { @@ -85,7 +85,7 @@ export default new Command({ }, any: new Command({ description: "Display info about a guild by finding its name or ID.", - async run($: CommonLibrary): Promise { + async run($) { // If a guild ID is provided (avoid the "number" subcommand because of inaccuracies), search for that guild if ($.args.length === 1 && /^\d{17,19}$/.test($.args[0])) { const id = $.args[0]; @@ -112,14 +112,16 @@ export default new Command({ }, user: new Command({ description: "Displays info about mentioned user.", - async run($: CommonLibrary): Promise { + async run($) { // Transforms the User object into a GuildMember object of the current guild. const member = await $.guild?.members.fetch($.args[0]); - if (!member) - return $.channel.send( + if (!member) { + $.channel.send( "No member object was found by that user! Are you sure you used this command in a server?" ); + return; + } const roles = member.roles.cache .sort((a: {position: number}, b: {position: number}) => b.position - a.position) diff --git a/src/commands/scanemotes.ts b/src/commands/scanemotes.ts index f93a949..9596f9f 100644 --- a/src/commands/scanemotes.ts +++ b/src/commands/scanemotes.ts @@ -1,5 +1,5 @@ -import Command from "../core/command"; -import {CommonLibrary} from "../core/lib"; +import Command, {handler} from "../core/command"; +import {pluralise} from "../core/lib"; import moment from "moment"; import {Collection, TextChannel} from "discord.js"; @@ -8,8 +8,11 @@ const lastUsedTimestamps: {[id: string]: number} = {}; export default new Command({ description: "Scans all text channels in the current guild and returns the number of times each emoji specific to the guild has been used. Has a cooldown of 24 hours per guild.", - async run($: CommonLibrary): Promise { - if (!$.guild) return $.channel.send(`You must use this command on a server!`); + async run($) { + if (!$.guild) { + $.channel.send(`You must use this command on a server!`); + return; + } // Test if the command is on cooldown. This isn't the strictest cooldown possible, because in the event that the bot crashes, the cooldown will be reset. But for all intends and purposes, it's a good enough cooldown. It's a per-server cooldown. const startTime = Date.now(); @@ -19,11 +22,12 @@ export default new Command({ const howLong = moment(startTime).to(lastUsedTimestamp + cooldown); // If it's been less than an hour since the command was last used, prevent it from executing. - if (difference < cooldown) - return $.channel.send( + if (difference < cooldown) { + $.channel.send( `This command requires a day to cooldown. You'll be able to activate this command ${howLong}.` ); - else lastUsedTimestamps[$.guild.id] = startTime; + return; + } else lastUsedTimestamps[$.guild.id] = startTime; const stats: { [id: string]: { @@ -155,7 +159,8 @@ export default new Command({ const finishTime = Date.now(); clearInterval(interval); statusMessage.edit( - `Finished operation in ${moment.duration(finishTime - startTime).humanize()} with ${$(warnings).pluralise( + `Finished operation in ${moment.duration(finishTime - startTime).humanize()} with ${pluralise( + warnings, "inconsistenc", "ies", "y" @@ -181,6 +186,6 @@ export default new Command({ ); } - $.channel.send(lines, {split: true}).catch($.handler.bind($)); + $.channel.send(lines, {split: true}).catch(handler.bind($)); } }); diff --git a/src/commands/template.ts b/src/commands/template.ts index 832fecd..686fe4a 100644 --- a/src/commands/template.ts +++ b/src/commands/template.ts @@ -1,5 +1,4 @@ import Command from "../core/command"; -import {CommonLibrary} from "../core/lib"; export default new Command({ description: @@ -8,7 +7,7 @@ export default new Command({ usage: "", permission: null, aliases: [], - async run($: CommonLibrary): Promise { + async run($) { // code }, subcommands: { @@ -19,7 +18,7 @@ export default new Command({ usage: "", permission: null, aliases: [], - async run($: CommonLibrary): Promise { + async run($) { // code } }) @@ -30,7 +29,7 @@ export default new Command({ endpoint: false, usage: "", permission: null, - async run($: CommonLibrary): Promise { + async run($) { // code } }), @@ -40,7 +39,7 @@ export default new Command({ endpoint: false, usage: "", permission: null, - async run($: CommonLibrary): Promise { + async run($) { // code } }), @@ -50,7 +49,7 @@ export default new Command({ endpoint: false, usage: "", permission: null, - async run($: CommonLibrary): Promise { + async run($) { // code } }) diff --git a/src/commands/utilities/desc.ts b/src/commands/utilities/desc.ts index c5e75f3..0ab3aaf 100644 --- a/src/commands/utilities/desc.ts +++ b/src/commands/utilities/desc.ts @@ -1,18 +1,25 @@ import Command from "../../core/command"; -import {CommonLibrary} from "../../core/lib"; export default new Command({ description: "Renames current voice channel.", usage: "", - async run($: CommonLibrary): Promise { + async run($) { const voiceChannel = $.message.member?.voice.channel; - if (!voiceChannel) return $.channel.send("You are not in a voice channel."); + if (!voiceChannel) { + $.channel.send("You are not in a voice channel."); + return; + } - if (!voiceChannel.guild.me?.hasPermission("MANAGE_CHANNELS")) - return $.channel.send("I am lacking the required permissions to perform this action."); + if (!voiceChannel.guild.me?.hasPermission("MANAGE_CHANNELS")) { + $.channel.send("I am lacking the required permissions to perform this action."); + return; + } - if ($.args.length === 0) return $.channel.send("Please provide a new voice channel name."); + if ($.args.length === 0) { + $.channel.send("Please provide a new voice channel name."); + return; + } const prevName = voiceChannel.name; const newName = $.args.join(" "); diff --git a/src/commands/utilities/emote.ts b/src/commands/utilities/emote.ts index 912774c..b84d9ef 100644 --- a/src/commands/utilities/emote.ts +++ b/src/commands/utilities/emote.ts @@ -1,6 +1,6 @@ import Command from "../../core/command"; import {queryClosestEmoteByName} from "./subcommands/emote-utils"; -import {botHasPermission} from "../../core/lib"; +import {botHasPermission} from "../../core/libd"; import {Permissions} from "discord.js"; export default new Command({ diff --git a/src/commands/utilities/lsemotes.ts b/src/commands/utilities/lsemotes.ts index 23311cb..6188439 100644 --- a/src/commands/utilities/lsemotes.ts +++ b/src/commands/utilities/lsemotes.ts @@ -1,26 +1,35 @@ import {GuildEmoji} from "discord.js"; import {MessageEmbed} from "discord.js"; import Command from "../../core/command"; -import {CommonLibrary} from "../../core/lib"; +import {split} from "../../core/lib"; +import {paginate} from "../../core/libd"; import vm from "vm"; +import {TextChannel} from "discord.js"; +import {DMChannel} from "discord.js"; +import {NewsChannel} from "discord.js"; +import {User} from "discord.js"; const REGEX_TIMEOUT_MS = 1000; export default new Command({ description: "Lists all emotes the bot has in it's registry,", usage: " (-flags)", - async run($: CommonLibrary): Promise { - displayEmoteList($, $.client.emojis.cache.array()); + async run($) { + displayEmoteList($.client.emojis.cache.array(), $.channel, $.author); }, any: new Command({ description: "Filters emotes by via a regular expression. Flags can be added by adding a dash at the end. For example, to do a case-insensitive search, do %prefix%lsemotes somepattern -i", - async run($: CommonLibrary): Promise { + async run($) { // If a guild ID is provided, filter all emotes by that guild (but only if there aren't any arguments afterward) if ($.args.length === 1 && /^\d{17,19}$/.test($.args[0])) { const guildID: string = $.args[0]; - displayEmoteList($, $.client.emojis.cache.filter((emote) => emote.guild.id === guildID).array()); + displayEmoteList( + $.client.emojis.cache.filter((emote) => emote.guild.id === guildID).array(), + $.channel, + $.author + ); } else { // Otherwise, search via a regex pattern let flags: string | undefined = undefined; @@ -55,7 +64,7 @@ export default new Command({ script.runInContext(context, {timeout: REGEX_TIMEOUT_MS}); emotes = sandbox.emotes; emoteCollection = emoteCollection.filter((emote) => emotes.has(emote.id)); // Only allow emotes that haven't been deleted. - displayEmoteList($, emoteCollection); + displayEmoteList(emoteCollection, $.channel, $.author); } catch (error) { if (error.code === "ERR_SCRIPT_EXECUTION_TIMEOUT") { $.channel.send( @@ -73,7 +82,7 @@ export default new Command({ }) }); -async function displayEmoteList($: CommonLibrary, emotes: GuildEmoji[]) { +async function displayEmoteList(emotes: GuildEmoji[], channel: TextChannel | DMChannel | NewsChannel, author: User) { emotes.sort((a, b) => { const first = a.name.toLowerCase(); const second = b.name.toLowerCase(); @@ -82,7 +91,7 @@ async function displayEmoteList($: CommonLibrary, emotes: GuildEmoji[]) { else if (first < second) return -1; else return 0; }); - const sections = $(emotes).split(20); + const sections = split(emotes, 20); const pages = sections.length; const embed = new MessageEmbed().setTitle("**Emotes**").setColor("AQUA"); let desc = ""; @@ -97,9 +106,9 @@ async function displayEmoteList($: CommonLibrary, emotes: GuildEmoji[]) { if (pages > 1) { embed.setTitle(`**Emotes** (Page 1 of ${pages})`); - const msg = await $.channel.send({embed}); + const msg = await channel.send({embed}); - $.paginate(msg, $.author.id, pages, (page) => { + paginate(msg, author.id, pages, (page) => { let desc = ""; for (const emote of sections[page]) { desc += `${emote} ${emote.name} (**${emote.guild.name}**)\n`; @@ -109,9 +118,9 @@ async function displayEmoteList($: CommonLibrary, emotes: GuildEmoji[]) { msg.edit(embed); }); } else { - await $.channel.send({embed}); + channel.send({embed}); } } else { - $.channel.send("No valid emotes found by that query."); + channel.send("No valid emotes found by that query."); } } diff --git a/src/commands/utilities/react.ts b/src/commands/utilities/react.ts index ceef800..cc7c031 100644 --- a/src/commands/utilities/react.ts +++ b/src/commands/utilities/react.ts @@ -1,5 +1,4 @@ import Command from "../../core/command"; -import {CommonLibrary} from "../../core/lib"; import {Message, Channel, TextChannel} from "discord.js"; import {queryClosestEmoteByName} from "./subcommands/emote-utils"; @@ -7,7 +6,7 @@ export default new Command({ description: "Reacts to the a previous message in your place. You have to react with the same emote before the bot removes that reaction.", usage: 'react ()', - async run($: CommonLibrary): Promise { + async run($) { let target: Message | undefined; let distance = 1; @@ -106,5 +105,7 @@ export default new Command({ reaction.users.remove($.client.user!); }, 5000); } + + return; } }); diff --git a/src/commands/utilities/say.ts b/src/commands/utilities/say.ts index f4d74f0..e58b90e 100644 --- a/src/commands/utilities/say.ts +++ b/src/commands/utilities/say.ts @@ -1,5 +1,4 @@ import Command from "../../core/command"; -import {CommonLibrary} from "../../core/lib"; export default new Command({ description: "Repeats your message.", @@ -7,7 +6,7 @@ export default new Command({ run: "Please provide a message for me to say!", any: new Command({ description: "Message to repeat.", - async run($: CommonLibrary): Promise { + async run($) { $.channel.send(`*${$.author} says:*\n${$.args.join(" ")}`); } }) diff --git a/src/commands/utilities/shorten.ts b/src/commands/utilities/shorten.ts index 5d0b854..79961f3 100644 --- a/src/commands/utilities/shorten.ts +++ b/src/commands/utilities/shorten.ts @@ -1,12 +1,11 @@ import Command from "../../core/command"; -import {CommonLibrary} from "../../core/lib"; import * as https from "https"; export default new Command({ description: "Shortens a given URL.", run: "Please provide a URL.", any: new Command({ - async run($: CommonLibrary): Promise { + async run($) { https.get("https://is.gd/create.php?format=simple&url=" + encodeURIComponent($.args[0]), function (res) { var body = ""; res.on("data", function (chunk) { diff --git a/src/commands/utilities/time.ts b/src/commands/utilities/time.ts index 8cdecc1..d520271 100644 --- a/src/commands/utilities/time.ts +++ b/src/commands/utilities/time.ts @@ -1,4 +1,5 @@ import Command from "../../core/command"; +import {ask, askYesOrNo, askMultipleChoice, prompt, callMemberByUsername} from "../../core/libd"; import {Storage} from "../../core/structures"; import {User} from "discord.js"; import moment from "moment"; @@ -176,7 +177,7 @@ export default new Command({ // Welcome to callback hell. We hope you enjoy your stay here! setup: new Command({ description: "Registers your timezone information for the bot.", - async run({author, channel, ask, askYesOrNo, askMultipleChoice}) { + async run({author, channel}) { const profile = Storage.getUser(author.id); profile.timezone = null; profile.daylightSavingsRegion = null; @@ -328,7 +329,7 @@ export default new Command({ }), delete: new Command({ description: "Delete your timezone information.", - async run({channel, author, prompt}) { + async run({channel, author}) { prompt( await channel.send( "Are you sure you want to delete your timezone information?\n*(This message will automatically be deleted after 10 seconds.)*" @@ -382,7 +383,7 @@ export default new Command({ }), any: new Command({ description: "See what time it is for someone else (by their username).", - async run({channel, args, message, callMemberByUsername}) { + async run({channel, args, message}) { callMemberByUsername(message, args.join(" "), (member) => { channel.send(getTimeEmbed(member.user)); }); diff --git a/src/core/command.ts b/src/core/command.ts index ae2e73c..5ee8a5b 100644 --- a/src/core/command.ts +++ b/src/core/command.ts @@ -1,16 +1,27 @@ -import {isType, parseVars, CommonLibrary} from "./lib"; +import {parseVars} from "./libd"; import {Collection} from "discord.js"; +import {Client, Message, TextChannel, DMChannel, NewsChannel, Guild, User, GuildMember} from "discord.js"; import {PERMISSIONS} from "./permissions"; import {getPrefix} from "../core/structures"; import glob from "glob"; +interface CommandMenu { + args: any[]; + client: Client; + message: Message; + channel: TextChannel | DMChannel | NewsChannel; + guild: Guild | null; + author: User; + member: GuildMember | null; +} + interface CommandOptions { description?: string; endpoint?: boolean; usage?: string; permission?: PERMISSIONS | null; aliases?: string[]; - run?: (($: CommonLibrary) => Promise) | string; + run?: (($: CommandMenu) => Promise) | string; subcommands?: {[key: string]: Command}; user?: Command; number?: Command; @@ -32,7 +43,7 @@ export default class Command { public readonly permission: PERMISSIONS | null; public readonly aliases: string[]; // This is to keep the array intact for parent Command instances to use. It'll also be used when loading top-level aliases. public originalCommandName: string | null; // If the command is an alias, what's the original name? - public run: (($: CommonLibrary) => Promise) | string; + public run: (($: CommandMenu) => Promise) | string; public readonly subcommands: Collection; // This is the final data structure you'll actually use to work with the commands the aliases point to. public user: Command | null; public number: Command | null; @@ -96,11 +107,11 @@ export default class Command { ); } - public execute($: CommonLibrary) { - if (isType(this.run, String)) { + public execute($: CommandMenu) { + if (typeof this.run === "string") { $.channel.send( parseVars( - this.run as string, + this.run, { author: $.author.toString(), prefix: getPrefix($.guild) @@ -108,7 +119,7 @@ export default class Command { "???" ) ); - } else (this.run as Function)($).catch($.handler.bind($)); + } else this.run($).catch(handler.bind($)); } public resolve(param: string): TYPES { @@ -224,3 +235,20 @@ function globP(path: string) { }); }); } + +// If you use promises, use this function to display the error in chat. +// Case #1: await $.channel.send(""); --> Automatically caught by Command.execute(). +// Case #2: $.channel.send("").catch(handler.bind($)); --> Manually caught by the user. +// TODO: Find a way to catch unhandled rejections automatically, forgoing the need for this. +export function handler(this: CommandMenu, error: Error) { + if (this) + this.channel.send( + `There was an error while trying to execute that command!\`\`\`${error.stack ?? error}\`\`\`` + ); + else + console.warn( + "No context was attached to $.handler! Make sure to use .catch($.handler.bind($)) or .catch(error => $.handler(error)) instead!" + ); + + console.error(error); +} diff --git a/src/core/wrappers.test.ts b/src/core/lib.test.ts similarity index 58% rename from src/core/wrappers.test.ts rename to src/core/lib.test.ts index e540c63..0cd4cc9 100644 --- a/src/core/wrappers.test.ts +++ b/src/core/lib.test.ts @@ -1,46 +1,46 @@ import {strict as assert} from "assert"; -import {NumberWrapper, StringWrapper, ArrayWrapper} from "./wrappers"; +import {pluralise, pluraliseSigned, replaceAll, toTitleCase, split} from "./lib"; // I can't figure out a way to run the test suite while running the bot. describe("Wrappers", () => { describe("NumberWrapper", () => { describe("#pluralise()", () => { it('should return "5 credits"', () => { - assert.strictEqual(new NumberWrapper(5).pluralise("credit", "s"), "5 credits"); + assert.strictEqual(pluralise(5, "credit", "s"), "5 credits"); }); it('should return "1 credit"', () => { - assert.strictEqual(new NumberWrapper(1).pluralise("credit", "s"), "1 credit"); + assert.strictEqual(pluralise(1, "credit", "s"), "1 credit"); }); it('should return "-1 credits"', () => { - assert.strictEqual(new NumberWrapper(-1).pluralise("credit", "s"), "-1 credits"); + assert.strictEqual(pluralise(-1, "credit", "s"), "-1 credits"); }); it("should be able to work with a plural suffix", () => { - assert.strictEqual(new NumberWrapper(2).pluralise("part", "ies", "y"), "2 parties"); + assert.strictEqual(pluralise(2, "part", "ies", "y"), "2 parties"); }); it("should be able to work with a singular suffix", () => { - assert.strictEqual(new NumberWrapper(1).pluralise("part", "ies", "y"), "1 party"); + assert.strictEqual(pluralise(1, "part", "ies", "y"), "1 party"); }); it("should be able to exclude the number", () => { - assert.strictEqual(new NumberWrapper(1).pluralise("credit", "s", "", true), "credit"); + assert.strictEqual(pluralise(1, "credit", "s", "", true), "credit"); }); }); describe("#pluraliseSigned()", () => { it('should return "-1 credits"', () => { - assert.strictEqual(new NumberWrapper(-1).pluraliseSigned("credit", "s"), "-1 credits"); + assert.strictEqual(pluraliseSigned(-1, "credit", "s"), "-1 credits"); }); it('should return "+0 credits"', () => { - assert.strictEqual(new NumberWrapper(0).pluraliseSigned("credit", "s"), "+0 credits"); + assert.strictEqual(pluraliseSigned(0, "credit", "s"), "+0 credits"); }); it('should return "+1 credit"', () => { - assert.strictEqual(new NumberWrapper(1).pluraliseSigned("credit", "s"), "+1 credit"); + assert.strictEqual(pluraliseSigned(1, "credit", "s"), "+1 credit"); }); }); }); @@ -48,14 +48,14 @@ describe("Wrappers", () => { describe("StringWrapper", () => { describe("#replaceAll()", () => { it('should convert "test" to "zesz"', () => { - assert.strictEqual(new StringWrapper("test").replaceAll("t", "z"), "zesz"); + assert.strictEqual(replaceAll("test", "t", "z"), "zesz"); }); }); describe("#toTitleCase()", () => { it("should capitalize the first letter of each word", () => { assert.strictEqual( - new StringWrapper("yeetus deletus find salvation from jesus").toTitleCase(), + toTitleCase("yeetus deletus find salvation from jesus"), "Yeetus Deletus Find Salvation From Jesus" ); }); @@ -65,7 +65,7 @@ describe("Wrappers", () => { describe("ArrayWrapper", () => { describe("#split()", () => { it("should split [1,2,3,4,5,6,7,8,9,10] into [[1,2,3],[4,5,6],[7,8,9],[10]]", () => { - assert.deepStrictEqual(new ArrayWrapper([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]).split(3), [ + assert.deepStrictEqual(split([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 3), [ [1, 2, 3], [4, 5, 6], [7, 8, 9], diff --git a/src/core/lib.ts b/src/core/lib.ts index d2e7d5b..a0ebcb7 100644 --- a/src/core/lib.ts +++ b/src/core/lib.ts @@ -1,483 +1,61 @@ -import {GenericWrapper, NumberWrapper, StringWrapper, ArrayWrapper} from "./wrappers"; -import {Client, Message, TextChannel, DMChannel, NewsChannel, Guild, User, GuildMember, Permissions} from "discord.js"; -import {get} from "https"; -import FileManager from "./storage"; -import {eventListeners} from "../events/messageReactionRemove"; -import {client} from "../index"; -import {EmoteRegistryDump, EmoteRegistryDumpEntry} from "./structures"; - -/** A type that describes what the library module does. */ -export interface CommonLibrary { - // Wrapper Object // - /** Wraps the value you enter with an object that provides extra functionality and provides common utility functions. */ - (value: number): NumberWrapper; - (value: string): StringWrapper; - (value: T[]): ArrayWrapper; - (value: T): GenericWrapper; - - // Common Library Functions // - /** .catch($.handler.bind($)) or .catch(error => $.handler(error)) */ - handler: (error: Error) => void; - paginate: ( - message: Message, - senderID: string, - total: number, - callback: (page: number) => void, - duration?: number - ) => void; - prompt: (message: Message, senderID: string, onConfirm: () => void, duration?: number) => void; - getMemberByUsername: (guild: Guild, username: string) => Promise; - callMemberByUsername: ( - message: Message, - username: string, - onSuccess: (member: GuildMember) => void - ) => Promise; - ask: ( - message: Message, - senderID: string, - condition: (reply: string) => boolean, - onSuccess: () => void, - onReject: () => string, - timeout?: number - ) => void; - askYesOrNo: (message: Message, senderID: string, timeout?: number) => Promise; - askMultipleChoice: (message: Message, senderID: string, callbackStack: (() => void)[], timeout?: number) => void; - - // Dynamic Properties // - args: any[]; - client: Client; - message: Message; - channel: TextChannel | DMChannel | NewsChannel; - guild: Guild | null; - author: User; - member: GuildMember | null; -} - -export default function $(value: number): NumberWrapper; -export default function $(value: string): StringWrapper; -export default function $(value: T[]): ArrayWrapper; -export default function $(value: T): GenericWrapper; -export default function $(value: any) { - if (isType(value, Number)) return new NumberWrapper(value); - else if (isType(value, String)) return new StringWrapper(value); - else if (isType(value, Array)) return new ArrayWrapper(value); - else return new GenericWrapper(value); -} - -// If you use promises, use this function to display the error in chat. -// Case #1: await $.channel.send(""); --> Automatically caught by Command.execute(). -// Case #2: $.channel.send("").catch($.handler.bind($)); --> Manually caught by the user. -$.handler = function (this: CommonLibrary, error: Error) { - if (this) - this.channel.send( - `There was an error while trying to execute that command!\`\`\`${error.stack ?? error}\`\`\`` - ); - else - console.warn( - "No context was attached to $.handler! Make sure to use .catch($.handler.bind($)) or .catch(error => $.handler(error)) instead!" - ); - - console.error(error); -}; - -export function botHasPermission(guild: Guild | null, permission: number): boolean { - return !!guild?.me?.hasPermission(permission); -} - -export function updateGlobalEmoteRegistry(): void { - const data: EmoteRegistryDump = {version: 1, list: []}; - - for (const guild of client.guilds.cache.values()) { - for (const emote of guild.emojis.cache.values()) { - data.list.push({ - ref: emote.name, - id: emote.id, - name: emote.name, - requires_colons: emote.requiresColons || false, - animated: emote.animated, - url: emote.url, - guild_id: emote.guild.name, - guild_name: emote.guild.name - }); - } - } - - FileManager.write("emote-registry", data, true); -} - -// Maybe promisify this section to reduce the potential for creating callback hell? Especially if multiple questions in a row are being asked. - -// Pagination function that allows for customization via a callback. -// Define your own pages outside the function because this only manages the actual turning of pages. -$.paginate = async ( - message: Message, - senderID: string, - total: number, - callback: (page: number) => void, - duration = 60000 -) => { - let page = 0; - const turn = (amount: number) => { - page += amount; - - if (page < 0) page += total; - else if (page >= total) page -= total; - - callback(page); - }; - const BACKWARDS_EMOJI = "⬅️"; - const FORWARDS_EMOJI = "➡️"; - const handle = (emote: string, reacterID: string) => { - switch (emote) { - case BACKWARDS_EMOJI: - turn(-1); - break; - case FORWARDS_EMOJI: - turn(1); - break; - } - }; - - // Listen for reactions and call the handler. - let backwardsReaction = await message.react(BACKWARDS_EMOJI); - let forwardsReaction = await message.react(FORWARDS_EMOJI); - eventListeners.set(message.id, handle); - await message.awaitReactions( - (reaction, user) => { - if (user.id === senderID) { - // The reason this is inside the call is because it's possible to switch a user's permissions halfway and suddenly throw an error. - // This will dynamically adjust for that, switching modes depending on whether it currently has the "Manage Messages" permission. - const canDeleteEmotes = botHasPermission(message.guild, Permissions.FLAGS.MANAGE_MESSAGES); - handle(reaction.emoji.name, user.id); - - if (canDeleteEmotes) reaction.users.remove(user); - } - - return false; - }, - {time: duration} - ); - // When time's up, remove the bot's own reactions. - eventListeners.delete(message.id); - backwardsReaction.users.remove(message.author); - forwardsReaction.users.remove(message.author); -}; - -// Waits for the sender to either confirm an action or let it pass (and delete the message). -// This should probably be renamed to "confirm" now that I think of it, "prompt" is better used elsewhere. -// Append "\n*(This message will automatically be deleted after 10 seconds.)*" in the future? -$.prompt = async (message: Message, senderID: string, onConfirm: () => void, duration = 10000) => { - let isDeleted = false; - - message.react("✅"); - await message.awaitReactions( - (reaction, user) => { - if (user.id === senderID) { - if (reaction.emoji.name === "✅") { - onConfirm(); - isDeleted = true; - message.delete(); - } - } - - // CollectorFilter requires a boolean to be returned. - // My guess is that the return value of awaitReactions can be altered by making a boolean filter. - // However, because that's not my concern with this command, I don't have to worry about it. - // May as well just set it to false because I'm not concerned with collecting any reactions. - return false; - }, - {time: duration} - ); - - if (!isDeleted) message.delete(); -}; - -// A list of "channel-message" and callback pairs. Also, I imagine that the callback will be much more maintainable when discord.js v13 comes out with a dedicated message.referencedMessage property. -// Also, I'm defining it here instead of the message event because the load order screws up if you export it from there. Yeah... I'm starting to notice just how much technical debt has been built up. The command handler needs to be modularized and refactored sooner rather than later. Define all constants in one area then grab from there. -export const replyEventListeners = new Map void>(); - -// Asks the user for some input using the inline reply feature. The message here is a message you send beforehand. -// If the reply is rejected, reply with an error message (when stable support comes from discord.js). -// Append "\n*(Note: Make sure to use Discord's inline reply feature or this won't work!)*" in the future? And also the "you can now reply to this message" edit. -$.ask = async ( - message: Message, - senderID: string, - condition: (reply: string) => boolean, - onSuccess: () => void, - onReject: () => string, - timeout = 60000 -) => { - const referenceID = `${message.channel.id}-${message.id}`; - - replyEventListeners.set(referenceID, (reply) => { - if (reply.author.id === senderID) { - if (condition(reply.content)) { - onSuccess(); - replyEventListeners.delete(referenceID); - } else { - reply.reply(onReject()); - } - } - }); - - setTimeout(() => { - replyEventListeners.set(referenceID, (reply) => { - reply.reply("that action timed out, try using the command again"); - replyEventListeners.delete(referenceID); - }); - }, timeout); -}; - -$.askYesOrNo = (message: Message, senderID: string, timeout = 30000): Promise => { - return new Promise(async (resolve, reject) => { - let isDeleted = false; - - await message.react("✅"); - message.react("❌"); - await message.awaitReactions( - (reaction, user) => { - if (user.id === senderID) { - const isCheckReacted = reaction.emoji.name === "✅"; - - if (isCheckReacted || reaction.emoji.name === "❌") { - resolve(isCheckReacted); - isDeleted = true; - message.delete(); - } - } - - return false; - }, - {time: timeout} - ); - - if (!isDeleted) { - message.delete(); - reject("Prompt timed out."); - } - }); -}; - -// This MUST be split into an array. These emojis are made up of several characters each, adding up to 29 in length. -const multiNumbers = ["1️⃣", "2️⃣", "3️⃣", "4️⃣", "5️⃣", "6️⃣", "7️⃣", "8️⃣", "9️⃣", "🔟"]; - -// This will bring up an option to let the user choose between one option out of many. -// This definitely needs a single callback alternative, because using the numerical version isn't actually that uncommon of a pattern. -$.askMultipleChoice = async (message: Message, senderID: string, callbackStack: (() => void)[], timeout = 90000) => { - if (callbackStack.length > multiNumbers.length) { - message.channel.send( - `\`ERROR: The amount of callbacks in "askMultipleChoice" must not exceed the total amount of allowed options (${multiNumbers.length})!\`` - ); - return; - } - - let isDeleted = false; - - for (let i = 0; i < callbackStack.length; i++) { - await message.react(multiNumbers[i]); - } - - await message.awaitReactions( - (reaction, user) => { - if (user.id === senderID) { - const index = multiNumbers.indexOf(reaction.emoji.name); - - if (index !== -1) { - callbackStack[index](); - isDeleted = true; - message.delete(); - } - } - - return false; - }, - {time: timeout} - ); - - if (!isDeleted) message.delete(); -}; - -$.getMemberByUsername = async (guild: Guild, username: string) => { - return ( - await guild.members.fetch({ - query: username, - limit: 1 - }) - ).first(); -}; - -/** Convenience function to handle false cases automatically. */ -$.callMemberByUsername = async (message: Message, username: string, onSuccess: (member: GuildMember) => void) => { - const guild = message.guild; - const send = message.channel.send; - - if (guild) { - const member = await $.getMemberByUsername(guild, username); - - if (member) onSuccess(member); - else send(`Couldn't find a user by the name of \`${username}\`!`); - } else send("You must execute this command in a server!"); -}; +// Library for pure functions /** - * Splits a command by spaces while accounting for quotes which capture string arguments. - * - `\"` = `"` - * - `\\` = `\` + * Pluralises a word and chooses a suffix attached to the root provided. + * - pluralise("credit", "s") = credit/credits + * - pluralise("part", "ies", "y") = party/parties + * - pluralise("sheep") = sheep */ -export function parseArgs(line: string): string[] { - let result = []; - let selection = ""; - let inString = false; - let isEscaped = false; +export function pluralise(value: number, word: string, plural = "", singular = "", excludeNumber = false): string { + let result = excludeNumber ? "" : `${value} `; - for (let c of line) { - if (isEscaped) { - if (['"', "\\"].includes(c)) selection += c; - else selection += "\\" + c; - - isEscaped = false; - } else if (c === "\\") isEscaped = true; - else if (c === '"') inString = !inString; - else if (c === " " && !inString) { - result.push(selection); - selection = ""; - } else selection += c; - } - - if (selection.length > 0) result.push(selection); + if (value === 1) result += word + singular; + else result += word + plural; return result; } /** - * Allows you to store a template string with variable markers and parse it later. - * - Use `%name%` for variables - * - `%%` = `%` - * - If the invalid token is null/undefined, nothing is changed. + * Pluralises a word for changes. + * - (-1).pluraliseSigned() = '-1 credits' + * - (0).pluraliseSigned() = '+0 credits' + * - (1).pluraliseSigned() = '+1 credit' */ -export function parseVars(line: string, definitions: {[key: string]: string}, invalid: string | null = ""): string { - let result = ""; - let inVariable = false; - let token = ""; - - for (const c of line) { - if (c === "%") { - if (inVariable) { - if (token === "") result += "%"; - else { - if (token in definitions) result += definitions[token]; - else if (invalid === null) result += `%${token}%`; - else result += invalid; - - token = ""; - } - } - - inVariable = !inVariable; - } else if (inVariable) token += c; - else result += c; - } - - return result; +export function pluraliseSigned( + value: number, + word: string, + plural = "", + singular = "", + excludeNumber = false +): string { + const sign = value >= 0 ? "+" : ""; + return `${sign}${pluralise(value, word, plural, singular, excludeNumber)}`; } -export function isType(value: any, type: any): boolean { - if (value === undefined && type === undefined) return true; - else if (value === null && type === null) return true; - else return value !== undefined && value !== null && value.constructor === type; +export function replaceAll(text: string, before: string, after: string): string { + while (text.indexOf(before) !== -1) text = text.replace(before, after); + return text; +} + +export function toTitleCase(text: string): string { + return text.replace(/([^\W_]+[^\s-]*) */g, (txt) => txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase()); +} + +/** Returns a random element from this array. */ +export function random(array: T[]): T { + return array[Math.floor(Math.random() * array.length)]; } /** - * Checks a value to see if it matches the fallback's type, otherwise returns the fallback. - * For the purposes of the templates system, this function will only check array types, objects should be checked under their own type (as you'd do anyway with something like a User object). - * If at any point the value doesn't match the data structure provided, the fallback is returned. - * Warning: Type checking is based on the fallback's type. Be sure that the "type" parameter is accurate to this! + * Splits up this array into a specified length. + * `$([1,2,3,4,5,6,7,8,9,10]).split(3)` = `[[1,2,3],[4,5,6],[7,8,9],[10]]` */ -export function select(value: any, fallback: T, type: Function, isArray = false): T { - if (isArray && isType(value, Array)) { - for (let item of value) if (!isType(item, type)) return fallback; - return value; - } else { - if (isType(value, type)) return value; - else return fallback; - } +export function split(array: T[], lengthOfEachSection: number): T[][] { + const amountOfSections = Math.ceil(array.length / lengthOfEachSection); + const sections = new Array(amountOfSections); + + for (let index = 0; index < amountOfSections; index++) + sections[index] = array.slice(index * lengthOfEachSection, (index + 1) * lengthOfEachSection); + + return sections; } - -export function clean(text: any) { - if (typeof text === "string") - return text.replace(/`/g, "`" + String.fromCharCode(8203)).replace(/@/g, "@" + String.fromCharCode(8203)); - else return text; -} - -export function trimArray(arr: any, maxLen = 10) { - if (arr.length > maxLen) { - const len = arr.length - maxLen; - arr = arr.slice(0, maxLen); - arr.push(`${len} more...`); - } - return arr; -} - -export function formatBytes(bytes: any) { - if (bytes === 0) return "0 Bytes"; - const sizes = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]; - const i = Math.floor(Math.log(bytes) / Math.log(1024)); - return `${parseFloat((bytes / Math.pow(1024, i)).toFixed(2))} ${sizes[i]}`; -} - -export function getContent(url: any) { - return new Promise((resolve, reject) => { - get(url, (res: {resume?: any; setEncoding?: any; on?: any; statusCode?: any}) => { - const {statusCode} = res; - if (statusCode !== 200) { - res.resume(); - reject(`Request failed. Status code: ${statusCode}`); - } - res.setEncoding("utf8"); - let rawData = ""; - res.on("data", (chunk: string) => { - rawData += chunk; - }); - res.on("end", () => { - try { - const parsedData = JSON.parse(rawData); - resolve(parsedData); - } catch (e) { - reject(`Error: ${e.message}`); - } - }); - }).on("error", (err: {message: any}) => { - reject(`Error: ${err.message}`); - }); - }); -} - -export interface GenericJSON { - [key: string]: any; -} - -export abstract class GenericStructure { - private __meta__ = "generic"; - - constructor(tag?: string) { - this.__meta__ = tag || this.__meta__; - } - - public save(asynchronous = true) { - const tag = this.__meta__; - /// @ts-ignore - delete this.__meta__; - FileManager.write(tag, this, asynchronous); - this.__meta__ = tag; - } -} - -// A 50% chance would be "Math.random() < 0.5" because Math.random() can be [0, 1), so to make two equal ranges, you'd need [0, 0.5)U[0.5, 1). -// Similar logic would follow for any other percentage. Math.random() < 1 is always true (100% chance) and Math.random() < 0 is always false (0% chance). -export const Random = { - num: (min: number, max: number) => Math.random() * (max - min) + min, - int: (min: number, max: number) => Math.floor(Random.num(min, max)), - chance: (decimal: number) => Math.random() < decimal, - sign: (number = 1) => number * (Random.chance(0.5) ? -1 : 1), - deviation: (base: number, deviation: number) => Random.num(base - deviation, base + deviation) -}; diff --git a/src/core/libd.ts b/src/core/libd.ts new file mode 100644 index 0000000..9c44fa4 --- /dev/null +++ b/src/core/libd.ts @@ -0,0 +1,418 @@ +// Library for Discord-specific functions +import {Message, Guild, GuildMember, Permissions} from "discord.js"; +import {get} from "https"; +import FileManager from "./storage"; +import {eventListeners} from "../events/messageReactionRemove"; +import {client} from "../index"; +import {EmoteRegistryDump} from "./structures"; + +export function botHasPermission(guild: Guild | null, permission: number): boolean { + return !!guild?.me?.hasPermission(permission); +} + +export function updateGlobalEmoteRegistry(): void { + const data: EmoteRegistryDump = {version: 1, list: []}; + + for (const guild of client.guilds.cache.values()) { + for (const emote of guild.emojis.cache.values()) { + data.list.push({ + ref: emote.name, + id: emote.id, + name: emote.name, + requires_colons: emote.requiresColons || false, + animated: emote.animated, + url: emote.url, + guild_id: emote.guild.name, + guild_name: emote.guild.name + }); + } + } + + FileManager.write("emote-registry", data, true); +} + +// Maybe promisify this section to reduce the potential for creating callback hell? Especially if multiple questions in a row are being asked. + +// Pagination function that allows for customization via a callback. +// Define your own pages outside the function because this only manages the actual turning of pages. +export async function paginate( + message: Message, + senderID: string, + total: number, + callback: (page: number) => void, + duration = 60000 +) { + let page = 0; + const turn = (amount: number) => { + page += amount; + + if (page < 0) page += total; + else if (page >= total) page -= total; + + callback(page); + }; + const BACKWARDS_EMOJI = "⬅️"; + const FORWARDS_EMOJI = "➡️"; + const handle = (emote: string, reacterID: string) => { + switch (emote) { + case BACKWARDS_EMOJI: + turn(-1); + break; + case FORWARDS_EMOJI: + turn(1); + break; + } + }; + + // Listen for reactions and call the handler. + let backwardsReaction = await message.react(BACKWARDS_EMOJI); + let forwardsReaction = await message.react(FORWARDS_EMOJI); + eventListeners.set(message.id, handle); + await message.awaitReactions( + (reaction, user) => { + if (user.id === senderID) { + // The reason this is inside the call is because it's possible to switch a user's permissions halfway and suddenly throw an error. + // This will dynamically adjust for that, switching modes depending on whether it currently has the "Manage Messages" permission. + const canDeleteEmotes = botHasPermission(message.guild, Permissions.FLAGS.MANAGE_MESSAGES); + handle(reaction.emoji.name, user.id); + + if (canDeleteEmotes) reaction.users.remove(user); + } + + return false; + }, + {time: duration} + ); + // When time's up, remove the bot's own reactions. + eventListeners.delete(message.id); + backwardsReaction.users.remove(message.author); + forwardsReaction.users.remove(message.author); +} + +// Waits for the sender to either confirm an action or let it pass (and delete the message). +// This should probably be renamed to "confirm" now that I think of it, "prompt" is better used elsewhere. +// Append "\n*(This message will automatically be deleted after 10 seconds.)*" in the future? +export async function prompt(message: Message, senderID: string, onConfirm: () => void, duration = 10000) { + let isDeleted = false; + + message.react("✅"); + await message.awaitReactions( + (reaction, user) => { + if (user.id === senderID) { + if (reaction.emoji.name === "✅") { + onConfirm(); + isDeleted = true; + message.delete(); + } + } + + // CollectorFilter requires a boolean to be returned. + // My guess is that the return value of awaitReactions can be altered by making a boolean filter. + // However, because that's not my concern with this command, I don't have to worry about it. + // May as well just set it to false because I'm not concerned with collecting any reactions. + return false; + }, + {time: duration} + ); + + if (!isDeleted) message.delete(); +} + +// A list of "channel-message" and callback pairs. Also, I imagine that the callback will be much more maintainable when discord.js v13 comes out with a dedicated message.referencedMessage property. +// Also, I'm defining it here instead of the message event because the load order screws up if you export it from there. Yeah... I'm starting to notice just how much technical debt has been built up. The command handler needs to be modularized and refactored sooner rather than later. Define all constants in one area then grab from there. +export const replyEventListeners = new Map void>(); + +// Asks the user for some input using the inline reply feature. The message here is a message you send beforehand. +// If the reply is rejected, reply with an error message (when stable support comes from discord.js). +// Append "\n*(Note: Make sure to use Discord's inline reply feature or this won't work!)*" in the future? And also the "you can now reply to this message" edit. +export function ask( + message: Message, + senderID: string, + condition: (reply: string) => boolean, + onSuccess: () => void, + onReject: () => string, + timeout = 60000 +) { + const referenceID = `${message.channel.id}-${message.id}`; + + replyEventListeners.set(referenceID, (reply) => { + if (reply.author.id === senderID) { + if (condition(reply.content)) { + onSuccess(); + replyEventListeners.delete(referenceID); + } else { + reply.reply(onReject()); + } + } + }); + + setTimeout(() => { + replyEventListeners.set(referenceID, (reply) => { + reply.reply("that action timed out, try using the command again"); + replyEventListeners.delete(referenceID); + }); + }, timeout); +} + +export function askYesOrNo(message: Message, senderID: string, timeout = 30000): Promise { + return new Promise(async (resolve, reject) => { + let isDeleted = false; + + await message.react("✅"); + message.react("❌"); + await message.awaitReactions( + (reaction, user) => { + if (user.id === senderID) { + const isCheckReacted = reaction.emoji.name === "✅"; + + if (isCheckReacted || reaction.emoji.name === "❌") { + resolve(isCheckReacted); + isDeleted = true; + message.delete(); + } + } + + return false; + }, + {time: timeout} + ); + + if (!isDeleted) { + message.delete(); + reject("Prompt timed out."); + } + }); +} + +// This MUST be split into an array. These emojis are made up of several characters each, adding up to 29 in length. +const multiNumbers = ["1️⃣", "2️⃣", "3️⃣", "4️⃣", "5️⃣", "6️⃣", "7️⃣", "8️⃣", "9️⃣", "🔟"]; + +// This will bring up an option to let the user choose between one option out of many. +// This definitely needs a single callback alternative, because using the numerical version isn't actually that uncommon of a pattern. +export async function askMultipleChoice( + message: Message, + senderID: string, + callbackStack: (() => void)[], + timeout = 90000 +) { + if (callbackStack.length > multiNumbers.length) { + message.channel.send( + `\`ERROR: The amount of callbacks in "askMultipleChoice" must not exceed the total amount of allowed options (${multiNumbers.length})!\`` + ); + return; + } + + let isDeleted = false; + + for (let i = 0; i < callbackStack.length; i++) { + await message.react(multiNumbers[i]); + } + + await message.awaitReactions( + (reaction, user) => { + if (user.id === senderID) { + const index = multiNumbers.indexOf(reaction.emoji.name); + + if (index !== -1) { + callbackStack[index](); + isDeleted = true; + message.delete(); + } + } + + return false; + }, + {time: timeout} + ); + + if (!isDeleted) message.delete(); +} + +export async function getMemberByUsername(guild: Guild, username: string) { + return ( + await guild.members.fetch({ + query: username, + limit: 1 + }) + ).first(); +} + +/** Convenience function to handle false cases automatically. */ +export async function callMemberByUsername( + message: Message, + username: string, + onSuccess: (member: GuildMember) => void +) { + const guild = message.guild; + const send = message.channel.send; + + if (guild) { + const member = await getMemberByUsername(guild, username); + + if (member) onSuccess(member); + else send(`Couldn't find a user by the name of \`${username}\`!`); + } else send("You must execute this command in a server!"); +} + +/** + * Splits a command by spaces while accounting for quotes which capture string arguments. + * - `\"` = `"` + * - `\\` = `\` + */ +export function parseArgs(line: string): string[] { + let result = []; + let selection = ""; + let inString = false; + let isEscaped = false; + + for (let c of line) { + if (isEscaped) { + if (['"', "\\"].includes(c)) selection += c; + else selection += "\\" + c; + + isEscaped = false; + } else if (c === "\\") isEscaped = true; + else if (c === '"') inString = !inString; + else if (c === " " && !inString) { + result.push(selection); + selection = ""; + } else selection += c; + } + + if (selection.length > 0) result.push(selection); + + return result; +} + +/** + * Allows you to store a template string with variable markers and parse it later. + * - Use `%name%` for variables + * - `%%` = `%` + * - If the invalid token is null/undefined, nothing is changed. + */ +export function parseVars(line: string, definitions: {[key: string]: string}, invalid: string | null = ""): string { + let result = ""; + let inVariable = false; + let token = ""; + + for (const c of line) { + if (c === "%") { + if (inVariable) { + if (token === "") result += "%"; + else { + if (token in definitions) result += definitions[token]; + else if (invalid === null) result += `%${token}%`; + else result += invalid; + + token = ""; + } + } + + inVariable = !inVariable; + } else if (inVariable) token += c; + else result += c; + } + + return result; +} + +export function isType(value: any, type: any): boolean { + if (value === undefined && type === undefined) return true; + else if (value === null && type === null) return true; + else return value !== undefined && value !== null && value.constructor === type; +} + +/** + * Checks a value to see if it matches the fallback's type, otherwise returns the fallback. + * For the purposes of the templates system, this function will only check array types, objects should be checked under their own type (as you'd do anyway with something like a User object). + * If at any point the value doesn't match the data structure provided, the fallback is returned. + * Warning: Type checking is based on the fallback's type. Be sure that the "type" parameter is accurate to this! + */ +export function select(value: any, fallback: T, type: Function, isArray = false): T { + if (isArray && isType(value, Array)) { + for (let item of value) if (!isType(item, type)) return fallback; + return value; + } else { + if (isType(value, type)) return value; + else return fallback; + } +} + +export function clean(text: any) { + if (typeof text === "string") + return text.replace(/`/g, "`" + String.fromCharCode(8203)).replace(/@/g, "@" + String.fromCharCode(8203)); + else return text; +} + +export function trimArray(arr: any, maxLen = 10) { + if (arr.length > maxLen) { + const len = arr.length - maxLen; + arr = arr.slice(0, maxLen); + arr.push(`${len} more...`); + } + return arr; +} + +export function formatBytes(bytes: any) { + if (bytes === 0) return "0 Bytes"; + const sizes = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]; + const i = Math.floor(Math.log(bytes) / Math.log(1024)); + return `${parseFloat((bytes / Math.pow(1024, i)).toFixed(2))} ${sizes[i]}`; +} + +export function getContent(url: any) { + return new Promise((resolve, reject) => { + get(url, (res: {resume?: any; setEncoding?: any; on?: any; statusCode?: any}) => { + const {statusCode} = res; + if (statusCode !== 200) { + res.resume(); + reject(`Request failed. Status code: ${statusCode}`); + } + res.setEncoding("utf8"); + let rawData = ""; + res.on("data", (chunk: string) => { + rawData += chunk; + }); + res.on("end", () => { + try { + const parsedData = JSON.parse(rawData); + resolve(parsedData); + } catch (e) { + reject(`Error: ${e.message}`); + } + }); + }).on("error", (err: {message: any}) => { + reject(`Error: ${err.message}`); + }); + }); +} + +export interface GenericJSON { + [key: string]: any; +} + +export abstract class GenericStructure { + private __meta__ = "generic"; + + constructor(tag?: string) { + this.__meta__ = tag || this.__meta__; + } + + public save(asynchronous = true) { + const tag = this.__meta__; + /// @ts-ignore + delete this.__meta__; + FileManager.write(tag, this, asynchronous); + this.__meta__ = tag; + } +} + +// A 50% chance would be "Math.random() < 0.5" because Math.random() can be [0, 1), so to make two equal ranges, you'd need [0, 0.5)U[0.5, 1). +// Similar logic would follow for any other percentage. Math.random() < 1 is always true (100% chance) and Math.random() < 0 is always false (0% chance). +export const Random = { + num: (min: number, max: number) => Math.random() * (max - min) + min, + int: (min: number, max: number) => Math.floor(Random.num(min, max)), + chance: (decimal: number) => Math.random() < decimal, + sign: (number = 1) => number * (Random.chance(0.5) ? -1 : 1), + deviation: (base: number, deviation: number) => Random.num(base - deviation, base + deviation) +}; diff --git a/src/core/structures.ts b/src/core/structures.ts index 74e5adf..07d8f5c 100644 --- a/src/core/structures.ts +++ b/src/core/structures.ts @@ -1,5 +1,5 @@ import FileManager from "./storage"; -import {select, GenericJSON, GenericStructure} from "./lib"; +import {select, GenericJSON, GenericStructure} from "./libd"; import {watch} from "fs"; import {Guild as DiscordGuild, Snowflake} from "discord.js"; diff --git a/src/core/wrappers.ts b/src/core/wrappers.ts deleted file mode 100644 index edf9b35..0000000 --- a/src/core/wrappers.ts +++ /dev/null @@ -1,73 +0,0 @@ -export class GenericWrapper { - protected readonly value: T; - - public constructor(value: T) { - this.value = value; - } -} - -export class NumberWrapper extends GenericWrapper { - /** - * Pluralises a word and chooses a suffix attached to the root provided. - * - pluralise("credit", "s") = credit/credits - * - pluralise("part", "ies", "y") = party/parties - * - pluralise("sheep") = sheep - */ - public pluralise(word: string, plural = "", singular = "", excludeNumber = false): string { - let result = excludeNumber ? "" : `${this.value} `; - - if (this.value === 1) result += word + singular; - else result += word + plural; - - return result; - } - - /** - * Pluralises a word for changes. - * - (-1).pluraliseSigned() = '-1 credits' - * - (0).pluraliseSigned() = '+0 credits' - * - (1).pluraliseSigned() = '+1 credit' - */ - public pluraliseSigned(word: string, plural = "", singular = "", excludeNumber = false): string { - const sign = this.value >= 0 ? "+" : ""; - return `${sign}${this.pluralise(word, plural, singular, excludeNumber)}`; - } -} - -export class StringWrapper extends GenericWrapper { - public replaceAll(before: string, after: string): string { - let result = this.value; - - while (result.indexOf(before) !== -1) result = result.replace(before, after); - - return result; - } - - public toTitleCase(): string { - return this.value.replace( - /([^\W_]+[^\s-]*) */g, - (txt) => txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase() - ); - } -} - -export class ArrayWrapper extends GenericWrapper { - /** Returns a random element from this array. */ - public random(): T { - return this.value[Math.floor(Math.random() * this.value.length)]; - } - - /** - * Splits up this array into a specified length. - * `$([1,2,3,4,5,6,7,8,9,10]).split(3)` = `[[1,2,3],[4,5,6],[7,8,9],[10]]` - */ - public split(lengthOfEachSection: number): T[][] { - const amountOfSections = Math.ceil(this.value.length / lengthOfEachSection); - const sections: T[][] = new Array(amountOfSections); - - for (let index = 0; index < amountOfSections; index++) - sections[index] = this.value.slice(index * lengthOfEachSection, (index + 1) * lengthOfEachSection); - - return sections; - } -} diff --git a/src/events/emojiCreate.ts b/src/events/emojiCreate.ts index 2af3f82..92ef88e 100644 --- a/src/events/emojiCreate.ts +++ b/src/events/emojiCreate.ts @@ -1,5 +1,5 @@ import Event from "../core/event"; -import {updateGlobalEmoteRegistry} from "../core/lib"; +import {updateGlobalEmoteRegistry} from "../core/libd"; export default new Event<"emojiCreate">({ on(emote) { diff --git a/src/events/emojiDelete.ts b/src/events/emojiDelete.ts index 08687c9..dc34190 100644 --- a/src/events/emojiDelete.ts +++ b/src/events/emojiDelete.ts @@ -1,5 +1,5 @@ import Event from "../core/event"; -import {updateGlobalEmoteRegistry} from "../core/lib"; +import {updateGlobalEmoteRegistry} from "../core/libd"; export default new Event<"emojiDelete">({ on() { diff --git a/src/events/emojiUpdate.ts b/src/events/emojiUpdate.ts index 7dbe125..bd0b6b0 100644 --- a/src/events/emojiUpdate.ts +++ b/src/events/emojiUpdate.ts @@ -1,5 +1,5 @@ import Event from "../core/event"; -import {updateGlobalEmoteRegistry} from "../core/lib"; +import {updateGlobalEmoteRegistry} from "../core/libd"; export default new Event<"emojiUpdate">({ on() { diff --git a/src/events/guildCreate.ts b/src/events/guildCreate.ts index 61fdec5..480bd4d 100644 --- a/src/events/guildCreate.ts +++ b/src/events/guildCreate.ts @@ -1,5 +1,5 @@ import Event from "../core/event"; -import {updateGlobalEmoteRegistry} from "../core/lib"; +import {updateGlobalEmoteRegistry} from "../core/libd"; export default new Event<"guildCreate">({ on() { diff --git a/src/events/guildDelete.ts b/src/events/guildDelete.ts index a8e7eaa..755a049 100644 --- a/src/events/guildDelete.ts +++ b/src/events/guildDelete.ts @@ -1,5 +1,5 @@ import Event from "../core/event"; -import {updateGlobalEmoteRegistry} from "../core/lib"; +import {updateGlobalEmoteRegistry} from "../core/libd"; export default new Event<"guildDelete">({ on() { diff --git a/src/events/message.ts b/src/events/message.ts index 701db08..352bb57 100644 --- a/src/events/message.ts +++ b/src/events/message.ts @@ -3,7 +3,7 @@ import Command, {loadableCommands} from "../core/command"; import {hasPermission, getPermissionLevel, PermissionNames} from "../core/permissions"; import {Permissions} from "discord.js"; import {getPrefix} from "../core/structures"; -import $, {replyEventListeners} from "../core/lib"; +import {replyEventListeners} from "../core/libd"; import quote from "../modules/message_embed"; export default new Event<"message">({ @@ -132,20 +132,14 @@ export default new Event<"message">({ // The purpose of using $.bind($) is to clone the function so as to not modify the original $. // The cloned function doesn't copy the properties, so Object.assign() is used. // Object.assign() modifies the first element and returns that, the second element applies its properties and the third element applies its own overriding the second one. - command.execute( - Object.assign( - $.bind($), - { - args: params, - author: message.author, - channel: message.channel, - client: message.client, - guild: message.guild, - member: message.member, - message: message - }, - $ - ) - ); + command.execute({ + args: params, + author: message.author, + channel: message.channel, + client: message.client, + guild: message.guild, + member: message.member, + message: message + }); } }); diff --git a/src/events/messageReactionRemove.ts b/src/events/messageReactionRemove.ts index 10b1af2..4b3dbb2 100644 --- a/src/events/messageReactionRemove.ts +++ b/src/events/messageReactionRemove.ts @@ -1,6 +1,6 @@ import Event from "../core/event"; import {Permissions} from "discord.js"; -import {botHasPermission} from "../core/lib"; +import {botHasPermission} from "../core/libd"; // A list of message ID and callback pairs. You get the emote name and ID of the user reacting. export const eventListeners: Map void> = new Map(); diff --git a/src/events/ready.ts b/src/events/ready.ts index 9e2ec12..a4e1b9c 100644 --- a/src/events/ready.ts +++ b/src/events/ready.ts @@ -1,7 +1,7 @@ import Event from "../core/event"; import {client} from "../index"; import {Config} from "../core/structures"; -import {updateGlobalEmoteRegistry} from "../core/lib"; +import {updateGlobalEmoteRegistry} from "../core/libd"; export default new Event<"ready">({ once() { From 475ecb3d5dee5a92bfc9ae6e99ca66b6adb6464b Mon Sep 17 00:00:00 2001 From: WatDuhHekBro <44940783+WatDuhHekBro@users.noreply.github.com> Date: Tue, 30 Mar 2021 05:54:52 -0500 Subject: [PATCH 113/178] Reworked permission handling --- src/commands/admin.ts | 20 +++---- src/commands/help.ts | 8 +-- src/commands/template.ts | 10 ++-- src/core/command.ts | 8 ++- src/core/permissions.ts | 113 ++++++++++++++++++--------------------- src/events/message.ts | 10 ++-- src/globals.ts | 12 +++++ 7 files changed, 95 insertions(+), 86 deletions(-) diff --git a/src/commands/admin.ts b/src/commands/admin.ts index 9545799..ff2d968 100644 --- a/src/commands/admin.ts +++ b/src/commands/admin.ts @@ -1,7 +1,7 @@ import Command, {handler} from "../core/command"; import {botHasPermission, clean} from "../core/libd"; import {Config, Storage} from "../core/structures"; -import {PermissionNames, getPermissionLevel} from "../core/permissions"; +import {getPermissionLevel, getPermissionName} from "../core/permissions"; import {Permissions} from "discord.js"; import * as discord from "discord.js"; import {logs} from "../globals"; @@ -30,14 +30,14 @@ export default new Command({ } const permLevel = getPermissionLevel($.member); $.channel.send( - `${$.author.toString()}, your permission level is \`${PermissionNames[permLevel]}\` (${permLevel}).` + `${$.author.toString()}, your permission level is \`${getPermissionName(permLevel)}\` (${permLevel}).` ); }, subcommands: { set: new Command({ description: "Set different per-guild settings for the bot.", run: "You have to specify the option you want to set.", - permission: Command.PERMISSIONS.ADMIN, + permission: PERMISSIONS.ADMIN, subcommands: { prefix: new Command({ description: "Set a custom prefix for your guild. Removes your custom prefix if none is provided.", @@ -61,7 +61,7 @@ export default new Command({ }), diag: new Command({ description: 'Requests a debug log with the "info" verbosity level.', - permission: Command.PERMISSIONS.BOT_SUPPORT, + permission: PERMISSIONS.BOT_SUPPORT, async run($) { $.channel.send(getLogBuffer("info")); }, @@ -82,7 +82,7 @@ export default new Command({ }), status: new Command({ description: "Changes the bot's status.", - permission: Command.PERMISSIONS.BOT_SUPPORT, + permission: PERMISSIONS.BOT_SUPPORT, async run($) { $.channel.send("Setting status to `online`..."); }, @@ -101,7 +101,7 @@ export default new Command({ }), purge: new Command({ description: "Purges bot messages.", - permission: Command.PERMISSIONS.BOT_SUPPORT, + permission: PERMISSIONS.BOT_SUPPORT, async run($) { if ($.message.channel instanceof discord.DMChannel) { return; @@ -142,7 +142,7 @@ export default new Command({ eval: new Command({ description: "Evaluate code.", usage: "", - permission: Command.PERMISSIONS.BOT_OWNER, + permission: PERMISSIONS.BOT_OWNER, // You have to bring everything into scope to use them. AFAIK, there isn't a more maintainable way to do this, but at least TS will let you know if anything gets removed. async run({args, author, channel, client, guild, member, message}) { try { @@ -158,7 +158,7 @@ export default new Command({ }), nick: new Command({ description: "Change the bot's nickname.", - permission: Command.PERMISSIONS.BOT_SUPPORT, + permission: PERMISSIONS.BOT_SUPPORT, async run($) { const nickName = $.args.join(" "); await $.guild?.me?.setNickname(nickName); @@ -169,7 +169,7 @@ export default new Command({ }), guilds: new Command({ description: "Shows a list of all guilds the bot is a member of.", - permission: Command.PERMISSIONS.BOT_SUPPORT, + permission: PERMISSIONS.BOT_SUPPORT, async run($) { const guildList = $.client.guilds.cache.array().map((e) => e.name); $.channel.send(guildList); @@ -177,7 +177,7 @@ export default new Command({ }), activity: new Command({ description: "Set the activity of the bot.", - permission: Command.PERMISSIONS.BOT_SUPPORT, + permission: PERMISSIONS.BOT_SUPPORT, usage: " ", async run($) { $.client.user?.setActivity(".help", { diff --git a/src/commands/help.ts b/src/commands/help.ts index 02b8650..71f2ae3 100644 --- a/src/commands/help.ts +++ b/src/commands/help.ts @@ -1,7 +1,7 @@ import Command from "../core/command"; import {toTitleCase} from "../core/lib"; import {loadableCommands, categories} from "../core/command"; -import {PermissionNames} from "../core/permissions"; +import {getPermissionName} from "../core/permissions"; export default new Command({ description: "Lists all commands. If a command is specified, their arguments are listed as well.", @@ -44,7 +44,7 @@ export default new Command({ if (command.originalCommandName) header = command.originalCommandName; else console.warn(`originalCommandName isn't defined for ${header}?!`); - let permLevel = command.permission ?? Command.PERMISSIONS.NONE; + let permLevel = command.permission ?? 0; let usage = command.usage; let invalid = false; @@ -135,7 +135,9 @@ export default new Command({ } $.channel.send( - `Command: \`${header}\`\nAliases: ${aliases}\nCategory: \`${selectedCategory}\`\nPermission Required: \`${PermissionNames[permLevel]}\` (${permLevel})\nDescription: ${command.description}\n${append}`, + `Command: \`${header}\`\nAliases: ${aliases}\nCategory: \`${selectedCategory}\`\nPermission Required: \`${getPermissionName( + permLevel + )}\` (${permLevel})\nDescription: ${command.description}\n${append}`, {split: true} ); } diff --git a/src/commands/template.ts b/src/commands/template.ts index 686fe4a..a97e8e2 100644 --- a/src/commands/template.ts +++ b/src/commands/template.ts @@ -5,7 +5,7 @@ export default new Command({ 'This is a template/testing command providing common functionality. Remove what you don\'t need, and rename/delete this file to generate a fresh command file here. This command should be automatically excluded from the help command. The "usage" parameter (string) overrides the default usage for the help command. The "endpoint" parameter (boolean) prevents further arguments from being passed. Also, as long as you keep the run function async, it\'ll return a promise allowing the program to automatically catch any synchronous errors. However, you\'ll have to do manual error handling if you go the then and catch route.', endpoint: false, usage: "", - permission: null, + permission: -1, aliases: [], async run($) { // code @@ -16,7 +16,7 @@ export default new Command({ 'This is a named subcommand, meaning that the key name is what determines the keyword to use. With default settings for example, "$test layer".', endpoint: false, usage: "", - permission: null, + permission: -1, aliases: [], async run($) { // code @@ -28,7 +28,7 @@ export default new Command({ 'This is the subcommand for getting users by pinging them or copying their ID. With default settings for example, "$test 237359961842253835". The argument will be a user object and won\'t run if no user is found by that ID.', endpoint: false, usage: "", - permission: null, + permission: -1, async run($) { // code } @@ -38,7 +38,7 @@ export default new Command({ 'This is a numeric subcommand, meaning that any type of number (excluding Infinity/NaN) will route to this command if present. With default settings for example, "$test -5.2". The argument with the number is already parsed so you can just use it without converting it.', endpoint: false, usage: "", - permission: null, + permission: -1, async run($) { // code } @@ -48,7 +48,7 @@ export default new Command({ "This is a generic subcommand, meaning that if there isn't a more specific subcommand that's called, it falls to this. With default settings for example, \"$test reeee\".", endpoint: false, usage: "", - permission: null, + permission: -1, async run($) { // code } diff --git a/src/core/command.ts b/src/core/command.ts index 5ee8a5b..c40d5a0 100644 --- a/src/core/command.ts +++ b/src/core/command.ts @@ -1,7 +1,6 @@ import {parseVars} from "./libd"; import {Collection} from "discord.js"; import {Client, Message, TextChannel, DMChannel, NewsChannel, Guild, User, GuildMember} from "discord.js"; -import {PERMISSIONS} from "./permissions"; import {getPrefix} from "../core/structures"; import glob from "glob"; @@ -19,7 +18,7 @@ interface CommandOptions { description?: string; endpoint?: boolean; usage?: string; - permission?: PERMISSIONS | null; + permission?: number; aliases?: string[]; run?: (($: CommandMenu) => Promise) | string; subcommands?: {[key: string]: Command}; @@ -40,7 +39,7 @@ export default class Command { public readonly description: string; public readonly endpoint: boolean; public readonly usage: string; - public readonly permission: PERMISSIONS | null; + public readonly permission: number; // -1 (default) indicates to inherit, 0 is the lowest rank, 1 is second lowest rank, and so on. public readonly aliases: string[]; // This is to keep the array intact for parent Command instances to use. It'll also be used when loading top-level aliases. public originalCommandName: string | null; // If the command is an alias, what's the original name? public run: (($: CommandMenu) => Promise) | string; @@ -49,13 +48,12 @@ export default class Command { public number: Command | null; public any: Command | null; public static readonly TYPES = TYPES; - public static readonly PERMISSIONS = PERMISSIONS; constructor(options?: CommandOptions) { this.description = options?.description || "No description."; this.endpoint = options?.endpoint || false; this.usage = options?.usage || ""; - this.permission = options?.permission ?? null; + this.permission = options?.permission ?? -1; this.aliases = options?.aliases ?? []; this.originalCommandName = null; this.run = options?.run || "No action was set on this command!"; diff --git a/src/core/permissions.ts b/src/core/permissions.ts index 336ffb5..c4c89ba 100644 --- a/src/core/permissions.ts +++ b/src/core/permissions.ts @@ -1,75 +1,68 @@ -import {GuildMember, Permissions} from "discord.js"; +import {User, GuildMember, Permissions} from "discord.js"; import {Config} from "./structures"; -export enum PERMISSIONS { - NONE, - MOD, - ADMIN, - OWNER, - BOT_SUPPORT, - BOT_ADMIN, - BOT_OWNER +interface PermissionLevel { + name: string; + check: (user: User, member: GuildMember | null) => boolean; } -export const PermissionNames = [ - "User", - "Moderator", - "Administrator", - "Server Owner", - "Bot Support", - "Bot Admin", - "Bot Owner" -]; - -// Here is where you enter in the functions that check for permissions. -const PermissionChecker: ((member: GuildMember) => boolean)[] = [ - // NONE // - () => true, - - // MOD // - (member) => - member.hasPermission(Permissions.FLAGS.MANAGE_ROLES) || - member.hasPermission(Permissions.FLAGS.MANAGE_MESSAGES) || - member.hasPermission(Permissions.FLAGS.KICK_MEMBERS) || - member.hasPermission(Permissions.FLAGS.BAN_MEMBERS), - - // ADMIN // - (member) => member.hasPermission(Permissions.FLAGS.ADMINISTRATOR), - - // OWNER // - (member) => member.guild.ownerID === member.id, - - // BOT_SUPPORT // - (member) => Config.support.includes(member.id), - - // BOT_ADMIN // - (member) => Config.admins.includes(member.id), - - // BOT_OWNER // - (member) => Config.owner === member.id +export const PermissionLevels: PermissionLevel[] = [ + { + // NONE // + name: "User", + check: () => true + }, + { + // MOD // + name: "Moderator", + check: (_, member) => + !!member && + (member.hasPermission(Permissions.FLAGS.MANAGE_ROLES) || + member.hasPermission(Permissions.FLAGS.MANAGE_MESSAGES) || + member.hasPermission(Permissions.FLAGS.KICK_MEMBERS) || + member.hasPermission(Permissions.FLAGS.BAN_MEMBERS)) + }, + { + // ADMIN // + name: "Administrator", + check: (_, member) => !!member && member.hasPermission(Permissions.FLAGS.ADMINISTRATOR) + }, + { + // OWNER // + name: "Server Owner", + check: (_, member) => !!member && member.guild.ownerID === member.id + }, + { + // BOT_SUPPORT // + name: "Bot Support", + check: (user) => Config.support.includes(user.id) + }, + { + // BOT_ADMIN // + name: "Bot Admin", + check: (user) => Config.admins.includes(user.id) + }, + { + // BOT_OWNER // + name: "Bot Owner", + check: (user) => Config.owner === user.id + } ]; // After checking the lengths of these three objects, use this as the length for consistency. -const length = Object.keys(PERMISSIONS).length / 2; +const length = PermissionLevels.length; -export function hasPermission(member: GuildMember, permission: PERMISSIONS): boolean { - for (let i = length - 1; i >= permission; i--) if (PermissionChecker[i](member)) return true; +export function hasPermission(member: GuildMember, permission: number): boolean { + for (let i = length - 1; i >= permission; i--) if (PermissionLevels[i].check(member.user, member)) return true; return false; } export function getPermissionLevel(member: GuildMember): number { - for (let i = length - 1; i >= 0; i--) if (PermissionChecker[i](member)) return i; + for (let i = length - 1; i >= 0; i--) if (PermissionLevels[i].check(member.user, member)) return i; return 0; } -// Length Checking -(() => { - const lenNames = PermissionNames.length; - const lenChecker = PermissionChecker.length; - - // By transitive property, lenNames and lenChecker have to be equal to each other as well. - if (length !== lenNames || length !== lenChecker) - console.error( - `Permission object lengths aren't equal! Enum Length (${length}), Names Length (${lenNames}), and Functions Length (${lenChecker}). This WILL cause problems!` - ); -})(); +export function getPermissionName(level: number) { + if (level > length || length < 0) return "N/A"; + else return PermissionLevels[level].name; +} diff --git a/src/events/message.ts b/src/events/message.ts index 352bb57..d805ed8 100644 --- a/src/events/message.ts +++ b/src/events/message.ts @@ -1,6 +1,6 @@ import Event from "../core/event"; import Command, {loadableCommands} from "../core/command"; -import {hasPermission, getPermissionLevel, PermissionNames} from "../core/permissions"; +import {hasPermission, getPermissionLevel, getPermissionName} from "../core/permissions"; import {Permissions} from "discord.js"; import {getPrefix} from "../core/structures"; import {replyEventListeners} from "../core/libd"; @@ -91,7 +91,7 @@ export default new Event<"message">({ if (!command) return console.warn(`Command "${header}" was called but for some reason it's still undefined!`); const params: any[] = []; let isEndpoint = false; - let permLevel = command.permission ?? Command.PERMISSIONS.NONE; + let permLevel = command.permission ?? 0; for (let param of args) { if (command.endpoint) { @@ -122,7 +122,11 @@ export default new Event<"message">({ if (!hasPermission(message.member, permLevel)) { const userPermLevel = getPermissionLevel(message.member); return message.channel.send( - `You don't have access to this command! Your permission level is \`${PermissionNames[userPermLevel]}\` (${userPermLevel}), but this command requires a permission level of \`${PermissionNames[permLevel]}\` (${permLevel}).` + `You don't have access to this command! Your permission level is \`${getPermissionName( + userPermLevel + )}\` (${userPermLevel}), but this command requires a permission level of \`${getPermissionName( + permLevel + )}\` (${permLevel}).` ); } diff --git a/src/globals.ts b/src/globals.ts index 3c8d4c7..4f2e377 100644 --- a/src/globals.ts +++ b/src/globals.ts @@ -2,13 +2,25 @@ import chalk from "chalk"; declare global { var IS_DEV_MODE: boolean; + var PERMISSIONS: typeof PermissionsEnum; interface Console { ready: (...data: any[]) => void; } } +enum PermissionsEnum { + NONE, + MOD, + ADMIN, + OWNER, + BOT_SUPPORT, + BOT_ADMIN, + BOT_OWNER +} + global.IS_DEV_MODE = process.argv[2] === "dev"; +global.PERMISSIONS = PermissionsEnum; const oldConsole = console; From 02c18f57c74a6155e669155c3818e10b506116d0 Mon Sep 17 00:00:00 2001 From: WatDuhHekBro <44940783+WatDuhHekBro@users.noreply.github.com> Date: Tue, 30 Mar 2021 07:16:31 -0500 Subject: [PATCH 114/178] Reworked paginate function --- src/commands/fun/subcommands/eco-shop.ts | 20 ++--- src/commands/utilities/lsemotes.ts | 32 +++---- src/core/libd.ts | 107 ++++++++++++++--------- 3 files changed, 83 insertions(+), 76 deletions(-) diff --git a/src/commands/fun/subcommands/eco-shop.ts b/src/commands/fun/subcommands/eco-shop.ts index 24ebcd7..f5c167b 100644 --- a/src/commands/fun/subcommands/eco-shop.ts +++ b/src/commands/fun/subcommands/eco-shop.ts @@ -10,7 +10,7 @@ export const ShopCommand = new Command({ description: "Displays the list of items you can buy in the shop.", async run({guild, channel, author}) { if (isAuthorized(guild, channel)) { - function getShopEmbed(selection: ShopItem[], title = "Shop") { + function getShopEmbed(selection: ShopItem[], title: string) { const fields: EmbedField[] = []; for (const item of selection) @@ -32,17 +32,15 @@ export const ShopCommand = new Command({ }; } - // In case there's just one page, omit unnecessary details. - if (ShopItems.length <= 5) channel.send(getShopEmbed(ShopItems)); - else { - const shopPages = split(ShopItems, 5); - const pageAmount = shopPages.length; - const msg = await channel.send(getShopEmbed(shopPages[0], `Shop (Page 1 of ${pageAmount})`)); + const shopPages = split(ShopItems, 5); + const pageAmount = shopPages.length; - paginate(msg, author.id, pageAmount, (page) => { - msg.edit(getShopEmbed(shopPages[page], `Shop (Page ${page + 1} of ${pageAmount})`)); - }); - } + paginate(channel, author.id, pageAmount, (page, hasMultiplePages) => { + return getShopEmbed( + shopPages[page], + hasMultiplePages ? `Shop (Page ${page + 1} of ${pageAmount})` : "Shop" + ); + }); } } }); diff --git a/src/commands/utilities/lsemotes.ts b/src/commands/utilities/lsemotes.ts index 6188439..c369ab0 100644 --- a/src/commands/utilities/lsemotes.ts +++ b/src/commands/utilities/lsemotes.ts @@ -93,33 +93,21 @@ async function displayEmoteList(emotes: GuildEmoji[], channel: TextChannel | DMC }); const sections = split(emotes, 20); const pages = sections.length; - const embed = new MessageEmbed().setTitle("**Emotes**").setColor("AQUA"); - let desc = ""; + const embed = new MessageEmbed().setColor("AQUA"); // Gather the first page (if it even exists, which it might not if there no valid emotes appear) if (pages > 0) { - for (const emote of sections[0]) { - desc += `${emote} ${emote.name} (**${emote.guild.name}**)\n`; - } + paginate(channel, author.id, pages, (page, hasMultiplePages) => { + embed.setTitle(hasMultiplePages ? `**Emotes** (Page ${page + 1} of ${pages})` : "**Emotes**"); - embed.setDescription(desc); + let desc = ""; + for (const emote of sections[page]) { + desc += `${emote} ${emote.name} (**${emote.guild.name}**)\n`; + } + embed.setDescription(desc); - if (pages > 1) { - embed.setTitle(`**Emotes** (Page 1 of ${pages})`); - const msg = await channel.send({embed}); - - paginate(msg, author.id, pages, (page) => { - let desc = ""; - for (const emote of sections[page]) { - desc += `${emote} ${emote.name} (**${emote.guild.name}**)\n`; - } - embed.setTitle(`**Emotes** (Page ${page + 1} of ${pages})`); - embed.setDescription(desc); - msg.edit(embed); - }); - } else { - channel.send({embed}); - } + return embed; + }); } else { channel.send("No valid emotes found by that query."); } diff --git a/src/core/libd.ts b/src/core/libd.ts index 9c44fa4..3cc6b04 100644 --- a/src/core/libd.ts +++ b/src/core/libd.ts @@ -1,5 +1,14 @@ // Library for Discord-specific functions -import {Message, Guild, GuildMember, Permissions} from "discord.js"; +import { + Message, + Guild, + GuildMember, + Permissions, + TextChannel, + DMChannel, + NewsChannel, + MessageOptions +} from "discord.js"; import {get} from "https"; import FileManager from "./storage"; import {eventListeners} from "../events/messageReactionRemove"; @@ -36,57 +45,69 @@ export function updateGlobalEmoteRegistry(): void { // Pagination function that allows for customization via a callback. // Define your own pages outside the function because this only manages the actual turning of pages. export async function paginate( - message: Message, + channel: TextChannel | DMChannel | NewsChannel, senderID: string, total: number, - callback: (page: number) => void, + callback: (page: number, hasMultiplePages: boolean) => MessageOptions & {split?: false}, duration = 60000 ) { - let page = 0; - const turn = (amount: number) => { - page += amount; + const hasMultiplePages = total > 1; + const message = await channel.send(callback(0, hasMultiplePages)); - if (page < 0) page += total; - else if (page >= total) page -= total; + if (hasMultiplePages) { + let page = 0; + const turn = (amount: number) => { + page += amount; - callback(page); - }; - const BACKWARDS_EMOJI = "⬅️"; - const FORWARDS_EMOJI = "➡️"; - const handle = (emote: string, reacterID: string) => { - switch (emote) { - case BACKWARDS_EMOJI: - turn(-1); - break; - case FORWARDS_EMOJI: - turn(1); - break; - } - }; + if (page < 0) page += total; + else if (page >= total) page -= total; - // Listen for reactions and call the handler. - let backwardsReaction = await message.react(BACKWARDS_EMOJI); - let forwardsReaction = await message.react(FORWARDS_EMOJI); - eventListeners.set(message.id, handle); - await message.awaitReactions( - (reaction, user) => { - if (user.id === senderID) { - // The reason this is inside the call is because it's possible to switch a user's permissions halfway and suddenly throw an error. - // This will dynamically adjust for that, switching modes depending on whether it currently has the "Manage Messages" permission. - const canDeleteEmotes = botHasPermission(message.guild, Permissions.FLAGS.MANAGE_MESSAGES); - handle(reaction.emoji.name, user.id); - - if (canDeleteEmotes) reaction.users.remove(user); + message.edit(callback(page, true)); + }; + const BACKWARDS_EMOJI = "⬅️"; + const FORWARDS_EMOJI = "➡️"; + const handle = (emote: string, reacterID: string) => { + if (senderID === reacterID) { + switch (emote) { + case BACKWARDS_EMOJI: + turn(-1); + break; + case FORWARDS_EMOJI: + turn(1); + break; + } } + }; - return false; - }, - {time: duration} - ); - // When time's up, remove the bot's own reactions. - eventListeners.delete(message.id); - backwardsReaction.users.remove(message.author); - forwardsReaction.users.remove(message.author); + // Listen for reactions and call the handler. + let backwardsReaction = await message.react(BACKWARDS_EMOJI); + let forwardsReaction = await message.react(FORWARDS_EMOJI); + eventListeners.set(message.id, handle); + const collector = message.createReactionCollector( + (reaction, user) => { + if (user.id === senderID) { + // The reason this is inside the call is because it's possible to switch a user's permissions halfway and suddenly throw an error. + // This will dynamically adjust for that, switching modes depending on whether it currently has the "Manage Messages" permission. + const canDeleteEmotes = botHasPermission(message.guild, Permissions.FLAGS.MANAGE_MESSAGES); + handle(reaction.emoji.name, user.id); + if (canDeleteEmotes) reaction.users.remove(user); + collector.resetTimer(); + } + + return false; + }, + // Apparently, regardless of whether you put "time" or "idle", it won't matter to the collector. + // In order to actually reset the timer, you have to do it manually via collector.resetTimer(). + {time: duration} + ); + + // When time's up, remove the bot's own reactions. + collector.on("end", () => { + eventListeners.delete(message.id); + backwardsReaction.users.remove(message.author); + forwardsReaction.users.remove(message.author); + }); + } } // Waits for the sender to either confirm an action or let it pass (and delete the message). From 86ccb74ac2b519de114c641bd2a8eb70eea063ef Mon Sep 17 00:00:00 2001 From: WatDuhHekBro <44940783+WatDuhHekBro@users.noreply.github.com> Date: Tue, 30 Mar 2021 18:14:15 -0500 Subject: [PATCH 115/178] Highly biased code review --- data/endpoints.json | 31 ---------- src/commands/admin.ts | 36 ++++++----- src/commands/fun/cookie.ts | 71 ++++++++++++---------- src/commands/fun/neko.ts | 45 +++++++++++--- src/commands/fun/ok.ts | 119 +++++++++++++++++++------------------ src/commands/info.ts | 26 ++++++-- src/core/libd.ts | 4 +- src/index.ts | 2 +- 8 files changed, 182 insertions(+), 152 deletions(-) delete mode 100644 data/endpoints.json diff --git a/data/endpoints.json b/data/endpoints.json deleted file mode 100644 index 69f15ac..0000000 --- a/data/endpoints.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "sfw": { - "tickle": "/img/tickle", - "slap": "/img/slap", - "poke": "/img/poke", - "pat": "/img/pat", - "neko": "/img/neko", - "meow": "/img/meow", - "lizard": "/img/lizard", - "kiss": "/img/kiss", - "hug": "/img/hug", - "foxGirl": "/img/fox_girl", - "feed": "/img/feed", - "cuddle": "/img/cuddle", - "why": "/why", - "catText": "/cat", - "fact": "/fact", - "nekoGif": "/img/ngif", - "kemonomimi": "/img/kemonomimi", - "holo": "/img/holo", - "smug": "/img/smug", - "baka": "/img/baka", - "woof": "/img/woof", - "spoiler": "/spoiler", - "wallpaper": "/img/wallpaper", - "goose": "/img/goose", - "gecg": "/img/gecg", - "avatar": "/img/avatar", - "waifu": "/img/waifu" - } -} \ No newline at end of file diff --git a/src/commands/admin.ts b/src/commands/admin.ts index ff2d968..e0c4362 100644 --- a/src/commands/admin.ts +++ b/src/commands/admin.ts @@ -100,24 +100,28 @@ export default new Command({ }) }), purge: new Command({ - description: "Purges bot messages.", + description: "Purges the bot's own messages.", permission: PERMISSIONS.BOT_SUPPORT, async run($) { - if ($.message.channel instanceof discord.DMChannel) { - return; - } - $.message.delete(); - const msgs = await $.channel.messages.fetch({ - limit: 100 - }); - const travMessages = msgs.filter((m) => m.author.id === $.client.user?.id); + // It's probably better to go through the bot's own messages instead of calling bulkDelete which requires MANAGE_MESSAGES. + if (botHasPermission($.guild, Permissions.FLAGS.MANAGE_MESSAGES) && $.channel.type !== "dm") { + $.message.delete(); + const msgs = await $.channel.messages.fetch({ + limit: 100 + }); + const travMessages = msgs.filter((m) => m.author.id === $.client.user?.id); - await $.message.channel.send(`Found ${travMessages.size} messages to delete.`).then((m) => - m.delete({ - timeout: 5000 - }) - ); - await $.message.channel.bulkDelete(travMessages); + await $.channel.send(`Found ${travMessages.size} messages to delete.`).then((m) => + m.delete({ + timeout: 5000 + }) + ); + await $.channel.bulkDelete(travMessages); + } else { + $.channel.send( + "This command must be executed in a guild where I have the `MANAGE_MESSAGES` permission." + ); + } } }), clear: new Command({ @@ -172,7 +176,7 @@ export default new Command({ permission: PERMISSIONS.BOT_SUPPORT, async run($) { const guildList = $.client.guilds.cache.array().map((e) => e.name); - $.channel.send(guildList); + $.channel.send(guildList, {split: true}); } }), activity: new Command({ diff --git a/src/commands/fun/cookie.ts b/src/commands/fun/cookie.ts index 99ace4b..39a3948 100644 --- a/src/commands/fun/cookie.ts +++ b/src/commands/fun/cookie.ts @@ -1,51 +1,58 @@ +import {User} from "discord.js"; import Command from "../../core/command"; +import {random} from "../../core/lib"; +import {parseVars} from "../../core/libd"; + +const cookies = [ + `has given %target% a chocolate chip cookie!`, + `has given %target% a soft homemade oatmeal cookie!`, + `has given %target% a plain, dry, old cookie. It was the last one in the bag. Gross.`, + `gives %target% a sugar cookie. What, no frosting and sprinkles? 0/10 would not touch.`, + `gives %target% a chocolate chip cookie. Oh wait, those are raisins. Bleck!`, + `gives %target% an enormous cookie. Poking it gives you more cookies. Weird.`, + `gives %target% a fortune cookie. It reads "Why aren't you working on any projects?"`, + `gives %target% a fortune cookie. It reads "Give that special someone a compliment"`, + `gives %target% a fortune cookie. It reads "Take a risk!"`, + `gives %target% a fortune cookie. It reads "Go outside."`, + `gives %target% a fortune cookie. It reads "Don't forget to eat your veggies!"`, + `gives %target% a fortune cookie. It reads "Do you even lift?"`, + `gives %target% a fortune cookie. It reads "m808 pls"`, + `gives %target% a fortune cookie. It reads "If you move your hips, you'll get all the ladies."`, + `gives %target% a fortune cookie. It reads "I love you."`, + `gives %target% a Golden Cookie. You can't eat it because it is made of gold. Dammit.`, + `gives %target% an Oreo cookie with a glass of milk!`, + `gives %target% a rainbow cookie made with love :heart:`, + `gives %target% an old cookie that was left out in the rain, it's moldy.`, + `bakes %target% fresh cookies, it smells amazing.` +]; export default new Command({ description: "Gives specified user a cookie.", usage: "['all'/@user]", run: ":cookie: Here's a cookie!", - any: new Command({ - async run($) { - if ($.args[0] == "all") $.channel.send(`${$.author} gave everybody a cookie!`); - } - }), + subcommands: { + all: new Command({ + async run($) { + $.channel.send(`${$.author} gave everybody a cookie!`); + } + }) + }, user: new Command({ description: "User to give cookie to.", async run($) { const sender = $.author; - const mention = $.message.mentions.users.first(); - - if (!mention) return; - - const cookies = [ - `has given <@${mention.id}> a chocolate chip cookie!`, - `has given <@${mention.id}> a soft homemade oatmeal cookie!`, - `has given <@${mention.id}> a plain, dry, old cookie. It was the last one in the bag. Gross.`, - `gives <@${mention.id}> a sugar cookie. What, no frosting and sprinkles? 0/10 would not touch.`, - `gives <@${mention.id}> a chocolate chip cookie. Oh wait, those are raisins. Bleck!`, - `gives <@${mention.id}> an enormous cookie. Poking it gives you more cookies. Weird.`, - `gives <@${mention.id}> a fortune cookie. It reads "Why aren't you working on any projects?"`, - `gives <@${mention.id}> a fortune cookie. It reads "Give that special someone a compliment"`, - `gives <@${mention.id}> a fortune cookie. It reads "Take a risk!"`, - `gives <@${mention.id}> a fortune cookie. It reads "Go outside."`, - `gives <@${mention.id}> a fortune cookie. It reads "Don't forget to eat your veggies!"`, - `gives <@${mention.id}> a fortune cookie. It reads "Do you even lift?"`, - `gives <@${mention.id}> a fortune cookie. It reads "m808 pls"`, - `gives <@${mention.id}> a fortune cookie. It reads "If you move your hips, you'll get all the ladies."`, - `gives <@${mention.id}> a fortune cookie. It reads "I love you."`, - `gives <@${mention.id}> a Golden Cookie. You can't eat it because it is made of gold. Dammit.`, - `gives <@${mention.id}> an Oreo cookie with a glass of milk!`, - `gives <@${mention.id}> a rainbow cookie made with love :heart:`, - `gives <@${mention.id}> an old cookie that was left out in the rain, it's moldy.`, - `bakes <@${mention.id}> fresh cookies, it smells amazing.` - ]; + const mention: User = $.args[0]; if (mention.id == sender.id) { $.channel.send("You can't give yourself cookies!"); return; } - $.channel.send(`:cookie: <@${sender.id}> ` + cookies[Math.floor(Math.random() * cookies.length)]); + $.channel.send( + `:cookie: <@${sender.id}> ${parseVars(random(cookies), { + target: mention.toString() + })}` + ); } }) }); diff --git a/src/commands/fun/neko.ts b/src/commands/fun/neko.ts index 68ab1bf..1990bc2 100644 --- a/src/commands/fun/neko.ts +++ b/src/commands/fun/neko.ts @@ -1,15 +1,42 @@ -/// @ts-nocheck import {URL} from "url"; -import FileManager from "../../core/storage"; import Command from "../../core/command"; import {getContent} from "../../core/libd"; -const endpoints = FileManager.read("endpoints"); +const endpoints: {sfw: {[key: string]: string}} = { + sfw: { + tickle: "/img/tickle", + slap: "/img/slap", + poke: "/img/poke", + pat: "/img/pat", + neko: "/img/neko", + meow: "/img/meow", + lizard: "/img/lizard", + kiss: "/img/kiss", + hug: "/img/hug", + foxGirl: "/img/fox_girl", + feed: "/img/feed", + cuddle: "/img/cuddle", + why: "/why", + catText: "/cat", + fact: "/fact", + nekoGif: "/img/ngif", + kemonomimi: "/img/kemonomimi", + holo: "/img/holo", + smug: "/img/smug", + baka: "/img/baka", + woof: "/img/woof", + spoiler: "/spoiler", + wallpaper: "/img/wallpaper", + goose: "/img/goose", + gecg: "/img/gecg", + avatar: "/img/avatar", + waifu: "/img/waifu" + } +}; export default new Command({ description: "Provides you with a random image with the selected argument.", async run($) { - console.log(endpoints.sfw); $.channel.send( `Please provide an image type. Available arguments:\n\`[${Object.keys(endpoints.sfw).join(", ")}]\`.` ); @@ -17,10 +44,14 @@ export default new Command({ any: new Command({ description: "Image type to send.", async run($) { - if (!($.args[0] in endpoints.sfw)) return $.channel.send("Couldn't find that endpoint!"); + const arg = $.args[0]; - let baseURL = "https://nekos.life/api/v2"; - let url = new URL(`${baseURL}${endpoints.sfw[$.args[0]]}`); + if (!(arg in endpoints.sfw)) { + $.channel.send("Couldn't find that endpoint!"); + return; + } + + let url = new URL(`https://nekos.life/api/v2${endpoints.sfw[arg]}`); const content = await getContent(url.toString()); $.channel.send(content.url); } diff --git a/src/commands/fun/ok.ts b/src/commands/fun/ok.ts index 9c8d641..a4ea44a 100644 --- a/src/commands/fun/ok.ts +++ b/src/commands/fun/ok.ts @@ -1,66 +1,67 @@ import Command from "../../core/command"; +import {random} from "../../core/lib"; + +const responses = [ + "boomer", + "zoomer", + "the last generationer", + "the last airbender", + "fire nation", + "fire lord", + "guy fieri", + "guy from final fight", + "haggar", + "Max Thunder from Streets of Rage 2", + "police guy who fires bazookas", + "Mr. X", + "Leon Its Wrong If Its Not Ada Wong S. Kennedy.", + "Jill", + "JFK", + "george bush", + "obama", + "the world", + "copy of scott pilgrim vs the world", + "ok", + "ko", + "Hot Daddy Venomous", + "big daddy", + "John Cena", + "BubbleSpurJarJarBinks", + "T-Series", + "pewdiepie", + "markiplier", + "jacksepticeye", + "vanossgaming", + "miniladd", + "Traves", + "Wilbur Soot", + "sootrhianna", + "person with tiny ears", + "anti-rabbit", + "homo sapiens", + "homo", + "cute kitty", + "ugly kitty", + "sadness", + "doomer", + "gloomer", + "bloomer", + "edgelord", + "weeb", + "m'lady", + "Mr. Crabs", + "hand", + "lahoma", + "big man", + "fox", + "pear", + "cat", + "large man" +]; export default new Command({ description: "Sends random ok message.", async run($) { - const responses = [ - "boomer", - "zoomer", - "the last generationer", - "the last airbender", - "fire nation", - "fire lord", - "guy fieri", - "guy from final fight", - "haggar", - "Max Thunder from Streets of Rage 2", - "police guy who fires bazookas", - "Mr. X", - "Leon Its Wrong If Its Not Ada Wong S. Kennedy.", - "Jill", - "JFK", - "george bush", - "obama", - "the world", - "copy of scott pilgrim vs the world", - "ok", - "ko", - "Hot Daddy Venomous", - "big daddy", - "John Cena", - "BubbleSpurJarJarBinks", - "T-Series", - "pewdiepie", - "markiplier", - "jacksepticeye", - "vanossgaming", - "miniladd", - "Traves", - "Wilbur Soot", - "sootrhianna", - "person with tiny ears", - "anti-rabbit", - "homo sapiens", - "homo", - "cute kitty", - "ugly kitty", - "sadness", - "doomer", - "gloomer", - "bloomer", - "edgelord", - "weeb", - "m'lady", - "Mr. Crabs", - "hand", - "lahoma", - "big man", - "fox", - "pear", - "cat", - "large man" - ]; - - $.channel.send("ok " + responses[Math.floor(Math.random() * responses.length)]); + $.channel.send(`ok ${random(responses)}`); } }); diff --git a/src/commands/info.ts b/src/commands/info.ts index 7b4b103..7142ba5 100644 --- a/src/commands/info.ts +++ b/src/commands/info.ts @@ -2,14 +2,12 @@ import {MessageEmbed, version as djsversion} from "discord.js"; import ms from "ms"; import os from "os"; import Command from "../core/command"; -import {formatBytes, trimArray} from "../core/libd"; -import {verificationLevels, filterLevels, regions, flags} from "../defs/info"; +import {formatBytes, trimArray, getMemberByUsername} from "../core/libd"; +import {verificationLevels, filterLevels, regions} from "../defs/info"; import moment from "moment"; import utc from "moment"; import {Guild} from "discord.js"; -const {version} = require("../../package.json"); - export default new Command({ description: "Command to provide all sorts of info about the current server, a user, etc.", run: "Please provide an argument.\nFor help, run `%prefix%help info`.", @@ -30,6 +28,26 @@ export default new Command({ }) ); } + }), + any: new Command({ + description: "Shows another user's avatar by searching their name", + async run($) { + if ($.guild) { + const name = $.args.join(" "); + const member = await getMemberByUsername($.guild, name); + + if (member) { + $.channel.send( + member.user.displayAvatarURL({ + dynamic: true, + size: 2048 + }) + ); + } else { + $.channel.send(`No user found by the name \`${name}\`!`); + } + } + } }) }), bot: new Command({ diff --git a/src/core/libd.ts b/src/core/libd.ts index 3cc6b04..20a1ab1 100644 --- a/src/core/libd.ts +++ b/src/core/libd.ts @@ -381,9 +381,9 @@ export function formatBytes(bytes: any) { return `${parseFloat((bytes / Math.pow(1024, i)).toFixed(2))} ${sizes[i]}`; } -export function getContent(url: any) { +export function getContent(url: string): Promise<{url: string}> { return new Promise((resolve, reject) => { - get(url, (res: {resume?: any; setEncoding?: any; on?: any; statusCode?: any}) => { + get(url, (res) => { const {statusCode} = res; if (statusCode !== 200) { res.resume(); diff --git a/src/index.ts b/src/index.ts index 426046c..c6320a8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -53,7 +53,7 @@ export const client = new discord.Client(); } ] }, - prefix: "!!", + prefix: Config.prefix, helpCmd: "mhelp", admins: ["717352467280691331"] }); From 6a4107c01e5073952d37e7f451907a8ffd2e4d21 Mon Sep 17 00:00:00 2001 From: WatDuhHekBro <44940783+WatDuhHekBro@users.noreply.github.com> Date: Tue, 30 Mar 2021 20:40:29 -0500 Subject: [PATCH 116/178] Copied over the rest of TravBot v2 commands --- package-lock.json | 1804 ++++++++++++++++++++++++++- package.json | 9 +- src/commands/fun/figlet.ts | 20 + src/commands/fun/insult.ts | 14 + src/commands/fun/love.ts | 13 + src/commands/fun/ravi.ts | 38 + src/commands/fun/thonk.ts | 41 + src/commands/fun/urban.ts | 27 + src/commands/fun/weather.ts | 39 + src/commands/fun/whois.ts | 83 ++ src/commands/utilities/calc.ts | 26 + src/commands/utilities/code.ts | 6 + src/commands/utilities/invite.ts | 12 + src/commands/utilities/todo.ts | 62 + src/commands/utilities/translate.ts | 38 + src/core/structures.ts | 11 + 16 files changed, 2227 insertions(+), 16 deletions(-) create mode 100644 src/commands/fun/figlet.ts create mode 100644 src/commands/fun/insult.ts create mode 100644 src/commands/fun/love.ts create mode 100644 src/commands/fun/ravi.ts create mode 100644 src/commands/fun/thonk.ts create mode 100644 src/commands/fun/urban.ts create mode 100644 src/commands/fun/weather.ts create mode 100644 src/commands/fun/whois.ts create mode 100644 src/commands/utilities/calc.ts create mode 100644 src/commands/utilities/code.ts create mode 100644 src/commands/utilities/invite.ts create mode 100644 src/commands/utilities/todo.ts create mode 100644 src/commands/utilities/translate.ts diff --git a/package-lock.json b/package-lock.json index 20330c8..9dd4614 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,12 +12,19 @@ "chalk": "^4.1.0", "discord.js": "^12.5.1", "discord.js-lavalink-lib": "^0.1.8", + "figlet": "^1.5.0", "inquirer": "^7.3.3", + "mathjs": "^9.3.0", "moment": "^2.29.1", - "ms": "^2.1.3" + "ms": "^2.1.3", + "relevant-urban": "^2.0.0", + "translate-google": "^1.4.3", + "weather-js": "^2.0.0" }, "devDependencies": { + "@types/figlet": "^1.5.0", "@types/inquirer": "^6.5.0", + "@types/mathjs": "^6.0.11", "@types/mocha": "^8.2.0", "@types/ms": "^0.7.31", "@types/node": "^14.14.20", @@ -72,11 +79,55 @@ "npm": ">=5" } }, + "node_modules/@sindresorhus/is": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.0.0.tgz", + "integrity": "sha512-FyD2meJpDPjyNQejSjvnhpgI/azsQkA4lGbuu5BQZfjvJ9cbRZXzeWL2HceCekW4lixO9JPesIIQkSoLjeJHNQ==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/@szmarczak/http-timer": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.5.tgz", + "integrity": "sha512-PyRA9sm1Yayuj5OIoJ1hGt2YISX45w9WcFbh6ddT0Z/0yaFxOtGLInr4jUfU1EAFVs0Yfyfev4RNwBlUaHdlDQ==", + "dependencies": { + "defer-to-connect": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@types/cacheable-request": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.1.tgz", + "integrity": "sha512-ykFq2zmBGOCbpIXtoVbz4SKY5QriWPh3AjyU4G74RYbtt5yOc5OfaY75ftjg7mikMOla1CTGpX3lLbuJh8DTrQ==", + "dependencies": { + "@types/http-cache-semantics": "*", + "@types/keyv": "*", + "@types/node": "*", + "@types/responselike": "*" + } + }, "node_modules/@types/color-name": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==" }, + "node_modules/@types/figlet": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@types/figlet/-/figlet-1.5.0.tgz", + "integrity": "sha512-LffgrNrwx2bs52gXSuycuMSbxwnVgNeWJAEyo8dNnDcIqNCZ13zNJeoNU2IWs62EeUORIj34zkEN7oeMrWdJ8Q==", + "dev": true + }, + "node_modules/@types/http-cache-semantics": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.0.tgz", + "integrity": "sha512-c3Xy026kOF7QOTn00hbIllV1dLR9hG9NkSrLQgCVs8NF6sBU+VGWjD3wLPhmh1TYAc7ugCFsvHYMN4VcBN1U1A==" + }, "node_modules/@types/inquirer": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/@types/inquirer/-/inquirer-6.5.0.tgz", @@ -87,6 +138,23 @@ "rxjs": "^6.4.0" } }, + "node_modules/@types/keyv": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.1.tgz", + "integrity": "sha512-MPtoySlAZQ37VoLaPcTHCu1RWJ4llDkULYZIzOYxlhxBqYPB0RsRlmMU0R6tahtFe27mIdkHV+551ZWV4PLmVw==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/mathjs": { + "version": "6.0.11", + "resolved": "https://registry.npmjs.org/@types/mathjs/-/mathjs-6.0.11.tgz", + "integrity": "sha512-q9B8ZreO41L38iTY76bCZEtAqzeRs4mNIOZpZ1sLSlcYgvgfFrnf8y8qfmas0tVWrsODjmQbQJFD6RJJJCqJbQ==", + "dev": true, + "dependencies": { + "decimal.js": "^10.0.0" + } + }, "node_modules/@types/mocha": { "version": "8.2.0", "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-8.2.0.tgz", @@ -102,8 +170,15 @@ "node_modules/@types/node": { "version": "14.14.20", "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.20.tgz", - "integrity": "sha512-Y93R97Ouif9JEOWPIUyU+eyIdyRqQR0I8Ez1dzku4hDx34NWh4HbtIc3WNzwB1Y9ULvNGeu5B8h8bVL5cAk4/A==", - "dev": true + "integrity": "sha512-Y93R97Ouif9JEOWPIUyU+eyIdyRqQR0I8Ez1dzku4hDx34NWh4HbtIc3WNzwB1Y9ULvNGeu5B8h8bVL5cAk4/A==" + }, + "node_modules/@types/responselike": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.0.tgz", + "integrity": "sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==", + "dependencies": { + "@types/node": "*" + } }, "node_modules/@types/through": { "version": "0.0.30", @@ -140,6 +215,21 @@ "node": ">=6.5" } }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, "node_modules/ansi-colors": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", @@ -208,11 +298,40 @@ "sprintf-js": "~1.0.2" } }, + "node_modules/asn1": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "dependencies": { + "safer-buffer": "~2.1.0" + } + }, + "node_modules/assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "engines": { + "node": ">=0.8" + } + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" }, + "node_modules/aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", + "engines": { + "node": "*" + } + }, + "node_modules/aws4": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", + "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==" + }, "node_modules/axios": { "version": "0.21.1", "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz", @@ -227,6 +346,19 @@ "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", "dev": true }, + "node_modules/bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "dependencies": { + "tweetnacl": "^0.14.3" + } + }, + "node_modules/bcrypt-pbkdf/node_modules/tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" + }, "node_modules/binary-extensions": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz", @@ -270,6 +402,31 @@ "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", "dev": true }, + "node_modules/cacheable-lookup": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", + "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==", + "engines": { + "node": ">=10.6.0" + } + }, + "node_modules/cacheable-request": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.1.tgz", + "integrity": "sha512-lt0mJ6YAnsrBErpTMWeu5kl/tg9xMAWjavYTN6VQXM1A/teBITuNcccXsCxF0tDQQJf9DfAaX5O4e0zp0KlfZw==", + "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": "^4.1.0", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/camelcase": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", @@ -279,6 +436,11 @@ "node": ">=6" } }, + "node_modules/caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" + }, "node_modules/chalk": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", @@ -397,6 +559,14 @@ "node": ">=6" } }, + "node_modules/clone-response": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", + "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=", + "dependencies": { + "mimic-response": "^1.0.0" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -424,12 +594,41 @@ "node": ">= 0.8" } }, + "node_modules/complex.js": { + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/complex.js/-/complex.js-2.0.12.tgz", + "integrity": "sha512-oQX99fwL6LrTVg82gDY1dIWXy6qZRnRL35N+YhIX0N7tSwsa0KFy6IEMHTNuCW4mP7FS7MEqZ/2I/afzYwPldw==", + "engines": { + "node": "*" + } + }, "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/configstore": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz", + "integrity": "sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==", + "dependencies": { + "dot-prop": "^5.2.0", + "graceful-fs": "^4.1.2", + "make-dir": "^3.0.0", + "unique-string": "^2.0.0", + "write-file-atomic": "^3.0.0", + "xdg-basedir": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, "node_modules/create-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", @@ -450,6 +649,25 @@ "node": ">= 8" } }, + "node_modules/crypto-random-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", + "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", + "engines": { + "node": ">=8" + } + }, + "node_modules/dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "dependencies": { + "assert-plus": "^1.0.0" + }, + "engines": { + "node": ">=0.10" + } + }, "node_modules/debug": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", @@ -477,6 +695,44 @@ "node": ">=0.10.0" } }, + "node_modules/decimal.js": { + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.2.1.tgz", + "integrity": "sha512-KaL7+6Fw6i5A2XSnsbhm/6B+NuEA7TZ4vqxnd5tXz9sbKtrN9Srj8ab4vKVdK8YAqZO9P1kg45Y6YLoduPf+kw==" + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decompress-response/node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/defer-to-connect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", + "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", + "engines": { + "node": ">=10" + } + }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -485,6 +741,14 @@ "node": ">=0.4.0" } }, + "node_modules/detect-indent": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.0.0.tgz", + "integrity": "sha512-oSyFlqaTHCItVRGK5RmrmjB+CmaMOW7IaNA/kdxqhoa6d17j/5ce9O9eWXmV/KEdRwqpQA+Vqe8a8Bsybu4YnA==", + "engines": { + "node": ">=8" + } + }, "node_modules/diff": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", @@ -523,17 +787,71 @@ "lavacord": "^1.1.7" } }, + "node_modules/docopt": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/docopt/-/docopt-0.6.2.tgz", + "integrity": "sha1-so6eIiDaXsSffqW7JKR3h0Be6xE=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/dot-json": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/dot-json/-/dot-json-1.2.2.tgz", + "integrity": "sha512-AKL+GsO4wSEU4LU+fAk/PqN4nQ6PB1vT3HpMiZous9xCzK5S0kh4DzfUY0EfU67jsIXLlu0ty71659N9Nmg+Tw==", + "dependencies": { + "detect-indent": "~6.0.0", + "docopt": "~0.6.2", + "underscore-keypath": "~0.0.22" + }, + "bin": { + "dot-json": "bin/dot-json.js" + } + }, + "node_modules/dot-prop": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", + "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", + "dependencies": { + "is-obj": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/duplexer": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=", "dev": true }, + "node_modules/ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "dependencies": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/escape-latex": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/escape-latex/-/escape-latex-1.2.0.tgz", + "integrity": "sha512-nV5aVWW1K0wEiUIEdZ4erkGGH8mDxGyxSeqPzRNtWP7ataw+/olFObw7hujFWlVjNsaDFw5VZ5NzVSIqRgfTiw==" + }, "node_modules/escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", @@ -578,6 +896,11 @@ "node": ">=6" } }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, "node_modules/external-editor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", @@ -591,6 +914,32 @@ "node": ">=4" } }, + "node_modules/extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", + "engines": [ + "node >=0.6.0" + ] + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" + }, + "node_modules/figlet": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/figlet/-/figlet-1.5.0.tgz", + "integrity": "sha512-ZQJM4aifMpz6H19AW1VqvZ7l4pOE9p7i/3LyxgO2kp+PO/VcDYNqIHEMtkccqIhTXMKci4kjueJr/iCQEaT/Ww==", + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/figures": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", @@ -644,6 +993,35 @@ "node": ">=4.0" } }, + "node_modules/forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", + "engines": { + "node": "*" + } + }, + "node_modules/form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 0.12" + } + }, + "node_modules/fraction.js": { + "version": "4.0.13", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.0.13.tgz", + "integrity": "sha512-E1fz2Xs9ltlUp+qbiyx9wmt2n9dRzPsS11Jtdb8D2o+cC7wr9xkkKsVKJuBX0ST+LVS+LhLO+SbLJNtfWcJvXA==", + "engines": { + "node": "*" + } + }, "node_modules/from": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz", @@ -678,6 +1056,28 @@ "node": "6.* || 8.* || >= 10.*" } }, + "node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "dependencies": { + "assert-plus": "^1.0.0" + } + }, "node_modules/glob": { "version": "7.1.6", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", @@ -707,6 +1107,35 @@ "node": ">= 6" } }, + "node_modules/got": { + "version": "11.8.2", + "resolved": "https://registry.npmjs.org/got/-/got-11.8.2.tgz", + "integrity": "sha512-D0QywKgIe30ODs+fm8wMZiAcZjypcCodPNuMz5H9Mny7RJ+IjJ10BdmGW7OM7fHXP+O7r6ZwapQ/YQmMSvB0UQ==", + "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.1", + "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" + }, + "engines": { + "node": ">=10.19.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/got?sponsor=1" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", + "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==" + }, "node_modules/growl": { "version": "1.10.5", "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", @@ -716,6 +1145,27 @@ "node": ">=4.x" } }, + "node_modules/har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", + "engines": { + "node": ">=4" + } + }, + "node_modules/har-validator": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", + "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", + "deprecated": "this library is no longer supported", + "dependencies": { + "ajv": "^6.12.3", + "har-schema": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -733,6 +1183,37 @@ "he": "bin/he" } }, + "node_modules/http-cache-semantics": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", + "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==" + }, + "node_modules/http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "dependencies": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + }, + "engines": { + "node": ">=0.8", + "npm": ">=1.3.7" + } + }, + "node_modules/http2-wrapper": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", + "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", + "dependencies": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.0.0" + }, + "engines": { + "node": ">=10.19.0" + } + }, "node_modules/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -744,6 +1225,14 @@ "node": ">=0.10.0" } }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "engines": { + "node": ">=0.8.19" + } + }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -824,6 +1313,14 @@ "node": ">=0.10.0" } }, + "node_modules/is-keyword-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-keyword-js/-/is-keyword-js-1.0.3.tgz", + "integrity": "sha1-rDDc81tnH0snsX9ctXI1EmAhEy0=", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -833,6 +1330,14 @@ "node": ">=0.12.0" } }, + "node_modules/is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "engines": { + "node": ">=8" + } + }, "node_modules/is-plain-obj": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", @@ -842,12 +1347,32 @@ "node": ">=8" } }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" + }, + "node_modules/is-url": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz", + "integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==" + }, "node_modules/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/isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" + }, + "node_modules/javascript-natural-sort": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz", + "integrity": "sha1-+eIwPUUH9tdDVac2ZNFED7Wg71k=" + }, "node_modules/js-yaml": { "version": "3.14.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz", @@ -861,6 +1386,53 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==" + }, + "node_modules/json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" + }, + "node_modules/jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "engines": [ + "node >=0.6.0" + ], + "dependencies": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "node_modules/keyv": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.0.3.tgz", + "integrity": "sha512-zdGa2TOpSZPq5mU6iowDARnMBZgtCqJ11dJROFi6tg6kTn4nuUdU09lFyLFSaHrWqpIJ+EBq4E8/Dc0Vx5vLdA==", + "dependencies": { + "json-buffer": "3.0.1" + } + }, "node_modules/lavacord": { "version": "1.1.9", "resolved": "https://registry.npmjs.org/lavacord/-/lavacord-1.1.9.tgz", @@ -891,6 +1463,11 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==" }, + "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=" + }, "node_modules/log-symbols": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.0.0.tgz", @@ -903,6 +1480,28 @@ "node": ">=10" } }, + "node_modules/lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "engines": { + "node": ">=8" + } + }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", @@ -915,6 +1514,27 @@ "integrity": "sha1-5WqpTEyAVaFkBKBnS3jyFffI4ZQ=", "dev": true }, + "node_modules/mathjs": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/mathjs/-/mathjs-9.3.0.tgz", + "integrity": "sha512-0kYW+TXgB8lCqUj5wHR2hqAO2twSbPRelSFgRJXiwAx4nM6FrIb43Jd6XhW7sVbwYB+9HCNiyg5Kn8VYeB7ilg==", + "dependencies": { + "complex.js": "^2.0.11", + "decimal.js": "^10.2.1", + "escape-latex": "^1.2.0", + "fraction.js": "^4.0.13", + "javascript-natural-sort": "^0.7.1", + "seedrandom": "^3.0.5", + "tiny-emitter": "^2.1.0", + "typed-function": "^2.0.0" + }, + "bin": { + "mathjs": "bin/cli.js" + }, + "engines": { + "node": ">= 10" + } + }, "node_modules/mime-db": { "version": "1.45.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.45.0.tgz", @@ -942,6 +1562,14 @@ "node": ">=6" } }, + "node_modules/mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", + "engines": { + "node": ">=4" + } + }, "node_modules/minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", @@ -1074,11 +1702,34 @@ "node": ">=0.10.0" } }, + "node_modules/normalize-url": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.0.tgz", + "integrity": "sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/num-or-not": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/num-or-not/-/num-or-not-1.0.1.tgz", + "integrity": "sha1-4EM9fUab01R97ybYWVwSucr5Cko=", + "dependencies": { + "trim": "0.0.1" + } + }, + "node_modules/oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "engines": { + "node": "*" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, "dependencies": { "wrappy": "1" } @@ -1102,6 +1753,14 @@ "node": ">=0.10.0" } }, + "node_modules/p-cancelable": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.0.tgz", + "integrity": "sha512-HAZyB3ZodPo+BDpb4/Iu7Jv4P6cSazBz9ZM0ChhEXp70scx834aWCEjQRwgt41UzzejUAPdbqqONfRWTPYrPAQ==", + "engines": { + "node": ">=8" + } + }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -1171,6 +1830,11 @@ "through": "~2.3" } }, + "node_modules/performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" + }, "node_modules/picomatch": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", @@ -1212,6 +1876,47 @@ "node": ">= 0.10" } }, + "node_modules/psl": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", + "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==" + }, + "node_modules/pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "engines": { + "node": ">=6" + } + }, + "node_modules/qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -1233,6 +1938,45 @@ "node": ">=8.10.0" } }, + "node_modules/relevant-urban": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/relevant-urban/-/relevant-urban-2.0.0.tgz", + "integrity": "sha1-abvU9DpnSQjbkI4n8jCWyw8kuLk=", + "dependencies": { + "snekfetch": "^3.0.1" + } + }, + "node_modules/request": { + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", + "deprecated": "request has been deprecated, see https://github.com/request/request/issues/3142", + "dependencies": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -1248,6 +1992,19 @@ "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", "dev": true }, + "node_modules/resolve-alpn": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.0.0.tgz", + "integrity": "sha512-rTuiIEqFmGxne4IovivKSDzld2lWW9QCjqv80SYjPgf+gS35eaCAjaP54CCwGAwBtnCsvNLYtqxe1Nw+i6JEmA==" + }, + "node_modules/responselike": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.0.tgz", + "integrity": "sha512-xH48u3FTB9VsZw7R+vvgaKeLKzT6jOogbQhEe/jewwnZgzPcnyWui2Av6JpoYZF/91uueC+lqhWqeURw5/qhCw==", + "dependencies": { + "lowercase-keys": "^2.0.0" + } + }, "node_modules/restore-cursor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", @@ -1282,14 +2039,36 @@ "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + }, + "node_modules/safe-eval": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/safe-eval/-/safe-eval-0.4.1.tgz", + "integrity": "sha512-wmiu4RSYVZ690RP1+cv/LxfPK1dIlEN35aW7iv4SMYdqDrHbkll4+NJcHmKm7PbCuI1df1otOcPwgcc2iFR85g==" }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, + "node_modules/sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + }, + "node_modules/seedrandom": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.5.tgz", + "integrity": "sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==" + }, + "node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/serialize-javascript": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-5.0.1.tgz", @@ -1336,6 +2115,12 @@ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==" }, + "node_modules/snekfetch": { + "version": "3.6.4", + "resolved": "https://registry.npmjs.org/snekfetch/-/snekfetch-3.6.4.tgz", + "integrity": "sha512-NjxjITIj04Ffqid5lqr7XdgwM7X61c/Dns073Ly170bPQHLm6jkmelye/eglS++1nfTWktpP6Y2bFXjdPlQqdw==", + "deprecated": "use node-fetch instead" + }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -1373,6 +2158,35 @@ "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", "dev": true }, + "node_modules/sshpk": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", + "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", + "dependencies": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + }, + "bin": { + "sshpk-conv": "bin/sshpk-conv", + "sshpk-sign": "bin/sshpk-sign", + "sshpk-verify": "bin/sshpk-verify" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sshpk/node_modules/tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" + }, "node_modules/stream-combiner": { "version": "0.0.4", "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz", @@ -1440,6 +2254,11 @@ "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" }, + "node_modules/tiny-emitter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz", + "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==" + }, "node_modules/tmp": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", @@ -1463,6 +2282,38 @@ "node": ">=8.0" } }, + "node_modules/tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "dependencies": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/translate-google": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/translate-google/-/translate-google-1.4.3.tgz", + "integrity": "sha512-EEocI3roainbPs7qsxpbrly4zYlN6HAVLFRS/J0ha/OQG9KPW9VvKk5S+U3CBuQAtATmvTYmdOuBuvFGYMWH4A==", + "dependencies": { + "configstore": "^5.0.1", + "got": "^11.5.1", + "is-keyword-js": "^1.0.3", + "is-url": "^1.2.4", + "lodash": "^4.17.19", + "num-or-not": "^1.0.1", + "safe-eval": "^0.4.1", + "user-agents": "^1.0.559" + } + }, + "node_modules/trim": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/trim/-/trim-0.0.1.tgz", + "integrity": "sha1-WFhUf2spB1fulczMZm+1AITEYN0=" + }, "node_modules/ts-node": { "version": "9.1.1", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-9.1.1.tgz", @@ -1510,6 +2361,17 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz", "integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==" }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, "node_modules/tweetnacl": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", @@ -1523,6 +2385,22 @@ "node": ">=8" } }, + "node_modules/typed-function": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/typed-function/-/typed-function-2.0.0.tgz", + "integrity": "sha512-Hhy1Iwo/e4AtLZNK10ewVVcP2UEs408DS35ubP825w/YgSBK1KVLwALvvIG4yX75QJrxjCpcWkzkVRB0BwwYlA==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "dependencies": { + "is-typedarray": "^1.0.0" + } + }, "node_modules/typescript": { "version": "3.9.9", "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.9.tgz", @@ -1536,6 +2414,77 @@ "node": ">=4.2.0" } }, + "node_modules/underscore": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.12.1.tgz", + "integrity": "sha512-hEQt0+ZLDVUMhebKxL4x1BTtDY7bavVofhZ9KZ4aI26X9SRaE+Y3m83XUL1UP2jn8ynjndwCCpEHdUG+9pP1Tw==" + }, + "node_modules/underscore-keypath": { + "version": "0.0.22", + "resolved": "https://registry.npmjs.org/underscore-keypath/-/underscore-keypath-0.0.22.tgz", + "integrity": "sha1-SKUoOSu278QkvhyqVtpLX6zPJk0=", + "dependencies": { + "underscore": "*" + } + }, + "node_modules/unique-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", + "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", + "dependencies": { + "crypto-random-string": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/user-agents": { + "version": "1.0.606", + "resolved": "https://registry.npmjs.org/user-agents/-/user-agents-1.0.606.tgz", + "integrity": "sha512-8xjRsDuRJNYnnH82K97Z092ZPLfhpv3LLaiOvCMPGj208EXwZ9Xg3SmXV/DP8lTUTfMBuEl4KcIhrAgoZ3lkOw==", + "dependencies": { + "dot-json": "^1.2.2", + "lodash.clonedeep": "^4.5.0" + } + }, + "node_modules/uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "bin": { + "uuid": "bin/uuid" + } + }, + "node_modules/verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "engines": [ + "node >=0.6.0" + ], + "dependencies": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "node_modules/weather-js": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/weather-js/-/weather-js-2.0.0.tgz", + "integrity": "sha1-DZvNsFpPhNBrm585kAKKYB6kofw=", + "dependencies": { + "request": "2.x.x", + "xml2js": "0.4.x" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -1709,8 +2658,18 @@ "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "node_modules/write-file-atomic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "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" + } }, "node_modules/ws": { "version": "7.4.2", @@ -1720,6 +2679,34 @@ "node": ">=8.3.0" } }, + "node_modules/xdg-basedir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz", + "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==", + "engines": { + "node": ">=8" + } + }, + "node_modules/xml2js": { + "version": "0.4.23", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", + "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", + "dependencies": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", + "engines": { + "node": ">=4.0" + } + }, "node_modules/y18n": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.1.tgz", @@ -1949,11 +2936,46 @@ } } }, + "@sindresorhus/is": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.0.0.tgz", + "integrity": "sha512-FyD2meJpDPjyNQejSjvnhpgI/azsQkA4lGbuu5BQZfjvJ9cbRZXzeWL2HceCekW4lixO9JPesIIQkSoLjeJHNQ==" + }, + "@szmarczak/http-timer": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.5.tgz", + "integrity": "sha512-PyRA9sm1Yayuj5OIoJ1hGt2YISX45w9WcFbh6ddT0Z/0yaFxOtGLInr4jUfU1EAFVs0Yfyfev4RNwBlUaHdlDQ==", + "requires": { + "defer-to-connect": "^2.0.0" + } + }, + "@types/cacheable-request": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.1.tgz", + "integrity": "sha512-ykFq2zmBGOCbpIXtoVbz4SKY5QriWPh3AjyU4G74RYbtt5yOc5OfaY75ftjg7mikMOla1CTGpX3lLbuJh8DTrQ==", + "requires": { + "@types/http-cache-semantics": "*", + "@types/keyv": "*", + "@types/node": "*", + "@types/responselike": "*" + } + }, "@types/color-name": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==" }, + "@types/figlet": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@types/figlet/-/figlet-1.5.0.tgz", + "integrity": "sha512-LffgrNrwx2bs52gXSuycuMSbxwnVgNeWJAEyo8dNnDcIqNCZ13zNJeoNU2IWs62EeUORIj34zkEN7oeMrWdJ8Q==", + "dev": true + }, + "@types/http-cache-semantics": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.0.tgz", + "integrity": "sha512-c3Xy026kOF7QOTn00hbIllV1dLR9hG9NkSrLQgCVs8NF6sBU+VGWjD3wLPhmh1TYAc7ugCFsvHYMN4VcBN1U1A==" + }, "@types/inquirer": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/@types/inquirer/-/inquirer-6.5.0.tgz", @@ -1964,6 +2986,23 @@ "rxjs": "^6.4.0" } }, + "@types/keyv": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.1.tgz", + "integrity": "sha512-MPtoySlAZQ37VoLaPcTHCu1RWJ4llDkULYZIzOYxlhxBqYPB0RsRlmMU0R6tahtFe27mIdkHV+551ZWV4PLmVw==", + "requires": { + "@types/node": "*" + } + }, + "@types/mathjs": { + "version": "6.0.11", + "resolved": "https://registry.npmjs.org/@types/mathjs/-/mathjs-6.0.11.tgz", + "integrity": "sha512-q9B8ZreO41L38iTY76bCZEtAqzeRs4mNIOZpZ1sLSlcYgvgfFrnf8y8qfmas0tVWrsODjmQbQJFD6RJJJCqJbQ==", + "dev": true, + "requires": { + "decimal.js": "^10.0.0" + } + }, "@types/mocha": { "version": "8.2.0", "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-8.2.0.tgz", @@ -1979,8 +3018,15 @@ "@types/node": { "version": "14.14.20", "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.20.tgz", - "integrity": "sha512-Y93R97Ouif9JEOWPIUyU+eyIdyRqQR0I8Ez1dzku4hDx34NWh4HbtIc3WNzwB1Y9ULvNGeu5B8h8bVL5cAk4/A==", - "dev": true + "integrity": "sha512-Y93R97Ouif9JEOWPIUyU+eyIdyRqQR0I8Ez1dzku4hDx34NWh4HbtIc3WNzwB1Y9ULvNGeu5B8h8bVL5cAk4/A==" + }, + "@types/responselike": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.0.tgz", + "integrity": "sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==", + "requires": { + "@types/node": "*" + } }, "@types/through": { "version": "0.0.30", @@ -2014,6 +3060,17 @@ "event-target-shim": "^5.0.0" } }, + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "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", @@ -2067,11 +3124,34 @@ "sprintf-js": "~1.0.2" } }, + "asn1": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "requires": { + "safer-buffer": "~2.1.0" + } + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + }, "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" + }, + "aws4": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", + "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==" + }, "axios": { "version": "0.21.1", "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz", @@ -2086,6 +3166,21 @@ "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", "dev": true }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "requires": { + "tweetnacl": "^0.14.3" + }, + "dependencies": { + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" + } + } + }, "binary-extensions": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz", @@ -2123,12 +3218,36 @@ "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", "dev": true }, + "cacheable-lookup": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", + "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==" + }, + "cacheable-request": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.1.tgz", + "integrity": "sha512-lt0mJ6YAnsrBErpTMWeu5kl/tg9xMAWjavYTN6VQXM1A/teBITuNcccXsCxF0tDQQJf9DfAaX5O4e0zp0KlfZw==", + "requires": { + "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": "^4.1.0", + "responselike": "^2.0.0" + } + }, "camelcase": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", "dev": true }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" + }, "chalk": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", @@ -2223,6 +3342,14 @@ } } }, + "clone-response": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", + "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=", + "requires": { + "mimic-response": "^1.0.0" + } + }, "color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -2244,12 +3371,35 @@ "delayed-stream": "~1.0.0" } }, + "complex.js": { + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/complex.js/-/complex.js-2.0.12.tgz", + "integrity": "sha512-oQX99fwL6LrTVg82gDY1dIWXy6qZRnRL35N+YhIX0N7tSwsa0KFy6IEMHTNuCW4mP7FS7MEqZ/2I/afzYwPldw==" + }, "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 }, + "configstore": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz", + "integrity": "sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==", + "requires": { + "dot-prop": "^5.2.0", + "graceful-fs": "^4.1.2", + "make-dir": "^3.0.0", + "unique-string": "^2.0.0", + "write-file-atomic": "^3.0.0", + "xdg-basedir": "^4.0.0" + } + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, "create-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", @@ -2267,6 +3417,19 @@ "which": "^2.0.1" } }, + "crypto-random-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", + "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==" + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "requires": { + "assert-plus": "^1.0.0" + } + }, "debug": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", @@ -2290,11 +3453,41 @@ "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", "dev": true }, + "decimal.js": { + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.2.1.tgz", + "integrity": "sha512-KaL7+6Fw6i5A2XSnsbhm/6B+NuEA7TZ4vqxnd5tXz9sbKtrN9Srj8ab4vKVdK8YAqZO9P1kg45Y6YLoduPf+kw==" + }, + "decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "requires": { + "mimic-response": "^3.1.0" + }, + "dependencies": { + "mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==" + } + } + }, + "defer-to-connect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", + "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==" + }, "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" }, + "detect-indent": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.0.0.tgz", + "integrity": "sha512-oSyFlqaTHCItVRGK5RmrmjB+CmaMOW7IaNA/kdxqhoa6d17j/5ce9O9eWXmV/KEdRwqpQA+Vqe8a8Bsybu4YnA==" + }, "diff": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", @@ -2327,17 +3520,62 @@ "lavacord": "^1.1.7" } }, + "docopt": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/docopt/-/docopt-0.6.2.tgz", + "integrity": "sha1-so6eIiDaXsSffqW7JKR3h0Be6xE=" + }, + "dot-json": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/dot-json/-/dot-json-1.2.2.tgz", + "integrity": "sha512-AKL+GsO4wSEU4LU+fAk/PqN4nQ6PB1vT3HpMiZous9xCzK5S0kh4DzfUY0EfU67jsIXLlu0ty71659N9Nmg+Tw==", + "requires": { + "detect-indent": "~6.0.0", + "docopt": "~0.6.2", + "underscore-keypath": "~0.0.22" + } + }, + "dot-prop": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", + "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", + "requires": { + "is-obj": "^2.0.0" + } + }, "duplexer": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=", "dev": true }, + "ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "requires": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, "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==" }, + "end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "requires": { + "once": "^1.4.0" + } + }, + "escape-latex": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/escape-latex/-/escape-latex-1.2.0.tgz", + "integrity": "sha512-nV5aVWW1K0wEiUIEdZ4erkGGH8mDxGyxSeqPzRNtWP7ataw+/olFObw7hujFWlVjNsaDFw5VZ5NzVSIqRgfTiw==" + }, "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", @@ -2369,6 +3607,11 @@ "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==" }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, "external-editor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", @@ -2379,6 +3622,26 @@ "tmp": "^0.0.33" } }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" + }, + "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==" + }, + "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==" + }, + "figlet": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/figlet/-/figlet-1.5.0.tgz", + "integrity": "sha512-ZQJM4aifMpz6H19AW1VqvZ7l4pOE9p7i/3LyxgO2kp+PO/VcDYNqIHEMtkccqIhTXMKci4kjueJr/iCQEaT/Ww==" + }, "figures": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", @@ -2417,6 +3680,26 @@ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.1.tgz", "integrity": "sha512-SSG5xmZh1mkPGyKzjZP8zLjltIfpW32Y5QpdNJyjcfGxK3qo3NDDkZOZSFiGn1A6SclQxY9GzEwAHQ3dmYRWpg==" }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" + }, + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "fraction.js": { + "version": "4.0.13", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.0.13.tgz", + "integrity": "sha512-E1fz2Xs9ltlUp+qbiyx9wmt2n9dRzPsS11Jtdb8D2o+cC7wr9xkkKsVKJuBX0ST+LVS+LhLO+SbLJNtfWcJvXA==" + }, "from": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz", @@ -2442,6 +3725,22 @@ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true }, + "get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "requires": { + "pump": "^3.0.0" + } + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "requires": { + "assert-plus": "^1.0.0" + } + }, "glob": { "version": "7.1.6", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", @@ -2465,12 +3764,49 @@ "is-glob": "^4.0.1" } }, + "got": { + "version": "11.8.2", + "resolved": "https://registry.npmjs.org/got/-/got-11.8.2.tgz", + "integrity": "sha512-D0QywKgIe30ODs+fm8wMZiAcZjypcCodPNuMz5H9Mny7RJ+IjJ10BdmGW7OM7fHXP+O7r6ZwapQ/YQmMSvB0UQ==", + "requires": { + "@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.1", + "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" + } + }, + "graceful-fs": { + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", + "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==" + }, "growl": { "version": "1.10.5", "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", "dev": true }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" + }, + "har-validator": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", + "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", + "requires": { + "ajv": "^6.12.3", + "har-schema": "^2.0.0" + } + }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -2482,6 +3818,30 @@ "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", "dev": true }, + "http-cache-semantics": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", + "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==" + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, + "http2-wrapper": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", + "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", + "requires": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.0.0" + } + }, "iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -2490,6 +3850,11 @@ "safer-buffer": ">= 2.1.2 < 3" } }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=" + }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -2555,24 +3920,54 @@ "is-extglob": "^2.1.1" } }, + "is-keyword-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-keyword-js/-/is-keyword-js-1.0.3.tgz", + "integrity": "sha1-rDDc81tnH0snsX9ctXI1EmAhEy0=" + }, "is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true }, + "is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==" + }, "is-plain-obj": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", "dev": true }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" + }, + "is-url": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz", + "integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==" + }, "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", "dev": true }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" + }, + "javascript-natural-sort": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz", + "integrity": "sha1-+eIwPUUH9tdDVac2ZNFED7Wg71k=" + }, "js-yaml": { "version": "3.14.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz", @@ -2583,6 +3978,50 @@ "esprima": "^4.0.0" } }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" + }, + "json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==" + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" + }, + "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==" + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" + }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "keyv": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.0.3.tgz", + "integrity": "sha512-zdGa2TOpSZPq5mU6iowDARnMBZgtCqJ11dJROFi6tg6kTn4nuUdU09lFyLFSaHrWqpIJ+EBq4E8/Dc0Vx5vLdA==", + "requires": { + "json-buffer": "3.0.1" + } + }, "lavacord": { "version": "1.1.9", "resolved": "https://registry.npmjs.org/lavacord/-/lavacord-1.1.9.tgz", @@ -2606,6 +4045,11 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==" }, + "lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=" + }, "log-symbols": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.0.0.tgz", @@ -2615,6 +4059,19 @@ "chalk": "^4.0.0" } }, + "lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==" + }, + "make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "requires": { + "semver": "^6.0.0" + } + }, "make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", @@ -2627,6 +4084,21 @@ "integrity": "sha1-5WqpTEyAVaFkBKBnS3jyFffI4ZQ=", "dev": true }, + "mathjs": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/mathjs/-/mathjs-9.3.0.tgz", + "integrity": "sha512-0kYW+TXgB8lCqUj5wHR2hqAO2twSbPRelSFgRJXiwAx4nM6FrIb43Jd6XhW7sVbwYB+9HCNiyg5Kn8VYeB7ilg==", + "requires": { + "complex.js": "^2.0.11", + "decimal.js": "^10.2.1", + "escape-latex": "^1.2.0", + "fraction.js": "^4.0.13", + "javascript-natural-sort": "^0.7.1", + "seedrandom": "^3.0.5", + "tiny-emitter": "^2.1.0", + "typed-function": "^2.0.0" + } + }, "mime-db": { "version": "1.45.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.45.0.tgz", @@ -2645,6 +4117,11 @@ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==" }, + "mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==" + }, "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", @@ -2748,11 +4225,28 @@ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "dev": true }, + "normalize-url": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.0.tgz", + "integrity": "sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ==" + }, + "num-or-not": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/num-or-not/-/num-or-not-1.0.1.tgz", + "integrity": "sha1-4EM9fUab01R97ybYWVwSucr5Cko=", + "requires": { + "trim": "0.0.1" + } + }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" + }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, "requires": { "wrappy": "1" } @@ -2770,6 +4264,11 @@ "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" }, + "p-cancelable": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.0.tgz", + "integrity": "sha512-HAZyB3ZodPo+BDpb4/Iu7Jv4P6cSazBz9ZM0ChhEXp70scx834aWCEjQRwgt41UzzejUAPdbqqONfRWTPYrPAQ==" + }, "p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -2821,6 +4320,11 @@ "through": "~2.3" } }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" + }, "picomatch": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", @@ -2847,6 +4351,35 @@ "event-stream": "=3.3.4" } }, + "psl": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", + "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==" + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" + }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + }, + "quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==" + }, "randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -2865,6 +4398,41 @@ "picomatch": "^2.2.1" } }, + "relevant-urban": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/relevant-urban/-/relevant-urban-2.0.0.tgz", + "integrity": "sha1-abvU9DpnSQjbkI4n8jCWyw8kuLk=", + "requires": { + "snekfetch": "^3.0.1" + } + }, + "request": { + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + } + }, "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -2877,6 +4445,19 @@ "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", "dev": true }, + "resolve-alpn": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.0.0.tgz", + "integrity": "sha512-rTuiIEqFmGxne4IovivKSDzld2lWW9QCjqv80SYjPgf+gS35eaCAjaP54CCwGAwBtnCsvNLYtqxe1Nw+i6JEmA==" + }, + "responselike": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.0.tgz", + "integrity": "sha512-xH48u3FTB9VsZw7R+vvgaKeLKzT6jOogbQhEe/jewwnZgzPcnyWui2Av6JpoYZF/91uueC+lqhWqeURw5/qhCw==", + "requires": { + "lowercase-keys": "^2.0.0" + } + }, "restore-cursor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", @@ -2902,14 +4483,33 @@ "safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + }, + "safe-eval": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/safe-eval/-/safe-eval-0.4.1.tgz", + "integrity": "sha512-wmiu4RSYVZ690RP1+cv/LxfPK1dIlEN35aW7iv4SMYdqDrHbkll4+NJcHmKm7PbCuI1df1otOcPwgcc2iFR85g==" }, "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, + "sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + }, + "seedrandom": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.5.tgz", + "integrity": "sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==" + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + }, "serialize-javascript": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-5.0.1.tgz", @@ -2950,6 +4550,11 @@ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==" }, + "snekfetch": { + "version": "3.6.4", + "resolved": "https://registry.npmjs.org/snekfetch/-/snekfetch-3.6.4.tgz", + "integrity": "sha512-NjxjITIj04Ffqid5lqr7XdgwM7X61c/Dns073Ly170bPQHLm6jkmelye/eglS++1nfTWktpP6Y2bFXjdPlQqdw==" + }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -2981,6 +4586,29 @@ "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", "dev": true }, + "sshpk": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", + "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", + "requires": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + }, + "dependencies": { + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" + } + } + }, "stream-combiner": { "version": "0.0.4", "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz", @@ -3033,6 +4661,11 @@ "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" }, + "tiny-emitter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz", + "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==" + }, "tmp": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", @@ -3050,6 +4683,35 @@ "is-number": "^7.0.0" } }, + "tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "requires": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + } + }, + "translate-google": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/translate-google/-/translate-google-1.4.3.tgz", + "integrity": "sha512-EEocI3roainbPs7qsxpbrly4zYlN6HAVLFRS/J0ha/OQG9KPW9VvKk5S+U3CBuQAtATmvTYmdOuBuvFGYMWH4A==", + "requires": { + "configstore": "^5.0.1", + "got": "^11.5.1", + "is-keyword-js": "^1.0.3", + "is-url": "^1.2.4", + "lodash": "^4.17.19", + "num-or-not": "^1.0.1", + "safe-eval": "^0.4.1", + "user-agents": "^1.0.559" + } + }, + "trim": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/trim/-/trim-0.0.1.tgz", + "integrity": "sha1-WFhUf2spB1fulczMZm+1AITEYN0=" + }, "ts-node": { "version": "9.1.1", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-9.1.1.tgz", @@ -3082,6 +4744,14 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz", "integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==" }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "requires": { + "safe-buffer": "^5.0.1" + } + }, "tweetnacl": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", @@ -3092,12 +4762,87 @@ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.11.0.tgz", "integrity": "sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==" }, + "typed-function": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/typed-function/-/typed-function-2.0.0.tgz", + "integrity": "sha512-Hhy1Iwo/e4AtLZNK10ewVVcP2UEs408DS35ubP825w/YgSBK1KVLwALvvIG4yX75QJrxjCpcWkzkVRB0BwwYlA==" + }, + "typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "requires": { + "is-typedarray": "^1.0.0" + } + }, "typescript": { "version": "3.9.9", "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.9.tgz", "integrity": "sha512-kdMjTiekY+z/ubJCATUPlRDl39vXYiMV9iyeMuEuXZh2we6zz80uovNN2WlAxmmdE/Z/YQe+EbOEXB5RHEED3w==", "dev": true }, + "underscore": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.12.1.tgz", + "integrity": "sha512-hEQt0+ZLDVUMhebKxL4x1BTtDY7bavVofhZ9KZ4aI26X9SRaE+Y3m83XUL1UP2jn8ynjndwCCpEHdUG+9pP1Tw==" + }, + "underscore-keypath": { + "version": "0.0.22", + "resolved": "https://registry.npmjs.org/underscore-keypath/-/underscore-keypath-0.0.22.tgz", + "integrity": "sha1-SKUoOSu278QkvhyqVtpLX6zPJk0=", + "requires": { + "underscore": "*" + } + }, + "unique-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", + "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", + "requires": { + "crypto-random-string": "^2.0.0" + } + }, + "uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "requires": { + "punycode": "^2.1.0" + } + }, + "user-agents": { + "version": "1.0.606", + "resolved": "https://registry.npmjs.org/user-agents/-/user-agents-1.0.606.tgz", + "integrity": "sha512-8xjRsDuRJNYnnH82K97Z092ZPLfhpv3LLaiOvCMPGj208EXwZ9Xg3SmXV/DP8lTUTfMBuEl4KcIhrAgoZ3lkOw==", + "requires": { + "dot-json": "^1.2.2", + "lodash.clonedeep": "^4.5.0" + } + }, + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "weather-js": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/weather-js/-/weather-js-2.0.0.tgz", + "integrity": "sha1-DZvNsFpPhNBrm585kAKKYB6kofw=", + "requires": { + "request": "2.x.x", + "xml2js": "0.4.x" + } + }, "which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -3239,14 +4984,43 @@ "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "write-file-atomic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "requires": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" + } }, "ws": { "version": "7.4.2", "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.2.tgz", "integrity": "sha512-T4tewALS3+qsrpGI/8dqNMLIVdq/g/85U98HPMa6F0m6xTbvhXU6RCQLqPH3+SlomNV/LdY6RXEbBpMH6EOJnA==" }, + "xdg-basedir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz", + "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==" + }, + "xml2js": { + "version": "0.4.23", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", + "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", + "requires": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + } + }, + "xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==" + }, "y18n": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.1.tgz", diff --git a/package.json b/package.json index 49eaccf..855d84d 100644 --- a/package.json +++ b/package.json @@ -8,12 +8,19 @@ "chalk": "^4.1.0", "discord.js": "^12.5.1", "discord.js-lavalink-lib": "^0.1.8", + "figlet": "^1.5.0", "inquirer": "^7.3.3", + "mathjs": "^9.3.0", "moment": "^2.29.1", - "ms": "^2.1.3" + "ms": "^2.1.3", + "relevant-urban": "^2.0.0", + "translate-google": "^1.4.3", + "weather-js": "^2.0.0" }, "devDependencies": { + "@types/figlet": "^1.5.0", "@types/inquirer": "^6.5.0", + "@types/mathjs": "^6.0.11", "@types/mocha": "^8.2.0", "@types/ms": "^0.7.31", "@types/node": "^14.14.20", diff --git a/src/commands/fun/figlet.ts b/src/commands/fun/figlet.ts new file mode 100644 index 0000000..ded3d94 --- /dev/null +++ b/src/commands/fun/figlet.ts @@ -0,0 +1,20 @@ +import Command from "../../core/command"; +import figlet from "figlet"; + +export default new Command({ + description: "Generates a figlet of your input.", + async run($) { + const input = $.args.join(" "); + if (!$.args[0]) { + $.channel.send("You have to provide input for me to create a figlet!"); + return; + } + $.channel.send( + "```" + + figlet.textSync(`${input}`, { + horizontalLayout: "full" + }) + + "```" + ); + } +}); diff --git a/src/commands/fun/insult.ts b/src/commands/fun/insult.ts new file mode 100644 index 0000000..c2e0260 --- /dev/null +++ b/src/commands/fun/insult.ts @@ -0,0 +1,14 @@ +import Command from "../../core/command"; + +export default new Command({ + description: "Insult TravBot! >:D", + async run($) { + $.channel.startTyping(); + setTimeout(() => { + $.channel.send( + `${$.author.toString()} What the fuck did you just fucking say about me, you little bitch? I'll have you know I graduated top of my class in the Navy Seals, and I've been involved in numerous secret raids on Al-Quaeda, and I have over 300 confirmed kills. I am trained in gorilla warfare and I'm the top sniper in the entire US armed forces. You are nothing to me but just another target. I will wipe you the fuck out with precision the likes of which has never been seen before on this Earth, mark my fucking words. You think you can get away with saying that shit to me over the Internet? Think again, fucker. As we speak I am contacting my secret network of spies across the USA and your IP is being traced right now so you better prepare for the storm, maggot. The storm that wipes out the pathetic little thing you call your life. You're fucking dead, kid. I can be anywhere, anytime, and I can kill you in over seven hundred ways, and that's just with my bare hands. Not only am I extensively trained in unarmed combat, but I have access to the entire arsenal of the United States Marine Corps and I will use it to its full extent to wipe your miserable ass off the face of the continent, you little shit. If only you could have known what unholy retribution your little "clever" comment was about to bring down upon you, maybe you would have held your fucking tongue. But you couldn't, you didn't, and now you're paying the price, you goddamn idiot. I will shit fury all over you and you will drown in it. You're fucking dead, kiddo.` + ); + $.channel.stopTyping(); + }, 60000); + } +}); diff --git a/src/commands/fun/love.ts b/src/commands/fun/love.ts new file mode 100644 index 0000000..d0f9a5a --- /dev/null +++ b/src/commands/fun/love.ts @@ -0,0 +1,13 @@ +import Command from "../../core/command"; + +export default new Command({ + description: "Chooses someone to love.", + async run($) { + if ($.guild) { + const member = $.guild.members.cache.random(); + $.channel.send(`I love ${member.user.username}!`); + } else { + $.channel.send("You must use this command in a guild!"); + } + } +}); diff --git a/src/commands/fun/ravi.ts b/src/commands/fun/ravi.ts new file mode 100644 index 0000000..fbcf14e --- /dev/null +++ b/src/commands/fun/ravi.ts @@ -0,0 +1,38 @@ +import Command from "../../core/command"; +import {Random} from "../../core/lib"; + +export default new Command({ + description: "Ravioli ravioli...", + usage: "[number from 1 to 9]", + async run($) { + $.channel.send({ + embed: { + title: "Ravioli ravioli...", + image: { + url: `https://raw.githubusercontent.com/keanuplayz/TravBot/master/assets/ravi${Random.int( + 1, + 10 + )}.png` + } + } + }); + }, + number: new Command({ + async run($) { + const arg: number = $.args[0]; + + if (arg >= 1 && arg <= 9) { + $.channel.send({ + embed: { + title: "Ravioli ravioli...", + image: { + url: `https://raw.githubusercontent.com/keanuplayz/TravBot/master/assets/ravi${arg}.png` + } + } + }); + } else { + $.channel.send("Please provide a number between 1 and 9."); + } + } + }) +}); diff --git a/src/commands/fun/thonk.ts b/src/commands/fun/thonk.ts new file mode 100644 index 0000000..a09c6e4 --- /dev/null +++ b/src/commands/fun/thonk.ts @@ -0,0 +1,41 @@ +import Command from "../../core/command"; + +const letters: {[letter: string]: string[]} = { + a: "aáàảãạâấầẩẫậăắằẳẵặ".split(""), + e: "eéèẻẽẹêếềểễệ".split(""), + i: "iíìỉĩị".split(""), + o: "oóòỏõọôốồổỗộơớờởỡợ".split(""), + u: "uúùủũụưứừửữự".split(""), + y: "yýỳỷỹỵ".split(""), + d: "dđ".split("") +}; + +function transform(str: string) { + let out = ""; + + for (const c of str) { + const token = c.toLowerCase(); + const isUpperCase = token !== c; + + if (token in letters) { + const set = letters[token]; + const add = set[Math.floor(Math.random() * set.length)]; + out += isUpperCase ? add.toUpperCase() : add; + } else { + out += c; + } + } + + return out; +} + +let phrase = "I have no currently set phrase!"; + +export default new Command({ + description: "Transforms your text into vietnamese.", + usage: "thonk ([text])", + async run($) { + if ($.args.length > 0) phrase = $.args.join(" "); + $.channel.send(transform(phrase)); + } +}); diff --git a/src/commands/fun/urban.ts b/src/commands/fun/urban.ts new file mode 100644 index 0000000..1f21c2e --- /dev/null +++ b/src/commands/fun/urban.ts @@ -0,0 +1,27 @@ +import Command from "../../core/command"; +import {MessageEmbed} from "discord.js"; +// Anycasting Alert +const urban = require("relevant-urban"); + +export default new Command({ + description: "Gives you a definition of the inputted word.", + async run($) { + if (!$.args[0]) { + $.channel.send("Please input a word."); + } + const res = await urban($.args.join(" ")).catch((e: Error) => { + return $.channel.send("Sorry, that word was not found."); + }); + const embed = new MessageEmbed() + .setColor(0x1d2439) + .setTitle(res.word) + .setURL(res.urbanURL) + .setDescription(`**Definition:**\n*${res.definition}*\n\n**Example:**\n*${res.example}*`) + .addField("Author", res.author, true) + .addField("Rating", `**\`Upvotes: ${res.thumbsUp} | Downvotes: ${res.thumbsDown}\`**`); + if (res.tags.length > 0 && res.tags.join(" ").length < 1024) { + embed.addField("Tags", res.tags.join(", "), true); + } + $.channel.send(embed); + } +}); diff --git a/src/commands/fun/weather.ts b/src/commands/fun/weather.ts new file mode 100644 index 0000000..aaac509 --- /dev/null +++ b/src/commands/fun/weather.ts @@ -0,0 +1,39 @@ +import Command from "../../core/command"; +import {MessageEmbed} from "discord.js"; +// Anycasting Alert +const weather = require("weather-js"); + +export default new Command({ + description: "Shows weather info of specified location.", + async run($) { + if ($.args.length == 0) { + $.channel.send("You need to provide a city."); + return; + } + weather.find( + { + search: $.args.join(" "), + degreeType: "C" + }, + function (err: any, result: any) { + if (err) $.channel.send(err); + var current = result[0].current; + var location = result[0].location; + const embed = new MessageEmbed() + .setDescription(`**${current.skytext}**`) + .setAuthor(`Weather for ${current.observationpoint}`) + .setThumbnail(current.imageUrl) + .setColor(0x00ae86) + .addField("Timezone", `UTC${location.timezone}`, true) + .addField("Degree Type", "C", true) + .addField("Temperature", `${current.temperature} Degrees`, true) + .addField("Feels like", `${current.feelslike} Degrees`, true) + .addField("Winds", current.winddisplay, true) + .addField("Humidity", `${current.humidity}%`, true); + $.channel.send({ + embed + }); + } + ); + } +}); diff --git a/src/commands/fun/whois.ts b/src/commands/fun/whois.ts new file mode 100644 index 0000000..283610a --- /dev/null +++ b/src/commands/fun/whois.ts @@ -0,0 +1,83 @@ +import {User} from "discord.js"; +import Command from "../../core/command"; + +// Quotes must be used here or the numbers will change +const registry: {[id: string]: string} = { + "465662909645848577": "You're an idiot, that's what.", + "306499531665833984": + "Kuma, you eldritch fuck, I demand you to release me from this Discord bot and let me see my Chromebook!", + "137323711844974592": "The purple haired gunner man who makes loud noises.", + "208763015657553921": "Minzy's master.", + "229636002443034624": "The ***God*** of being Smug.", + "280876114153308161": "The best girl.", + "175823837835821067": "The somehow sentient pear.", + "145839753118351360": "The blueberry with horns.", + "173917366504259585": "A talented developer.", + "216112465321263105": "The red strawberry cat.", + "394808963356688394": "The cutest, bestest, most caring girl ever.", + "142200534781132800": "The masters of chaos.", + "186496078273708033": "The cute blue cat.", + "241293368267767808": "The cute catgirl.", + "540419616803913738": "The generically Generic hologram man.", + "157598993298227211": "The somehow sentient bowl of nachos.", + "225214401228177408": "The CMD user.", + "224619540263337984": "The guy that did 50% of the work.", + "374298111255773184": "The cutest fox around.", + "150400803503472640": "The big huggy turtle boye.", + "620777734427115523": "The small huggy turtle boye.", + "310801870048198667": "An extremely talented artist and modder.", + "328223274133880833": "The stealthiest hitman.", + "219661798742163467": "An extremely talented artist and modder.", + "440399719076855818": + "You are, uhh, Stay Put, Soft Puppy, Es-Pee, Swift Pacemaker, Smug Poyo, and many more.\n...Seriously, this woman has too many names." +}; + +export default new Command({ + description: "Tells you who you or the specified user is.", + aliases: ["whoami"], + async run($) { + const id = $.author.id; + + if (id in registry) { + $.channel.send(registry[id]); + } else { + $.channel.send("You haven't been added to the registry yet!"); + } + }, + user: new Command({ + async run($) { + const user: User = $.args[0]; + const id = user.id; + + if (id in registry) { + $.channel.send(`\`${user.username}\` - ${registry[id]}`); + } else { + $.channel.send(`\`${user.username}#${user.discriminator}\` hasn't been added to the registry yet!`); + } + } + }), + any: new Command({ + async run($) { + if ($.guild) { + const query: string = $.args.join(" "); + const member = await $.getMemberByUsername($.guild, query); + + if (member && member.id in registry) { + const id = member.id; + + if (id in registry) { + $.channel.send(`\`${member.user.username}\` - ${registry[member.id]}`); + } else { + $.channel.send(`\`${member.user.username}\` hasn't been added to the registry yet!`); + } + } else { + $.channel.send(`Couldn't find a user by the name of \`${query}\`!`); + } + } else { + $.channel.send( + "You must run this in a guild! (*If you have the user's ID, you don't have to be in a guild.*)" + ); + } + } + }) +}); diff --git a/src/commands/utilities/calc.ts b/src/commands/utilities/calc.ts new file mode 100644 index 0000000..a3fe45e --- /dev/null +++ b/src/commands/utilities/calc.ts @@ -0,0 +1,26 @@ +import Command from "../../core/command"; +import * as math from "mathjs"; +import {MessageEmbed} from "discord.js"; + +export default new Command({ + description: "Calculates a specified math expression.", + async run($) { + if (!$.args[0]) { + $.channel.send("Please provide a calculation."); + return; + } + let resp; + try { + resp = math.evaluate($.args.join(" ")); + } catch (e) { + $.channel.send("Please provide a *valid* calculation."); + return; + } + const embed = new MessageEmbed() + .setColor(0xffffff) + .setTitle("Math Calculation") + .addField("Input", `\`\`\`js\n${$.args.join("")}\`\`\``) + .addField("Output", `\`\`\`js\n${resp}\`\`\``); + $.channel.send(embed); + } +}); diff --git a/src/commands/utilities/code.ts b/src/commands/utilities/code.ts new file mode 100644 index 0000000..b473b57 --- /dev/null +++ b/src/commands/utilities/code.ts @@ -0,0 +1,6 @@ +import Command from "../../core/command"; + +export default new Command({ + description: "Gives you the Github link.", + run: "https://github.com/keanuplayz/TravBot-v3" +}); diff --git a/src/commands/utilities/invite.ts b/src/commands/utilities/invite.ts new file mode 100644 index 0000000..7f1fc94 --- /dev/null +++ b/src/commands/utilities/invite.ts @@ -0,0 +1,12 @@ +import Command from "../../core/command"; + +export default new Command({ + description: "Gives you the invite link.", + async run($) { + $.channel.send( + `https://discordapp.com/api/oauth2/authorize?client_id=${$.client.user!.id}&permissions=${ + $.args[0] || 8 + }&scope=bot` + ); + } +}); diff --git a/src/commands/utilities/todo.ts b/src/commands/utilities/todo.ts new file mode 100644 index 0000000..1f6c639 --- /dev/null +++ b/src/commands/utilities/todo.ts @@ -0,0 +1,62 @@ +import Command from "../../core/command"; +import moment from "moment"; +import {Storage} from "../../core/structures"; +import {MessageEmbed} from "discord.js"; + +export default new Command({ + description: "Keep and edit your personal todo list.", + async run($) { + const user = Storage.getUser($.author.id); + const embed = new MessageEmbed().setTitle(`Todo list for ${$.author.tag}`).setColor("BLUE"); + + for (const timestamp in user.todoList) { + const date = new Date(Number(timestamp)); + embed.addField( + `${moment(date).format("LT")} ${moment(date).format("LL")} (${moment(date).fromNow()})`, + user.todoList[timestamp] + ); + } + + $.channel.send(embed); + }, + subcommands: { + add: new Command({ + async run($) { + const user = Storage.getUser($.author.id); + const note = $.args.join(" "); + user.todoList[Date.now().toString()] = note; + console.debug(user.todoList); + Storage.save(); + $.channel.send(`Successfully added \`${note}\` to your todo list.`); + } + }), + remove: new Command({ + async run($) { + const user = Storage.getUser($.author.id); + const note = $.args.join(" "); + let isFound = false; + + for (const timestamp in user.todoList) { + const selectedNote = user.todoList[timestamp]; + + if (selectedNote === note) { + delete user.todoList[timestamp]; + Storage.save(); + isFound = true; + $.channel.send(`Removed \`${note}\` from your todo list.`); + } + } + + if (!isFound) $.channel.send("That item couldn't be found."); + } + }), + clear: new Command({ + async run($) { + const user = Storage.getUser($.author.id); + user.todoList = {}; + Storage.save(); + $.channel.send("Cleared todo list."); + } + }) + } +}); diff --git a/src/commands/utilities/translate.ts b/src/commands/utilities/translate.ts new file mode 100644 index 0000000..2e56087 --- /dev/null +++ b/src/commands/utilities/translate.ts @@ -0,0 +1,38 @@ +import Command from "../../core/command"; +// Anycasting Alert +const translate = require("translate-google"); + +export default new Command({ + description: "Translates your input.", + usage: " ", + async run($) { + const lang = $.args[0]; + const input = $.args.slice(1).join(" "); + translate(input, { + to: lang + }) + .then((res: any) => { + $.channel.send({ + embed: { + title: "Translation", + fields: [ + { + name: "Input", + value: `\`\`\`${input}\`\`\`` + }, + { + name: "Output", + value: `\`\`\`${res}\`\`\`` + } + ] + } + }); + }) + .catch((err: any) => { + console.error(err); + $.channel.send( + `${err}\nPlease use the following list: https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes` + ); + }); + } +}); diff --git a/src/core/structures.ts b/src/core/structures.ts index 37b2194..ba01e1f 100644 --- a/src/core/structures.ts +++ b/src/core/structures.ts @@ -26,6 +26,7 @@ class User { public lastMonday: number; public timezone: number | null; // This is for the standard timezone only, not the daylight savings timezone public daylightSavingsRegion: "na" | "eu" | "sh" | null; + public todoList: {[timestamp: string]: string}; constructor(data?: GenericJSON) { this.money = select(data?.money, 0, Number); @@ -35,6 +36,16 @@ class User { this.daylightSavingsRegion = /^((na)|(eu)|(sh))$/.test(data?.daylightSavingsRegion) ? data?.daylightSavingsRegion : null; + this.todoList = {}; + + if (data) { + for (const timestamp in data.todoList) { + const note = data.todoList[timestamp]; + if (typeof note === "string") { + this.todoList[timestamp] = note; + } + } + } } } From 3ef487c4a4d7eb1227091923c4399e004e409cdb Mon Sep 17 00:00:00 2001 From: WatDuhHekBro <44940783+WatDuhHekBro@users.noreply.github.com> Date: Tue, 30 Mar 2021 21:19:04 -0500 Subject: [PATCH 117/178] Reorganized lib/libd functions and Lavalink --- src/commands/admin.ts | 4 +- src/commands/fun/cookie.ts | 2 +- src/commands/fun/neko.ts | 2 +- src/commands/info.ts | 3 +- src/core/command.ts | 2 +- src/core/lib.ts | 165 +++++++++++++++++++++++++++++++++++++ src/core/libd.ts | 164 ------------------------------------ src/core/structures.ts | 2 +- src/index.ts | 56 +------------ src/modules/lavalink.ts | 53 ++++++++++++ 10 files changed, 230 insertions(+), 223 deletions(-) create mode 100644 src/modules/lavalink.ts diff --git a/src/commands/admin.ts b/src/commands/admin.ts index e0c4362..72c5686 100644 --- a/src/commands/admin.ts +++ b/src/commands/admin.ts @@ -1,9 +1,9 @@ import Command, {handler} from "../core/command"; -import {botHasPermission, clean} from "../core/libd"; +import {clean} from "../core/lib"; +import {botHasPermission} from "../core/libd"; import {Config, Storage} from "../core/structures"; import {getPermissionLevel, getPermissionName} from "../core/permissions"; import {Permissions} from "discord.js"; -import * as discord from "discord.js"; import {logs} from "../globals"; function getLogBuffer(type: string) { diff --git a/src/commands/fun/cookie.ts b/src/commands/fun/cookie.ts index 39a3948..06c7086 100644 --- a/src/commands/fun/cookie.ts +++ b/src/commands/fun/cookie.ts @@ -1,7 +1,7 @@ import {User} from "discord.js"; import Command from "../../core/command"; import {random} from "../../core/lib"; -import {parseVars} from "../../core/libd"; +import {parseVars} from "../../core/lib"; const cookies = [ `has given %target% a chocolate chip cookie!`, diff --git a/src/commands/fun/neko.ts b/src/commands/fun/neko.ts index 1990bc2..113fb15 100644 --- a/src/commands/fun/neko.ts +++ b/src/commands/fun/neko.ts @@ -1,6 +1,6 @@ import {URL} from "url"; import Command from "../../core/command"; -import {getContent} from "../../core/libd"; +import {getContent} from "../../core/lib"; const endpoints: {sfw: {[key: string]: string}} = { sfw: { diff --git a/src/commands/info.ts b/src/commands/info.ts index 7142ba5..b95f19a 100644 --- a/src/commands/info.ts +++ b/src/commands/info.ts @@ -2,7 +2,8 @@ import {MessageEmbed, version as djsversion} from "discord.js"; import ms from "ms"; import os from "os"; import Command from "../core/command"; -import {formatBytes, trimArray, getMemberByUsername} from "../core/libd"; +import {formatBytes, trimArray} from "../core/lib"; +import {getMemberByUsername} from "../core/libd"; import {verificationLevels, filterLevels, regions} from "../defs/info"; import moment from "moment"; import utc from "moment"; diff --git a/src/core/command.ts b/src/core/command.ts index c40d5a0..c13b3a3 100644 --- a/src/core/command.ts +++ b/src/core/command.ts @@ -1,4 +1,4 @@ -import {parseVars} from "./libd"; +import {parseVars} from "./lib"; import {Collection} from "discord.js"; import {Client, Message, TextChannel, DMChannel, NewsChannel, Guild, User, GuildMember} from "discord.js"; import {getPrefix} from "../core/structures"; diff --git a/src/core/lib.ts b/src/core/lib.ts index a0ebcb7..21b4a95 100644 --- a/src/core/lib.ts +++ b/src/core/lib.ts @@ -1,4 +1,169 @@ // Library for pure functions +import {get} from "https"; +import FileManager from "./storage"; + +/** + * Splits a command by spaces while accounting for quotes which capture string arguments. + * - `\"` = `"` + * - `\\` = `\` + */ +export function parseArgs(line: string): string[] { + let result = []; + let selection = ""; + let inString = false; + let isEscaped = false; + + for (let c of line) { + if (isEscaped) { + if (['"', "\\"].includes(c)) selection += c; + else selection += "\\" + c; + + isEscaped = false; + } else if (c === "\\") isEscaped = true; + else if (c === '"') inString = !inString; + else if (c === " " && !inString) { + result.push(selection); + selection = ""; + } else selection += c; + } + + if (selection.length > 0) result.push(selection); + + return result; +} + +/** + * Allows you to store a template string with variable markers and parse it later. + * - Use `%name%` for variables + * - `%%` = `%` + * - If the invalid token is null/undefined, nothing is changed. + */ +export function parseVars(line: string, definitions: {[key: string]: string}, invalid: string | null = ""): string { + let result = ""; + let inVariable = false; + let token = ""; + + for (const c of line) { + if (c === "%") { + if (inVariable) { + if (token === "") result += "%"; + else { + if (token in definitions) result += definitions[token]; + else if (invalid === null) result += `%${token}%`; + else result += invalid; + + token = ""; + } + } + + inVariable = !inVariable; + } else if (inVariable) token += c; + else result += c; + } + + return result; +} + +export function isType(value: any, type: any): boolean { + if (value === undefined && type === undefined) return true; + else if (value === null && type === null) return true; + else return value !== undefined && value !== null && value.constructor === type; +} + +/** + * Checks a value to see if it matches the fallback's type, otherwise returns the fallback. + * For the purposes of the templates system, this function will only check array types, objects should be checked under their own type (as you'd do anyway with something like a User object). + * If at any point the value doesn't match the data structure provided, the fallback is returned. + * Warning: Type checking is based on the fallback's type. Be sure that the "type" parameter is accurate to this! + */ +export function select(value: any, fallback: T, type: Function, isArray = false): T { + if (isArray && isType(value, Array)) { + for (let item of value) if (!isType(item, type)) return fallback; + return value; + } else { + if (isType(value, type)) return value; + else return fallback; + } +} + +export function clean(text: any) { + if (typeof text === "string") + return text.replace(/`/g, "`" + String.fromCharCode(8203)).replace(/@/g, "@" + String.fromCharCode(8203)); + else return text; +} + +export function trimArray(arr: any, maxLen = 10) { + if (arr.length > maxLen) { + const len = arr.length - maxLen; + arr = arr.slice(0, maxLen); + arr.push(`${len} more...`); + } + return arr; +} + +export function formatBytes(bytes: any) { + if (bytes === 0) return "0 Bytes"; + const sizes = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]; + const i = Math.floor(Math.log(bytes) / Math.log(1024)); + return `${parseFloat((bytes / Math.pow(1024, i)).toFixed(2))} ${sizes[i]}`; +} + +export function getContent(url: string): Promise<{url: string}> { + return new Promise((resolve, reject) => { + get(url, (res) => { + const {statusCode} = res; + if (statusCode !== 200) { + res.resume(); + reject(`Request failed. Status code: ${statusCode}`); + } + res.setEncoding("utf8"); + let rawData = ""; + res.on("data", (chunk: string) => { + rawData += chunk; + }); + res.on("end", () => { + try { + const parsedData = JSON.parse(rawData); + resolve(parsedData); + } catch (e) { + reject(`Error: ${e.message}`); + } + }); + }).on("error", (err: {message: any}) => { + reject(`Error: ${err.message}`); + }); + }); +} + +export interface GenericJSON { + [key: string]: any; +} + +export abstract class GenericStructure { + private __meta__ = "generic"; + + constructor(tag?: string) { + this.__meta__ = tag || this.__meta__; + } + + public save(asynchronous = true) { + const tag = this.__meta__; + /// @ts-ignore + delete this.__meta__; + FileManager.write(tag, this, asynchronous); + this.__meta__ = tag; + } +} + +// A 50% chance would be "Math.random() < 0.5" because Math.random() can be [0, 1), so to make two equal ranges, you'd need [0, 0.5)U[0.5, 1). +// Similar logic would follow for any other percentage. Math.random() < 1 is always true (100% chance) and Math.random() < 0 is always false (0% chance). +export const Random = { + num: (min: number, max: number) => Math.random() * (max - min) + min, + int: (min: number, max: number) => Math.floor(Random.num(min, max)), + chance: (decimal: number) => Math.random() < decimal, + sign: (number = 1) => number * (Random.chance(0.5) ? -1 : 1), + deviation: (base: number, deviation: number) => Random.num(base - deviation, base + deviation) +}; /** * Pluralises a word and chooses a suffix attached to the root provided. diff --git a/src/core/libd.ts b/src/core/libd.ts index 20a1ab1..ab391cf 100644 --- a/src/core/libd.ts +++ b/src/core/libd.ts @@ -9,7 +9,6 @@ import { NewsChannel, MessageOptions } from "discord.js"; -import {get} from "https"; import FileManager from "./storage"; import {eventListeners} from "../events/messageReactionRemove"; import {client} from "../index"; @@ -274,166 +273,3 @@ export async function callMemberByUsername( else send(`Couldn't find a user by the name of \`${username}\`!`); } else send("You must execute this command in a server!"); } - -/** - * Splits a command by spaces while accounting for quotes which capture string arguments. - * - `\"` = `"` - * - `\\` = `\` - */ -export function parseArgs(line: string): string[] { - let result = []; - let selection = ""; - let inString = false; - let isEscaped = false; - - for (let c of line) { - if (isEscaped) { - if (['"', "\\"].includes(c)) selection += c; - else selection += "\\" + c; - - isEscaped = false; - } else if (c === "\\") isEscaped = true; - else if (c === '"') inString = !inString; - else if (c === " " && !inString) { - result.push(selection); - selection = ""; - } else selection += c; - } - - if (selection.length > 0) result.push(selection); - - return result; -} - -/** - * Allows you to store a template string with variable markers and parse it later. - * - Use `%name%` for variables - * - `%%` = `%` - * - If the invalid token is null/undefined, nothing is changed. - */ -export function parseVars(line: string, definitions: {[key: string]: string}, invalid: string | null = ""): string { - let result = ""; - let inVariable = false; - let token = ""; - - for (const c of line) { - if (c === "%") { - if (inVariable) { - if (token === "") result += "%"; - else { - if (token in definitions) result += definitions[token]; - else if (invalid === null) result += `%${token}%`; - else result += invalid; - - token = ""; - } - } - - inVariable = !inVariable; - } else if (inVariable) token += c; - else result += c; - } - - return result; -} - -export function isType(value: any, type: any): boolean { - if (value === undefined && type === undefined) return true; - else if (value === null && type === null) return true; - else return value !== undefined && value !== null && value.constructor === type; -} - -/** - * Checks a value to see if it matches the fallback's type, otherwise returns the fallback. - * For the purposes of the templates system, this function will only check array types, objects should be checked under their own type (as you'd do anyway with something like a User object). - * If at any point the value doesn't match the data structure provided, the fallback is returned. - * Warning: Type checking is based on the fallback's type. Be sure that the "type" parameter is accurate to this! - */ -export function select(value: any, fallback: T, type: Function, isArray = false): T { - if (isArray && isType(value, Array)) { - for (let item of value) if (!isType(item, type)) return fallback; - return value; - } else { - if (isType(value, type)) return value; - else return fallback; - } -} - -export function clean(text: any) { - if (typeof text === "string") - return text.replace(/`/g, "`" + String.fromCharCode(8203)).replace(/@/g, "@" + String.fromCharCode(8203)); - else return text; -} - -export function trimArray(arr: any, maxLen = 10) { - if (arr.length > maxLen) { - const len = arr.length - maxLen; - arr = arr.slice(0, maxLen); - arr.push(`${len} more...`); - } - return arr; -} - -export function formatBytes(bytes: any) { - if (bytes === 0) return "0 Bytes"; - const sizes = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]; - const i = Math.floor(Math.log(bytes) / Math.log(1024)); - return `${parseFloat((bytes / Math.pow(1024, i)).toFixed(2))} ${sizes[i]}`; -} - -export function getContent(url: string): Promise<{url: string}> { - return new Promise((resolve, reject) => { - get(url, (res) => { - const {statusCode} = res; - if (statusCode !== 200) { - res.resume(); - reject(`Request failed. Status code: ${statusCode}`); - } - res.setEncoding("utf8"); - let rawData = ""; - res.on("data", (chunk: string) => { - rawData += chunk; - }); - res.on("end", () => { - try { - const parsedData = JSON.parse(rawData); - resolve(parsedData); - } catch (e) { - reject(`Error: ${e.message}`); - } - }); - }).on("error", (err: {message: any}) => { - reject(`Error: ${err.message}`); - }); - }); -} - -export interface GenericJSON { - [key: string]: any; -} - -export abstract class GenericStructure { - private __meta__ = "generic"; - - constructor(tag?: string) { - this.__meta__ = tag || this.__meta__; - } - - public save(asynchronous = true) { - const tag = this.__meta__; - /// @ts-ignore - delete this.__meta__; - FileManager.write(tag, this, asynchronous); - this.__meta__ = tag; - } -} - -// A 50% chance would be "Math.random() < 0.5" because Math.random() can be [0, 1), so to make two equal ranges, you'd need [0, 0.5)U[0.5, 1). -// Similar logic would follow for any other percentage. Math.random() < 1 is always true (100% chance) and Math.random() < 0 is always false (0% chance). -export const Random = { - num: (min: number, max: number) => Math.random() * (max - min) + min, - int: (min: number, max: number) => Math.floor(Random.num(min, max)), - chance: (decimal: number) => Math.random() < decimal, - sign: (number = 1) => number * (Random.chance(0.5) ? -1 : 1), - deviation: (base: number, deviation: number) => Random.num(base - deviation, base + deviation) -}; diff --git a/src/core/structures.ts b/src/core/structures.ts index 07d8f5c..74e5adf 100644 --- a/src/core/structures.ts +++ b/src/core/structures.ts @@ -1,5 +1,5 @@ import FileManager from "./storage"; -import {select, GenericJSON, GenericStructure} from "./libd"; +import {select, GenericJSON, GenericStructure} from "./lib"; import {watch} from "fs"; import {Guild as DiscordGuild, Snowflake} from "discord.js"; diff --git a/src/index.ts b/src/index.ts index c6320a8..2ccf92c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,62 +1,14 @@ import "./globals"; -import * as discord from "discord.js"; +import {Client} from "discord.js"; import setup from "./setup"; import {Config} from "./core/structures"; import {loadEvents} from "./core/event"; -import "discord.js-lavalink-lib"; -import LavalinkMusic from "discord.js-lavalink-lib"; - -declare module "discord.js" { - interface Presence { - patch(data: any): void; - } -} - -// The terrible hacks were written by none other than The Noble Programmer On The White PC. - -// NOTE: Terrible hack ahead!!! In order to reduce the memory usage of the bot -// we only store the information from presences that we actually end up using, -// which currently is only the (online/idle/dnd/offline/...) status (see -// `src/commands/info.ts`). What data is retrieved from the `data` object -// (which contains the data received from the Gateway) and how can be seen -// here: -// . -const oldPresencePatch = discord.Presence.prototype.patch; -discord.Presence.prototype.patch = function patch(data: any) { - oldPresencePatch.call(this, {status: data.status}); -}; +import {attachToClient} from "./modules/lavalink"; // This is here in order to make it much less of a headache to access the client from other files. // This of course won't actually do anything until the setup process is complete and it logs in. -export const client = new discord.Client(); - -// NOTE: Terrible hack continued!!! Unfortunately we can't receive the presence -// data at all when the GUILD_PRESENCES intent is disabled, so while we do -// waste network bandwidth and the CPU time for decoding the incoming packets, -// the function which handles those packets is NOP-ed out, which, among other -// things, skips the code which caches the referenced users in the packet. See -// . -(client["actions"] as any)["PresenceUpdate"].handle = () => {}; - -(client as any).music = LavalinkMusic(client, { - lavalink: { - restnode: { - host: "localhost", - port: 2333, - password: "youshallnotpass" - }, - nodes: [ - { - host: "localhost", - port: 2333, - password: "youshallnotpass" - } - ] - }, - prefix: Config.prefix, - helpCmd: "mhelp", - admins: ["717352467280691331"] -}); +export const client = new Client(); +attachToClient(client); // Command loading will start as soon as an instance of "core/command" is loaded, which is loaded during "events/message". setup.init().then(() => { diff --git a/src/modules/lavalink.ts b/src/modules/lavalink.ts new file mode 100644 index 0000000..5e0ff90 --- /dev/null +++ b/src/modules/lavalink.ts @@ -0,0 +1,53 @@ +import {Presence, Client} from "discord.js"; +import LavalinkMusic from "discord.js-lavalink-lib"; +import {Config} from "../core/structures"; + +declare module "discord.js" { + interface Presence { + patch(data: any): void; + } +} + +// The terrible hacks were written by none other than The Noble Programmer On The White PC. + +// NOTE: Terrible hack ahead!!! In order to reduce the memory usage of the bot +// we only store the information from presences that we actually end up using, +// which currently is only the (online/idle/dnd/offline/...) status (see +// `src/commands/info.ts`). What data is retrieved from the `data` object +// (which contains the data received from the Gateway) and how can be seen +// here: +// . +const oldPresencePatch = Presence.prototype.patch; +Presence.prototype.patch = function patch(data: any) { + oldPresencePatch.call(this, {status: data.status}); +}; + +// NOTE: Terrible hack continued!!! Unfortunately we can't receive the presence +// data at all when the GUILD_PRESENCES intent is disabled, so while we do +// waste network bandwidth and the CPU time for decoding the incoming packets, +// the function which handles those packets is NOP-ed out, which, among other +// things, skips the code which caches the referenced users in the packet. See +// . +export function attachToClient(client: Client) { + (client["actions"] as any)["PresenceUpdate"].handle = () => {}; + + (client as any).music = LavalinkMusic(client, { + lavalink: { + restnode: { + host: "localhost", + port: 2333, + password: "youshallnotpass" + }, + nodes: [ + { + host: "localhost", + port: 2333, + password: "youshallnotpass" + } + ] + }, + prefix: Config.prefix, + helpCmd: "mhelp", + admins: ["717352467280691331"] + }); +} From 945102b7cf54ce67f1c5abaf82664884444aeb01 Mon Sep 17 00:00:00 2001 From: WatDuhHekBro <44940783+WatDuhHekBro@users.noreply.github.com> Date: Tue, 30 Mar 2021 21:56:25 -0500 Subject: [PATCH 118/178] Reworked event loading --- .gitignore | 3 +- src/core/event.ts | 38 ------- src/core/handler.ts | 158 ++++++++++++++++++++++++++++ src/core/libd.ts | 37 +++---- src/events/channelCreate.ts | 13 --- src/events/channelDelete.ts | 13 --- src/events/emojiCreate.ts | 9 -- src/events/emojiDelete.ts | 9 -- src/events/emojiUpdate.ts | 9 -- src/events/guildCreate.ts | 9 -- src/events/guildDelete.ts | 9 -- src/events/message.ts | 149 -------------------------- src/events/messageReactionRemove.ts | 18 ---- src/events/ready.ts | 17 --- src/index.ts | 13 ++- src/modules/channelListener.ts | 20 ++++ src/modules/emoteRegistry.ts | 53 ++++++++++ src/modules/lavalink.ts | 39 ++++--- 18 files changed, 272 insertions(+), 344 deletions(-) delete mode 100644 src/core/event.ts create mode 100644 src/core/handler.ts delete mode 100644 src/events/channelCreate.ts delete mode 100644 src/events/channelDelete.ts delete mode 100644 src/events/emojiCreate.ts delete mode 100644 src/events/emojiDelete.ts delete mode 100644 src/events/emojiUpdate.ts delete mode 100644 src/events/guildCreate.ts delete mode 100644 src/events/guildDelete.ts delete mode 100644 src/events/message.ts delete mode 100644 src/events/messageReactionRemove.ts delete mode 100644 src/events/ready.ts create mode 100644 src/modules/channelListener.ts create mode 100644 src/modules/emoteRegistry.ts diff --git a/.gitignore b/.gitignore index a3becd9..109f5cd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,6 @@ # Specific to this repository dist/ -data/* -!data/endpoints.json +data/ tmp/ test* !test/ diff --git a/src/core/event.ts b/src/core/event.ts deleted file mode 100644 index d2b6f9a..0000000 --- a/src/core/event.ts +++ /dev/null @@ -1,38 +0,0 @@ -import {Client, ClientEvents, Constants} from "discord.js"; -import Storage from "./storage"; - -interface EventOptions { - readonly on?: (...args: ClientEvents[K]) => void; - readonly once?: (...args: ClientEvents[K]) => void; -} - -export default class Event { - private readonly on?: (...args: ClientEvents[K]) => void; - private readonly once?: (...args: ClientEvents[K]) => void; - - constructor(options: EventOptions) { - this.on = options.on; - this.once = options.once; - } - - // For this function, I'm going to assume that the event is used with the correct arguments and that the event tag is checked in "storage.ts". - public attach(client: Client, event: K) { - if (this.on) client.on(event, this.on); - if (this.once) client.once(event, this.once); - } -} - -export async function loadEvents(client: Client) { - for (const file of Storage.open("dist/events", (filename: string) => filename.endsWith(".js"))) { - const header = file.substring(0, file.indexOf(".js")); - const event = (await import(`../events/${header}`)).default; - - if ((Object.values(Constants.Events) as string[]).includes(header)) { - event.attach(client, header); - console.log(`Loading Event: ${header}`); - } else - console.warn( - `"${header}" is not a valid event type! Did you misspell it? (Note: If you fixed the issue, delete "dist" because the compiler won't automatically delete any extra files.)` - ); - } -} diff --git a/src/core/handler.ts b/src/core/handler.ts new file mode 100644 index 0000000..1de6c1e --- /dev/null +++ b/src/core/handler.ts @@ -0,0 +1,158 @@ +import {client} from "../index"; +import Command, {loadableCommands} from "../core/command"; +import {hasPermission, getPermissionLevel, getPermissionName} from "../core/permissions"; +import {Permissions} from "discord.js"; +import {getPrefix} from "../core/structures"; +import {replyEventListeners} from "../core/libd"; +import quote from "../modules/message_embed"; +import {Config} from "../core/structures"; + +client.on("message", async (message) => { + const commands = await loadableCommands; + + if (message.content.toLowerCase().includes("remember to drink water")) { + message.react("🚱"); + } + + // Message Setup // + if (message.author.bot) return; + + // If there's an inline reply, fire off that event listener (if it exists). + if (message.reference) { + const reference = message.reference; + replyEventListeners.get(`${reference.channelID}-${reference.messageID}`)?.(message); + } + + let prefix = getPrefix(message.guild); + const originalPrefix = prefix; + let exitEarly = !message.content.startsWith(prefix); + const clientUser = message.client.user; + let usesBotSpecificPrefix = false; + + if (!message.content.startsWith(prefix)) { + return quote(message); + } + + // If the client user exists, check if it starts with the bot-specific prefix. + if (clientUser) { + // If the prefix starts with the bot-specific prefix, go off that instead (these two options must mutually exclude each other). + // The pattern here has an optional space at the end to capture that and make it not mess with the header and args. + const matches = message.content.match(new RegExp(`^<@!?${clientUser.id}> ?`)); + + if (matches) { + prefix = matches[0]; + exitEarly = false; + usesBotSpecificPrefix = true; + } + } + + // If it doesn't start with the current normal prefix or the bot-specific unique prefix, exit the thread of execution early. + // Inline replies should still be captured here because if it doesn't exit early, two characters for a two-length prefix would still trigger commands. + if (exitEarly) return; + + const [header, ...args] = message.content.substring(prefix.length).split(/ +/); + + // If the message is just the prefix itself, move onto this block. + if (header === "" && args.length === 0) { + // I moved the bot-specific prefix to a separate conditional block to separate the logic. + // And because it listens for the mention as a prefix instead of a free-form mention, inline replies (probably) shouldn't ever trigger this unintentionally. + if (usesBotSpecificPrefix) { + message.channel.send(`${message.author.toString()}, my prefix on this guild is \`${originalPrefix}\`.`); + return; + } + } + + if (!commands.has(header)) return; + + if ( + message.channel.type === "text" && + !message.channel.permissionsFor(message.client.user || "")?.has(Permissions.FLAGS.SEND_MESSAGES) + ) { + let status; + + if (message.member?.hasPermission(Permissions.FLAGS.ADMINISTRATOR)) + status = + "Because you're a server admin, you have the ability to change that channel's permissions to match if that's what you intended."; + else + status = + "Try using a different channel or contacting a server admin to change permissions of that channel if you think something's wrong."; + + return message.author.send( + `I don't have permission to send messages in ${message.channel.toString()}. ${status}` + ); + } + + console.log( + `${message.author.username}#${message.author.discriminator} executed the command "${header}" with arguments "${args}".` + ); + + // Subcommand Recursion // + let command = commands.get(header); + if (!command) return console.warn(`Command "${header}" was called but for some reason it's still undefined!`); + const params: any[] = []; + let isEndpoint = false; + let permLevel = command.permission ?? 0; + + for (let param of args) { + if (command.endpoint) { + if (command.subcommands.size > 0 || command.user || command.number || command.any) + console.warn(`An endpoint cannot have subcommands! Check ${originalPrefix}${header} again.`); + isEndpoint = true; + break; + } + + const type = command.resolve(param); + command = command.get(param); + permLevel = command.permission ?? permLevel; + + if (type === Command.TYPES.USER) { + const id = param.match(/\d+/g)![0]; + try { + params.push(await message.client.users.fetch(id)); + } catch (error) { + return message.channel.send(`No user found by the ID \`${id}\`!`); + } + } else if (type === Command.TYPES.NUMBER) params.push(Number(param)); + else if (type !== Command.TYPES.SUBCOMMAND) params.push(param); + } + + if (!message.member) + return console.warn("This command was likely called from a DM channel meaning the member object is null."); + + if (!hasPermission(message.member, permLevel)) { + const userPermLevel = getPermissionLevel(message.member); + return message.channel.send( + `You don't have access to this command! Your permission level is \`${getPermissionName( + userPermLevel + )}\` (${userPermLevel}), but this command requires a permission level of \`${getPermissionName( + permLevel + )}\` (${permLevel}).` + ); + } + + if (isEndpoint) return message.channel.send("Too many arguments!"); + + // Execute with dynamic library attached. // + // The purpose of using $.bind($) is to clone the function so as to not modify the original $. + // The cloned function doesn't copy the properties, so Object.assign() is used. + // Object.assign() modifies the first element and returns that, the second element applies its properties and the third element applies its own overriding the second one. + command.execute({ + args: params, + author: message.author, + channel: message.channel, + client: message.client, + guild: message.guild, + member: message.member, + message: message + }); +}); + +client.once("ready", () => { + if (client.user) { + console.ready(`Logged in as ${client.user.username}#${client.user.discriminator}.`); + client.user.setActivity({ + type: "LISTENING", + name: `${Config.prefix}help` + }); + } +}); diff --git a/src/core/libd.ts b/src/core/libd.ts index ab391cf..71ba70a 100644 --- a/src/core/libd.ts +++ b/src/core/libd.ts @@ -9,36 +9,25 @@ import { NewsChannel, MessageOptions } from "discord.js"; -import FileManager from "./storage"; -import {eventListeners} from "../events/messageReactionRemove"; import {client} from "../index"; -import {EmoteRegistryDump} from "./structures"; + +// A list of message ID and callback pairs. You get the emote name and ID of the user reacting. +const eventListeners: Map void> = new Map(); + +// Attached to the client, there can be one event listener attached to a message ID which is executed if present. +client.on("messageReactionRemove", (reaction, user) => { + const canDeleteEmotes = botHasPermission(reaction.message.guild, Permissions.FLAGS.MANAGE_MESSAGES); + + if (!canDeleteEmotes) { + const callback = eventListeners.get(reaction.message.id); + callback && callback(reaction.emoji.name, user.id); + } +}); export function botHasPermission(guild: Guild | null, permission: number): boolean { return !!guild?.me?.hasPermission(permission); } -export function updateGlobalEmoteRegistry(): void { - const data: EmoteRegistryDump = {version: 1, list: []}; - - for (const guild of client.guilds.cache.values()) { - for (const emote of guild.emojis.cache.values()) { - data.list.push({ - ref: emote.name, - id: emote.id, - name: emote.name, - requires_colons: emote.requiresColons || false, - animated: emote.animated, - url: emote.url, - guild_id: emote.guild.name, - guild_name: emote.guild.name - }); - } - } - - FileManager.write("emote-registry", data, true); -} - // Maybe promisify this section to reduce the potential for creating callback hell? Especially if multiple questions in a row are being asked. // Pagination function that allows for customization via a callback. diff --git a/src/events/channelCreate.ts b/src/events/channelCreate.ts deleted file mode 100644 index 47c2aa3..0000000 --- a/src/events/channelCreate.ts +++ /dev/null @@ -1,13 +0,0 @@ -import Event from "../core/event"; -import {client} from "../index"; -import * as discord from "discord.js"; - -export default new Event<"channelCreate">({ - async on(channel) { - const botGuilds = client.guilds; - if (channel instanceof discord.GuildChannel) { - const createdGuild = await botGuilds.fetch(channel.guild.id); - console.log(`Channel created in '${createdGuild.name}' called '#${channel.name}'`); - } - } -}); diff --git a/src/events/channelDelete.ts b/src/events/channelDelete.ts deleted file mode 100644 index ac835ed..0000000 --- a/src/events/channelDelete.ts +++ /dev/null @@ -1,13 +0,0 @@ -import Event from "../core/event"; -import {client} from "../index"; -import * as discord from "discord.js"; - -export default new Event<"channelDelete">({ - async on(channel) { - const botGuilds = client.guilds; - if (channel instanceof discord.GuildChannel) { - const createdGuild = await botGuilds.fetch(channel.guild.id); - console.log(`Channel deleted in '${createdGuild.name}' called '#${channel.name}'`); - } - } -}); diff --git a/src/events/emojiCreate.ts b/src/events/emojiCreate.ts deleted file mode 100644 index 92ef88e..0000000 --- a/src/events/emojiCreate.ts +++ /dev/null @@ -1,9 +0,0 @@ -import Event from "../core/event"; -import {updateGlobalEmoteRegistry} from "../core/libd"; - -export default new Event<"emojiCreate">({ - on(emote) { - console.log(`Updated emote registry. ${emote.name}`); - updateGlobalEmoteRegistry(); - } -}); diff --git a/src/events/emojiDelete.ts b/src/events/emojiDelete.ts deleted file mode 100644 index dc34190..0000000 --- a/src/events/emojiDelete.ts +++ /dev/null @@ -1,9 +0,0 @@ -import Event from "../core/event"; -import {updateGlobalEmoteRegistry} from "../core/libd"; - -export default new Event<"emojiDelete">({ - on() { - console.log("Updated emote registry."); - updateGlobalEmoteRegistry(); - } -}); diff --git a/src/events/emojiUpdate.ts b/src/events/emojiUpdate.ts deleted file mode 100644 index bd0b6b0..0000000 --- a/src/events/emojiUpdate.ts +++ /dev/null @@ -1,9 +0,0 @@ -import Event from "../core/event"; -import {updateGlobalEmoteRegistry} from "../core/libd"; - -export default new Event<"emojiUpdate">({ - on() { - console.log("Updated emote registry."); - updateGlobalEmoteRegistry(); - } -}); diff --git a/src/events/guildCreate.ts b/src/events/guildCreate.ts deleted file mode 100644 index 480bd4d..0000000 --- a/src/events/guildCreate.ts +++ /dev/null @@ -1,9 +0,0 @@ -import Event from "../core/event"; -import {updateGlobalEmoteRegistry} from "../core/libd"; - -export default new Event<"guildCreate">({ - on() { - console.log("Updated emote registry."); - updateGlobalEmoteRegistry(); - } -}); diff --git a/src/events/guildDelete.ts b/src/events/guildDelete.ts deleted file mode 100644 index 755a049..0000000 --- a/src/events/guildDelete.ts +++ /dev/null @@ -1,9 +0,0 @@ -import Event from "../core/event"; -import {updateGlobalEmoteRegistry} from "../core/libd"; - -export default new Event<"guildDelete">({ - on() { - console.log("Updated emote registry."); - updateGlobalEmoteRegistry(); - } -}); diff --git a/src/events/message.ts b/src/events/message.ts deleted file mode 100644 index d805ed8..0000000 --- a/src/events/message.ts +++ /dev/null @@ -1,149 +0,0 @@ -import Event from "../core/event"; -import Command, {loadableCommands} from "../core/command"; -import {hasPermission, getPermissionLevel, getPermissionName} from "../core/permissions"; -import {Permissions} from "discord.js"; -import {getPrefix} from "../core/structures"; -import {replyEventListeners} from "../core/libd"; -import quote from "../modules/message_embed"; - -export default new Event<"message">({ - async on(message) { - const commands = await loadableCommands; - - if (message.content.toLowerCase().includes("remember to drink water")) { - message.react("🚱"); - } - - // Message Setup // - if (message.author.bot) return; - - // If there's an inline reply, fire off that event listener (if it exists). - if (message.reference) { - const reference = message.reference; - replyEventListeners.get(`${reference.channelID}-${reference.messageID}`)?.(message); - } - - let prefix = getPrefix(message.guild); - const originalPrefix = prefix; - let exitEarly = !message.content.startsWith(prefix); - const clientUser = message.client.user; - let usesBotSpecificPrefix = false; - - if (!message.content.startsWith(prefix)) { - return quote(message); - } - - // If the client user exists, check if it starts with the bot-specific prefix. - if (clientUser) { - // If the prefix starts with the bot-specific prefix, go off that instead (these two options must mutually exclude each other). - // The pattern here has an optional space at the end to capture that and make it not mess with the header and args. - const matches = message.content.match(new RegExp(`^<@!?${clientUser.id}> ?`)); - - if (matches) { - prefix = matches[0]; - exitEarly = false; - usesBotSpecificPrefix = true; - } - } - - // If it doesn't start with the current normal prefix or the bot-specific unique prefix, exit the thread of execution early. - // Inline replies should still be captured here because if it doesn't exit early, two characters for a two-length prefix would still trigger commands. - if (exitEarly) return; - - const [header, ...args] = message.content.substring(prefix.length).split(/ +/); - - // If the message is just the prefix itself, move onto this block. - if (header === "" && args.length === 0) { - // I moved the bot-specific prefix to a separate conditional block to separate the logic. - // And because it listens for the mention as a prefix instead of a free-form mention, inline replies (probably) shouldn't ever trigger this unintentionally. - if (usesBotSpecificPrefix) { - message.channel.send(`${message.author.toString()}, my prefix on this guild is \`${originalPrefix}\`.`); - return; - } - } - - if (!commands.has(header)) return; - - if ( - message.channel.type === "text" && - !message.channel.permissionsFor(message.client.user || "")?.has(Permissions.FLAGS.SEND_MESSAGES) - ) { - let status; - - if (message.member?.hasPermission(Permissions.FLAGS.ADMINISTRATOR)) - status = - "Because you're a server admin, you have the ability to change that channel's permissions to match if that's what you intended."; - else - status = - "Try using a different channel or contacting a server admin to change permissions of that channel if you think something's wrong."; - - return message.author.send( - `I don't have permission to send messages in ${message.channel.toString()}. ${status}` - ); - } - - console.log( - `${message.author.username}#${message.author.discriminator} executed the command "${header}" with arguments "${args}".` - ); - - // Subcommand Recursion // - let command = commands.get(header); - if (!command) return console.warn(`Command "${header}" was called but for some reason it's still undefined!`); - const params: any[] = []; - let isEndpoint = false; - let permLevel = command.permission ?? 0; - - for (let param of args) { - if (command.endpoint) { - if (command.subcommands.size > 0 || command.user || command.number || command.any) - console.warn(`An endpoint cannot have subcommands! Check ${originalPrefix}${header} again.`); - isEndpoint = true; - break; - } - - const type = command.resolve(param); - command = command.get(param); - permLevel = command.permission ?? permLevel; - - if (type === Command.TYPES.USER) { - const id = param.match(/\d+/g)![0]; - try { - params.push(await message.client.users.fetch(id)); - } catch (error) { - return message.channel.send(`No user found by the ID \`${id}\`!`); - } - } else if (type === Command.TYPES.NUMBER) params.push(Number(param)); - else if (type !== Command.TYPES.SUBCOMMAND) params.push(param); - } - - if (!message.member) - return console.warn("This command was likely called from a DM channel meaning the member object is null."); - - if (!hasPermission(message.member, permLevel)) { - const userPermLevel = getPermissionLevel(message.member); - return message.channel.send( - `You don't have access to this command! Your permission level is \`${getPermissionName( - userPermLevel - )}\` (${userPermLevel}), but this command requires a permission level of \`${getPermissionName( - permLevel - )}\` (${permLevel}).` - ); - } - - if (isEndpoint) return message.channel.send("Too many arguments!"); - - // Execute with dynamic library attached. // - // The purpose of using $.bind($) is to clone the function so as to not modify the original $. - // The cloned function doesn't copy the properties, so Object.assign() is used. - // Object.assign() modifies the first element and returns that, the second element applies its properties and the third element applies its own overriding the second one. - command.execute({ - args: params, - author: message.author, - channel: message.channel, - client: message.client, - guild: message.guild, - member: message.member, - message: message - }); - } -}); diff --git a/src/events/messageReactionRemove.ts b/src/events/messageReactionRemove.ts deleted file mode 100644 index 4b3dbb2..0000000 --- a/src/events/messageReactionRemove.ts +++ /dev/null @@ -1,18 +0,0 @@ -import Event from "../core/event"; -import {Permissions} from "discord.js"; -import {botHasPermission} from "../core/libd"; - -// A list of message ID and callback pairs. You get the emote name and ID of the user reacting. -export const eventListeners: Map void> = new Map(); - -// Attached to the client, there can be one event listener attached to a message ID which is executed if present. -export default new Event<"messageReactionRemove">({ - on(reaction, user) { - const canDeleteEmotes = botHasPermission(reaction.message.guild, Permissions.FLAGS.MANAGE_MESSAGES); - - if (!canDeleteEmotes) { - const callback = eventListeners.get(reaction.message.id); - callback && callback(reaction.emoji.name, user.id); - } - } -}); diff --git a/src/events/ready.ts b/src/events/ready.ts deleted file mode 100644 index a4e1b9c..0000000 --- a/src/events/ready.ts +++ /dev/null @@ -1,17 +0,0 @@ -import Event from "../core/event"; -import {client} from "../index"; -import {Config} from "../core/structures"; -import {updateGlobalEmoteRegistry} from "../core/libd"; - -export default new Event<"ready">({ - once() { - if (client.user) { - console.ready(`Logged in as ${client.user.username}#${client.user.discriminator}.`); - client.user.setActivity({ - type: "LISTENING", - name: `${Config.prefix}help` - }); - } - updateGlobalEmoteRegistry(); - } -}); diff --git a/src/index.ts b/src/index.ts index 2ccf92c..9f709e0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,17 +1,20 @@ +// Bootstrapping Section // import "./globals"; import {Client} from "discord.js"; import setup from "./setup"; import {Config} from "./core/structures"; -import {loadEvents} from "./core/event"; -import {attachToClient} from "./modules/lavalink"; // This is here in order to make it much less of a headache to access the client from other files. // This of course won't actually do anything until the setup process is complete and it logs in. export const client = new Client(); -attachToClient(client); -// Command loading will start as soon as an instance of "core/command" is loaded, which is loaded during "events/message". +// Send the login request to Discord's API and then load modules while waiting for it. setup.init().then(() => { - loadEvents(client); client.login(Config.token).catch(setup.again); }); + +// Initialize Modules // +import "./core/handler"; // Command loading will start as soon as an instance of "core/command" is loaded, which is loaded in "core/handler". +import "./modules/lavalink"; +import "./modules/emoteRegistry"; +import "./modules/channelListener"; diff --git a/src/modules/channelListener.ts b/src/modules/channelListener.ts new file mode 100644 index 0000000..910d1ab --- /dev/null +++ b/src/modules/channelListener.ts @@ -0,0 +1,20 @@ +import {client} from "../index"; +import {GuildChannel} from "discord.js"; + +client.on("channelCreate", async (channel) => { + const botGuilds = client.guilds; + + if (channel instanceof GuildChannel) { + const createdGuild = await botGuilds.fetch(channel.guild.id); + console.log(`Channel created in '${createdGuild.name}' called '#${channel.name}'`); + } +}); + +client.on("channelDelete", async (channel) => { + const botGuilds = client.guilds; + + if (channel instanceof GuildChannel) { + const createdGuild = await botGuilds.fetch(channel.guild.id); + console.log(`Channel deleted in '${createdGuild.name}' called '#${channel.name}'`); + } +}); diff --git a/src/modules/emoteRegistry.ts b/src/modules/emoteRegistry.ts new file mode 100644 index 0000000..bcc7ed0 --- /dev/null +++ b/src/modules/emoteRegistry.ts @@ -0,0 +1,53 @@ +import {client} from "../index"; +import FileManager from "../core/storage"; +import {EmoteRegistryDump} from "../core/structures"; + +function updateGlobalEmoteRegistry(): void { + const data: EmoteRegistryDump = {version: 1, list: []}; + + for (const guild of client.guilds.cache.values()) { + for (const emote of guild.emojis.cache.values()) { + data.list.push({ + ref: emote.name, + id: emote.id, + name: emote.name, + requires_colons: emote.requiresColons || false, + animated: emote.animated, + url: emote.url, + guild_id: emote.guild.name, + guild_name: emote.guild.name + }); + } + } + + FileManager.write("emote-registry", data, true); +} + +client.on("emojiCreate", (emote) => { + console.log(`Updated emote registry. ${emote.name}`); + updateGlobalEmoteRegistry(); +}); + +client.on("emojiDelete", () => { + console.log("Updated emote registry."); + updateGlobalEmoteRegistry(); +}); + +client.on("emojiUpdate", () => { + console.log("Updated emote registry."); + updateGlobalEmoteRegistry(); +}); + +client.on("guildCreate", () => { + console.log("Updated emote registry."); + updateGlobalEmoteRegistry(); +}); + +client.on("guildDelete", () => { + console.log("Updated emote registry."); + updateGlobalEmoteRegistry(); +}); + +client.on("ready", () => { + updateGlobalEmoteRegistry(); +}); diff --git a/src/modules/lavalink.ts b/src/modules/lavalink.ts index 5e0ff90..39dc5c8 100644 --- a/src/modules/lavalink.ts +++ b/src/modules/lavalink.ts @@ -1,6 +1,7 @@ -import {Presence, Client} from "discord.js"; +import {Presence} from "discord.js"; import LavalinkMusic from "discord.js-lavalink-lib"; import {Config} from "../core/structures"; +import {client} from "../index"; declare module "discord.js" { interface Presence { @@ -28,26 +29,24 @@ Presence.prototype.patch = function patch(data: any) { // the function which handles those packets is NOP-ed out, which, among other // things, skips the code which caches the referenced users in the packet. See // . -export function attachToClient(client: Client) { - (client["actions"] as any)["PresenceUpdate"].handle = () => {}; +(client["actions"] as any)["PresenceUpdate"].handle = () => {}; - (client as any).music = LavalinkMusic(client, { - lavalink: { - restnode: { +(client as any).music = LavalinkMusic(client, { + lavalink: { + restnode: { + host: "localhost", + port: 2333, + password: "youshallnotpass" + }, + nodes: [ + { host: "localhost", port: 2333, password: "youshallnotpass" - }, - nodes: [ - { - host: "localhost", - port: 2333, - password: "youshallnotpass" - } - ] - }, - prefix: Config.prefix, - helpCmd: "mhelp", - admins: ["717352467280691331"] - }); -} + } + ] + }, + prefix: Config.prefix, + helpCmd: "mhelp", + admins: ["717352467280691331"] +}); From 974985586d133fe0a99ef7e6c8f3d9f327ec0573 Mon Sep 17 00:00:00 2001 From: WatDuhHekBro <44940783+WatDuhHekBro@users.noreply.github.com> Date: Tue, 30 Mar 2021 22:22:25 -0500 Subject: [PATCH 119/178] Rearranged command categories --- src/commands/fun/eco.ts | 8 ++++---- .../fun/{subcommands => modules}/eco-core.ts | 0 .../fun/{subcommands => modules}/eco-extras.ts | 0 .../fun/{subcommands => modules}/eco-shop-items.ts | 0 .../fun/{subcommands => modules}/eco-shop.ts | 0 .../fun/{subcommands => modules}/eco-utils.ts | 0 src/commands/fun/owoify.ts | 6 +++--- src/commands/{ => system}/admin.ts | 12 ++++++------ src/commands/{ => system}/help.ts | 8 ++++---- src/commands/{utilities => utility}/desc.ts | 0 src/commands/{utilities => utility}/emote.ts | 2 +- src/commands/{ => utility}/info.ts | 8 ++++---- src/commands/{utilities => utility}/lsemotes.ts | 0 .../subcommands => utility/modules}/emote-utils.ts | 0 src/commands/{utilities => utility}/react.ts | 2 +- src/commands/{utilities => utility}/say.ts | 0 src/commands/{ => utility}/scanemotes.ts | 4 ++-- src/commands/{utilities => utility}/shorten.ts | 0 src/commands/{utilities => utility}/time.ts | 0 src/core/permissions.ts | 2 +- 20 files changed, 26 insertions(+), 26 deletions(-) rename src/commands/fun/{subcommands => modules}/eco-core.ts (100%) rename src/commands/fun/{subcommands => modules}/eco-extras.ts (100%) rename src/commands/fun/{subcommands => modules}/eco-shop-items.ts (100%) rename src/commands/fun/{subcommands => modules}/eco-shop.ts (100%) rename src/commands/fun/{subcommands => modules}/eco-utils.ts (100%) rename src/commands/{ => system}/admin.ts (96%) rename src/commands/{ => system}/help.ts (96%) rename src/commands/{utilities => utility}/desc.ts (100%) rename src/commands/{utilities => utility}/emote.ts (89%) rename src/commands/{ => utility}/info.ts (98%) rename src/commands/{utilities => utility}/lsemotes.ts (100%) rename src/commands/{utilities/subcommands => utility/modules}/emote-utils.ts (100%) rename src/commands/{utilities => utility}/react.ts (98%) rename src/commands/{utilities => utility}/say.ts (100%) rename src/commands/{ => utility}/scanemotes.ts (99%) rename src/commands/{utilities => utility}/shorten.ts (100%) rename src/commands/{utilities => utility}/time.ts (100%) diff --git a/src/commands/fun/eco.ts b/src/commands/fun/eco.ts index 6df36ae..380cc33 100644 --- a/src/commands/fun/eco.ts +++ b/src/commands/fun/eco.ts @@ -1,8 +1,8 @@ import Command from "../../core/command"; -import {isAuthorized, getMoneyEmbed} from "./subcommands/eco-utils"; -import {DailyCommand, PayCommand, GuildCommand, LeaderboardCommand} from "./subcommands/eco-core"; -import {BuyCommand, ShopCommand} from "./subcommands/eco-shop"; -import {MondayCommand} from "./subcommands/eco-extras"; +import {isAuthorized, getMoneyEmbed} from "./modules/eco-utils"; +import {DailyCommand, PayCommand, GuildCommand, LeaderboardCommand} from "./modules/eco-core"; +import {BuyCommand, ShopCommand} from "./modules/eco-shop"; +import {MondayCommand} from "./modules/eco-extras"; import {callMemberByUsername} from "../../core/libd"; export default new Command({ diff --git a/src/commands/fun/subcommands/eco-core.ts b/src/commands/fun/modules/eco-core.ts similarity index 100% rename from src/commands/fun/subcommands/eco-core.ts rename to src/commands/fun/modules/eco-core.ts diff --git a/src/commands/fun/subcommands/eco-extras.ts b/src/commands/fun/modules/eco-extras.ts similarity index 100% rename from src/commands/fun/subcommands/eco-extras.ts rename to src/commands/fun/modules/eco-extras.ts diff --git a/src/commands/fun/subcommands/eco-shop-items.ts b/src/commands/fun/modules/eco-shop-items.ts similarity index 100% rename from src/commands/fun/subcommands/eco-shop-items.ts rename to src/commands/fun/modules/eco-shop-items.ts diff --git a/src/commands/fun/subcommands/eco-shop.ts b/src/commands/fun/modules/eco-shop.ts similarity index 100% rename from src/commands/fun/subcommands/eco-shop.ts rename to src/commands/fun/modules/eco-shop.ts diff --git a/src/commands/fun/subcommands/eco-utils.ts b/src/commands/fun/modules/eco-utils.ts similarity index 100% rename from src/commands/fun/subcommands/eco-utils.ts rename to src/commands/fun/modules/eco-utils.ts diff --git a/src/commands/fun/owoify.ts b/src/commands/fun/owoify.ts index 789d74a..af041df 100644 --- a/src/commands/fun/owoify.ts +++ b/src/commands/fun/owoify.ts @@ -1,12 +1,12 @@ -/// @ts-nocheck import Command from "../../core/command"; -import {getContent} from "../../core/libd"; +import {getContent} from "../../core/lib"; +import {URL} from "url"; export default new Command({ description: "OwO-ifies the input.", async run($) { let url = new URL(`https://nekos.life/api/v2/owoify?text=${$.args.join(" ")}`); - const content = await getContent(url.toString()); + const content = (await getContent(url.toString())) as any; // Apparently, the object in question is {owo: string}. $.channel.send(content.owo); } }); diff --git a/src/commands/admin.ts b/src/commands/system/admin.ts similarity index 96% rename from src/commands/admin.ts rename to src/commands/system/admin.ts index 72c5686..15ab73d 100644 --- a/src/commands/admin.ts +++ b/src/commands/system/admin.ts @@ -1,10 +1,10 @@ -import Command, {handler} from "../core/command"; -import {clean} from "../core/lib"; -import {botHasPermission} from "../core/libd"; -import {Config, Storage} from "../core/structures"; -import {getPermissionLevel, getPermissionName} from "../core/permissions"; +import Command, {handler} from "../../core/command"; +import {clean} from "../../core/lib"; +import {botHasPermission} from "../../core/libd"; +import {Config, Storage} from "../../core/structures"; +import {getPermissionLevel, getPermissionName} from "../../core/permissions"; import {Permissions} from "discord.js"; -import {logs} from "../globals"; +import {logs} from "../../globals"; function getLogBuffer(type: string) { return { diff --git a/src/commands/help.ts b/src/commands/system/help.ts similarity index 96% rename from src/commands/help.ts rename to src/commands/system/help.ts index 71f2ae3..fdf1249 100644 --- a/src/commands/help.ts +++ b/src/commands/system/help.ts @@ -1,7 +1,7 @@ -import Command from "../core/command"; -import {toTitleCase} from "../core/lib"; -import {loadableCommands, categories} from "../core/command"; -import {getPermissionName} from "../core/permissions"; +import Command from "../../core/command"; +import {toTitleCase} from "../../core/lib"; +import {loadableCommands, categories} from "../../core/command"; +import {getPermissionName} from "../../core/permissions"; export default new Command({ description: "Lists all commands. If a command is specified, their arguments are listed as well.", diff --git a/src/commands/utilities/desc.ts b/src/commands/utility/desc.ts similarity index 100% rename from src/commands/utilities/desc.ts rename to src/commands/utility/desc.ts diff --git a/src/commands/utilities/emote.ts b/src/commands/utility/emote.ts similarity index 89% rename from src/commands/utilities/emote.ts rename to src/commands/utility/emote.ts index b84d9ef..addaec0 100644 --- a/src/commands/utilities/emote.ts +++ b/src/commands/utility/emote.ts @@ -1,5 +1,5 @@ import Command from "../../core/command"; -import {queryClosestEmoteByName} from "./subcommands/emote-utils"; +import {queryClosestEmoteByName} from "./modules/emote-utils"; import {botHasPermission} from "../../core/libd"; import {Permissions} from "discord.js"; diff --git a/src/commands/info.ts b/src/commands/utility/info.ts similarity index 98% rename from src/commands/info.ts rename to src/commands/utility/info.ts index b95f19a..e2e6025 100644 --- a/src/commands/info.ts +++ b/src/commands/utility/info.ts @@ -1,10 +1,10 @@ import {MessageEmbed, version as djsversion} from "discord.js"; import ms from "ms"; import os from "os"; -import Command from "../core/command"; -import {formatBytes, trimArray} from "../core/lib"; -import {getMemberByUsername} from "../core/libd"; -import {verificationLevels, filterLevels, regions} from "../defs/info"; +import Command from "../../core/command"; +import {formatBytes, trimArray} from "../../core/lib"; +import {getMemberByUsername} from "../../core/libd"; +import {verificationLevels, filterLevels, regions} from "../../defs/info"; import moment from "moment"; import utc from "moment"; import {Guild} from "discord.js"; diff --git a/src/commands/utilities/lsemotes.ts b/src/commands/utility/lsemotes.ts similarity index 100% rename from src/commands/utilities/lsemotes.ts rename to src/commands/utility/lsemotes.ts diff --git a/src/commands/utilities/subcommands/emote-utils.ts b/src/commands/utility/modules/emote-utils.ts similarity index 100% rename from src/commands/utilities/subcommands/emote-utils.ts rename to src/commands/utility/modules/emote-utils.ts diff --git a/src/commands/utilities/react.ts b/src/commands/utility/react.ts similarity index 98% rename from src/commands/utilities/react.ts rename to src/commands/utility/react.ts index cc7c031..152e98d 100644 --- a/src/commands/utilities/react.ts +++ b/src/commands/utility/react.ts @@ -1,6 +1,6 @@ import Command from "../../core/command"; import {Message, Channel, TextChannel} from "discord.js"; -import {queryClosestEmoteByName} from "./subcommands/emote-utils"; +import {queryClosestEmoteByName} from "./modules/emote-utils"; export default new Command({ description: diff --git a/src/commands/utilities/say.ts b/src/commands/utility/say.ts similarity index 100% rename from src/commands/utilities/say.ts rename to src/commands/utility/say.ts diff --git a/src/commands/scanemotes.ts b/src/commands/utility/scanemotes.ts similarity index 99% rename from src/commands/scanemotes.ts rename to src/commands/utility/scanemotes.ts index 9596f9f..b087793 100644 --- a/src/commands/scanemotes.ts +++ b/src/commands/utility/scanemotes.ts @@ -1,5 +1,5 @@ -import Command, {handler} from "../core/command"; -import {pluralise} from "../core/lib"; +import Command, {handler} from "../../core/command"; +import {pluralise} from "../../core/lib"; import moment from "moment"; import {Collection, TextChannel} from "discord.js"; diff --git a/src/commands/utilities/shorten.ts b/src/commands/utility/shorten.ts similarity index 100% rename from src/commands/utilities/shorten.ts rename to src/commands/utility/shorten.ts diff --git a/src/commands/utilities/time.ts b/src/commands/utility/time.ts similarity index 100% rename from src/commands/utilities/time.ts rename to src/commands/utility/time.ts diff --git a/src/core/permissions.ts b/src/core/permissions.ts index c4c89ba..37b0000 100644 --- a/src/core/permissions.ts +++ b/src/core/permissions.ts @@ -63,6 +63,6 @@ export function getPermissionLevel(member: GuildMember): number { } export function getPermissionName(level: number) { - if (level > length || length < 0) return "N/A"; + if (level > length || level < 0) return "N/A"; else return PermissionLevels[level].name; } From df3e4e8e6eaad6d0d1aeae2c05af8fb74277d0af Mon Sep 17 00:00:00 2001 From: WatDuhHekBro <44940783+WatDuhHekBro@users.noreply.github.com> Date: Wed, 31 Mar 2021 02:00:03 -0500 Subject: [PATCH 120/178] Made some minor changes to modules --- src/commands/system/admin.ts | 2 +- src/commands/system/help.ts | 2 ++ src/index.ts | 5 +++-- src/{ => modules}/globals.ts | 0 src/modules/lavalink.ts | 34 +++------------------------------- src/modules/presence.ts | 30 ++++++++++++++++++++++++++++++ src/{ => modules}/setup.ts | 4 ++-- 7 files changed, 41 insertions(+), 36 deletions(-) rename src/{ => modules}/globals.ts (100%) create mode 100644 src/modules/presence.ts rename src/{ => modules}/setup.ts (96%) diff --git a/src/commands/system/admin.ts b/src/commands/system/admin.ts index 15ab73d..8dae940 100644 --- a/src/commands/system/admin.ts +++ b/src/commands/system/admin.ts @@ -4,7 +4,7 @@ import {botHasPermission} from "../../core/libd"; import {Config, Storage} from "../../core/structures"; import {getPermissionLevel, getPermissionName} from "../../core/permissions"; import {Permissions} from "discord.js"; -import {logs} from "../../globals"; +import {logs} from "../../modules/globals"; function getLogBuffer(type: string) { return { diff --git a/src/commands/system/help.ts b/src/commands/system/help.ts index fdf1249..8ef9fe2 100644 --- a/src/commands/system/help.ts +++ b/src/commands/system/help.ts @@ -65,6 +65,8 @@ export default new Command({ command = command.get(param); permLevel = command.permission ?? permLevel; + if (permLevel === -1) permLevel = command.permission; + switch (type) { case Command.TYPES.SUBCOMMAND: header += ` ${command.originalCommandName}`; diff --git a/src/index.ts b/src/index.ts index 9f709e0..497c4f6 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,7 +1,7 @@ // Bootstrapping Section // -import "./globals"; +import "./modules/globals"; import {Client} from "discord.js"; -import setup from "./setup"; +import setup from "./modules/setup"; import {Config} from "./core/structures"; // This is here in order to make it much less of a headache to access the client from other files. @@ -15,6 +15,7 @@ setup.init().then(() => { // Initialize Modules // import "./core/handler"; // Command loading will start as soon as an instance of "core/command" is loaded, which is loaded in "core/handler". +import "./modules/presence"; import "./modules/lavalink"; import "./modules/emoteRegistry"; import "./modules/channelListener"; diff --git a/src/globals.ts b/src/modules/globals.ts similarity index 100% rename from src/globals.ts rename to src/modules/globals.ts diff --git a/src/modules/lavalink.ts b/src/modules/lavalink.ts index 39dc5c8..92d6ee9 100644 --- a/src/modules/lavalink.ts +++ b/src/modules/lavalink.ts @@ -1,37 +1,9 @@ -import {Presence} from "discord.js"; -import LavalinkMusic from "discord.js-lavalink-lib"; +import attachClientToLavalink from "discord.js-lavalink-lib"; import {Config} from "../core/structures"; import {client} from "../index"; -declare module "discord.js" { - interface Presence { - patch(data: any): void; - } -} - -// The terrible hacks were written by none other than The Noble Programmer On The White PC. - -// NOTE: Terrible hack ahead!!! In order to reduce the memory usage of the bot -// we only store the information from presences that we actually end up using, -// which currently is only the (online/idle/dnd/offline/...) status (see -// `src/commands/info.ts`). What data is retrieved from the `data` object -// (which contains the data received from the Gateway) and how can be seen -// here: -// . -const oldPresencePatch = Presence.prototype.patch; -Presence.prototype.patch = function patch(data: any) { - oldPresencePatch.call(this, {status: data.status}); -}; - -// NOTE: Terrible hack continued!!! Unfortunately we can't receive the presence -// data at all when the GUILD_PRESENCES intent is disabled, so while we do -// waste network bandwidth and the CPU time for decoding the incoming packets, -// the function which handles those packets is NOP-ed out, which, among other -// things, skips the code which caches the referenced users in the packet. See -// . -(client["actions"] as any)["PresenceUpdate"].handle = () => {}; - -(client as any).music = LavalinkMusic(client, { +// Although the example showed to do "client.music = LavaLink(...)" and "(client as any).music = Lavalink(...)" was done to match that, nowhere in the library is client.music ever actually used nor does the function return anything. In other words, client.music is undefined and is never used. +attachClientToLavalink(client, { lavalink: { restnode: { host: "localhost", diff --git a/src/modules/presence.ts b/src/modules/presence.ts new file mode 100644 index 0000000..50c5f64 --- /dev/null +++ b/src/modules/presence.ts @@ -0,0 +1,30 @@ +import {Presence} from "discord.js"; +import {client} from "../index"; + +declare module "discord.js" { + interface Presence { + patch(data: any): void; + } +} + +// The terrible hacks were written by none other than The Noble Programmer On The White PC. + +// NOTE: Terrible hack ahead!!! In order to reduce the memory usage of the bot +// we only store the information from presences that we actually end up using, +// which currently is only the (online/idle/dnd/offline/...) status (see +// `src/commands/info.ts`). What data is retrieved from the `data` object +// (which contains the data received from the Gateway) and how can be seen +// here: +// . +const oldPresencePatch = Presence.prototype.patch; +Presence.prototype.patch = function patch(data: any) { + oldPresencePatch.call(this, {status: data.status}); +}; + +// NOTE: Terrible hack continued!!! Unfortunately we can't receive the presence +// data at all when the GUILD_PRESENCES intent is disabled, so while we do +// waste network bandwidth and the CPU time for decoding the incoming packets, +// the function which handles those packets is NOP-ed out, which, among other +// things, skips the code which caches the referenced users in the packet. See +// . +(client["actions"] as any)["PresenceUpdate"].handle = () => {}; diff --git a/src/setup.ts b/src/modules/setup.ts similarity index 96% rename from src/setup.ts rename to src/modules/setup.ts index 97dc6e6..99ca135 100644 --- a/src/setup.ts +++ b/src/modules/setup.ts @@ -1,7 +1,7 @@ import {existsSync as exists, readFileSync as read, writeFile as write} from "fs"; import inquirer from "inquirer"; -import Storage, {generateHandler} from "./core/storage"; -import {Config} from "./core/structures"; +import Storage, {generateHandler} from "../core/storage"; +import {Config} from "../core/structures"; // The template should be built with a reductionist mentality. // Provide everything the user needs and then let them remove whatever they want. From 9adc5eea6e86ee7c89c03df17ff35f8acf165443 Mon Sep 17 00:00:00 2001 From: WatDuhHekBro <44940783+WatDuhHekBro@users.noreply.github.com> Date: Thu, 1 Apr 2021 05:44:44 -0500 Subject: [PATCH 121/178] Started attempting to split up core handler --- jest.config.js | 5 +- src/core/eventListeners.ts | 28 +++++ src/core/handler.ts | 117 ++++++++++-------- src/core/libd.ts | 25 +--- src/index.ts | 3 + src/modules/intercept.ts | 7 ++ src/modules/messageEmbed.test.ts | 55 ++++++++ .../{message_embed.ts => messageEmbed.ts} | 32 +++-- 8 files changed, 186 insertions(+), 86 deletions(-) create mode 100644 src/core/eventListeners.ts create mode 100644 src/modules/intercept.ts create mode 100644 src/modules/messageEmbed.test.ts rename src/modules/{message_embed.ts => messageEmbed.ts} (65%) diff --git a/jest.config.js b/jest.config.js index 09097b3..7917d47 100644 --- a/jest.config.js +++ b/jest.config.js @@ -3,5 +3,8 @@ module.exports = { testMatch: ["**/*.test.+(ts|tsx)"], transform: { "^.+\\.(ts|tsx)$": "ts-jest" - } + }, + // The environment is the DOM by default, so discord.js fails to load because it's calling a Node-specific function. + // https://github.com/discordjs/discord.js/issues/3971#issuecomment-602010271 + testEnvironment: "node" }; diff --git a/src/core/eventListeners.ts b/src/core/eventListeners.ts new file mode 100644 index 0000000..e32db03 --- /dev/null +++ b/src/core/eventListeners.ts @@ -0,0 +1,28 @@ +import {client} from "../index"; +import {botHasPermission} from "./libd"; +import {Permissions, Message} from "discord.js"; + +// A list of message ID and callback pairs. You get the emote name and ID of the user reacting. +export const unreactEventListeners: Map void> = new Map(); + +// Attached to the client, there can be one event listener attached to a message ID which is executed if present. +client.on("messageReactionRemove", (reaction, user) => { + const canDeleteEmotes = botHasPermission(reaction.message.guild, Permissions.FLAGS.MANAGE_MESSAGES); + + if (!canDeleteEmotes) { + const callback = unreactEventListeners.get(reaction.message.id); + callback && callback(reaction.emoji.name, user.id); + } +}); + +// A list of "channel-message" and callback pairs. Also, I imagine that the callback will be much more maintainable when discord.js v13 comes out with a dedicated message.referencedMessage property. +// Also, I'm defining it here instead of the message event because the load order screws up if you export it from there. Yeah... I'm starting to notice just how much technical debt has been built up. The command handler needs to be modularized and refactored sooner rather than later. Define all constants in one area then grab from there. +export const replyEventListeners = new Map void>(); + +client.on("message", (message) => { + // If there's an inline reply, fire off that event listener (if it exists). + if (message.reference) { + const reference = message.reference; + replyEventListeners.get(`${reference.channelID}-${reference.messageID}`)?.(message); + } +}); diff --git a/src/core/handler.ts b/src/core/handler.ts index 1de6c1e..2a9fe42 100644 --- a/src/core/handler.ts +++ b/src/core/handler.ts @@ -1,38 +1,43 @@ import {client} from "../index"; -import Command, {loadableCommands} from "../core/command"; -import {hasPermission, getPermissionLevel, getPermissionName} from "../core/permissions"; -import {Permissions} from "discord.js"; -import {getPrefix} from "../core/structures"; -import {replyEventListeners} from "../core/libd"; -import quote from "../modules/message_embed"; -import {Config} from "../core/structures"; +import Command, {loadableCommands} from "./command"; +import {hasPermission, getPermissionLevel, getPermissionName} from "./permissions"; +import {Permissions, Message} from "discord.js"; +import {getPrefix} from "./structures"; +import {Config} from "./structures"; + +/////////// +// Steps // +/////////// +// 1. Someone sends a message in chat. +// 2. Check if bot, then load commands. +// 3. Check if "...". If not, check if "@...". Resolve prefix and cropped message (if possible). +// 4. Test if bot has permission to send messages. +// 5. Once confirmed as a command, resolve the subcommand. +// 6. Check permission level and whether or not it's an endpoint. +// 7. Execute command if all successful. + +// For custom message events that want to cancel this one on certain conditions. +const interceptRules: ((message: Message) => boolean)[] = [(message) => message.author.bot]; + +export function addInterceptRule(handler: (message: Message) => boolean) { + interceptRules.push(handler); +} client.on("message", async (message) => { + for (const shouldIntercept of interceptRules) { + if (shouldIntercept(message)) { + return; + } + } + const commands = await loadableCommands; - if (message.content.toLowerCase().includes("remember to drink water")) { - message.react("🚱"); - } - - // Message Setup // - if (message.author.bot) return; - - // If there's an inline reply, fire off that event listener (if it exists). - if (message.reference) { - const reference = message.reference; - replyEventListeners.get(`${reference.channelID}-${reference.messageID}`)?.(message); - } - let prefix = getPrefix(message.guild); const originalPrefix = prefix; let exitEarly = !message.content.startsWith(prefix); const clientUser = message.client.user; let usesBotSpecificPrefix = false; - if (!message.content.startsWith(prefix)) { - return quote(message); - } - // If the client user exists, check if it starts with the bot-specific prefix. if (clientUser) { // If the prefix starts with the bot-specific prefix, go off that instead (these two options must mutually exclude each other). @@ -87,34 +92,8 @@ client.on("message", async (message) => { ); // Subcommand Recursion // - let command = commands.get(header); - if (!command) return console.warn(`Command "${header}" was called but for some reason it's still undefined!`); - const params: any[] = []; - let isEndpoint = false; - let permLevel = command.permission ?? 0; - - for (let param of args) { - if (command.endpoint) { - if (command.subcommands.size > 0 || command.user || command.number || command.any) - console.warn(`An endpoint cannot have subcommands! Check ${originalPrefix}${header} again.`); - isEndpoint = true; - break; - } - - const type = command.resolve(param); - command = command.get(param); - permLevel = command.permission ?? permLevel; - - if (type === Command.TYPES.USER) { - const id = param.match(/\d+/g)![0]; - try { - params.push(await message.client.users.fetch(id)); - } catch (error) { - return message.channel.send(`No user found by the ID \`${id}\`!`); - } - } else if (type === Command.TYPES.NUMBER) params.push(Number(param)); - else if (type !== Command.TYPES.SUBCOMMAND) params.push(param); - } + let command = commands.get(header)!; + //resolveSubcommand() if (!message.member) return console.warn("This command was likely called from a DM channel meaning the member object is null."); @@ -147,6 +126,40 @@ client.on("message", async (message) => { }); }); +// Takes a base command and a list of string parameters and returns: +// - The resolved subcommand +// - The resolved parameters +// - Whether or not an endpoint has been broken +// - The permission level required +async function resolveSubcommand(command: Command, args: string[]): [Command, any[], boolean, number] { + const params: any[] = []; + let isEndpoint = false; + let permLevel = command.permission ?? 0; + + for (const param of args) { + if (command.endpoint) { + if (command.subcommands.size > 0 || command.user || command.number || command.any) + console.warn("An endpoint cannot have subcommands!"); + isEndpoint = true; + break; + } + + const type = command.resolve(param); + command = command.get(param); + permLevel = command.permission ?? permLevel; + + if (type === Command.TYPES.USER) { + const id = param.match(/\d+/g)![0]; + try { + params.push(await message.client.users.fetch(id)); + } catch (error) { + return message.channel.send(`No user found by the ID \`${id}\`!`); + } + } else if (type === Command.TYPES.NUMBER) params.push(Number(param)); + else if (type !== Command.TYPES.SUBCOMMAND) params.push(param); + } +} + client.once("ready", () => { if (client.user) { console.ready(`Logged in as ${client.user.username}#${client.user.discriminator}.`); diff --git a/src/core/libd.ts b/src/core/libd.ts index 71ba70a..3746b6a 100644 --- a/src/core/libd.ts +++ b/src/core/libd.ts @@ -9,20 +9,9 @@ import { NewsChannel, MessageOptions } from "discord.js"; -import {client} from "../index"; +import {unreactEventListeners, replyEventListeners} from "./eventListeners"; -// A list of message ID and callback pairs. You get the emote name and ID of the user reacting. -const eventListeners: Map void> = new Map(); - -// Attached to the client, there can be one event listener attached to a message ID which is executed if present. -client.on("messageReactionRemove", (reaction, user) => { - const canDeleteEmotes = botHasPermission(reaction.message.guild, Permissions.FLAGS.MANAGE_MESSAGES); - - if (!canDeleteEmotes) { - const callback = eventListeners.get(reaction.message.id); - callback && callback(reaction.emoji.name, user.id); - } -}); +export type SingleMessageOptions = MessageOptions & {split?: false}; export function botHasPermission(guild: Guild | null, permission: number): boolean { return !!guild?.me?.hasPermission(permission); @@ -36,7 +25,7 @@ export async function paginate( channel: TextChannel | DMChannel | NewsChannel, senderID: string, total: number, - callback: (page: number, hasMultiplePages: boolean) => MessageOptions & {split?: false}, + callback: (page: number, hasMultiplePages: boolean) => SingleMessageOptions, duration = 60000 ) { const hasMultiplePages = total > 1; @@ -70,7 +59,7 @@ export async function paginate( // Listen for reactions and call the handler. let backwardsReaction = await message.react(BACKWARDS_EMOJI); let forwardsReaction = await message.react(FORWARDS_EMOJI); - eventListeners.set(message.id, handle); + unreactEventListeners.set(message.id, handle); const collector = message.createReactionCollector( (reaction, user) => { if (user.id === senderID) { @@ -91,7 +80,7 @@ export async function paginate( // When time's up, remove the bot's own reactions. collector.on("end", () => { - eventListeners.delete(message.id); + unreactEventListeners.delete(message.id); backwardsReaction.users.remove(message.author); forwardsReaction.users.remove(message.author); }); @@ -127,10 +116,6 @@ export async function prompt(message: Message, senderID: string, onConfirm: () = if (!isDeleted) message.delete(); } -// A list of "channel-message" and callback pairs. Also, I imagine that the callback will be much more maintainable when discord.js v13 comes out with a dedicated message.referencedMessage property. -// Also, I'm defining it here instead of the message event because the load order screws up if you export it from there. Yeah... I'm starting to notice just how much technical debt has been built up. The command handler needs to be modularized and refactored sooner rather than later. Define all constants in one area then grab from there. -export const replyEventListeners = new Map void>(); - // Asks the user for some input using the inline reply feature. The message here is a message you send beforehand. // If the reply is rejected, reply with an error message (when stable support comes from discord.js). // Append "\n*(Note: Make sure to use Discord's inline reply feature or this won't work!)*" in the future? And also the "you can now reply to this message" edit. diff --git a/src/index.ts b/src/index.ts index 497c4f6..5d1f52c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -15,7 +15,10 @@ setup.init().then(() => { // Initialize Modules // import "./core/handler"; // Command loading will start as soon as an instance of "core/command" is loaded, which is loaded in "core/handler". +import "./core/eventListeners"; import "./modules/presence"; import "./modules/lavalink"; import "./modules/emoteRegistry"; import "./modules/channelListener"; +import "./modules/intercept"; +import "./modules/messageEmbed"; diff --git a/src/modules/intercept.ts b/src/modules/intercept.ts new file mode 100644 index 0000000..c9a517d --- /dev/null +++ b/src/modules/intercept.ts @@ -0,0 +1,7 @@ +import {client} from "../index"; + +client.on("message", (message) => { + if (message.content.toLowerCase().includes("remember to drink water")) { + message.react("🚱"); + } +}); diff --git a/src/modules/messageEmbed.test.ts b/src/modules/messageEmbed.test.ts new file mode 100644 index 0000000..3508de2 --- /dev/null +++ b/src/modules/messageEmbed.test.ts @@ -0,0 +1,55 @@ +jest.useFakeTimers(); +import {strict as assert} from "assert"; +//import {extractFirstMessageLink} from "./messageEmbed"; + +/*describe("modules/messageEmbed", () => { + describe("extractFirstMessageLink()", () => { + const guildID = "802906483866631183"; + const channelID = "681747101169682147" + const messageID = "996363055050949479"; + const post = `channels/${guildID}/${channelID}/${messageID}`; + const commonUrl = `https://discord.com/channels/${post}`; + const combined = [guildID, channelID, messageID]; + + it('should return work and extract correctly on an isolated link', () => { + const result = extractFirstMessageLink(commonUrl); + assert.deepStrictEqual(result, combined); + }) + + it('should return work and extract correctly on a link within a message', () => { + const result = extractFirstMessageLink(`sample text${commonUrl}, more sample text`); + assert.deepStrictEqual(result, combined); + }) + + it('should return null on "!link"', () => { + const result = extractFirstMessageLink(`just some !${commonUrl} text`); + assert.strictEqual(result, null); + }) + + it('should return null on ""', () => { + const result = extractFirstMessageLink(`just some <${commonUrl}> text`); + assert.strictEqual(result, null); + }) + + it('should return work and extract correctly on " { + const result = extractFirstMessageLink(`just some <${commonUrl} text`); + assert.deepStrictEqual(result, combined); + }) + + it('should return work and extract correctly on "link>"', () => { + const result = extractFirstMessageLink(`just some ${commonUrl}> text`); + assert.deepStrictEqual(result, combined); + }) + + it('should return work and extract correctly on a canary link', () => { + const result = extractFirstMessageLink(`https://canary.discord.com/${post}`); + assert.deepStrictEqual(result, combined); + }) + }) +});*/ + +describe("placeholder", () => { + it("placeholder", async () => { + assert.strictEqual(1, 1); + }); +}); diff --git a/src/modules/message_embed.ts b/src/modules/messageEmbed.ts similarity index 65% rename from src/modules/message_embed.ts rename to src/modules/messageEmbed.ts index e8bdb6d..1d5c1c5 100644 --- a/src/modules/message_embed.ts +++ b/src/modules/messageEmbed.ts @@ -1,19 +1,14 @@ -import {client} from ".."; -import {Message, TextChannel, APIMessage, MessageEmbed} from "discord.js"; +import {client} from "../index"; +import {TextChannel, APIMessage, MessageEmbed} from "discord.js"; import {getPrefix} from "../core/structures"; import {DiscordAPIError} from "discord.js"; -export default async function quote(message: Message) { - if (message.author.bot) return; - // const message_link_regex = message.content.match(/(!)?https?:\/\/\w+\.com\/channels\/(\d+)\/(\d+)\/(\d+)/) - const message_link_regex = message.content.match( - /([?)/ - ); - - if (message_link_regex == null) return; - const [, char, guildID, channelID, messageID] = message_link_regex; - - if (char || message.content.startsWith(getPrefix(message.guild))) return; +client.on("message", async (message) => { + // Only execute if the message is from a user and isn't a command. + if (message.content.startsWith(getPrefix(message.guild)) || message.author.bot) return; + const messageLink = extractFirstMessageLink(message.content); + if (!messageLink) return; + const [guildID, channelID, messageID] = messageLink; try { const channel = client.guilds.cache.get(guildID)?.channels.cache.get(channelID) as TextChannel; @@ -58,4 +53,15 @@ export default async function quote(message: Message) { } return console.error(error); } +}); + +export function extractFirstMessageLink(message: string): [string, string, string] | null { + const messageLinkMatch = message.match( + /([!<])?https?:\/\/(?:ptb\.|canary\.)?discord(?:app)?\.com\/channels\/(\d{17,19})\/(\d{17,19})\/(\d{17,19})(>)?/ + ); + if (messageLinkMatch === null) return null; + const [, leftToken, guildID, channelID, messageID, rightToken] = messageLinkMatch; + // "!link" and "" will cancel the embed request. + if (leftToken === "!" || (leftToken === "<" && rightToken === ">")) return null; + else return [guildID, channelID, messageID]; } From ee9c88996ec9a5458c5b5a38b2cd1618a7fd2eed Mon Sep 17 00:00:00 2001 From: WatDuhHekBro <44940783+WatDuhHekBro@users.noreply.github.com> Date: Thu, 1 Apr 2021 08:41:49 -0500 Subject: [PATCH 122/178] Ported events and welcome event --- package-lock.json | 944 +++++++++++++++++++++++++++++++++-- package.json | 1 + src/commands/admin.ts | 73 +++ src/core/structures.ts | 15 + src/events/guildCreate.ts | 8 +- src/events/guildDelete.ts | 4 +- src/events/guildMemberAdd.ts | 73 +++ src/events/ready.ts | 4 +- 8 files changed, 1074 insertions(+), 48 deletions(-) create mode 100644 src/events/guildMemberAdd.ts diff --git a/package-lock.json b/package-lock.json index 9dd4614..9ea7c14 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "0.0.1", "license": "MIT", "dependencies": { + "canvas": "^2.7.0", "chalk": "^4.1.0", "discord.js": "^12.5.1", "discord.js-lavalink-lib": "^0.1.8", @@ -204,6 +205,11 @@ "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", "dev": true }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" + }, "node_modules/abort-controller": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", @@ -283,6 +289,20 @@ "node": ">= 8" } }, + "node_modules/aproba": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" + }, + "node_modules/are-we-there-yet": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", + "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, "node_modules/arg": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", @@ -343,8 +363,7 @@ "node_modules/balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" }, "node_modules/bcrypt-pbkdf": { "version": "1.0.2", @@ -372,7 +391,6 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -436,6 +454,20 @@ "node": ">=6" } }, + "node_modules/canvas": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/canvas/-/canvas-2.7.0.tgz", + "integrity": "sha512-pzCxtkHb+5su5MQjTtepMDlIOtaXo277x0C0u3nMOxtkhTyQ+h2yNKhlROAaDllWgRyePAUitC08sXw26Eb6aw==", + "hasInstallScript": true, + "dependencies": { + "nan": "^2.14.0", + "node-pre-gyp": "^0.15.0", + "simple-get": "^3.0.3" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/caseless": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", @@ -479,6 +511,11 @@ "fsevents": "~2.1.2" } }, + "node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" + }, "node_modules/cli-cursor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", @@ -567,6 +604,14 @@ "mimic-response": "^1.0.0" } }, + "node_modules/code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -605,8 +650,7 @@ "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 + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, "node_modules/configstore": { "version": "5.0.1", @@ -624,6 +668,11 @@ "node": ">=8" } }, + "node_modules/console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" + }, "node_modules/core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", @@ -725,6 +774,14 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/defer-to-connect": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", @@ -741,6 +798,11 @@ "node": ">=0.4.0" } }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" + }, "node_modules/detect-indent": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.0.0.tgz", @@ -749,6 +811,17 @@ "node": ">=8" } }, + "node_modules/detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=", + "bin": { + "detect-libc": "bin/detect-libc.js" + }, + "engines": { + "node": ">=0.10" + } + }, "node_modules/diff": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", @@ -1028,11 +1101,18 @@ "integrity": "sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4=", "dev": true }, + "node_modules/fs-minipass": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz", + "integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==", + "dependencies": { + "minipass": "^2.6.0" + } + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, "node_modules/fsevents": { "version": "2.1.3", @@ -1047,6 +1127,64 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/gauge": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", + "dependencies": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + } + }, + "node_modules/gauge/node_modules/ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gauge/node_modules/is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dependencies": { + "number-is-nan": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gauge/node_modules/string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dependencies": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gauge/node_modules/strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", @@ -1082,7 +1220,6 @@ "version": "7.1.6", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -1174,6 +1311,11 @@ "node": ">=8" } }, + "node_modules/has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" + }, "node_modules/he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", @@ -1225,6 +1367,14 @@ "node": ">=0.10.0" } }, + "node_modules/ignore-walk": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.3.tgz", + "integrity": "sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw==", + "dependencies": { + "minimatch": "^3.0.4" + } + }, "node_modules/imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", @@ -1237,7 +1387,6 @@ "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" @@ -1246,8 +1395,12 @@ "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 + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" }, "node_modules/inquirer": { "version": "7.3.3", @@ -1357,6 +1510,11 @@ "resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz", "integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==" }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -1574,7 +1732,6 @@ "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" }, @@ -1582,6 +1739,39 @@ "node": "*" } }, + "node_modules/minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" + }, + "node_modules/minipass": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz", + "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", + "dependencies": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + } + }, + "node_modules/minizlib": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz", + "integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==", + "dependencies": { + "minipass": "^2.9.0" + } + }, + "node_modules/mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dependencies": { + "minimist": "^1.2.5" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, "node_modules/mocha": { "version": "8.2.1", "resolved": "https://registry.npmjs.org/mocha/-/mocha-8.2.1.tgz", @@ -1667,6 +1857,11 @@ "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==" }, + "node_modules/nan": { + "version": "2.14.2", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.2.tgz", + "integrity": "sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==" + }, "node_modules/nanoid": { "version": "3.1.12", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.12.tgz", @@ -1679,6 +1874,30 @@ "node": "^10 || ^12 || >=13.7" } }, + "node_modules/needle": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/needle/-/needle-2.6.0.tgz", + "integrity": "sha512-KKYdza4heMsEfSWD7VPUIz3zX2XDwOyX2d+geb4vrERZMT5RMU6ujjaD+I5Yr54uZxQ2w6XRTAhHBbSCyovZBg==", + "dependencies": { + "debug": "^3.2.6", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" + }, + "bin": { + "needle": "bin/needle" + }, + "engines": { + "node": ">= 4.4.x" + } + }, + "node_modules/needle/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dependencies": { + "ms": "^2.1.1" + } + }, "node_modules/node-cleanup": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/node-cleanup/-/node-cleanup-2.1.2.tgz", @@ -1693,6 +1912,47 @@ "node": "4.x || >=6.0.0" } }, + "node_modules/node-pre-gyp": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.15.0.tgz", + "integrity": "sha512-7QcZa8/fpaU/BKenjcaeFF9hLz2+7S9AqyXFhlH/rilsQ/hPZKK32RtR5EQHJElgu+q5RfbJ34KriI79UWaorA==", + "deprecated": "Please upgrade to @mapbox/node-pre-gyp: the non-scoped node-pre-gyp package is deprecated and only the @mapbox scoped package will recieve updates in the future", + "dependencies": { + "detect-libc": "^1.0.2", + "mkdirp": "^0.5.3", + "needle": "^2.5.0", + "nopt": "^4.0.1", + "npm-packlist": "^1.1.6", + "npmlog": "^4.0.2", + "rc": "^1.2.7", + "rimraf": "^2.6.1", + "semver": "^5.3.0", + "tar": "^4.4.2" + }, + "bin": { + "node-pre-gyp": "bin/node-pre-gyp" + } + }, + "node_modules/node-pre-gyp/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/nopt": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz", + "integrity": "sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg==", + "dependencies": { + "abbrev": "1", + "osenv": "^0.1.4" + }, + "bin": { + "nopt": "bin/nopt.js" + } + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -1710,6 +1970,40 @@ "node": ">=8" } }, + "node_modules/npm-bundled": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.1.tgz", + "integrity": "sha512-gqkfgGePhTpAEgUsGEgcq1rqPXA+tv/aVBlgEzfXwA1yiUJF7xtEt3CtVwOjNYQOVknDk0F20w58Fnm3EtG0fA==", + "dependencies": { + "npm-normalize-package-bin": "^1.0.1" + } + }, + "node_modules/npm-normalize-package-bin": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz", + "integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==" + }, + "node_modules/npm-packlist": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.8.tgz", + "integrity": "sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A==", + "dependencies": { + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1", + "npm-normalize-package-bin": "^1.0.1" + } + }, + "node_modules/npmlog": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", + "dependencies": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, "node_modules/num-or-not": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/num-or-not/-/num-or-not-1.0.1.tgz", @@ -1718,6 +2012,14 @@ "trim": "0.0.1" } }, + "node_modules/number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/oauth-sign": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", @@ -1726,6 +2028,14 @@ "node": "*" } }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -1745,6 +2055,14 @@ "node": ">=6" } }, + "node_modules/os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/os-tmpdir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", @@ -1753,6 +2071,15 @@ "node": ">=0.10.0" } }, + "node_modules/osenv": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", + "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", + "dependencies": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, "node_modules/p-cancelable": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.0.tgz", @@ -1807,7 +2134,6 @@ "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" } @@ -1861,6 +2187,11 @@ "resolved": "https://registry.npmjs.org/prism-media/-/prism-media-1.2.3.tgz", "integrity": "sha512-fSrR66n0l6roW9Rx4rSLMyTPTjRTiXy5RVqDOurACQ6si1rKHHKDU5gwBJoCsIV0R3o9gi+K50akl/qyw1C74A==" }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, "node_modules/ps-tree": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/ps-tree/-/ps-tree-1.2.0.tgz", @@ -1926,6 +2257,47 @@ "safe-buffer": "^5.1.0" } }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/rc/node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/readable-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, "node_modules/readdirp": { "version": "3.5.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", @@ -2017,6 +2389,17 @@ "node": ">=8" } }, + "node_modules/rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, "node_modules/run-async": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", @@ -2081,8 +2464,7 @@ "node_modules/set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", - "dev": true + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" }, "node_modules/setimmediate": { "version": "1.0.5", @@ -2115,6 +2497,57 @@ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==" }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/simple-get": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.0.tgz", + "integrity": "sha512-bCR6cP+aTdScaQCnQKbPKtJOKDp/hj9EDLJo3Nw4y1QksqaovlW/bnptB6/c1e+qmNIDHRK+oXFDdEqBT8WzUA==", + "dependencies": { + "decompress-response": "^4.2.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "node_modules/simple-get/node_modules/decompress-response": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz", + "integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==", + "dependencies": { + "mimic-response": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/simple-get/node_modules/mimic-response": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz", + "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/snekfetch": { "version": "3.6.4", "resolved": "https://registry.npmjs.org/snekfetch/-/snekfetch-3.6.4.tgz", @@ -2196,6 +2629,19 @@ "duplexer": "~0.1.1" } }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, "node_modules/string-argv": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.1.2.tgz", @@ -2249,6 +2695,23 @@ "node": ">=8" } }, + "node_modules/tar": { + "version": "4.4.13", + "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.13.tgz", + "integrity": "sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==", + "dependencies": { + "chownr": "^1.1.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.8.6", + "minizlib": "^1.2.1", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.2", + "yallist": "^3.0.3" + }, + "engines": { + "node": ">=4.5" + } + }, "node_modules/through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", @@ -2455,6 +2918,11 @@ "lodash.clonedeep": "^4.5.0" } }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, "node_modules/uuid": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", @@ -2510,7 +2978,6 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", - "dev": true, "dependencies": { "string-width": "^1.0.2 || 2" } @@ -2519,7 +2986,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true, "engines": { "node": ">=4" } @@ -2528,7 +2994,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true, "engines": { "node": ">=4" } @@ -2537,7 +3002,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, "dependencies": { "is-fullwidth-code-point": "^2.0.0", "strip-ansi": "^4.0.0" @@ -2550,7 +3014,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, "dependencies": { "ansi-regex": "^3.0.0" }, @@ -2713,6 +3176,11 @@ "integrity": "sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ==", "dev": true }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" + }, "node_modules/yargs": { "version": "13.3.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", @@ -3052,6 +3520,11 @@ "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", "dev": true }, + "abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" + }, "abort-controller": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", @@ -3109,6 +3582,20 @@ "picomatch": "^2.0.4" } }, + "aproba": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" + }, + "are-we-there-yet": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", + "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, "arg": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", @@ -3163,8 +3650,7 @@ "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" }, "bcrypt-pbkdf": { "version": "1.0.2", @@ -3191,7 +3677,6 @@ "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" @@ -3243,6 +3728,16 @@ "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", "dev": true }, + "canvas": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/canvas/-/canvas-2.7.0.tgz", + "integrity": "sha512-pzCxtkHb+5su5MQjTtepMDlIOtaXo277x0C0u3nMOxtkhTyQ+h2yNKhlROAaDllWgRyePAUitC08sXw26Eb6aw==", + "requires": { + "nan": "^2.14.0", + "node-pre-gyp": "^0.15.0", + "simple-get": "^3.0.3" + } + }, "caseless": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", @@ -3278,6 +3773,11 @@ "readdirp": "~3.5.0" } }, + "chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" + }, "cli-cursor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", @@ -3350,6 +3850,11 @@ "mimic-response": "^1.0.0" } }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" + }, "color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -3379,8 +3884,7 @@ "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 + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, "configstore": { "version": "5.0.1", @@ -3395,6 +3899,11 @@ "xdg-basedir": "^4.0.0" } }, + "console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" + }, "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", @@ -3473,6 +3982,11 @@ } } }, + "deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" + }, "defer-to-connect": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", @@ -3483,11 +3997,21 @@ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" }, + "delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" + }, "detect-indent": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.0.0.tgz", "integrity": "sha512-oSyFlqaTHCItVRGK5RmrmjB+CmaMOW7IaNA/kdxqhoa6d17j/5ce9O9eWXmV/KEdRwqpQA+Vqe8a8Bsybu4YnA==" }, + "detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=" + }, "diff": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", @@ -3706,11 +4230,18 @@ "integrity": "sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4=", "dev": true }, + "fs-minipass": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz", + "integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==", + "requires": { + "minipass": "^2.6.0" + } + }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, "fsevents": { "version": "2.1.3", @@ -3719,6 +4250,54 @@ "dev": true, "optional": true }, + "gauge": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "requires": { + "ansi-regex": "^2.0.0" + } + } + } + }, "get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", @@ -3745,7 +4324,6 @@ "version": "7.1.6", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -3812,6 +4390,11 @@ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" }, + "has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" + }, "he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", @@ -3850,6 +4433,14 @@ "safer-buffer": ">= 2.1.2 < 3" } }, + "ignore-walk": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.3.tgz", + "integrity": "sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw==", + "requires": { + "minimatch": "^3.0.4" + } + }, "imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", @@ -3859,7 +4450,6 @@ "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" @@ -3868,8 +4458,12 @@ "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" }, "inquirer": { "version": "7.3.3", @@ -3952,6 +4546,11 @@ "resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz", "integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==" }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -4126,11 +4725,40 @@ "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" } }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" + }, + "minipass": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz", + "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + } + }, + "minizlib": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz", + "integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==", + "requires": { + "minipass": "^2.9.0" + } + }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "requires": { + "minimist": "^1.2.5" + } + }, "mocha": { "version": "8.2.1", "resolved": "https://registry.npmjs.org/mocha/-/mocha-8.2.1.tgz", @@ -4202,12 +4830,37 @@ "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==" }, + "nan": { + "version": "2.14.2", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.2.tgz", + "integrity": "sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==" + }, "nanoid": { "version": "3.1.12", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.12.tgz", "integrity": "sha512-1qstj9z5+x491jfiC4Nelk+f8XBad7LN20PmyWINJEMRSf3wcAjAWysw1qaA8z6NSKe2sjq1hRSDpBH5paCb6A==", "dev": true }, + "needle": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/needle/-/needle-2.6.0.tgz", + "integrity": "sha512-KKYdza4heMsEfSWD7VPUIz3zX2XDwOyX2d+geb4vrERZMT5RMU6ujjaD+I5Yr54uZxQ2w6XRTAhHBbSCyovZBg==", + "requires": { + "debug": "^3.2.6", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "requires": { + "ms": "^2.1.1" + } + } + } + }, "node-cleanup": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/node-cleanup/-/node-cleanup-2.1.2.tgz", @@ -4219,6 +4872,39 @@ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==" }, + "node-pre-gyp": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.15.0.tgz", + "integrity": "sha512-7QcZa8/fpaU/BKenjcaeFF9hLz2+7S9AqyXFhlH/rilsQ/hPZKK32RtR5EQHJElgu+q5RfbJ34KriI79UWaorA==", + "requires": { + "detect-libc": "^1.0.2", + "mkdirp": "^0.5.3", + "needle": "^2.5.0", + "nopt": "^4.0.1", + "npm-packlist": "^1.1.6", + "npmlog": "^4.0.2", + "rc": "^1.2.7", + "rimraf": "^2.6.1", + "semver": "^5.3.0", + "tar": "^4.4.2" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + } + } + }, + "nopt": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz", + "integrity": "sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg==", + "requires": { + "abbrev": "1", + "osenv": "^0.1.4" + } + }, "normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -4230,6 +4916,40 @@ "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.0.tgz", "integrity": "sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ==" }, + "npm-bundled": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.1.tgz", + "integrity": "sha512-gqkfgGePhTpAEgUsGEgcq1rqPXA+tv/aVBlgEzfXwA1yiUJF7xtEt3CtVwOjNYQOVknDk0F20w58Fnm3EtG0fA==", + "requires": { + "npm-normalize-package-bin": "^1.0.1" + } + }, + "npm-normalize-package-bin": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz", + "integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==" + }, + "npm-packlist": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.8.tgz", + "integrity": "sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A==", + "requires": { + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1", + "npm-normalize-package-bin": "^1.0.1" + } + }, + "npmlog": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, "num-or-not": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/num-or-not/-/num-or-not-1.0.1.tgz", @@ -4238,11 +4958,21 @@ "trim": "0.0.1" } }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" + }, "oauth-sign": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -4259,11 +4989,25 @@ "mimic-fn": "^2.1.0" } }, + "os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" + }, "os-tmpdir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" }, + "osenv": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", + "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, "p-cancelable": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.0.tgz", @@ -4302,8 +5046,7 @@ "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 + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" }, "path-key": { "version": "3.1.1", @@ -4342,6 +5085,11 @@ "resolved": "https://registry.npmjs.org/prism-media/-/prism-media-1.2.3.tgz", "integrity": "sha512-fSrR66n0l6roW9Rx4rSLMyTPTjRTiXy5RVqDOurACQ6si1rKHHKDU5gwBJoCsIV0R3o9gi+K50akl/qyw1C74A==" }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, "ps-tree": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/ps-tree/-/ps-tree-1.2.0.tgz", @@ -4389,6 +5137,45 @@ "safe-buffer": "^5.1.0" } }, + "rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "requires": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "dependencies": { + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" + } + } + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + } + } + }, "readdirp": { "version": "3.5.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", @@ -4467,6 +5254,14 @@ "signal-exit": "^3.0.2" } }, + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "requires": { + "glob": "^7.1.3" + } + }, "run-async": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", @@ -4522,8 +5317,7 @@ "set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", - "dev": true + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" }, "setimmediate": { "version": "1.0.5", @@ -4550,6 +5344,36 @@ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==" }, + "simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==" + }, + "simple-get": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.0.tgz", + "integrity": "sha512-bCR6cP+aTdScaQCnQKbPKtJOKDp/hj9EDLJo3Nw4y1QksqaovlW/bnptB6/c1e+qmNIDHRK+oXFDdEqBT8WzUA==", + "requires": { + "decompress-response": "^4.2.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + }, + "dependencies": { + "decompress-response": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz", + "integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==", + "requires": { + "mimic-response": "^2.0.0" + } + }, + "mimic-response": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz", + "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==" + } + } + }, "snekfetch": { "version": "3.6.4", "resolved": "https://registry.npmjs.org/snekfetch/-/snekfetch-3.6.4.tgz", @@ -4618,6 +5442,21 @@ "duplexer": "~0.1.1" } }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + } + } + }, "string-argv": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.1.2.tgz", @@ -4656,6 +5495,20 @@ "has-flag": "^4.0.0" } }, + "tar": { + "version": "4.4.13", + "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.13.tgz", + "integrity": "sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==", + "requires": { + "chownr": "^1.1.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.8.6", + "minizlib": "^1.2.1", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.2", + "yallist": "^3.0.3" + } + }, "through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", @@ -4819,6 +5672,11 @@ "lodash.clonedeep": "^4.5.0" } }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, "uuid": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", @@ -4862,7 +5720,6 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", - "dev": true, "requires": { "string-width": "^1.0.2 || 2" }, @@ -4870,20 +5727,17 @@ "ansi-regex": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" }, "is-fullwidth-code-point": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" }, "string-width": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, "requires": { "is-fullwidth-code-point": "^2.0.0", "strip-ansi": "^4.0.0" @@ -4893,7 +5747,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, "requires": { "ansi-regex": "^3.0.0" } @@ -5027,6 +5880,11 @@ "integrity": "sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ==", "dev": true }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" + }, "yargs": { "version": "13.3.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", diff --git a/package.json b/package.json index 855d84d..2d85b31 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,7 @@ "main": "dist/index.js", "private": true, "dependencies": { + "canvas": "^2.7.0", "chalk": "^4.1.0", "discord.js": "^12.5.1", "discord.js-lavalink-lib": "^0.1.8", diff --git a/src/commands/admin.ts b/src/commands/admin.ts index f174750..2ea6d77 100644 --- a/src/commands/admin.ts +++ b/src/commands/admin.ts @@ -55,6 +55,79 @@ export default new Command({ $.channel.send(`The custom prefix for this guild is now \`${$.args[0]}\`.`); } }) + }), + welcome: new Command({ + description: "Configure your server's welcome settings for the bot.", + usage: "type/channel <...>", + run: "You need to specify which part to modify, `type`/`channel`.", + subcommands: { + type: new Command({ + description: + "Sets how welcome messages are displayed for your server. Removes welcome messages if unspecified.", + usage: "`none`/`text`/`graphical`", + async run($) { + if ($.guild) { + Storage.getGuild($.guild.id).welcomeType = "none"; + Storage.save(); + $.channel.send("Set this server's welcome type to `none`."); + } else { + $.channel.send("You must use this command in a server."); + } + }, + // I should probably make this a bit more dynamic... Oh well. + subcommands: { + text: new Command({ + async run($) { + if ($.guild) { + Storage.getGuild($.guild.id).welcomeType = "text"; + Storage.save(); + $.channel.send("Set this server's welcome type to `text`."); + } else { + $.channel.send("You must use this command in a server."); + } + } + }), + graphical: new Command({ + async run($) { + if ($.guild) { + Storage.getGuild($.guild.id).welcomeType = "graphical"; + Storage.save(); + $.channel.send("Set this server's welcome type to `graphical`."); + } else { + $.channel.send("You must use this command in a server."); + } + } + }) + } + }), + channel: new Command({ + description: "Sets the welcome channel for your server. Type `#` to reference the channel.", + usage: "", + run: "You need to specify a channel.", + // If/when channel types come out, this will be the perfect candidate to test it. + any: new Command({ + async run($) { + if ($.guild) { + const match = $.args[0].match(/^<#(\d{17,19})>$/); + + if (match) { + Storage.getGuild($.guild.id).welcomeChannel = match[1]; + Storage.save(); + $.channel.send( + `Successfully set this server's welcome channel to ${match[0]}.` + ); + } else { + $.channel.send( + "You must provide a reference channel. You can do this by typing `#` then searching for the proper channel." + ); + } + } else { + $.channel.send("You must use this command in a server."); + } + } + }) + }) + } }) } }), diff --git a/src/core/structures.ts b/src/core/structures.ts index ba01e1f..384184c 100644 --- a/src/core/structures.ts +++ b/src/core/structures.ts @@ -51,9 +51,24 @@ class User { class Guild { public prefix: string | null; + public welcomeType: "none" | "text" | "graphical"; + public welcomeChannel: string | null; constructor(data?: GenericJSON) { this.prefix = select(data?.prefix, null, String); + this.welcomeChannel = select(data?.welcomeChannel, null, String); + + switch (data?.welcomeType) { + case "text": + this.welcomeType = "text"; + break; + case "graphical": + this.welcomeType = "graphical"; + break; + default: + this.welcomeType = "none"; + break; + } } } diff --git a/src/events/guildCreate.ts b/src/events/guildCreate.ts index 2820db7..0fe94dd 100644 --- a/src/events/guildCreate.ts +++ b/src/events/guildCreate.ts @@ -3,8 +3,12 @@ import $ from "../core/lib"; import {updateGlobalEmoteRegistry} from "../core/lib"; export default new Event<"guildCreate">({ - on() { - $.log("Updated emote registry."); + on(guild) { + $.log( + `[GUILD JOIN] ${guild.name} (${guild.id}) added the bot. Owner: ${guild.owner!.user.tag} (${ + guild.owner!.user.id + }). Updated emote registry.` + ); updateGlobalEmoteRegistry(); } }); diff --git a/src/events/guildDelete.ts b/src/events/guildDelete.ts index 8fed108..d1a4f99 100644 --- a/src/events/guildDelete.ts +++ b/src/events/guildDelete.ts @@ -3,8 +3,8 @@ import $ from "../core/lib"; import {updateGlobalEmoteRegistry} from "../core/lib"; export default new Event<"guildDelete">({ - on() { - $.log("Updated emote registry."); + on(guild) { + $.log(`[GUILD LEAVE] ${guild.name} (${guild.id}) removed the bot. Updated emote registry.`); updateGlobalEmoteRegistry(); } }); diff --git a/src/events/guildMemberAdd.ts b/src/events/guildMemberAdd.ts new file mode 100644 index 0000000..ecaab3b --- /dev/null +++ b/src/events/guildMemberAdd.ts @@ -0,0 +1,73 @@ +import Event from "../core/event"; +import $, {parseVars} from "../core/lib"; +import {createCanvas, loadImage, Canvas} from "canvas"; +import {Storage} from "../core/structures"; +import {TextChannel, MessageAttachment} from "discord.js"; + +function applyText(canvas: Canvas, text: string) { + const ctx = canvas.getContext("2d"); + let fontSize = 70; + + do { + ctx.font = `${(fontSize -= 10)}px sans-serif`; + } while (ctx.measureText(text).width > canvas.width - 300); + + return ctx.font; +} + +export default new Event<"guildMemberAdd">({ + async on(member) { + const {welcomeType, welcomeChannel} = Storage.getGuild(member.guild.id); + + if (welcomeChannel) { + const channel = member.guild.channels.cache.get(welcomeChannel); + + if (channel && channel.type === "text") { + if (welcomeType === "graphical") { + const canvas = createCanvas(700, 250); + const ctx = canvas.getContext("2d"); + const background = await loadImage( + "https://raw.githubusercontent.com/keanuplayz/TravBot/dev/assets/wallpaper.png" + ); + ctx.drawImage(background, 0, 0, canvas.width, canvas.height); + + ctx.strokeStyle = "#74037b"; + ctx.strokeRect(0, 0, canvas.width, canvas.height); + + ctx.font = "28px sans-serif"; + ctx.fillStyle = "#ffffff"; + ctx.fillText("Welcome to the server,", canvas.width / 2.5, canvas.height / 3.5); + + ctx.font = applyText(canvas, member.displayName); + ctx.fillStyle = "#ffffff"; + ctx.fillText(`${member.displayName}!`, canvas.width / 2.5, canvas.height / 1.5); + + ctx.beginPath(); + ctx.arc(125, 125, 100, 0, Math.PI * 2, true); + ctx.closePath(); + ctx.clip(); + + const avatarURL = + member.user.avatarURL({ + dynamic: true, + size: 2048, + format: "png" + }) ?? member.user.defaultAvatarURL; + const avatar = await loadImage(avatarURL); + ctx.drawImage(avatar, 25, 25, 200, 200); + + const attachment = new MessageAttachment(canvas.toBuffer("image/png"), "welcome-image.png"); + (channel as TextChannel).send(`Welcome \`${member.user.tag}\`!`, attachment); + } else if (welcomeType === "text") { + (channel as TextChannel).send( + parseVars("Say hello to `%user%`, everyone! We all need a warm welcome sometimes :D", { + user: member.user.tag + }) + ); + } + } else { + $.error(`"${welcomeChannel}" is not a valid text channel ID!`); + } + } + } +}); diff --git a/src/events/ready.ts b/src/events/ready.ts index e3fe021..b8467aa 100644 --- a/src/events/ready.ts +++ b/src/events/ready.ts @@ -7,7 +7,9 @@ import {updateGlobalEmoteRegistry} from "../core/lib"; export default new Event<"ready">({ once() { if (client.user) { - $.ready(`Logged in as ${client.user.username}#${client.user.discriminator}.`); + $.ready( + `Logged in as ${client.user.tag}, ready to serve ${client.users.cache.size} users in ${client.guilds.cache.size} servers..` + ); client.user.setActivity({ type: "LISTENING", name: `${Config.prefix}help` From c1b298a407d8b661f60ada3308362e144d16ba7c Mon Sep 17 00:00:00 2001 From: WatDuhHekBro <44940783+WatDuhHekBro@users.noreply.github.com> Date: Fri, 2 Apr 2021 23:11:18 -0500 Subject: [PATCH 123/178] Added welcome message and system logs channel --- src/commands/admin.ts | 53 ++++++++++++++++++++++++++++++++++-- src/core/structures.ts | 6 ++++ src/events/guildCreate.ts | 18 ++++++++++++ src/events/guildDelete.ts | 14 ++++++++++ src/events/guildMemberAdd.ts | 12 +++++--- 5 files changed, 97 insertions(+), 6 deletions(-) diff --git a/src/commands/admin.ts b/src/commands/admin.ts index 2ea6d77..2622087 100644 --- a/src/commands/admin.ts +++ b/src/commands/admin.ts @@ -102,8 +102,18 @@ export default new Command({ }), channel: new Command({ description: "Sets the welcome channel for your server. Type `#` to reference the channel.", - usage: "", - run: "You need to specify a channel.", + usage: "()", + async run($) { + if ($.guild) { + Storage.getGuild($.guild.id).welcomeChannel = $.channel.id; + Storage.save(); + $.channel.send( + `Successfully set ${$.channel} as the welcome channel for this server.` + ); + } else { + $.channel.send("You must use this command in a server."); + } + }, // If/when channel types come out, this will be the perfect candidate to test it. any: new Command({ async run($) { @@ -126,6 +136,32 @@ export default new Command({ } } }) + }), + message: new Command({ + description: + "Sets a custom welcome message for your server. Use `%user%` as the placeholder for the user.", + usage: "()", + async run($) { + if ($.guild) { + Storage.getGuild($.guild.id).welcomeMessage = null; + Storage.save(); + $.channel.send("Reset your server's welcome message to the default."); + } else { + $.channel.send("You must use this command in a server."); + } + }, + any: new Command({ + async run($) { + if ($.guild) { + const message = $.args.join(" "); + Storage.getGuild($.guild.id).welcomeMessage = message; + Storage.save(); + $.channel.send(`Set your server's welcome message to \`${message}\`.`); + } else { + $.channel.send("You must use this command in a server."); + } + } + }) }) } }) @@ -275,6 +311,19 @@ export default new Command({ ); } }) + }), + syslog: new Command({ + description: "Sets up the current channel to receive system logs.", + permission: Command.PERMISSIONS.BOT_ADMIN, + async run($) { + if ($.guild) { + Config.systemLogsChannel = $.channel.id; + Config.save(); + $.channel.send(`Successfully set ${$.channel} as the system logs channel.`); + } else { + $.channel.send("DM system log channels aren't supported."); + } + } }) } }); diff --git a/src/core/structures.ts b/src/core/structures.ts index 384184c..13bdc9f 100644 --- a/src/core/structures.ts +++ b/src/core/structures.ts @@ -3,12 +3,15 @@ import $, {select, GenericJSON, GenericStructure} from "./lib"; import {watch} from "fs"; import {Guild as DiscordGuild, Snowflake} from "discord.js"; +// Maybe use getters and setters to auto-save on set? + class ConfigStructure extends GenericStructure { public token: string; public prefix: string; public owner: string; public admins: string[]; public support: string[]; + public systemLogsChannel: string | null; constructor(data: GenericJSON) { super("config"); @@ -17,6 +20,7 @@ class ConfigStructure extends GenericStructure { this.owner = select(data.owner, "", String); this.admins = select(data.admins, [], String, true); this.support = select(data.support, [], String, true); + this.systemLogsChannel = select(data.systemLogsChannel, null, String); } } @@ -53,10 +57,12 @@ class Guild { public prefix: string | null; public welcomeType: "none" | "text" | "graphical"; public welcomeChannel: string | null; + public welcomeMessage: string | null; constructor(data?: GenericJSON) { this.prefix = select(data?.prefix, null, String); this.welcomeChannel = select(data?.welcomeChannel, null, String); + this.welcomeMessage = select(data?.welcomeMessage, null, String); switch (data?.welcomeType) { case "text": diff --git a/src/events/guildCreate.ts b/src/events/guildCreate.ts index 0fe94dd..02189b3 100644 --- a/src/events/guildCreate.ts +++ b/src/events/guildCreate.ts @@ -1,6 +1,9 @@ import Event from "../core/event"; import $ from "../core/lib"; import {updateGlobalEmoteRegistry} from "../core/lib"; +import {client} from "../index"; +import {Config} from "../core/structures"; +import {TextChannel} from "discord.js"; export default new Event<"guildCreate">({ on(guild) { @@ -9,6 +12,21 @@ export default new Event<"guildCreate">({ guild.owner!.user.id }). Updated emote registry.` ); + + if (Config.systemLogsChannel) { + const channel = client.channels.cache.get(Config.systemLogsChannel); + + if (channel && channel.type === "text") { + (channel as TextChannel).send( + `TravBot joined: \`${guild.name}\`. The owner of this guild is: \`${guild.owner!.user.tag}\` (\`${ + guild.owner!.user.id + }\`)` + ); + } else { + console.warn(`${Config.systemLogsChannel} is not a valid text channel for system logs!`); + } + } + updateGlobalEmoteRegistry(); } }); diff --git a/src/events/guildDelete.ts b/src/events/guildDelete.ts index d1a4f99..41c656f 100644 --- a/src/events/guildDelete.ts +++ b/src/events/guildDelete.ts @@ -1,10 +1,24 @@ import Event from "../core/event"; import $ from "../core/lib"; import {updateGlobalEmoteRegistry} from "../core/lib"; +import {client} from "../index"; +import {Config} from "../core/structures"; +import {TextChannel} from "discord.js"; export default new Event<"guildDelete">({ on(guild) { $.log(`[GUILD LEAVE] ${guild.name} (${guild.id}) removed the bot. Updated emote registry.`); + + if (Config.systemLogsChannel) { + const channel = client.channels.cache.get(Config.systemLogsChannel); + + if (channel && channel.type === "text") { + (channel as TextChannel).send(`\`${guild.name}\` (\`${guild.id}\`) removed the bot.`); + } else { + console.warn(`${Config.systemLogsChannel} is not a valid text channel for system logs!`); + } + } + updateGlobalEmoteRegistry(); } }); diff --git a/src/events/guildMemberAdd.ts b/src/events/guildMemberAdd.ts index ecaab3b..1855094 100644 --- a/src/events/guildMemberAdd.ts +++ b/src/events/guildMemberAdd.ts @@ -17,7 +17,7 @@ function applyText(canvas: Canvas, text: string) { export default new Event<"guildMemberAdd">({ async on(member) { - const {welcomeType, welcomeChannel} = Storage.getGuild(member.guild.id); + const {welcomeType, welcomeChannel, welcomeMessage} = Storage.getGuild(member.guild.id); if (welcomeChannel) { const channel = member.guild.channels.cache.get(welcomeChannel); @@ -60,9 +60,13 @@ export default new Event<"guildMemberAdd">({ (channel as TextChannel).send(`Welcome \`${member.user.tag}\`!`, attachment); } else if (welcomeType === "text") { (channel as TextChannel).send( - parseVars("Say hello to `%user%`, everyone! We all need a warm welcome sometimes :D", { - user: member.user.tag - }) + parseVars( + welcomeMessage || + "Say hello to `%user%`, everyone! We all need a warm welcome sometimes :D", + { + user: member.user.tag + } + ) ); } } else { From f650faee8985eb0277e19bb0b1a86115b35ae0b0 Mon Sep 17 00:00:00 2001 From: WatDuhHekBro <44940783+WatDuhHekBro@users.noreply.github.com> Date: Sat, 3 Apr 2021 04:58:20 -0500 Subject: [PATCH 124/178] Reorganized code dealing with the command class --- src/commands/system/help.ts | 82 ++-------------- src/core/command.ts | 184 +++++++++++++++++++++++------------- src/core/handler.ts | 179 +++++++++-------------------------- src/core/loader.ts | 103 ++++++++++++++++++++ 4 files changed, 269 insertions(+), 279 deletions(-) create mode 100644 src/core/loader.ts diff --git a/src/commands/system/help.ts b/src/commands/system/help.ts index 8ef9fe2..6277fde 100644 --- a/src/commands/system/help.ts +++ b/src/commands/system/help.ts @@ -1,6 +1,6 @@ import Command from "../../core/command"; import {toTitleCase} from "../../core/lib"; -import {loadableCommands, categories} from "../../core/command"; +import {loadableCommands, categories} from "../../core/loader"; import {getPermissionName} from "../../core/permissions"; export default new Command({ @@ -32,69 +32,7 @@ export default new Command({ }, any: new Command({ async run($) { - const commands = await loadableCommands; - let header = $.args.shift() as string; - let command = commands.get(header); - - if (!command || header === "test") { - $.channel.send(`No command found by the name \`${header}\`!`); - return; - } - - if (command.originalCommandName) header = command.originalCommandName; - else console.warn(`originalCommandName isn't defined for ${header}?!`); - - let permLevel = command.permission ?? 0; - let usage = command.usage; - let invalid = false; - - let selectedCategory = "Unknown"; - - for (const [category, headers] of categories) { - if (headers.includes(header)) { - if (selectedCategory !== "Unknown") - console.warn( - `Command "${header}" is somehow in multiple categories. This means that the command loading stage probably failed in properly adding categories.` - ); - else selectedCategory = toTitleCase(category); - } - } - - for (const param of $.args) { - const type = command.resolve(param); - command = command.get(param); - permLevel = command.permission ?? permLevel; - - if (permLevel === -1) permLevel = command.permission; - - switch (type) { - case Command.TYPES.SUBCOMMAND: - header += ` ${command.originalCommandName}`; - break; - case Command.TYPES.USER: - header += " "; - break; - case Command.TYPES.NUMBER: - header += " "; - break; - case Command.TYPES.ANY: - header += " "; - break; - default: - header += ` ${param}`; - break; - } - - if (type === Command.TYPES.NONE) { - invalid = true; - break; - } - } - - if (invalid) { - $.channel.send(`No command found by the name \`${header}\`!`); - return; - } + // [category, commandName, command, subcommandInfo] = resolveCommandInfo(); let append = ""; @@ -123,18 +61,10 @@ export default new Command({ append = "Usages:" + (list.length > 0 ? `\n${list.join("\n")}` : " None."); } else append = `Usage: \`${header} ${usage}\``; - let aliases = "None"; - - if (command.aliases.length > 0) { - aliases = ""; - - for (let i = 0; i < command.aliases.length; i++) { - const alias = command.aliases[i]; - aliases += `\`${alias}\``; - - if (i !== command.aliases.length - 1) aliases += ", "; - } - } + const formattedAliases: string[] = []; + for (const alias of command.aliases) formattedAliases.push(`\`${alias}\``); + // Short circuit an empty string, in this case, if there are no aliases. + const aliases = formattedAliases.join(", ") || "None"; $.channel.send( `Command: \`${header}\`\nAliases: ${aliases}\nCategory: \`${selectedCategory}\`\nPermission Required: \`${getPermissionName( diff --git a/src/core/command.ts b/src/core/command.ts index c13b3a3..4cfb777 100644 --- a/src/core/command.ts +++ b/src/core/command.ts @@ -2,7 +2,8 @@ import {parseVars} from "./lib"; import {Collection} from "discord.js"; import {Client, Message, TextChannel, DMChannel, NewsChannel, Guild, User, GuildMember} from "discord.js"; import {getPrefix} from "../core/structures"; -import glob from "glob"; +import {SingleMessageOptions} from "./libd"; +import {hasPermission, getPermissionLevel, getPermissionName} from "./permissions"; interface CommandMenu { args: any[]; @@ -27,7 +28,7 @@ interface CommandOptions { any?: Command; } -export enum TYPES { +enum TYPES { SUBCOMMAND, USER, NUMBER, @@ -47,7 +48,6 @@ export default class Command { public user: Command | null; public number: Command | null; public any: Command | null; - public static readonly TYPES = TYPES; constructor(options?: CommandOptions) { this.description = options?.description || "No description."; @@ -120,6 +120,67 @@ export default class Command { } else this.run($).catch(handler.bind($)); } + // Will return null if it successfully executes, SingleMessageOptions if there's an error (to let the user know what it is). + public async actualExecute(args: string[], tmp: any): Promise { + // Subcommand Recursion // + let command = commands.get(header)!; + //resolveSubcommand() + const params: any[] = []; + let isEndpoint = false; + let permLevel = command.permission ?? 0; + + for (const param of args) { + if (command.endpoint) { + if (command.subcommands.size > 0 || command.user || command.number || command.any) + console.warn("An endpoint cannot have subcommands!"); + isEndpoint = true; + break; + } + + const type = command.resolve(param); + command = command.get(param); + permLevel = command.permission ?? permLevel; + + if (type === TYPES.USER) { + const id = param.match(/\d+/g)![0]; + try { + params.push(await message.client.users.fetch(id)); + } catch (error) { + return message.channel.send(`No user found by the ID \`${id}\`!`); + } + } else if (type === TYPES.NUMBER) params.push(Number(param)); + else if (type !== TYPES.SUBCOMMAND) params.push(param); + } + + if (!message.member) + return console.warn("This command was likely called from a DM channel meaning the member object is null."); + + if (!hasPermission(message.member, permLevel)) { + const userPermLevel = getPermissionLevel(message.member); + return message.channel.send( + `You don't have access to this command! Your permission level is \`${getPermissionName( + userPermLevel + )}\` (${userPermLevel}), but this command requires a permission level of \`${getPermissionName( + permLevel + )}\` (${permLevel}).` + ); + } + + if (isEndpoint) return message.channel.send("Too many arguments!"); + + command.execute({ + args: params, + author: message.author, + channel: message.channel, + client: message.client, + guild: message.guild, + member: message.member, + message: message + }); + + return null; + } + public resolve(param: string): TYPES { if (this.subcommands.has(param)) return TYPES.SUBCOMMAND; // Any Discord ID format will automatically format to a user ID. @@ -154,84 +215,73 @@ export default class Command { return command; } -} -// Internally, it'll keep its original capitalization. It's up to you to convert it to title case when you make a help command. -export const categories = new Collection(); + // Returns: [category, command name, command, available subcommands: [type, subcommand]] + public resolveCommandInfo(args: string[]): [string, string, Command, Collection] { + const commands = await loadableCommands; + let header = args.shift(); + let command = commands.get(header); -/** Returns the cache of the commands if it exists and searches the directory if not. */ -export const loadableCommands = (async () => { - const commands = new Collection(); - // Include all .ts files recursively in "src/commands/". - const files = await globP("src/commands/**/*.ts"); - // Extract the usable parts from "src/commands/" if: - // - The path is 1 to 2 subdirectories (a or a/b, not a/b/c) - // - Any leading directory isn't "modules" - // - The filename doesn't end in .test.ts (for jest testing) - // - The filename cannot be the hardcoded top-level "template.ts", reserved for generating templates - const pattern = /src\/commands\/(?!template\.ts)(?!modules\/)(\w+(?:\/\w+)?)(?:test\.)?\.ts/; - const lists: {[category: string]: string[]} = {}; + if (!command || header === "test") { + $.channel.send(`No command found by the name \`${header}\`!`); + return; + } - for (const path of files) { - const match = pattern.exec(path); + if (command.originalCommandName) header = command.originalCommandName; + else console.warn(`originalCommandName isn't defined for ${header}?!`); - if (match) { - const commandID = match[1]; // e.g. "utilities/info" - const slashIndex = commandID.indexOf("/"); - const isMiscCommand = slashIndex !== -1; - const category = isMiscCommand ? commandID.substring(0, slashIndex) : "miscellaneous"; - const commandName = isMiscCommand ? commandID.substring(slashIndex + 1) : commandID; // e.g. "info" - // If the dynamic import works, it must be an object at the very least. Then, just test to see if it's a proper instance. - const command = (await import(`../commands/${commandID}`)).default as unknown; + let permLevel = command.permission ?? 0; + let usage = command.usage; + let invalid = false; - if (command instanceof Command) { - command.originalCommandName = commandName; + let selectedCategory = "Unknown"; - if (commands.has(commandName)) { + for (const [category, headers] of categories) { + if (headers.includes(header)) { + if (selectedCategory !== "Unknown") console.warn( - `Command "${commandName}" already exists! Make sure to make each command uniquely identifiable across categories!` + `Command "${header}" is somehow in multiple categories. This means that the command loading stage probably failed in properly adding categories.` ); - } else { - commands.set(commandName, command); - } - - for (const alias of command.aliases) { - if (commands.has(alias)) { - console.warn( - `Top-level alias "${alias}" from command "${commandID}" already exists either as a command or alias!` - ); - } else { - commands.set(alias, command); - } - } - - if (!(category in lists)) lists[category] = []; - lists[category].push(commandName); - - console.log(`Loading Command: ${commandID}`); - } else { - console.warn(`Command "${commandID}" has no default export which is a Command instance!`); + else selectedCategory = toTitleCase(category); } } - } - for (const category in lists) { - categories.set(category, lists[category]); - } + for (const param of args) { + const type = command.resolve(param); + command = command.get(param); + permLevel = command.permission ?? permLevel; - return commands; -})(); + if (permLevel === -1) permLevel = command.permission; -function globP(path: string) { - return new Promise((resolve, reject) => { - glob(path, (error, files) => { - if (error) { - reject(error); - } else { - resolve(files); + switch (type) { + case TYPES.SUBCOMMAND: + header += ` ${command.originalCommandName}`; + break; + case TYPES.USER: + header += " "; + break; + case TYPES.NUMBER: + header += " "; + break; + case TYPES.ANY: + header += " "; + break; + default: + header += ` ${param}`; + break; } - }); - }); + + if (type === TYPES.NONE) { + invalid = true; + break; + } + } + + if (invalid) { + $.channel.send(`No command found by the name \`${header}\`!`); + return; + } + } } // If you use promises, use this function to display the error in chat. diff --git a/src/core/handler.ts b/src/core/handler.ts index 2a9fe42..d0cba3c 100644 --- a/src/core/handler.ts +++ b/src/core/handler.ts @@ -1,28 +1,17 @@ import {client} from "../index"; -import Command, {loadableCommands} from "./command"; -import {hasPermission, getPermissionLevel, getPermissionName} from "./permissions"; +import {loadableCommands} from "./loader"; import {Permissions, Message} from "discord.js"; import {getPrefix} from "./structures"; import {Config} from "./structures"; -/////////// -// Steps // -/////////// -// 1. Someone sends a message in chat. -// 2. Check if bot, then load commands. -// 3. Check if "...". If not, check if "@...". Resolve prefix and cropped message (if possible). -// 4. Test if bot has permission to send messages. -// 5. Once confirmed as a command, resolve the subcommand. -// 6. Check permission level and whether or not it's an endpoint. -// 7. Execute command if all successful. - -// For custom message events that want to cancel this one on certain conditions. +// For custom message events that want to cancel the command handler on certain conditions. const interceptRules: ((message: Message) => boolean)[] = [(message) => message.author.bot]; export function addInterceptRule(handler: (message: Message) => boolean) { interceptRules.push(handler); } +// Note: client.user is only undefined before the bot logs in, so by this point, client.user cannot be undefined. client.on("message", async (message) => { for (const shouldIntercept of interceptRules) { if (shouldIntercept(message)) { @@ -30,139 +19,57 @@ client.on("message", async (message) => { } } - const commands = await loadableCommands; - - let prefix = getPrefix(message.guild); - const originalPrefix = prefix; - let exitEarly = !message.content.startsWith(prefix); - const clientUser = message.client.user; - let usesBotSpecificPrefix = false; - - // If the client user exists, check if it starts with the bot-specific prefix. - if (clientUser) { - // If the prefix starts with the bot-specific prefix, go off that instead (these two options must mutually exclude each other). - // The pattern here has an optional space at the end to capture that and make it not mess with the header and args. - const matches = message.content.match(new RegExp(`^<@!?${clientUser.id}> ?`)); - - if (matches) { - prefix = matches[0]; - exitEarly = false; - usesBotSpecificPrefix = true; - } - } - - // If it doesn't start with the current normal prefix or the bot-specific unique prefix, exit the thread of execution early. - // Inline replies should still be captured here because if it doesn't exit early, two characters for a two-length prefix would still trigger commands. - if (exitEarly) return; - - const [header, ...args] = message.content.substring(prefix.length).split(/ +/); - - // If the message is just the prefix itself, move onto this block. - if (header === "" && args.length === 0) { - // I moved the bot-specific prefix to a separate conditional block to separate the logic. - // And because it listens for the mention as a prefix instead of a free-form mention, inline replies (probably) shouldn't ever trigger this unintentionally. - if (usesBotSpecificPrefix) { - message.channel.send(`${message.author.toString()}, my prefix on this guild is \`${originalPrefix}\`.`); - return; - } - } - - if (!commands.has(header)) return; - + // Continue if the bot has permission to send messages in this channel. if ( - message.channel.type === "text" && - !message.channel.permissionsFor(message.client.user || "")?.has(Permissions.FLAGS.SEND_MESSAGES) + message.channel.type === "dm" || + message.channel.permissionsFor(client.user!)!.has(Permissions.FLAGS.SEND_MESSAGES) ) { - let status; + const text = message.content; + const prefix = getPrefix(message.guild); - if (message.member?.hasPermission(Permissions.FLAGS.ADMINISTRATOR)) - status = - "Because you're a server admin, you have the ability to change that channel's permissions to match if that's what you intended."; - else - status = - "Try using a different channel or contacting a server admin to change permissions of that channel if you think something's wrong."; - - return message.author.send( - `I don't have permission to send messages in ${message.channel.toString()}. ${status}` - ); - } - - console.log( - `${message.author.username}#${message.author.discriminator} executed the command "${header}" with arguments "${args}".` - ); - - // Subcommand Recursion // - let command = commands.get(header)!; - //resolveSubcommand() - - if (!message.member) - return console.warn("This command was likely called from a DM channel meaning the member object is null."); - - if (!hasPermission(message.member, permLevel)) { - const userPermLevel = getPermissionLevel(message.member); - return message.channel.send( - `You don't have access to this command! Your permission level is \`${getPermissionName( - userPermLevel - )}\` (${userPermLevel}), but this command requires a permission level of \`${getPermissionName( - permLevel - )}\` (${permLevel}).` - ); - } - - if (isEndpoint) return message.channel.send("Too many arguments!"); - - // Execute with dynamic library attached. // - // The purpose of using $.bind($) is to clone the function so as to not modify the original $. - // The cloned function doesn't copy the properties, so Object.assign() is used. - // Object.assign() modifies the first element and returns that, the second element applies its properties and the third element applies its own overriding the second one. - command.execute({ - args: params, - author: message.author, - channel: message.channel, - client: message.client, - guild: message.guild, - member: message.member, - message: message - }); -}); - -// Takes a base command and a list of string parameters and returns: -// - The resolved subcommand -// - The resolved parameters -// - Whether or not an endpoint has been broken -// - The permission level required -async function resolveSubcommand(command: Command, args: string[]): [Command, any[], boolean, number] { - const params: any[] = []; - let isEndpoint = false; - let permLevel = command.permission ?? 0; - - for (const param of args) { - if (command.endpoint) { - if (command.subcommands.size > 0 || command.user || command.number || command.any) - console.warn("An endpoint cannot have subcommands!"); - isEndpoint = true; - break; + // First, test if the message is just a ping to the bot. + if (new RegExp(`^<@!?${client.user!.id}>$`).test(text)) { + message.channel.send(`${message.author}, my prefix on this guild is \`${prefix}\`.`); } + // Then check if it's a normal command. + else if (text.startsWith(prefix)) { + const [header, ...args] = text.substring(prefix.length).split(/ +/); + const commands = await loadableCommands; - const type = command.resolve(param); - command = command.get(param); - permLevel = command.permission ?? permLevel; + if (commands.has(header)) { + const command = commands.get(header)!; - if (type === Command.TYPES.USER) { - const id = param.match(/\d+/g)![0]; - try { - params.push(await message.client.users.fetch(id)); - } catch (error) { - return message.channel.send(`No user found by the ID \`${id}\`!`); + // Send the arguments to the command to resolve and execute. + // TMP[MAKE SURE TO REPLACE WITH command.execute WHEN FINISHED] + const result = await command.actualExecute(args, { + author: message.author, + channel: message.channel, + client: message.client, + guild: message.guild, + member: message.member, + message: message + }); + + // If something went wrong, let the user know (like if they don't have permission to use a command). + if (result) { + message.channel.send(result); + } } - } else if (type === Command.TYPES.NUMBER) params.push(Number(param)); - else if (type !== Command.TYPES.SUBCOMMAND) params.push(param); + } + } else { + message.author.send( + `I don't have permission to send messages in ${message.channel}. ${ + message.member!.hasPermission(Permissions.FLAGS.ADMINISTRATOR) + ? "Because you're a server admin, you have the ability to change that channel's permissions to match if that's what you intended." + : "Try using a different channel or contacting a server admin to change permissions of that channel if you think something's wrong." + }` + ); } -} +}); client.once("ready", () => { if (client.user) { - console.ready(`Logged in as ${client.user.username}#${client.user.discriminator}.`); + console.ready(`Logged in as ${client.user.tag}.`); client.user.setActivity({ type: "LISTENING", name: `${Config.prefix}help` diff --git a/src/core/loader.ts b/src/core/loader.ts new file mode 100644 index 0000000..a7f7d7d --- /dev/null +++ b/src/core/loader.ts @@ -0,0 +1,103 @@ +import {Collection} from "discord.js"; +import glob from "glob"; +import Command from "./command"; + +// Internally, it'll keep its original capitalization. It's up to you to convert it to title case when you make a help command. +export const categories = new Collection(); + +/** Returns the cache of the commands if it exists and searches the directory if not. */ +export const loadableCommands = (async () => { + const commands = new Collection(); + // Include all .ts files recursively in "src/commands/". + const files = await globP("src/commands/**/*.ts"); + // Extract the usable parts from "src/commands/" if: + // - The path is 1 to 2 subdirectories (a or a/b, not a/b/c) + // - Any leading directory isn't "modules" + // - The filename doesn't end in .test.ts (for jest testing) + // - The filename cannot be the hardcoded top-level "template.ts", reserved for generating templates + const pattern = /src\/commands\/(?!template\.ts)(?!modules\/)(\w+(?:\/\w+)?)(?:test\.)?\.ts/; + const lists: {[category: string]: string[]} = {}; + + for (const path of files) { + const match = pattern.exec(path); + + if (match) { + const commandID = match[1]; // e.g. "utilities/info" + const slashIndex = commandID.indexOf("/"); + const isMiscCommand = slashIndex !== -1; + const category = isMiscCommand ? commandID.substring(0, slashIndex) : "miscellaneous"; + const commandName = isMiscCommand ? commandID.substring(slashIndex + 1) : commandID; // e.g. "info" + // If the dynamic import works, it must be an object at the very least. Then, just test to see if it's a proper instance. + const command = (await import(`../commands/${commandID}`)).default as unknown; + + if (command instanceof Command) { + command.originalCommandName = commandName; + + if (commands.has(commandName)) { + console.warn( + `Command "${commandName}" already exists! Make sure to make each command uniquely identifiable across categories!` + ); + } else { + commands.set(commandName, command); + } + + for (const alias of command.aliases) { + if (commands.has(alias)) { + console.warn( + `Top-level alias "${alias}" from command "${commandID}" already exists either as a command or alias!` + ); + } else { + commands.set(alias, command); + } + } + + if (!(category in lists)) lists[category] = []; + lists[category].push(commandName); + + console.log(`Loading Command: ${commandID}`); + } else { + console.warn(`Command "${commandID}" has no default export which is a Command instance!`); + } + } + } + + for (const category in lists) { + categories.set(category, lists[category]); + } + + return commands; +})(); + +function globP(path: string) { + return new Promise((resolve, reject) => { + glob(path, (error, files) => { + if (error) { + reject(error); + } else { + resolve(files); + } + }); + }); +} + +// Gathers a list of categories and top-level commands. +// Returns: new Collection() +/*export async function getCommandList(): Promise> { + const categorizedCommands = new Collection(); + const commands = await loadableCommands; + + for (const [category, headers] of categories) { + const commandList: Command[] = []; + + for (const header of headers) { + if (header !== "test") { + // If this is somehow undefined, it'll show up as an error when implementing a help command. + commandList.push(commands.get(header)!); + } + } + + categorizedCommands.set(toTitleCase(category), commandList); + } + + return categorizedCommands; +}*/ From 3362f9fbbe0adac111e2e608d8b66d419f076a48 Mon Sep 17 00:00:00 2001 From: WatDuhHekBro <44940783+WatDuhHekBro@users.noreply.github.com> Date: Sat, 3 Apr 2021 21:35:55 -0500 Subject: [PATCH 125/178] Prototyped stream notifications like CheeseBot --- src/commands/admin.ts | 20 ++++++++ src/commands/utilities/streaminfo.ts | 18 +++++++ src/core/structures.ts | 2 + src/events/channelUpdate.ts | 14 ++++++ src/events/voiceStateUpdate.ts | 74 ++++++++++++++++++++++++++++ 5 files changed, 128 insertions(+) create mode 100644 src/commands/utilities/streaminfo.ts create mode 100644 src/events/channelUpdate.ts create mode 100644 src/events/voiceStateUpdate.ts diff --git a/src/commands/admin.ts b/src/commands/admin.ts index 2622087..e78068f 100644 --- a/src/commands/admin.ts +++ b/src/commands/admin.ts @@ -164,6 +164,26 @@ export default new Command({ }) }) } + }), + stream: new Command({ + description: "Set a channel to send stream notifications.", + async run($) { + if ($.guild) { + const guild = Storage.getGuild($.guild.id); + + if (guild.streamingChannel) { + guild.streamingChannel = null; + $.channel.send("Removed your server's stream notifications channel."); + } else { + guild.streamingChannel = $.channel.id; + $.channel.send(`Set your server's stream notifications channel to ${$.channel}.`); + } + + Storage.save(); + } else { + $.channel.send("You must use this command in a server."); + } + } }) } }), diff --git a/src/commands/utilities/streaminfo.ts b/src/commands/utilities/streaminfo.ts new file mode 100644 index 0000000..e4ea9aa --- /dev/null +++ b/src/commands/utilities/streaminfo.ts @@ -0,0 +1,18 @@ +import Command from "../../core/command"; +import {streamList, getStreamEmbed} from "../../events/voiceStateUpdate"; + +export default new Command({ + description: "Sets the description of your stream. You can embed links by writing `[some name](some link)`", + async run($) { + const userID = $.author.id; + + if (streamList.has(userID)) { + const stream = streamList.get(userID)!; + stream.description = $.args.join(" ") || undefined; + stream.message.edit(getStreamEmbed(stream.streamer, stream.channel, stream.description)); + } else { + // Alternatively, I could make descriptions last outside of just one stream. + $.channel.send("You can only use this command when streaming."); + } + } +}); diff --git a/src/core/structures.ts b/src/core/structures.ts index 13bdc9f..0e77494 100644 --- a/src/core/structures.ts +++ b/src/core/structures.ts @@ -58,11 +58,13 @@ class Guild { public welcomeType: "none" | "text" | "graphical"; public welcomeChannel: string | null; public welcomeMessage: string | null; + public streamingChannel: string | null; constructor(data?: GenericJSON) { this.prefix = select(data?.prefix, null, String); this.welcomeChannel = select(data?.welcomeChannel, null, String); this.welcomeMessage = select(data?.welcomeMessage, null, String); + this.streamingChannel = select(data?.streamingChannel, null, String); switch (data?.welcomeType) { case "text": diff --git a/src/events/channelUpdate.ts b/src/events/channelUpdate.ts new file mode 100644 index 0000000..0b325c9 --- /dev/null +++ b/src/events/channelUpdate.ts @@ -0,0 +1,14 @@ +import Event from "../core/event"; +import {streamList, getStreamEmbed} from "./voiceStateUpdate"; + +export default new Event<"channelUpdate">({ + async on(before, after) { + if (before.type === "voice" && after.type === "voice") { + for (const {streamer, channel, description, message} of streamList.values()) { + if (after.id === channel.id) { + message.edit(getStreamEmbed(streamer, channel, description)); + } + } + } + } +}); diff --git a/src/events/voiceStateUpdate.ts b/src/events/voiceStateUpdate.ts new file mode 100644 index 0000000..ded1a5a --- /dev/null +++ b/src/events/voiceStateUpdate.ts @@ -0,0 +1,74 @@ +import {GuildMember, VoiceChannel, MessageEmbed, TextChannel, Permissions, Message, Collection} from "discord.js"; +import Event from "../core/event"; +import $ from "../core/lib"; +import {Storage} from "../core/structures"; +import {client} from "../index"; + +type Stream = { + streamer: GuildMember; + channel: VoiceChannel; + description?: string; + message: Message; +}; + +// A list of user IDs and message embeds. +export const streamList = new Collection(); + +// Probably find a better, DRY way of doing this. +export function getStreamEmbed(streamer: GuildMember, channel: VoiceChannel, description?: string): MessageEmbed { + const user = streamer.user; + const embed = new MessageEmbed() + .setTitle(`Stream: \`#${channel.name}\``) + .setAuthor( + streamer.nickname ?? user.username, + user.avatarURL({ + dynamic: true, + format: "png" + }) ?? user.defaultAvatarURL + ) + .setColor(streamer.displayColor); + + if (description) { + embed.setDescription(description); + } + + return embed; +} + +export default new Event<"voiceStateUpdate">({ + async on(before, after) { + const isStartStreamEvent = !before.streaming && after.streaming; + const isStopStreamEvent = before.streaming && (!after.streaming || !after.channel); // If you were streaming before but now are either not streaming or have left the channel. + // Note: isStopStreamEvent can be called twice in a row - If Discord crashes/quits while you're streaming, it'll call once with a null channel and a second time with a channel. + + if (isStartStreamEvent || isStopStreamEvent) { + const {streamingChannel} = Storage.getGuild(after.guild.id); + + if (streamingChannel) { + const member = after.member!; + const voiceChannel = after.channel!; + const textChannel = client.channels.cache.get(streamingChannel); + + if (textChannel && textChannel instanceof TextChannel) { + if (isStartStreamEvent) { + streamList.set(member.id, { + streamer: member, + channel: voiceChannel, + message: await textChannel.send(getStreamEmbed(member, voiceChannel)) + }); + } else if (isStopStreamEvent) { + if (streamList.has(member.id)) { + const {message} = streamList.get(member.id)!; + message.delete(); + streamList.delete(member.id); + } + } + } else { + $.error( + `The streaming notifications channel ${streamingChannel} for guild ${after.guild.id} either doesn't exist or isn't a text channel.` + ); + } + } + } + } +}); From 4c7b8200dab4a13c6a5fbc5758a26fa9e8e94f01 Mon Sep 17 00:00:00 2001 From: Mijyuoon Date: Sun, 4 Apr 2021 23:54:51 +0300 Subject: [PATCH 126/178] Added my almighty ass to .whois list --- src/commands/fun/whois.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/commands/fun/whois.ts b/src/commands/fun/whois.ts index 283610a..056f1b1 100644 --- a/src/commands/fun/whois.ts +++ b/src/commands/fun/whois.ts @@ -29,7 +29,8 @@ const registry: {[id: string]: string} = { "328223274133880833": "The stealthiest hitman.", "219661798742163467": "An extremely talented artist and modder.", "440399719076855818": - "You are, uhh, Stay Put, Soft Puppy, Es-Pee, Swift Pacemaker, Smug Poyo, and many more.\n...Seriously, this woman has too many names." + "You are, uhh, Stay Put, Soft Puppy, Es-Pee, Swift Pacemaker, Smug Poyo, and many more.\n...Seriously, this woman has too many names.", + "243061915281129472": "Some random conlanger, worldbuilder and programmer doofus. ~~May also secretly be a nyan. :3~~" }; export default new Command({ From 63441b4aca8582e3e841aee202aedc12b60b73ec Mon Sep 17 00:00:00 2001 From: WatDuhHekBro <44940783+WatDuhHekBro@users.noreply.github.com> Date: Sun, 4 Apr 2021 17:28:32 -0500 Subject: [PATCH 127/178] Added more type guards/properties to Command class --- src/core/command.ts | 191 ++++++++++++++++++++++++++------------------ src/core/lib.ts | 10 +++ src/core/loader.ts | 6 +- 3 files changed, 126 insertions(+), 81 deletions(-) diff --git a/src/core/command.ts b/src/core/command.ts index 4cfb777..d25a647 100644 --- a/src/core/command.ts +++ b/src/core/command.ts @@ -1,10 +1,30 @@ -import {parseVars} from "./lib"; +import {parseVars, requireAllCasesHandledFor} from "./lib"; import {Collection} from "discord.js"; import {Client, Message, TextChannel, DMChannel, NewsChannel, Guild, User, GuildMember} from "discord.js"; import {getPrefix} from "../core/structures"; import {SingleMessageOptions} from "./libd"; import {hasPermission, getPermissionLevel, getPermissionName} from "./permissions"; +export enum TYPES { + SUBCOMMAND, + USER, + NUMBER, + ANY, + NONE +} + +// Callbacks don't work with discriminated unions: +// - https://github.com/microsoft/TypeScript/issues/41759 +// - https://github.com/microsoft/TypeScript/issues/35769 +// Therefore, there won't by any type narrowing on channel or guild of CommandMenu until this is fixed. +// Otherwise, you'd have to define channelType for every single subcommand, which would get very tedious. +// Just use type assertions when you specify a channel type. +export enum CHANNEL_TYPE { + ANY, + GUILD, + DM +} + interface CommandMenu { args: any[]; client: Client; @@ -12,97 +32,101 @@ interface CommandMenu { channel: TextChannel | DMChannel | NewsChannel; guild: Guild | null; author: User; + // According to the documentation, a message can be part of a guild while also not having a + // member object for the author. This will happen if the author of a message left the guild. member: GuildMember | null; } -interface CommandOptions { +interface CommandOptionsBase { description?: string; endpoint?: boolean; usage?: string; permission?: number; - aliases?: string[]; + nsfw?: boolean; + channelType?: CHANNEL_TYPE; run?: (($: CommandMenu) => Promise) | string; - subcommands?: {[key: string]: Command}; +} + +interface CommandOptionsEndpoint { + endpoint: true; +} + +// Prevents subcommands from being added by compile-time. +interface CommandOptionsNonEndpoint { + endpoint?: false; + subcommands?: {[key: string]: NamedCommand}; user?: Command; number?: Command; any?: Command; } -enum TYPES { - SUBCOMMAND, - USER, - NUMBER, - ANY, - NONE -} +type CommandOptions = CommandOptionsBase & (CommandOptionsEndpoint | CommandOptionsNonEndpoint); +type NamedCommandOptions = CommandOptions & {aliases?: string[]}; -export default class Command { +// RegEx patterns used for identifying/extracting each type from a string argument. +const patterns = { + // +}; + +export class Command { public readonly description: string; public readonly endpoint: boolean; public readonly usage: string; public readonly permission: number; // -1 (default) indicates to inherit, 0 is the lowest rank, 1 is second lowest rank, and so on. - public readonly aliases: string[]; // This is to keep the array intact for parent Command instances to use. It'll also be used when loading top-level aliases. - public originalCommandName: string | null; // If the command is an alias, what's the original name? - public run: (($: CommandMenu) => Promise) | string; - public readonly subcommands: Collection; // This is the final data structure you'll actually use to work with the commands the aliases point to. - public user: Command | null; - public number: Command | null; - public any: Command | null; + public readonly nsfw: boolean; + public readonly channelType: CHANNEL_TYPE; + protected run: (($: CommandMenu) => Promise) | string; + protected readonly subcommands: Collection; // This is the final data structure you'll actually use to work with the commands the aliases point to. + protected user: Command | null; + protected number: Command | null; + protected any: Command | null; + public static readonly CHANNEL_TYPE = CHANNEL_TYPE; constructor(options?: CommandOptions) { this.description = options?.description || "No description."; - this.endpoint = options?.endpoint || false; - this.usage = options?.usage || ""; + this.endpoint = !!options?.endpoint; + this.usage = options?.usage ?? ""; this.permission = options?.permission ?? -1; - this.aliases = options?.aliases ?? []; - this.originalCommandName = null; + this.nsfw = !!options?.nsfw; + this.channelType = options?.channelType ?? CHANNEL_TYPE.ANY; this.run = options?.run || "No action was set on this command!"; this.subcommands = new Collection(); // Populate this collection after setting subcommands. - this.user = options?.user || null; - this.number = options?.number || null; - this.any = options?.any || null; + this.user = null; + this.number = null; + this.any = null; - if (options?.subcommands) { - const baseSubcommands = Object.keys(options.subcommands); + if (options && !options.endpoint) { + this.user = options?.user || null; + this.number = options?.number || null; + this.any = options?.any || null; - // Loop once to set the base subcommands. - for (const name in options.subcommands) this.subcommands.set(name, options.subcommands[name]); + if (options?.subcommands) { + const baseSubcommands = Object.keys(options.subcommands); - // Then loop again to make aliases point to the base subcommands and warn if something's not right. - // This shouldn't be a problem because I'm hoping that JS stores these as references that point to the same object. - for (const name in options.subcommands) { - const subcmd = options.subcommands[name]; - subcmd.originalCommandName = name; - const aliases = subcmd.aliases; + // Loop once to set the base subcommands. + for (const name in options.subcommands) this.subcommands.set(name, options.subcommands[name]); - for (const alias of aliases) { - if (baseSubcommands.includes(alias)) - console.warn( - `"${alias}" in subcommand "${name}" was attempted to be declared as an alias but it already exists in the base commands! (Look at the next "Loading Command" line to see which command is affected.)` - ); - else if (this.subcommands.has(alias)) - console.warn( - `Duplicate alias "${alias}" at subcommand "${name}"! (Look at the next "Loading Command" line to see which command is affected.)` - ); - else this.subcommands.set(alias, subcmd); + // Then loop again to make aliases point to the base subcommands and warn if something's not right. + // This shouldn't be a problem because I'm hoping that JS stores these as references that point to the same object. + for (const name in options.subcommands) { + const subcmd = options.subcommands[name]; + subcmd.originalCommandName = name; + const aliases = subcmd.aliases; + + for (const alias of aliases) { + if (baseSubcommands.includes(alias)) + console.warn( + `"${alias}" in subcommand "${name}" was attempted to be declared as an alias but it already exists in the base commands! (Look at the next "Loading Command" line to see which command is affected.)` + ); + else if (this.subcommands.has(alias)) + console.warn( + `Duplicate alias "${alias}" at subcommand "${name}"! (Look at the next "Loading Command" line to see which command is affected.)` + ); + else this.subcommands.set(alias, subcmd); + } } } } - - if (this.user && this.user.aliases.length > 0) - console.warn( - `There are aliases defined for a "user"-type subcommand, but those aliases won't be used. (Look at the next "Loading Command" line to see which command is affected.)` - ); - - if (this.number && this.number.aliases.length > 0) - console.warn( - `There are aliases defined for a "number"-type subcommand, but those aliases won't be used. (Look at the next "Loading Command" line to see which command is affected.)` - ); - - if (this.any && this.any.aliases.length > 0) - console.warn( - `There are aliases defined for an "any"-type subcommand, but those aliases won't be used. (Look at the next "Loading Command" line to see which command is affected.)` - ); } public execute($: CommandMenu) { @@ -120,23 +144,18 @@ export default class Command { } else this.run($).catch(handler.bind($)); } + // Go through the arguments provided and find the right subcommand, then execute with the given arguments. // Will return null if it successfully executes, SingleMessageOptions if there's an error (to let the user know what it is). public async actualExecute(args: string[], tmp: any): Promise { + // For debug info, use this.originalCommandName? // Subcommand Recursion // - let command = commands.get(header)!; + let command: Command = new Command(); // = commands.get(header)!; //resolveSubcommand() const params: any[] = []; let isEndpoint = false; let permLevel = command.permission ?? 0; for (const param of args) { - if (command.endpoint) { - if (command.subcommands.size > 0 || command.user || command.number || command.any) - console.warn("An endpoint cannot have subcommands!"); - isEndpoint = true; - break; - } - const type = command.resolve(param); command = command.get(param); permLevel = command.permission ?? permLevel; @@ -181,7 +200,7 @@ export default class Command { return null; } - public resolve(param: string): TYPES { + private resolve(param: string): TYPES { if (this.subcommands.has(param)) return TYPES.SUBCOMMAND; // Any Discord ID format will automatically format to a user ID. else if (this.user && /\d{17,19}/.test(param)) return TYPES.USER; @@ -191,33 +210,35 @@ export default class Command { else return TYPES.NONE; } - public get(param: string): Command { + private get(param: string): Command { const type = this.resolve(param); let command: Command; switch (type) { case TYPES.SUBCOMMAND: - command = this.subcommands.get(param) as Command; + command = this.subcommands.get(param)!; break; case TYPES.USER: - command = this.user as Command; + command = this.user!; break; case TYPES.NUMBER: - command = this.number as Command; + command = this.number!; break; case TYPES.ANY: - command = this.any as Command; + command = this.any!; break; - default: + case TYPES.NONE: command = this; break; + default: + requireAllCasesHandledFor(type); } return command; } // Returns: [category, command name, command, available subcommands: [type, subcommand]] - public resolveCommandInfo(args: string[]): [string, string, Command, Collection] { + public async resolveInfo(args: string[]): [string, string, Command, Collection] | null { const commands = await loadableCommands; let header = args.shift(); let command = commands.get(header); @@ -253,6 +274,7 @@ export default class Command { if (permLevel === -1) permLevel = command.permission; + // Switch over to doing `$help info ` switch (type) { case TYPES.SUBCOMMAND: header += ` ${command.originalCommandName}`; @@ -266,9 +288,11 @@ export default class Command { case TYPES.ANY: header += " "; break; - default: + case TYPES.NONE: header += ` ${param}`; break; + default: + requireAllCasesHandledFor(type); } if (type === TYPES.NONE) { @@ -284,6 +308,17 @@ export default class Command { } } +export class NamedCommand extends Command { + public readonly aliases: string[]; // This is to keep the array intact for parent Command instances to use. It'll also be used when loading top-level aliases. + public originalCommandName: string | null; // If the command is an alias, what's the original name? + + constructor(options?: NamedCommandOptions) { + super(options); + this.aliases = options?.aliases || []; + this.originalCommandName = null; + } +} + // If you use promises, use this function to display the error in chat. // Case #1: await $.channel.send(""); --> Automatically caught by Command.execute(). // Case #2: $.channel.send("").catch(handler.bind($)); --> Manually caught by the user. diff --git a/src/core/lib.ts b/src/core/lib.ts index 21b4a95..477089f 100644 --- a/src/core/lib.ts +++ b/src/core/lib.ts @@ -224,3 +224,13 @@ export function split(array: T[], lengthOfEachSection: number): T[][] { return sections; } + +/** + * Utility function to require all possible cases to be handled at compile time. + * + * To use this function, place it in the "default" case of a switch statement or the "else" statement of an if-else branch. + * If all cases are handled, the variable being tested for should be of type "never", and if it isn't, that means not all cases are handled yet. + */ +export function requireAllCasesHandledFor(variable: never): never { + throw new Error(`This function should never be called but got the value: ${variable}`); +} diff --git a/src/core/loader.ts b/src/core/loader.ts index a7f7d7d..8333c1b 100644 --- a/src/core/loader.ts +++ b/src/core/loader.ts @@ -1,6 +1,6 @@ import {Collection} from "discord.js"; import glob from "glob"; -import Command from "./command"; +import {Command, NamedCommand} from "./command"; // Internally, it'll keep its original capitalization. It's up to you to convert it to title case when you make a help command. export const categories = new Collection(); @@ -30,7 +30,7 @@ export const loadableCommands = (async () => { // If the dynamic import works, it must be an object at the very least. Then, just test to see if it's a proper instance. const command = (await import(`../commands/${commandID}`)).default as unknown; - if (command instanceof Command) { + if (command instanceof NamedCommand) { command.originalCommandName = commandName; if (commands.has(commandName)) { @@ -56,7 +56,7 @@ export const loadableCommands = (async () => { console.log(`Loading Command: ${commandID}`); } else { - console.warn(`Command "${commandID}" has no default export which is a Command instance!`); + console.warn(`Command "${commandID}" has no default export which is a NamedCommand instance!`); } } } From 6eea0689099fbfbb83b162b99ef824e3225d36f8 Mon Sep 17 00:00:00 2001 From: WatDuhHekBro <44940783+WatDuhHekBro@users.noreply.github.com> Date: Sun, 4 Apr 2021 19:35:10 -0500 Subject: [PATCH 128/178] Reworked Command.execute and subcommand recursion --- src/core/command.ts | 305 ++++++++++++++++++++-------------------- src/core/handler.ts | 49 ++++--- src/core/permissions.ts | 14 +- 3 files changed, 192 insertions(+), 176 deletions(-) diff --git a/src/core/command.ts b/src/core/command.ts index d25a647..20b7f90 100644 --- a/src/core/command.ts +++ b/src/core/command.ts @@ -1,9 +1,10 @@ import {parseVars, requireAllCasesHandledFor} from "./lib"; import {Collection} from "discord.js"; -import {Client, Message, TextChannel, DMChannel, NewsChannel, Guild, User, GuildMember} from "discord.js"; +import {Client, Message, TextChannel, DMChannel, NewsChannel, Guild, User, GuildMember, GuildChannel} from "discord.js"; import {getPrefix} from "../core/structures"; import {SingleMessageOptions} from "./libd"; import {hasPermission, getPermissionLevel, getPermissionName} from "./permissions"; +import {client} from "../index"; export enum TYPES { SUBCOMMAND, @@ -13,6 +14,16 @@ export enum TYPES { NONE } +// RegEx patterns used for identifying/extracting each type from a string argument. +const patterns = { + channel: /^<#(\d{17,19})>$/, + role: /^<@&(\d{17,19})>$/, + emote: /^$/, + message: /(?:\d{17,19}\/(\d{17,19})\/(\d{17,19})$)|(?:^(\d{17,19})-(\d{17,19})$)/, + user: /^<@!?(\d{17,19})>$/, + id: /^(\d{17,19})$/ +}; + // Callbacks don't work with discriminated unions: // - https://github.com/microsoft/TypeScript/issues/41759 // - https://github.com/microsoft/TypeScript/issues/35769 @@ -26,55 +37,58 @@ export enum CHANNEL_TYPE { } interface CommandMenu { - args: any[]; - client: Client; - message: Message; - channel: TextChannel | DMChannel | NewsChannel; - guild: Guild | null; - author: User; + readonly args: any[]; + readonly client: Client; + readonly message: Message; + readonly channel: TextChannel | DMChannel | NewsChannel; + readonly guild: Guild | null; + readonly author: User; // According to the documentation, a message can be part of a guild while also not having a // member object for the author. This will happen if the author of a message left the guild. - member: GuildMember | null; + readonly member: GuildMember | null; } interface CommandOptionsBase { - description?: string; - endpoint?: boolean; - usage?: string; - permission?: number; - nsfw?: boolean; - channelType?: CHANNEL_TYPE; - run?: (($: CommandMenu) => Promise) | string; + readonly description?: string; + readonly endpoint?: boolean; + readonly usage?: string; + readonly permission?: number; + readonly nsfw?: boolean; + readonly channelType?: CHANNEL_TYPE; + readonly run?: (($: CommandMenu) => Promise) | string; } interface CommandOptionsEndpoint { - endpoint: true; + readonly endpoint: true; } // Prevents subcommands from being added by compile-time. interface CommandOptionsNonEndpoint { - endpoint?: false; - subcommands?: {[key: string]: NamedCommand}; - user?: Command; - number?: Command; - any?: Command; + readonly endpoint?: false; + readonly subcommands?: {[key: string]: NamedCommand}; + readonly user?: Command; + readonly number?: Command; + readonly any?: Command; } type CommandOptions = CommandOptionsBase & (CommandOptionsEndpoint | CommandOptionsNonEndpoint); type NamedCommandOptions = CommandOptions & {aliases?: string[]}; -// RegEx patterns used for identifying/extracting each type from a string argument. -const patterns = { - // -}; +interface ExecuteCommandMetadata { + readonly header: string; + readonly args: string[]; + permission: number; + nsfw: boolean; + channelType: CHANNEL_TYPE; +} export class Command { public readonly description: string; public readonly endpoint: boolean; public readonly usage: string; public readonly permission: number; // -1 (default) indicates to inherit, 0 is the lowest rank, 1 is second lowest rank, and so on. - public readonly nsfw: boolean; - public readonly channelType: CHANNEL_TYPE; + public readonly nsfw: boolean | null; // null (default) indicates to inherit + public readonly channelType: CHANNEL_TYPE | null; // null (default) indicates to inherit protected run: (($: CommandMenu) => Promise) | string; protected readonly subcommands: Collection; // This is the final data structure you'll actually use to work with the commands the aliases point to. protected user: Command | null; @@ -87,8 +101,8 @@ export class Command { this.endpoint = !!options?.endpoint; this.usage = options?.usage ?? ""; this.permission = options?.permission ?? -1; - this.nsfw = !!options?.nsfw; - this.channelType = options?.channelType ?? CHANNEL_TYPE.ANY; + this.nsfw = options?.nsfw ?? null; + this.channelType = options?.channelType ?? null; this.run = options?.run || "No action was set on this command!"; this.subcommands = new Collection(); // Populate this collection after setting subcommands. this.user = null; @@ -129,116 +143,135 @@ export class Command { } } - public execute($: CommandMenu) { - if (typeof this.run === "string") { - $.channel.send( - parseVars( - this.run, - { - author: $.author.toString(), - prefix: getPrefix($.guild) - }, - "???" - ) - ); - } else this.run($).catch(handler.bind($)); - } - // Go through the arguments provided and find the right subcommand, then execute with the given arguments. // Will return null if it successfully executes, SingleMessageOptions if there's an error (to let the user know what it is). - public async actualExecute(args: string[], tmp: any): Promise { - // For debug info, use this.originalCommandName? - // Subcommand Recursion // - let command: Command = new Command(); // = commands.get(header)!; - //resolveSubcommand() - const params: any[] = []; - let isEndpoint = false; - let permLevel = command.permission ?? 0; + public async execute( + args: string[], + menu: CommandMenu, + metadata: ExecuteCommandMetadata + ): Promise { + const param = args.shift(); - for (const param of args) { - const type = command.resolve(param); - command = command.get(param); - permLevel = command.permission ?? permLevel; + // If there are no arguments left, execute the current command. Otherwise, continue on. + if (!param) { + // See if there is anything that'll prevent the user from executing the command. - if (type === TYPES.USER) { - const id = param.match(/\d+/g)![0]; - try { - params.push(await message.client.users.fetch(id)); - } catch (error) { - return message.channel.send(`No user found by the ID \`${id}\`!`); + // 1. Does this command specify a required channel type? If so, does the channel type match? + if ( + metadata.channelType === CHANNEL_TYPE.GUILD && + (!(menu.channel instanceof GuildChannel) || menu.guild === null) + ) { + return {content: "This command must be executed in a server."}; + } else if ( + metadata.channelType === CHANNEL_TYPE.DM && + (menu.channel.type !== "dm" || menu.guild !== null) + ) { + return {content: "This command must be executed as a direct message."}; + } + + // 2. Is this an NSFW command where the channel prevents such use? (DM channels bypass this requirement.) + if (metadata.nsfw && menu.channel.type !== "dm" && !menu.channel.nsfw) { + return {content: "This command must be executed in either an NSFW channel or as a direct message."}; + } + + // 3. Does the user have permission to execute the command? + if (!hasPermission(menu.author, menu.member, metadata.permission)) { + const userPermLevel = getPermissionLevel(menu.author, menu.member); + + return { + content: `You don't have access to this command! Your permission level is \`${getPermissionName( + userPermLevel + )}\` (${userPermLevel}), but this command requires a permission level of \`${getPermissionName( + metadata.permission + )}\` (${metadata.permission}).` + }; + } + + // Then capture any potential errors. + try { + if (typeof this.run === "string") { + await menu.channel.send( + parseVars( + this.run, + { + author: menu.author.toString(), + prefix: getPrefix(menu.guild) + }, + "???" + ) + ); + } else { + await this.run(menu); } - } else if (type === TYPES.NUMBER) params.push(Number(param)); - else if (type !== TYPES.SUBCOMMAND) params.push(param); + + return null; + } catch (error) { + const errorMessage = error.stack ?? error; + console.error(`Command Error: ${metadata.header} (${metadata.args})\n${errorMessage}`); + + return { + content: `There was an error while trying to execute that command!\`\`\`${errorMessage}\`\`\`` + }; + } } - if (!message.member) - return console.warn("This command was likely called from a DM channel meaning the member object is null."); + // If the current command is an endpoint but there are still some arguments left, don't continue. + if (this.endpoint) return {content: "Too many arguments!"}; - if (!hasPermission(message.member, permLevel)) { - const userPermLevel = getPermissionLevel(message.member); - return message.channel.send( - `You don't have access to this command! Your permission level is \`${getPermissionName( - userPermLevel - )}\` (${userPermLevel}), but this command requires a permission level of \`${getPermissionName( - permLevel - )}\` (${permLevel}).` - ); + // If the current command's permission level isn't -1 (inherit), then set the permission metadata equal to that. + if (this.permission !== -1) metadata.permission = this.permission; + + // If the current command has an NSFW setting specified, set it. + if (this.nsfw !== null) metadata.nsfw = this.nsfw; + + // If the current command doesn't inherit its channel type, set it. + if (this.channelType !== null) metadata.channelType = this.channelType; + + // Resolve the value of the current command's argument (adding it to the resolved args), + // then pass the thread of execution to whichever subcommand is valid (if any). + if (this.subcommands.has(param)) { + return this.subcommands.get(param)!.execute(args, menu, metadata); + } else if (this.user && patterns.user.test(param)) { + const id = patterns.user.exec(param)![1]; + + try { + menu.args.push(await client.users.fetch(id)); + return this.user.execute(args, menu, metadata); + } catch { + return { + content: `No user found by the ID \`${id}\`!` + }; + } + } else if (this.number && !Number.isNaN(Number(param)) && param !== "Infinity" && param !== "-Infinity") { + menu.args.push(Number(param)); + return this.number.execute(args, menu, metadata); + } else if (this.any) { + menu.args.push(param); + return this.any.execute(args, menu, metadata); + } else { + // Continue adding on the rest of the arguments if there's no valid subcommand. + menu.args.push(param); + return this.execute(args, menu, metadata); } - if (isEndpoint) return message.channel.send("Too many arguments!"); - - command.execute({ - args: params, - author: message.author, - channel: message.channel, - client: message.client, - guild: message.guild, - member: message.member, - message: message - }); - - return null; + // Note: Do NOT add a return statement here. In case one of the other sections is missing + // a return statement, there'll be a compile error to catch that. } +} - private resolve(param: string): TYPES { - if (this.subcommands.has(param)) return TYPES.SUBCOMMAND; - // Any Discord ID format will automatically format to a user ID. - else if (this.user && /\d{17,19}/.test(param)) return TYPES.USER; - // Disallow infinity and allow for 0. - else if (this.number && (Number(param) || param === "0") && !param.includes("Infinity")) return TYPES.NUMBER; - else if (this.any) return TYPES.ANY; - else return TYPES.NONE; - } +export class NamedCommand extends Command { + public readonly aliases: string[]; // This is to keep the array intact for parent Command instances to use. It'll also be used when loading top-level aliases. + public originalCommandName: string | null; // If the command is an alias, what's the original name? - private get(param: string): Command { - const type = this.resolve(param); - let command: Command; - - switch (type) { - case TYPES.SUBCOMMAND: - command = this.subcommands.get(param)!; - break; - case TYPES.USER: - command = this.user!; - break; - case TYPES.NUMBER: - command = this.number!; - break; - case TYPES.ANY: - command = this.any!; - break; - case TYPES.NONE: - command = this; - break; - default: - requireAllCasesHandledFor(type); - } - - return command; + constructor(options?: NamedCommandOptions) { + super(options); + this.aliases = options?.aliases || []; + this.originalCommandName = null; } // Returns: [category, command name, command, available subcommands: [type, subcommand]] public async resolveInfo(args: string[]): [string, string, Command, Collection] | null { + // For debug info, use this.originalCommandName? (if it exists?) const commands = await loadableCommands; let header = args.shift(); let command = commands.get(header); @@ -307,31 +340,3 @@ export class Command { } } } - -export class NamedCommand extends Command { - public readonly aliases: string[]; // This is to keep the array intact for parent Command instances to use. It'll also be used when loading top-level aliases. - public originalCommandName: string | null; // If the command is an alias, what's the original name? - - constructor(options?: NamedCommandOptions) { - super(options); - this.aliases = options?.aliases || []; - this.originalCommandName = null; - } -} - -// If you use promises, use this function to display the error in chat. -// Case #1: await $.channel.send(""); --> Automatically caught by Command.execute(). -// Case #2: $.channel.send("").catch(handler.bind($)); --> Manually caught by the user. -// TODO: Find a way to catch unhandled rejections automatically, forgoing the need for this. -export function handler(this: CommandMenu, error: Error) { - if (this) - this.channel.send( - `There was an error while trying to execute that command!\`\`\`${error.stack ?? error}\`\`\`` - ); - else - console.warn( - "No context was attached to $.handler! Make sure to use .catch($.handler.bind($)) or .catch(error => $.handler(error)) instead!" - ); - - console.error(error); -} diff --git a/src/core/handler.ts b/src/core/handler.ts index d0cba3c..3566a77 100644 --- a/src/core/handler.ts +++ b/src/core/handler.ts @@ -3,6 +3,7 @@ import {loadableCommands} from "./loader"; import {Permissions, Message} from "discord.js"; import {getPrefix} from "./structures"; import {Config} from "./structures"; +import {CHANNEL_TYPE} from "./command"; // For custom message events that want to cancel the command handler on certain conditions. const interceptRules: ((message: Message) => boolean)[] = [(message) => message.author.bot]; @@ -19,17 +20,16 @@ client.on("message", async (message) => { } } + const {author, channel, content, guild, member} = message; + // Continue if the bot has permission to send messages in this channel. - if ( - message.channel.type === "dm" || - message.channel.permissionsFor(client.user!)!.has(Permissions.FLAGS.SEND_MESSAGES) - ) { - const text = message.content; - const prefix = getPrefix(message.guild); + if (channel.type === "dm" || channel.permissionsFor(client.user!)!.has(Permissions.FLAGS.SEND_MESSAGES)) { + const text = content; + const prefix = getPrefix(guild); // First, test if the message is just a ping to the bot. if (new RegExp(`^<@!?${client.user!.id}>$`).test(text)) { - message.channel.send(`${message.author}, my prefix on this guild is \`${prefix}\`.`); + channel.send(`${author}, my prefix on this guild is \`${prefix}\`.`); } // Then check if it's a normal command. else if (text.startsWith(prefix)) { @@ -41,25 +41,36 @@ client.on("message", async (message) => { // Send the arguments to the command to resolve and execute. // TMP[MAKE SURE TO REPLACE WITH command.execute WHEN FINISHED] - const result = await command.actualExecute(args, { - author: message.author, - channel: message.channel, - client: message.client, - guild: message.guild, - member: message.member, - message: message - }); + const result = await command.execute( + args, + { + author, + channel, + client, + guild, + member, + message, + args: [] + }, + { + header, + args, + permission: 0, + nsfw: false, + channelType: CHANNEL_TYPE.ANY + } + ); // If something went wrong, let the user know (like if they don't have permission to use a command). if (result) { - message.channel.send(result); + channel.send(result); } } } } else { - message.author.send( - `I don't have permission to send messages in ${message.channel}. ${ - message.member!.hasPermission(Permissions.FLAGS.ADMINISTRATOR) + author.send( + `I don't have permission to send messages in ${channel}. ${ + member!.hasPermission(Permissions.FLAGS.ADMINISTRATOR) ? "Because you're a server admin, you have the ability to change that channel's permissions to match if that's what you intended." : "Try using a different channel or contacting a server admin to change permissions of that channel if you think something's wrong." }` diff --git a/src/core/permissions.ts b/src/core/permissions.ts index 37b0000..9798a2a 100644 --- a/src/core/permissions.ts +++ b/src/core/permissions.ts @@ -15,7 +15,7 @@ export const PermissionLevels: PermissionLevel[] = [ { // MOD // name: "Moderator", - check: (_, member) => + check: (_user, member) => !!member && (member.hasPermission(Permissions.FLAGS.MANAGE_ROLES) || member.hasPermission(Permissions.FLAGS.MANAGE_MESSAGES) || @@ -25,12 +25,12 @@ export const PermissionLevels: PermissionLevel[] = [ { // ADMIN // name: "Administrator", - check: (_, member) => !!member && member.hasPermission(Permissions.FLAGS.ADMINISTRATOR) + check: (_user, member) => !!member && member.hasPermission(Permissions.FLAGS.ADMINISTRATOR) }, { // OWNER // name: "Server Owner", - check: (_, member) => !!member && member.guild.ownerID === member.id + check: (_user, member) => !!member && member.guild.ownerID === member.id }, { // BOT_SUPPORT // @@ -52,13 +52,13 @@ export const PermissionLevels: PermissionLevel[] = [ // After checking the lengths of these three objects, use this as the length for consistency. const length = PermissionLevels.length; -export function hasPermission(member: GuildMember, permission: number): boolean { - for (let i = length - 1; i >= permission; i--) if (PermissionLevels[i].check(member.user, member)) return true; +export function hasPermission(user: User, member: GuildMember | null, permission: number): boolean { + for (let i = length - 1; i >= permission; i--) if (PermissionLevels[i].check(user, member)) return true; return false; } -export function getPermissionLevel(member: GuildMember): number { - for (let i = length - 1; i >= 0; i--) if (PermissionLevels[i].check(member.user, member)) return i; +export function getPermissionLevel(user: User, member: GuildMember | null): number { + for (let i = length - 1; i >= 0; i--) if (PermissionLevels[i].check(user, member)) return i; return 0; } From 2a4d08d0bca51c56c7fea71dc5fd23d514370984 Mon Sep 17 00:00:00 2001 From: WatDuhHekBro <44940783+WatDuhHekBro@users.noreply.github.com> Date: Sun, 4 Apr 2021 20:01:29 -0500 Subject: [PATCH 129/178] Added special case for bot DMs --- src/core/handler.ts | 76 +++++++++++++++++++++++++++++++-------------- 1 file changed, 52 insertions(+), 24 deletions(-) diff --git a/src/core/handler.ts b/src/core/handler.ts index 3566a77..cff1c41 100644 --- a/src/core/handler.ts +++ b/src/core/handler.ts @@ -12,7 +12,14 @@ export function addInterceptRule(handler: (message: Message) => boolean) { interceptRules.push(handler); } +const defaultMetadata = { + permission: 0, + nsfw: false, + channelType: CHANNEL_TYPE.ANY +}; + // Note: client.user is only undefined before the bot logs in, so by this point, client.user cannot be undefined. +// Note: guild.available will never need to be checked because the message starts in either a DM channel or an already-available guild. client.on("message", async (message) => { for (const shouldIntercept of interceptRules) { if (shouldIntercept(message)) { @@ -20,46 +27,65 @@ client.on("message", async (message) => { } } + const commands = await loadableCommands; const {author, channel, content, guild, member} = message; + const text = content; + const menu = { + author, + channel, + client, + guild, + member, + message, + args: [] + }; + // Execute a dedicated block for messages in DM channels. + if (channel.type === "dm") { + // In a DM channel, simply forget about the prefix and execute any message as a command. + const [header, ...args] = text.split(/ +/); + + if (commands.has(header)) { + const command = commands.get(header)!; + + // Send the arguments to the command to resolve and execute. + const result = await command.execute(args, menu, { + header, + args, + ...defaultMetadata + }); + + // If something went wrong, let the user know (like if they don't have permission to use a command). + if (result) { + channel.send(result); + } + } else { + channel.send( + `I couldn't find the command or alias that starts with \`${header}\`. To see the list of commands, type \`help\`` + ); + } + } // Continue if the bot has permission to send messages in this channel. - if (channel.type === "dm" || channel.permissionsFor(client.user!)!.has(Permissions.FLAGS.SEND_MESSAGES)) { - const text = content; + else if (channel.permissionsFor(client.user!)!.has(Permissions.FLAGS.SEND_MESSAGES)) { const prefix = getPrefix(guild); // First, test if the message is just a ping to the bot. if (new RegExp(`^<@!?${client.user!.id}>$`).test(text)) { - channel.send(`${author}, my prefix on this guild is \`${prefix}\`.`); + channel.send(`${author}, my prefix on this server is \`${prefix}\`.`); } // Then check if it's a normal command. else if (text.startsWith(prefix)) { const [header, ...args] = text.substring(prefix.length).split(/ +/); - const commands = await loadableCommands; if (commands.has(header)) { const command = commands.get(header)!; // Send the arguments to the command to resolve and execute. - // TMP[MAKE SURE TO REPLACE WITH command.execute WHEN FINISHED] - const result = await command.execute( + const result = await command.execute(args, menu, { + header, args, - { - author, - channel, - client, - guild, - member, - message, - args: [] - }, - { - header, - args, - permission: 0, - nsfw: false, - channelType: CHANNEL_TYPE.ANY - } - ); + ...defaultMetadata + }); // If something went wrong, let the user know (like if they don't have permission to use a command). if (result) { @@ -67,7 +93,9 @@ client.on("message", async (message) => { } } } - } else { + } + // Otherwise, let the sender know that the bot doesn't have permission to send messages. + else { author.send( `I don't have permission to send messages in ${channel}. ${ member!.hasPermission(Permissions.FLAGS.ADMINISTRATOR) From 6ed4c0988f287a62e616d6fa69194d8a53281ad7 Mon Sep 17 00:00:00 2001 From: WatDuhHekBro <44940783+WatDuhHekBro@users.noreply.github.com> Date: Sun, 4 Apr 2021 22:40:31 -0500 Subject: [PATCH 130/178] Implemented rough draft of info resolver method --- src/commands/system/help.ts | 87 ++++++++------- src/core/command.ts | 208 ++++++++++++++++++++++-------------- src/core/handler.ts | 12 +-- src/core/loader.ts | 2 +- 4 files changed, 184 insertions(+), 125 deletions(-) diff --git a/src/commands/system/help.ts b/src/commands/system/help.ts index 6277fde..c772eef 100644 --- a/src/commands/system/help.ts +++ b/src/commands/system/help.ts @@ -1,9 +1,9 @@ -import Command from "../../core/command"; +import {Command, NamedCommand} from "../../core/command"; import {toTitleCase} from "../../core/lib"; import {loadableCommands, categories} from "../../core/loader"; import {getPermissionName} from "../../core/permissions"; -export default new Command({ +export default new NamedCommand({ description: "Lists all commands. If a command is specified, their arguments are listed as well.", usage: "([command, [subcommand/type], ...])", aliases: ["h"], @@ -16,13 +16,7 @@ export default new Command({ for (const header of headers) { if (header !== "test") { - const command = commands.get(header); - - if (!command) - return console.warn( - `Command "${header}" of category "${category}" unexpectedly doesn't exist!` - ); - + const command = commands.get(header)!; output += `\n- \`${header}\`: ${command.description}`; } } @@ -32,44 +26,63 @@ export default new Command({ }, any: new Command({ async run($) { - // [category, commandName, command, subcommandInfo] = resolveCommandInfo(); + // Setup the root command + const commands = await loadableCommands; + let header = $.args.shift() as string; + let command = commands.get(header); + if (!command || header === "test") return $.channel.send(`No command found by the name \`${header}\`.`); + if (!(command instanceof NamedCommand)) + return $.channel.send(`Command is not a proper instance of NamedCommand.`); + if (command.name) header = command.name; + + // Search categories + let category = "Unknown"; + for (const [referenceCategory, headers] of categories) { + if (headers.includes(header)) { + category = toTitleCase(referenceCategory); + break; + } + } + + // Gather info + const result = await command.resolveInfo($.args); + + if (result.type === "error") return $.channel.send(result.message); let append = ""; + command = result.command; - if (usage === "") { + if (command.usage === "") { const list: string[] = []; - command.subcommands.forEach((subcmd, subtag) => { - // Don't capture duplicates generated from aliases. - if (subcmd.originalCommandName === subtag) { - const customUsage = subcmd.usage ? ` ${subcmd.usage}` : ""; - list.push(`- \`${header} ${subtag}${customUsage}\` - ${subcmd.description}`); - } - }); + for (const [tag, subcommand] of result.keyedSubcommandInfo) { + const customUsage = subcommand.usage ? ` ${subcommand.usage}` : ""; + list.push(`- \`${header} ${tag}${customUsage}\` - ${subcommand.description}`); + } - const addDynamicType = (cmd: Command | null, type: string) => { - if (cmd) { - const customUsage = cmd.usage ? ` ${cmd.usage}` : ""; - list.push(`- \`${header} <${type}>${customUsage}\` - ${cmd.description}`); - } - }; - - addDynamicType(command.user, "user"); - addDynamicType(command.number, "number"); - addDynamicType(command.any, "any"); + for (const [type, subcommand] of result.subcommandInfo) { + const customUsage = subcommand.usage ? ` ${subcommand.usage}` : ""; + list.push(`- \`${header} ${type}${customUsage}\` - ${subcommand.description}`); + } append = "Usages:" + (list.length > 0 ? `\n${list.join("\n")}` : " None."); - } else append = `Usage: \`${header} ${usage}\``; + } else { + append = `Usage: \`${header} ${command.usage}\``; + } - const formattedAliases: string[] = []; - for (const alias of command.aliases) formattedAliases.push(`\`${alias}\``); - // Short circuit an empty string, in this case, if there are no aliases. - const aliases = formattedAliases.join(", ") || "None"; + let aliases = "N/A"; - $.channel.send( - `Command: \`${header}\`\nAliases: ${aliases}\nCategory: \`${selectedCategory}\`\nPermission Required: \`${getPermissionName( - permLevel - )}\` (${permLevel})\nDescription: ${command.description}\n${append}`, + if (command instanceof NamedCommand) { + const formattedAliases: string[] = []; + for (const alias of command.aliases) formattedAliases.push(`\`${alias}\``); + // Short circuit an empty string, in this case, if there are no aliases. + aliases = formattedAliases.join(", ") || "None"; + } + + return $.channel.send( + `Command: \`${header}\`\nAliases: ${aliases}\nCategory: \`${category}\`\nPermission Required: \`${getPermissionName( + result.permission + )}\` (${result.permission})\nDescription: ${command.description}\n${append}`, {split: true} ); } diff --git a/src/core/command.ts b/src/core/command.ts index 20b7f90..79b1693 100644 --- a/src/core/command.ts +++ b/src/core/command.ts @@ -1,4 +1,4 @@ -import {parseVars, requireAllCasesHandledFor} from "./lib"; +import {parseVars} from "./lib"; import {Collection} from "discord.js"; import {Client, Message, TextChannel, DMChannel, NewsChannel, Guild, User, GuildMember, GuildChannel} from "discord.js"; import {getPrefix} from "../core/structures"; @@ -82,6 +82,36 @@ interface ExecuteCommandMetadata { channelType: CHANNEL_TYPE; } +interface CommandInfo { + readonly type: "info"; + readonly command: Command; + readonly subcommandInfo: Collection; + readonly keyedSubcommandInfo: Collection; + readonly permission: number; + readonly nsfw: boolean; + readonly channelType: CHANNEL_TYPE; + readonly args: string[]; +} + +interface CommandInfoError { + readonly type: "error"; + readonly message: string; +} + +interface CommandInfoMetadata { + permission: number; + nsfw: boolean; + channelType: CHANNEL_TYPE; + args: string[]; + usage: string; +} + +export const defaultMetadata = { + permission: 0, + nsfw: false, + channelType: CHANNEL_TYPE.ANY +}; + export class Command { public readonly description: string; public readonly endpoint: boolean; @@ -90,7 +120,7 @@ export class Command { public readonly nsfw: boolean | null; // null (default) indicates to inherit public readonly channelType: CHANNEL_TYPE | null; // null (default) indicates to inherit protected run: (($: CommandMenu) => Promise) | string; - protected readonly subcommands: Collection; // This is the final data structure you'll actually use to work with the commands the aliases point to. + protected readonly subcommands: Collection; // This is the final data structure you'll actually use to work with the commands the aliases point to. protected user: Command | null; protected number: Command | null; protected any: Command | null; @@ -124,7 +154,7 @@ export class Command { // This shouldn't be a problem because I'm hoping that JS stores these as references that point to the same object. for (const name in options.subcommands) { const subcmd = options.subcommands[name]; - subcmd.originalCommandName = name; + subcmd.name = name; const aliases = subcmd.aliases; for (const alias of aliases) { @@ -153,7 +183,7 @@ export class Command { const param = args.shift(); // If there are no arguments left, execute the current command. Otherwise, continue on. - if (!param) { + if (param === undefined) { // See if there is anything that'll prevent the user from executing the command. // 1. Does this command specify a required channel type? If so, does the channel type match? @@ -218,13 +248,9 @@ export class Command { // If the current command is an endpoint but there are still some arguments left, don't continue. if (this.endpoint) return {content: "Too many arguments!"}; - // If the current command's permission level isn't -1 (inherit), then set the permission metadata equal to that. + // Update inherited properties if the current command specifies a property. if (this.permission !== -1) metadata.permission = this.permission; - - // If the current command has an NSFW setting specified, set it. if (this.nsfw !== null) metadata.nsfw = this.nsfw; - - // If the current command doesn't inherit its channel type, set it. if (this.channelType !== null) metadata.channelType = this.channelType; // Resolve the value of the current command's argument (adding it to the resolved args), @@ -257,11 +283,97 @@ export class Command { // Note: Do NOT add a return statement here. In case one of the other sections is missing // a return statement, there'll be a compile error to catch that. } + + // What this does is resolve the resulting subcommand as well as the inherited properties and the available subcommands. + public async resolveInfo(args: string[]): Promise { + return this.resolveInfoInternal(args, {...defaultMetadata, args: [], usage: ""}); + } + + private async resolveInfoInternal( + args: string[], + metadata: CommandInfoMetadata + ): Promise { + const param = args.shift(); + + // If there are no arguments left, return the data or an error message. + if (param === undefined) { + const keyedSubcommandInfo = new Collection(); + const subcommandInfo = new Collection(); + + // Get all the subcommands of the current command but without aliases. + for (const [tag, command] of this.subcommands.entries()) { + // Don't capture duplicates generated from aliases. + if (tag === command.name) { + keyedSubcommandInfo.set(tag, command); + } + } + + // Then get all the generic subcommands. + if (this.user) subcommandInfo.set("", this.user); + if (this.number) subcommandInfo.set("", this.number); + if (this.any) subcommandInfo.set("", this.any); + + return { + type: "info", + command: this, + keyedSubcommandInfo, + subcommandInfo, + ...metadata + }; + } + + // Update inherited properties if the current command specifies a property. + if (this.permission !== -1) metadata.permission = this.permission; + if (this.nsfw !== null) metadata.nsfw = this.nsfw; + if (this.channelType !== null) metadata.channelType = this.channelType; + if (this.usage !== "") metadata.usage = this.usage; + + // Then test if anything fits any hardcoded values, otherwise check if it's a valid keyed subcommand. + if (param === "") { + if (this.user) { + metadata.args.push(""); + return this.user.resolveInfoInternal(args, metadata); + } else { + return { + type: "error", + message: `No subcommand found by the argument list: \`${metadata.args.join(" ")}\`` + }; + } + } else if (param === "") { + if (this.number) { + metadata.args.push(""); + return this.number.resolveInfoInternal(args, metadata); + } else { + return { + type: "error", + message: `No subcommand found by the argument list: \`${metadata.args.join(" ")}\`` + }; + } + } else if (param === "") { + if (this.any) { + metadata.args.push(""); + return this.any.resolveInfoInternal(args, metadata); + } else { + return { + type: "error", + message: `No subcommand found by the argument list: \`${metadata.args.join(" ")}\`` + }; + } + } else if (this.subcommands?.has(param)) { + metadata.args.push(param); + return this.subcommands.get(param)!.resolveInfoInternal(args, metadata); + } else { + return { + type: "error", + message: `No subcommand found by the argument list: \`${metadata.args.join(" ")}\`` + }; + } + } } export class NamedCommand extends Command { public readonly aliases: string[]; // This is to keep the array intact for parent Command instances to use. It'll also be used when loading top-level aliases. - public originalCommandName: string | null; // If the command is an alias, what's the original name? + private originalCommandName: string | null; // If the command is an alias, what's the original name? constructor(options?: NamedCommandOptions) { super(options); @@ -269,74 +381,14 @@ export class NamedCommand extends Command { this.originalCommandName = null; } - // Returns: [category, command name, command, available subcommands: [type, subcommand]] - public async resolveInfo(args: string[]): [string, string, Command, Collection] | null { - // For debug info, use this.originalCommandName? (if it exists?) - const commands = await loadableCommands; - let header = args.shift(); - let command = commands.get(header); + public get name(): string { + if (this.originalCommandName === null) throw new Error("originalCommandName must be set before accessing it!"); + else return this.originalCommandName; + } - if (!command || header === "test") { - $.channel.send(`No command found by the name \`${header}\`!`); - return; - } - - if (command.originalCommandName) header = command.originalCommandName; - else console.warn(`originalCommandName isn't defined for ${header}?!`); - - let permLevel = command.permission ?? 0; - let usage = command.usage; - let invalid = false; - - let selectedCategory = "Unknown"; - - for (const [category, headers] of categories) { - if (headers.includes(header)) { - if (selectedCategory !== "Unknown") - console.warn( - `Command "${header}" is somehow in multiple categories. This means that the command loading stage probably failed in properly adding categories.` - ); - else selectedCategory = toTitleCase(category); - } - } - - for (const param of args) { - const type = command.resolve(param); - command = command.get(param); - permLevel = command.permission ?? permLevel; - - if (permLevel === -1) permLevel = command.permission; - - // Switch over to doing `$help info ` - switch (type) { - case TYPES.SUBCOMMAND: - header += ` ${command.originalCommandName}`; - break; - case TYPES.USER: - header += " "; - break; - case TYPES.NUMBER: - header += " "; - break; - case TYPES.ANY: - header += " "; - break; - case TYPES.NONE: - header += ` ${param}`; - break; - default: - requireAllCasesHandledFor(type); - } - - if (type === TYPES.NONE) { - invalid = true; - break; - } - } - - if (invalid) { - $.channel.send(`No command found by the name \`${header}\`!`); - return; - } + public set name(value: string) { + if (this.originalCommandName !== null) + throw new Error(`originalCommandName cannot be set twice! Attempted to set the value to "${value}".`); + else this.originalCommandName = value; } } diff --git a/src/core/handler.ts b/src/core/handler.ts index cff1c41..9a7582d 100644 --- a/src/core/handler.ts +++ b/src/core/handler.ts @@ -3,7 +3,7 @@ import {loadableCommands} from "./loader"; import {Permissions, Message} from "discord.js"; import {getPrefix} from "./structures"; import {Config} from "./structures"; -import {CHANNEL_TYPE} from "./command"; +import {defaultMetadata} from "./command"; // For custom message events that want to cancel the command handler on certain conditions. const interceptRules: ((message: Message) => boolean)[] = [(message) => message.author.bot]; @@ -12,12 +12,6 @@ export function addInterceptRule(handler: (message: Message) => boolean) { interceptRules.push(handler); } -const defaultMetadata = { - permission: 0, - nsfw: false, - channelType: CHANNEL_TYPE.ANY -}; - // Note: client.user is only undefined before the bot logs in, so by this point, client.user cannot be undefined. // Note: guild.available will never need to be checked because the message starts in either a DM channel or an already-available guild. client.on("message", async (message) => { @@ -51,7 +45,7 @@ client.on("message", async (message) => { // Send the arguments to the command to resolve and execute. const result = await command.execute(args, menu, { header, - args, + args: [...args], ...defaultMetadata }); @@ -83,7 +77,7 @@ client.on("message", async (message) => { // Send the arguments to the command to resolve and execute. const result = await command.execute(args, menu, { header, - args, + args: [...args], ...defaultMetadata }); diff --git a/src/core/loader.ts b/src/core/loader.ts index 8333c1b..6c6fa7f 100644 --- a/src/core/loader.ts +++ b/src/core/loader.ts @@ -31,7 +31,7 @@ export const loadableCommands = (async () => { const command = (await import(`../commands/${commandID}`)).default as unknown; if (command instanceof NamedCommand) { - command.originalCommandName = commandName; + command.name = commandName; if (commands.has(commandName)) { console.warn( From 5c3896c2dbcd15ed8deb3f47c42d1a51f27f17ac Mon Sep 17 00:00:00 2001 From: WatDuhHekBro <44940783+WatDuhHekBro@users.noreply.github.com> Date: Sun, 4 Apr 2021 23:35:12 -0500 Subject: [PATCH 131/178] Separated command handler from utility modules and fixed lingering errors in commands --- src/commands/fun/8ball.ts | 12 +- src/commands/fun/cookie.ts | 23 ++- src/commands/fun/eco.ts | 5 +- src/commands/fun/modules/eco-core.ts | 15 +- src/commands/fun/modules/eco-extras.ts | 6 +- src/commands/fun/modules/eco-shop-items.ts | 2 +- src/commands/fun/modules/eco-shop.ts | 11 +- src/commands/fun/modules/eco-utils.ts | 4 +- src/commands/fun/neko.ts | 18 +-- src/commands/fun/ok.ts | 10 +- src/commands/fun/owoify.ts | 12 +- src/commands/fun/poll.ts | 16 +-- src/commands/system/admin.ts | 154 ++++++++++----------- src/commands/system/help.ts | 24 ++-- src/commands/template.ts | 57 +------- src/commands/utility/desc.ts | 20 +-- src/commands/utility/emote.ts | 6 +- src/commands/utility/info.ts | 99 +++++++------ src/commands/utility/lsemotes.ts | 44 +++--- src/commands/utility/react.ts | 59 ++++---- src/commands/utility/say.ts | 8 +- src/commands/utility/scanemotes.ts | 32 ++--- src/commands/utility/shorten.ts | 10 +- src/commands/utility/time.ts | 15 +- src/core/command.ts | 28 ++-- src/core/handler.ts | 13 +- src/core/index.ts | 15 ++ src/core/permissions.ts | 2 +- src/index.ts | 3 +- src/{core => }/lib.test.ts | 0 src/{core => }/lib.ts | 2 +- src/modules/emoteRegistry.ts | 4 +- src/modules/lavalink.ts | 2 +- src/modules/messageEmbed.ts | 2 +- src/modules/ready.ts | 12 ++ src/modules/setup.ts | 4 +- src/{core => modules}/storage.ts | 0 src/{core => }/structures.ts | 2 +- 38 files changed, 344 insertions(+), 407 deletions(-) create mode 100644 src/core/index.ts rename src/{core => }/lib.test.ts (100%) rename src/{core => }/lib.ts (99%) create mode 100644 src/modules/ready.ts rename src/{core => modules}/storage.ts (100%) rename src/{core => }/structures.ts (99%) diff --git a/src/commands/fun/8ball.ts b/src/commands/fun/8ball.ts index 84488a7..2c45770 100644 --- a/src/commands/fun/8ball.ts +++ b/src/commands/fun/8ball.ts @@ -1,5 +1,5 @@ -import Command from "../../core/command"; -import {random} from "../../core/lib"; +import {Command, NamedCommand} from "../../core"; +import {random} from "../../lib"; const responses = [ "Most likely,", @@ -24,16 +24,16 @@ const responses = [ "Very doubtful," ]; -export default new Command({ +export default new NamedCommand({ description: "Answers your question in an 8-ball manner.", endpoint: false, usage: "", run: "Please provide a question.", any: new Command({ description: "Question to ask the 8-ball.", - async run($) { - const sender = $.message.author; - $.channel.send(`${random(responses)} <@${sender.id}>`); + async run({message, channel, guild, author, member, client, args}) { + const sender = message.author; + channel.send(`${random(responses)} <@${sender.id}>`); } }) }); diff --git a/src/commands/fun/cookie.ts b/src/commands/fun/cookie.ts index 06c7086..50b1b3d 100644 --- a/src/commands/fun/cookie.ts +++ b/src/commands/fun/cookie.ts @@ -1,7 +1,6 @@ import {User} from "discord.js"; -import Command from "../../core/command"; -import {random} from "../../core/lib"; -import {parseVars} from "../../core/lib"; +import {Command, NamedCommand} from "../../core"; +import {random, parseVars} from "../../lib"; const cookies = [ `has given %target% a chocolate chip cookie!`, @@ -26,29 +25,29 @@ const cookies = [ `bakes %target% fresh cookies, it smells amazing.` ]; -export default new Command({ +export default new NamedCommand({ description: "Gives specified user a cookie.", usage: "['all'/@user]", run: ":cookie: Here's a cookie!", subcommands: { - all: new Command({ - async run($) { - $.channel.send(`${$.author} gave everybody a cookie!`); + all: new NamedCommand({ + async run({message, channel, guild, author, member, client, args}) { + channel.send(`${author} gave everybody a cookie!`); } }) }, user: new Command({ description: "User to give cookie to.", - async run($) { - const sender = $.author; - const mention: User = $.args[0]; + async run({message, channel, guild, author, member, client, args}) { + const sender = author; + const mention: User = args[0]; if (mention.id == sender.id) { - $.channel.send("You can't give yourself cookies!"); + channel.send("You can't give yourself cookies!"); return; } - $.channel.send( + channel.send( `:cookie: <@${sender.id}> ${parseVars(random(cookies), { target: mention.toString() })}` diff --git a/src/commands/fun/eco.ts b/src/commands/fun/eco.ts index 380cc33..37c288e 100644 --- a/src/commands/fun/eco.ts +++ b/src/commands/fun/eco.ts @@ -1,11 +1,10 @@ -import Command from "../../core/command"; +import {Command, NamedCommand, callMemberByUsername} from "../../core"; import {isAuthorized, getMoneyEmbed} from "./modules/eco-utils"; import {DailyCommand, PayCommand, GuildCommand, LeaderboardCommand} from "./modules/eco-core"; import {BuyCommand, ShopCommand} from "./modules/eco-shop"; import {MondayCommand} from "./modules/eco-extras"; -import {callMemberByUsername} from "../../core/libd"; -export default new Command({ +export default new NamedCommand({ description: "Economy command for Monika.", async run({guild, channel, author}) { if (isAuthorized(guild, channel)) channel.send(getMoneyEmbed(author)); diff --git a/src/commands/fun/modules/eco-core.ts b/src/commands/fun/modules/eco-core.ts index ea174b1..b1966f2 100644 --- a/src/commands/fun/modules/eco-core.ts +++ b/src/commands/fun/modules/eco-core.ts @@ -1,10 +1,9 @@ -import Command from "../../../core/command"; -import {prompt} from "../../../core/libd"; -import {pluralise} from "../../../core/lib"; -import {Storage} from "../../../core/structures"; +import {Command, NamedCommand, prompt} from "../../../core"; +import {pluralise} from "../../../lib"; +import {Storage} from "../../../structures"; import {isAuthorized, getMoneyEmbed, getSendEmbed, ECO_EMBED_COLOR} from "./eco-utils"; -export const DailyCommand = new Command({ +export const DailyCommand = new NamedCommand({ description: "Pick up your daily Mons. The cooldown is per user and every 22 hours to allow for some leeway.", aliases: ["get"], async run({author, channel, guild}) { @@ -38,7 +37,7 @@ export const DailyCommand = new Command({ } }); -export const GuildCommand = new Command({ +export const GuildCommand = new NamedCommand({ description: "Get info on the guild's economy as a whole.", async run({guild, channel}) { if (isAuthorized(guild, channel)) { @@ -75,7 +74,7 @@ export const GuildCommand = new Command({ } }); -export const LeaderboardCommand = new Command({ +export const LeaderboardCommand = new NamedCommand({ description: "See the richest players.", aliases: ["top"], async run({guild, channel, client}) { @@ -109,7 +108,7 @@ export const LeaderboardCommand = new Command({ } }); -export const PayCommand = new Command({ +export const PayCommand = new NamedCommand({ description: "Send money to someone.", usage: " ", run: "Who are you sending this money to?", diff --git a/src/commands/fun/modules/eco-extras.ts b/src/commands/fun/modules/eco-extras.ts index 274bb16..f8de393 100644 --- a/src/commands/fun/modules/eco-extras.ts +++ b/src/commands/fun/modules/eco-extras.ts @@ -1,10 +1,10 @@ -import Command from "../../../core/command"; -import {Storage} from "../../../core/structures"; +import {Command, NamedCommand} from "../../../core/command"; +import {Storage} from "../../../structures"; import {isAuthorized, getMoneyEmbed} from "./eco-utils"; const WEEKDAY = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]; -export const MondayCommand = new Command({ +export const MondayCommand = new NamedCommand({ description: "Use this on a UTC Monday to get an extra Mon. Does not affect your 22 hour timer for `eco daily`.", async run({guild, channel, author}) { if (isAuthorized(guild, channel)) { diff --git a/src/commands/fun/modules/eco-shop-items.ts b/src/commands/fun/modules/eco-shop-items.ts index 0a90549..d50a779 100644 --- a/src/commands/fun/modules/eco-shop-items.ts +++ b/src/commands/fun/modules/eco-shop-items.ts @@ -1,5 +1,5 @@ import {Message} from "discord.js"; -import {random} from "../../../core/lib"; +import {random} from "../../../lib"; export interface ShopItem { cost: number; diff --git a/src/commands/fun/modules/eco-shop.ts b/src/commands/fun/modules/eco-shop.ts index f5c167b..47d0178 100644 --- a/src/commands/fun/modules/eco-shop.ts +++ b/src/commands/fun/modules/eco-shop.ts @@ -1,12 +1,11 @@ -import Command from "../../../core/command"; -import {pluralise, split} from "../../../core/lib"; -import {paginate} from "../../../core/libd"; -import {Storage, getPrefix} from "../../../core/structures"; +import {Command, NamedCommand, paginate} from "../../../core"; +import {pluralise, split} from "../../../lib"; +import {Storage, getPrefix} from "../../../structures"; import {isAuthorized, ECO_EMBED_COLOR} from "./eco-utils"; import {ShopItems, ShopItem} from "./eco-shop-items"; import {EmbedField} from "discord.js"; -export const ShopCommand = new Command({ +export const ShopCommand = new NamedCommand({ description: "Displays the list of items you can buy in the shop.", async run({guild, channel, author}) { if (isAuthorized(guild, channel)) { @@ -45,7 +44,7 @@ export const ShopCommand = new Command({ } }); -export const BuyCommand = new Command({ +export const BuyCommand = new NamedCommand({ description: "Buys an item from the shop.", usage: "", async run({guild, channel, args, message, author}) { diff --git a/src/commands/fun/modules/eco-utils.ts b/src/commands/fun/modules/eco-utils.ts index e4ea32f..327abdb 100644 --- a/src/commands/fun/modules/eco-utils.ts +++ b/src/commands/fun/modules/eco-utils.ts @@ -1,5 +1,5 @@ -import {pluralise} from "../../../core/lib"; -import {Storage} from "../../../core/structures"; +import {pluralise} from "../../../lib"; +import {Storage} from "../../../structures"; import {User, Guild, TextChannel, DMChannel, NewsChannel} from "discord.js"; export const ECO_EMBED_COLOR = 0xf1c40f; diff --git a/src/commands/fun/neko.ts b/src/commands/fun/neko.ts index 113fb15..46d0bfc 100644 --- a/src/commands/fun/neko.ts +++ b/src/commands/fun/neko.ts @@ -1,6 +1,6 @@ import {URL} from "url"; -import Command from "../../core/command"; -import {getContent} from "../../core/lib"; +import {Command, NamedCommand} from "../../core"; +import {getContent} from "../../lib"; const endpoints: {sfw: {[key: string]: string}} = { sfw: { @@ -34,26 +34,26 @@ const endpoints: {sfw: {[key: string]: string}} = { } }; -export default new Command({ +export default new NamedCommand({ description: "Provides you with a random image with the selected argument.", - async run($) { - $.channel.send( + async run({message, channel, guild, author, member, client, args}) { + channel.send( `Please provide an image type. Available arguments:\n\`[${Object.keys(endpoints.sfw).join(", ")}]\`.` ); }, any: new Command({ description: "Image type to send.", - async run($) { - const arg = $.args[0]; + async run({message, channel, guild, author, member, client, args}) { + const arg = args[0]; if (!(arg in endpoints.sfw)) { - $.channel.send("Couldn't find that endpoint!"); + channel.send("Couldn't find that endpoint!"); return; } let url = new URL(`https://nekos.life/api/v2${endpoints.sfw[arg]}`); const content = await getContent(url.toString()); - $.channel.send(content.url); + channel.send(content.url); } }) }); diff --git a/src/commands/fun/ok.ts b/src/commands/fun/ok.ts index a4ea44a..b2998ab 100644 --- a/src/commands/fun/ok.ts +++ b/src/commands/fun/ok.ts @@ -1,5 +1,5 @@ -import Command from "../../core/command"; -import {random} from "../../core/lib"; +import {Command, NamedCommand} from "../../core"; +import {random} from "../../lib"; const responses = [ "boomer", @@ -59,9 +59,9 @@ const responses = [ "large man" ]; -export default new Command({ +export default new NamedCommand({ description: "Sends random ok message.", - async run($) { - $.channel.send(`ok ${random(responses)}`); + async run({message, channel, guild, author, member, client, args}) { + channel.send(`ok ${random(responses)}`); } }); diff --git a/src/commands/fun/owoify.ts b/src/commands/fun/owoify.ts index af041df..2b4cdd7 100644 --- a/src/commands/fun/owoify.ts +++ b/src/commands/fun/owoify.ts @@ -1,12 +1,12 @@ -import Command from "../../core/command"; -import {getContent} from "../../core/lib"; +import {Command, NamedCommand} from "../../core"; +import {getContent} from "../../lib"; import {URL} from "url"; -export default new Command({ +export default new NamedCommand({ description: "OwO-ifies the input.", - async run($) { - let url = new URL(`https://nekos.life/api/v2/owoify?text=${$.args.join(" ")}`); + async run({message, channel, guild, author, member, client, args}) { + let url = new URL(`https://nekos.life/api/v2/owoify?text=${args.join(" ")}`); const content = (await getContent(url.toString())) as any; // Apparently, the object in question is {owo: string}. - $.channel.send(content.owo); + channel.send(content.owo); } }); diff --git a/src/commands/fun/poll.ts b/src/commands/fun/poll.ts index 8b6d6e1..38efb68 100644 --- a/src/commands/fun/poll.ts +++ b/src/commands/fun/poll.ts @@ -1,25 +1,25 @@ import {MessageEmbed} from "discord.js"; -import Command from "../../core/command"; +import {Command, NamedCommand} from "../../core"; -export default new Command({ +export default new NamedCommand({ description: "Create a poll.", usage: "", run: "Please provide a question.", any: new Command({ description: "Question for the poll.", - async run($) { + async run({message, channel, guild, author, member, client, args}) { const embed = new MessageEmbed() .setAuthor( - `Poll created by ${$.message.author.username}`, - $.message.guild?.iconURL({dynamic: true}) ?? undefined + `Poll created by ${message.author.username}`, + message.guild?.iconURL({dynamic: true}) ?? undefined ) .setColor(0xffffff) .setFooter("React to vote.") - .setDescription($.args.join(" ")); - const msg = await $.channel.send(embed); + .setDescription(args.join(" ")); + const msg = await channel.send(embed); await msg.react("✅"); await msg.react("⛔"); - $.message.delete({ + message.delete({ timeout: 1000 }); } diff --git a/src/commands/system/admin.ts b/src/commands/system/admin.ts index 8dae940..b2da0bb 100644 --- a/src/commands/system/admin.ts +++ b/src/commands/system/admin.ts @@ -1,8 +1,6 @@ -import Command, {handler} from "../../core/command"; -import {clean} from "../../core/lib"; -import {botHasPermission} from "../../core/libd"; -import {Config, Storage} from "../../core/structures"; -import {getPermissionLevel, getPermissionName} from "../../core/permissions"; +import {Command, NamedCommand, botHasPermission, getPermissionLevel, getPermissionName} from "../../core"; +import {clean} from "../../lib"; +import {Config, Storage} from "../../structures"; import {Permissions} from "discord.js"; import {logs} from "../../modules/globals"; @@ -20,59 +18,55 @@ function getLogBuffer(type: string) { const activities = ["playing", "listening", "streaming", "watching"]; const statuses = ["online", "idle", "dnd", "invisible"]; -export default new Command({ +export default new NamedCommand({ description: "An all-in-one command to do admin stuff. You need to be either an admin of the server or one of the bot's mechanics to use this command.", - async run($) { - if (!$.member) { - $.channel.send("Couldn't find a member object for you! Did you make sure you used this in a server?"); - return; - } - const permLevel = getPermissionLevel($.member); - $.channel.send( - `${$.author.toString()}, your permission level is \`${getPermissionName(permLevel)}\` (${permLevel}).` - ); + async run({message, channel, guild, author, member, client, args}) { + if (!member) + return channel.send("Couldn't find a member object for you! Did you make sure you used this in a server?"); + const permLevel = getPermissionLevel(author, member); + return channel.send(`${author}, your permission level is \`${getPermissionName(permLevel)}\` (${permLevel}).`); }, subcommands: { - set: new Command({ + set: new NamedCommand({ description: "Set different per-guild settings for the bot.", run: "You have to specify the option you want to set.", permission: PERMISSIONS.ADMIN, subcommands: { - prefix: new Command({ + prefix: new NamedCommand({ description: "Set a custom prefix for your guild. Removes your custom prefix if none is provided.", usage: "()", - async run($) { - Storage.getGuild($.guild?.id || "N/A").prefix = null; + async run({message, channel, guild, author, member, client, args}) { + Storage.getGuild(guild?.id || "N/A").prefix = null; Storage.save(); - $.channel.send( + channel.send( `The custom prefix for this guild has been removed. My prefix is now back to \`${Config.prefix}\`.` ); }, any: new Command({ - async run($) { - Storage.getGuild($.guild?.id || "N/A").prefix = $.args[0]; + async run({message, channel, guild, author, member, client, args}) { + Storage.getGuild(guild?.id || "N/A").prefix = args[0]; Storage.save(); - $.channel.send(`The custom prefix for this guild is now \`${$.args[0]}\`.`); + channel.send(`The custom prefix for this guild is now \`${args[0]}\`.`); } }) }) } }), - diag: new Command({ + diag: new NamedCommand({ description: 'Requests a debug log with the "info" verbosity level.', permission: PERMISSIONS.BOT_SUPPORT, - async run($) { - $.channel.send(getLogBuffer("info")); + async run({message, channel, guild, author, member, client, args}) { + channel.send(getLogBuffer("info")); }, any: new Command({ description: `Select a verbosity to listen to. Available levels: \`[${Object.keys(logs).join(", ")}]\``, - async run($) { - const type = $.args[0]; + async run({message, channel, guild, author, member, client, args}) { + const type = args[0]; - if (type in logs) $.channel.send(getLogBuffer(type)); + if (type in logs) channel.send(getLogBuffer(type)); else - $.channel.send( + channel.send( `Couldn't find a verbosity level named \`${type}\`! The available types are \`[${Object.keys( logs ).join(", ")}]\`.` @@ -80,75 +74,72 @@ export default new Command({ } }) }), - status: new Command({ + status: new NamedCommand({ description: "Changes the bot's status.", permission: PERMISSIONS.BOT_SUPPORT, - async run($) { - $.channel.send("Setting status to `online`..."); + async run({message, channel, guild, author, member, client, args}) { + channel.send("Setting status to `online`..."); }, any: new Command({ description: `Select a status to set to. Available statuses: \`[${statuses.join(", ")}]\`.`, - async run($) { - if (!statuses.includes($.args[0])) { - $.channel.send("That status doesn't exist!"); - return; + async run({message, channel, guild, author, member, client, args}) { + if (!statuses.includes(args[0])) { + return channel.send("That status doesn't exist!"); } else { - $.client.user?.setStatus($.args[0]); - $.channel.send(`Setting status to \`${$.args[0]}\`...`); + client.user?.setStatus(args[0]); + return channel.send(`Setting status to \`${args[0]}\`...`); } } }) }), - purge: new Command({ + purge: new NamedCommand({ description: "Purges the bot's own messages.", permission: PERMISSIONS.BOT_SUPPORT, - async run($) { + async run({message, channel, guild, author, member, client, args}) { // It's probably better to go through the bot's own messages instead of calling bulkDelete which requires MANAGE_MESSAGES. - if (botHasPermission($.guild, Permissions.FLAGS.MANAGE_MESSAGES) && $.channel.type !== "dm") { - $.message.delete(); - const msgs = await $.channel.messages.fetch({ + if (botHasPermission(guild, Permissions.FLAGS.MANAGE_MESSAGES) && channel.type !== "dm") { + message.delete(); + const msgs = await channel.messages.fetch({ limit: 100 }); - const travMessages = msgs.filter((m) => m.author.id === $.client.user?.id); + const travMessages = msgs.filter((m) => m.author.id === client.user?.id); - await $.channel.send(`Found ${travMessages.size} messages to delete.`).then((m) => + await channel.send(`Found ${travMessages.size} messages to delete.`).then((m) => m.delete({ timeout: 5000 }) ); - await $.channel.bulkDelete(travMessages); + await channel.bulkDelete(travMessages); } else { - $.channel.send( + channel.send( "This command must be executed in a guild where I have the `MANAGE_MESSAGES` permission." ); } } }), - clear: new Command({ + clear: new NamedCommand({ description: "Clears a given amount of messages.", usage: "", run: "A number was not provided.", number: new Command({ description: "Amount of messages to delete.", - async run($) { - if ($.channel.type === "dm") { - await $.channel.send("Can't clear messages in the DMs!"); - return; - } - $.message.delete(); - const fetched = await $.channel.messages.fetch({ - limit: $.args[0] + async run({message, channel, guild, author, member, client, args}) { + if (channel.type === "dm") return channel.send("Can't clear messages in the DMs!"); + message.delete(); + const fetched = await channel.messages.fetch({ + limit: args[0] }); - await $.channel.bulkDelete(fetched); + await channel.bulkDelete(fetched); + return; } }) }), - eval: new Command({ + eval: new NamedCommand({ description: "Evaluate code.", usage: "", permission: PERMISSIONS.BOT_OWNER, // You have to bring everything into scope to use them. AFAIK, there isn't a more maintainable way to do this, but at least TS will let you know if anything gets removed. - async run({args, author, channel, client, guild, member, message}) { + async run({message, channel, guild, author, member, client, args}) { try { const code = args.join(" "); let evaled = eval(code); @@ -160,49 +151,46 @@ export default new Command({ } } }), - nick: new Command({ + nick: new NamedCommand({ description: "Change the bot's nickname.", permission: PERMISSIONS.BOT_SUPPORT, - async run($) { - const nickName = $.args.join(" "); - await $.guild?.me?.setNickname(nickName); - if (botHasPermission($.guild, Permissions.FLAGS.MANAGE_MESSAGES)) - $.message.delete({timeout: 5000}).catch(handler.bind($)); - $.channel.send(`Nickname set to \`${nickName}\``).then((m) => m.delete({timeout: 5000})); + async run({message, channel, guild, author, member, client, args}) { + const nickName = args.join(" "); + await guild?.me?.setNickname(nickName); + if (botHasPermission(guild, Permissions.FLAGS.MANAGE_MESSAGES)) message.delete({timeout: 5000}); + channel.send(`Nickname set to \`${nickName}\``).then((m) => m.delete({timeout: 5000})); } }), - guilds: new Command({ + guilds: new NamedCommand({ description: "Shows a list of all guilds the bot is a member of.", permission: PERMISSIONS.BOT_SUPPORT, - async run($) { - const guildList = $.client.guilds.cache.array().map((e) => e.name); - $.channel.send(guildList, {split: true}); + async run({message, channel, guild, author, member, client, args}) { + const guildList = client.guilds.cache.array().map((e) => e.name); + channel.send(guildList, {split: true}); } }), - activity: new Command({ + activity: new NamedCommand({ description: "Set the activity of the bot.", permission: PERMISSIONS.BOT_SUPPORT, usage: " ", - async run($) { - $.client.user?.setActivity(".help", { + async run({message, channel, guild, author, member, client, args}) { + client.user?.setActivity(".help", { type: "LISTENING" }); - $.channel.send("Activity set to default."); + channel.send("Activity set to default."); }, any: new Command({ description: `Select an activity type to set. Available levels: \`[${activities.join(", ")}]\``, - async run($) { - const type = $.args[0]; + async run({message, channel, guild, author, member, client, args}) { + const type = args[0]; if (activities.includes(type)) { - $.client.user?.setActivity($.args.slice(1).join(" "), { - type: $.args[0].toUpperCase() + client.user?.setActivity(args.slice(1).join(" "), { + type: args[0].toUpperCase() }); - $.channel.send( - `Set activity to \`${$.args[0].toUpperCase()}\` \`${$.args.slice(1).join(" ")}\`.` - ); + channel.send(`Set activity to \`${args[0].toUpperCase()}\` \`${args.slice(1).join(" ")}\`.`); } else - $.channel.send( + channel.send( `Couldn't find an activity type named \`${type}\`! The available types are \`[${activities.join( ", " )}]\`.` diff --git a/src/commands/system/help.ts b/src/commands/system/help.ts index c772eef..e3b160c 100644 --- a/src/commands/system/help.ts +++ b/src/commands/system/help.ts @@ -1,13 +1,11 @@ -import {Command, NamedCommand} from "../../core/command"; -import {toTitleCase} from "../../core/lib"; -import {loadableCommands, categories} from "../../core/loader"; -import {getPermissionName} from "../../core/permissions"; +import {Command, NamedCommand, loadableCommands, categories, getPermissionName} from "../../core"; +import {toTitleCase} from "../../lib"; export default new NamedCommand({ description: "Lists all commands. If a command is specified, their arguments are listed as well.", usage: "([command, [subcommand/type], ...])", aliases: ["h"], - async run($) { + async run({message, channel, guild, author, member, client, args}) { const commands = await loadableCommands; let output = `Legend: \`\`, \`[list/of/stuff]\`, \`(optional)\`, \`()\`, \`([optional/list/...])\``; @@ -22,17 +20,17 @@ export default new NamedCommand({ } } - $.channel.send(output, {split: true}); + channel.send(output, {split: true}); }, any: new Command({ - async run($) { + async run({message, channel, guild, author, member, client, args}) { // Setup the root command const commands = await loadableCommands; - let header = $.args.shift() as string; + let header = args.shift() as string; let command = commands.get(header); - if (!command || header === "test") return $.channel.send(`No command found by the name \`${header}\`.`); + if (!command || header === "test") return channel.send(`No command found by the name \`${header}\`.`); if (!(command instanceof NamedCommand)) - return $.channel.send(`Command is not a proper instance of NamedCommand.`); + return channel.send(`Command is not a proper instance of NamedCommand.`); if (command.name) header = command.name; // Search categories @@ -45,9 +43,9 @@ export default new NamedCommand({ } // Gather info - const result = await command.resolveInfo($.args); + const result = await command.resolveInfo(args); - if (result.type === "error") return $.channel.send(result.message); + if (result.type === "error") return channel.send(result.message); let append = ""; command = result.command; @@ -79,7 +77,7 @@ export default new NamedCommand({ aliases = formattedAliases.join(", ") || "None"; } - return $.channel.send( + return channel.send( `Command: \`${header}\`\nAliases: ${aliases}\nCategory: \`${category}\`\nPermission Required: \`${getPermissionName( result.permission )}\` (${result.permission})\nDescription: ${command.description}\n${append}`, diff --git a/src/commands/template.ts b/src/commands/template.ts index a97e8e2..70d0e65 100644 --- a/src/commands/template.ts +++ b/src/commands/template.ts @@ -1,56 +1,7 @@ -import Command from "../core/command"; +import {Command, NamedCommand} from "../core"; -export default new Command({ - description: - 'This is a template/testing command providing common functionality. Remove what you don\'t need, and rename/delete this file to generate a fresh command file here. This command should be automatically excluded from the help command. The "usage" parameter (string) overrides the default usage for the help command. The "endpoint" parameter (boolean) prevents further arguments from being passed. Also, as long as you keep the run function async, it\'ll return a promise allowing the program to automatically catch any synchronous errors. However, you\'ll have to do manual error handling if you go the then and catch route.', - endpoint: false, - usage: "", - permission: -1, - aliases: [], - async run($) { +export default new NamedCommand({ + async run({message, channel, guild, author, member, client, args}) { // code - }, - subcommands: { - layer: new Command({ - description: - 'This is a named subcommand, meaning that the key name is what determines the keyword to use. With default settings for example, "$test layer".', - endpoint: false, - usage: "", - permission: -1, - aliases: [], - async run($) { - // code - } - }) - }, - user: new Command({ - description: - 'This is the subcommand for getting users by pinging them or copying their ID. With default settings for example, "$test 237359961842253835". The argument will be a user object and won\'t run if no user is found by that ID.', - endpoint: false, - usage: "", - permission: -1, - async run($) { - // code - } - }), - number: new Command({ - description: - 'This is a numeric subcommand, meaning that any type of number (excluding Infinity/NaN) will route to this command if present. With default settings for example, "$test -5.2". The argument with the number is already parsed so you can just use it without converting it.', - endpoint: false, - usage: "", - permission: -1, - async run($) { - // code - } - }), - any: new Command({ - description: - "This is a generic subcommand, meaning that if there isn't a more specific subcommand that's called, it falls to this. With default settings for example, \"$test reeee\".", - endpoint: false, - usage: "", - permission: -1, - async run($) { - // code - } - }) + } }); diff --git a/src/commands/utility/desc.ts b/src/commands/utility/desc.ts index 0ab3aaf..ed0b87c 100644 --- a/src/commands/utility/desc.ts +++ b/src/commands/utility/desc.ts @@ -1,29 +1,29 @@ -import Command from "../../core/command"; +import {Command, NamedCommand} from "../../core"; -export default new Command({ +export default new NamedCommand({ description: "Renames current voice channel.", usage: "", - async run($) { - const voiceChannel = $.message.member?.voice.channel; + async run({message, channel, guild, author, member, client, args}) { + const voiceChannel = message.member?.voice.channel; if (!voiceChannel) { - $.channel.send("You are not in a voice channel."); + channel.send("You are not in a voice channel."); return; } if (!voiceChannel.guild.me?.hasPermission("MANAGE_CHANNELS")) { - $.channel.send("I am lacking the required permissions to perform this action."); + channel.send("I am lacking the required permissions to perform this action."); return; } - if ($.args.length === 0) { - $.channel.send("Please provide a new voice channel name."); + if (args.length === 0) { + channel.send("Please provide a new voice channel name."); return; } const prevName = voiceChannel.name; - const newName = $.args.join(" "); + const newName = args.join(" "); await voiceChannel.setName(newName); - await $.channel.send(`Changed channel name from "${prevName}" to "${newName}".`); + await channel.send(`Changed channel name from "${prevName}" to "${newName}".`); } }); diff --git a/src/commands/utility/emote.ts b/src/commands/utility/emote.ts index addaec0..23a6563 100644 --- a/src/commands/utility/emote.ts +++ b/src/commands/utility/emote.ts @@ -1,9 +1,7 @@ -import Command from "../../core/command"; +import {Command, NamedCommand} from "../../core"; import {queryClosestEmoteByName} from "./modules/emote-utils"; -import {botHasPermission} from "../../core/libd"; -import {Permissions} from "discord.js"; -export default new Command({ +export default new NamedCommand({ description: "Send the specified emote.", run: "Please provide a command name.", any: new Command({ diff --git a/src/commands/utility/info.ts b/src/commands/utility/info.ts index e2e6025..68b4d35 100644 --- a/src/commands/utility/info.ts +++ b/src/commands/utility/info.ts @@ -1,29 +1,26 @@ -import {MessageEmbed, version as djsversion} from "discord.js"; +import {MessageEmbed, version as djsversion, Guild} from "discord.js"; import ms from "ms"; import os from "os"; -import Command from "../../core/command"; -import {formatBytes, trimArray} from "../../core/lib"; -import {getMemberByUsername} from "../../core/libd"; +import {Command, NamedCommand, getMemberByUsername} from "../../core"; +import {formatBytes, trimArray} from "../../lib"; import {verificationLevels, filterLevels, regions} from "../../defs/info"; -import moment from "moment"; -import utc from "moment"; -import {Guild} from "discord.js"; +import moment, {utc} from "moment"; -export default new Command({ +export default new NamedCommand({ description: "Command to provide all sorts of info about the current server, a user, etc.", run: "Please provide an argument.\nFor help, run `%prefix%help info`.", subcommands: { - avatar: new Command({ + avatar: new NamedCommand({ description: "Shows your own, or another user's avatar.", usage: "()", - async run($) { - $.channel.send($.author.displayAvatarURL({dynamic: true, size: 2048})); + async run({message, channel, guild, author, member, client, args}) { + channel.send(author.displayAvatarURL({dynamic: true, size: 2048})); }, user: new Command({ description: "Shows your own, or another user's avatar.", - async run($) { - $.channel.send( - $.args[0].displayAvatarURL({ + async run({message, channel, guild, author, member, client, args}) { + channel.send( + args[0].displayAvatarURL({ dynamic: true, size: 2048 }) @@ -32,39 +29,39 @@ export default new Command({ }), any: new Command({ description: "Shows another user's avatar by searching their name", - async run($) { - if ($.guild) { - const name = $.args.join(" "); - const member = await getMemberByUsername($.guild, name); + async run({message, channel, guild, author, member, client, args}) { + if (guild) { + const name = args.join(" "); + const member = await getMemberByUsername(guild, name); if (member) { - $.channel.send( + channel.send( member.user.displayAvatarURL({ dynamic: true, size: 2048 }) ); } else { - $.channel.send(`No user found by the name \`${name}\`!`); + channel.send(`No user found by the name \`${name}\`!`); } } } }) }), - bot: new Command({ + bot: new NamedCommand({ description: "Displays info about the bot.", - async run($) { + async run({message, channel, guild, author, member, client, args}) { const core = os.cpus()[0]; const embed = new MessageEmbed() - .setColor($.guild?.me?.displayHexColor || "BLUE") + .setColor(guild?.me?.displayHexColor || "BLUE") .addField("General", [ - `**❯ Client:** ${$.client.user?.tag} (${$.client.user?.id})`, - `**❯ Servers:** ${$.client.guilds.cache.size.toLocaleString()}`, - `**❯ Users:** ${$.client.guilds.cache + `**❯ Client:** ${client.user?.tag} (${client.user?.id})`, + `**❯ Servers:** ${client.guilds.cache.size.toLocaleString()}`, + `**❯ Users:** ${client.guilds.cache .reduce((a: any, b: {memberCount: any}) => a + b.memberCount, 0) .toLocaleString()}`, - `**❯ Channels:** ${$.client.channels.cache.size.toLocaleString()}`, - `**❯ Creation Date:** ${utc($.client.user?.createdTimestamp).format("Do MMMM YYYY HH:mm:ss")}`, + `**❯ Channels:** ${client.channels.cache.size.toLocaleString()}`, + `**❯ Creation Date:** ${utc(client.user?.createdTimestamp).format("Do MMMM YYYY HH:mm:ss")}`, `**❯ Node.JS:** ${process.version}`, `**❯ Version:** v${process.env.npm_package_version}`, `**❯ Discord.JS:** ${djsversion}`, @@ -84,45 +81,45 @@ export default new Command({ `\u3000 • Used: ${formatBytes(process.memoryUsage().heapUsed)}` ]) .setTimestamp(); - const avatarURL = $.client.user?.displayAvatarURL({ + const avatarURL = client.user?.displayAvatarURL({ dynamic: true, size: 2048 }); if (avatarURL) embed.setThumbnail(avatarURL); - $.channel.send(embed); + channel.send(embed); } }), - guild: new Command({ + guild: new NamedCommand({ description: "Displays info about the current guild or another guild.", usage: "(/)", - async run($) { - if ($.guild) { - $.channel.send(await getGuildInfo($.guild, $.guild)); + async run({message, channel, guild, author, member, client, args}) { + if (guild) { + channel.send(await getGuildInfo(guild, guild)); } else { - $.channel.send("Please execute this command in a guild."); + channel.send("Please execute this command in a guild."); } }, any: new Command({ description: "Display info about a guild by finding its name or ID.", - async run($) { + async run({message, channel, guild, author, member, client, args}) { // If a guild ID is provided (avoid the "number" subcommand because of inaccuracies), search for that guild - if ($.args.length === 1 && /^\d{17,19}$/.test($.args[0])) { - const id = $.args[0]; - const guild = $.client.guilds.cache.get(id); + if (args.length === 1 && /^\d{17,19}$/.test(args[0])) { + const id = args[0]; + const guild = client.guilds.cache.get(id); if (guild) { - $.channel.send(await getGuildInfo(guild, $.guild)); + channel.send(await getGuildInfo(guild, guild)); } else { - $.channel.send(`None of the servers I'm in matches the guild ID \`${id}\`!`); + channel.send(`None of the servers I'm in matches the guild ID \`${id}\`!`); } } else { - const query: string = $.args.join(" ").toLowerCase(); - const guild = $.client.guilds.cache.find((guild) => guild.name.toLowerCase().includes(query)); + const query: string = args.join(" ").toLowerCase(); + const guild = client.guilds.cache.find((guild) => guild.name.toLowerCase().includes(query)); if (guild) { - $.channel.send(await getGuildInfo(guild, $.guild)); + channel.send(await getGuildInfo(guild, guild)); } else { - $.channel.send(`None of the servers I'm in matches the query \`${query}\`!`); + channel.send(`None of the servers I'm in matches the query \`${query}\`!`); } } } @@ -131,12 +128,12 @@ export default new Command({ }, user: new Command({ description: "Displays info about mentioned user.", - async run($) { + async run({message, channel, guild, author, client, args}) { // Transforms the User object into a GuildMember object of the current guild. - const member = await $.guild?.members.fetch($.args[0]); + const member = await guild?.members.fetch(args[0]); if (!member) { - $.channel.send( + channel.send( "No member object was found by that user! Are you sure you used this command in a server?" ); return; @@ -166,16 +163,14 @@ export default new Command({ `**❯ Game:** ${member.user.presence.activities || "Not playing a game."}` ]) .addField("Member", [ - `**❯ Highest Role:** ${ - member.roles.highest.id === $.guild?.id ? "None" : member.roles.highest.name - }`, + `**❯ Highest Role:** ${member.roles.highest.id === guild?.id ? "None" : member.roles.highest.name}`, `**❯ Server Join Date:** ${moment(member.joinedAt).format("LL LTS")}`, `**❯ Hoist Role:** ${member.roles.hoist ? member.roles.hoist.name : "None"}`, `**❯ Roles:** [${roles.length}]: ${ roles.length == 0 ? "None" : roles.length <= 10 ? roles.join(", ") : trimArray(roles).join(", ") }` ]); - $.channel.send(embed); + channel.send(embed); } }) }); diff --git a/src/commands/utility/lsemotes.ts b/src/commands/utility/lsemotes.ts index c369ab0..a04f044 100644 --- a/src/commands/utility/lsemotes.ts +++ b/src/commands/utility/lsemotes.ts @@ -1,44 +1,38 @@ -import {GuildEmoji} from "discord.js"; -import {MessageEmbed} from "discord.js"; -import Command from "../../core/command"; -import {split} from "../../core/lib"; -import {paginate} from "../../core/libd"; +import {GuildEmoji, MessageEmbed, TextChannel, DMChannel, NewsChannel, User} from "discord.js"; +import {Command, NamedCommand, paginate} from "../../core"; +import {split} from "../../lib"; import vm from "vm"; -import {TextChannel} from "discord.js"; -import {DMChannel} from "discord.js"; -import {NewsChannel} from "discord.js"; -import {User} from "discord.js"; const REGEX_TIMEOUT_MS = 1000; -export default new Command({ +export default new NamedCommand({ description: "Lists all emotes the bot has in it's registry,", usage: " (-flags)", - async run($) { - displayEmoteList($.client.emojis.cache.array(), $.channel, $.author); + async run({message, channel, guild, author, member, client, args}) { + displayEmoteList(client.emojis.cache.array(), channel, author); }, any: new Command({ description: "Filters emotes by via a regular expression. Flags can be added by adding a dash at the end. For example, to do a case-insensitive search, do %prefix%lsemotes somepattern -i", - async run($) { + async run({message, channel, guild, author, member, client, args}) { // If a guild ID is provided, filter all emotes by that guild (but only if there aren't any arguments afterward) - if ($.args.length === 1 && /^\d{17,19}$/.test($.args[0])) { - const guildID: string = $.args[0]; + if (args.length === 1 && /^\d{17,19}$/.test(args[0])) { + const guildID: string = args[0]; displayEmoteList( - $.client.emojis.cache.filter((emote) => emote.guild.id === guildID).array(), - $.channel, - $.author + client.emojis.cache.filter((emote) => emote.guild.id === guildID).array(), + channel, + author ); } else { // Otherwise, search via a regex pattern let flags: string | undefined = undefined; - if (/^-[dgimsuy]{1,7}$/.test($.args[$.args.length - 1])) { - flags = $.args.pop().substring(1); + if (/^-[dgimsuy]{1,7}$/.test(args[args.length - 1])) { + flags = args.pop().substring(1); } - let emoteCollection = $.client.emojis.cache.array(); + let emoteCollection = client.emojis.cache.array(); // Creates a sandbox to stop a regular expression if it takes too much time to search. // To avoid passing in a giant data structure, I'll just pass in the structure {[id: string]: [name: string]}. //let emotes: {[id: string]: string} = {}; @@ -50,7 +44,7 @@ export default new Command({ // The result will be sandbox.emotes because it'll be modified in-place. const sandbox = { - regex: new RegExp($.args.join(" "), flags), + regex: new RegExp(args.join(" "), flags), emotes }; const context = vm.createContext(sandbox); @@ -64,10 +58,10 @@ export default new Command({ script.runInContext(context, {timeout: REGEX_TIMEOUT_MS}); emotes = sandbox.emotes; emoteCollection = emoteCollection.filter((emote) => emotes.has(emote.id)); // Only allow emotes that haven't been deleted. - displayEmoteList(emoteCollection, $.channel, $.author); + displayEmoteList(emoteCollection, channel, author); } catch (error) { if (error.code === "ERR_SCRIPT_EXECUTION_TIMEOUT") { - $.channel.send( + channel.send( `The regular expression you entered exceeded the time limit of ${REGEX_TIMEOUT_MS} milliseconds.` ); } else { @@ -75,7 +69,7 @@ export default new Command({ } } } else { - $.channel.send("Failed to initialize sandbox."); + channel.send("Failed to initialize sandbox."); } } } diff --git a/src/commands/utility/react.ts b/src/commands/utility/react.ts index 152e98d..43f3394 100644 --- a/src/commands/utility/react.ts +++ b/src/commands/utility/react.ts @@ -1,17 +1,17 @@ -import Command from "../../core/command"; +import {Command, NamedCommand} from "../../core"; import {Message, Channel, TextChannel} from "discord.js"; import {queryClosestEmoteByName} from "./modules/emote-utils"; -export default new Command({ +export default new NamedCommand({ description: "Reacts to the a previous message in your place. You have to react with the same emote before the bot removes that reaction.", usage: 'react ()', - async run($) { + async run({message, channel, guild, author, member, client, args}) { let target: Message | undefined; let distance = 1; - if ($.args.length >= 2) { - const last = $.args[$.args.length - 1]; // Because this is optional, do not .pop() unless you're sure it's a message link indicator. + if (args.length >= 2) { + const last = args[args.length - 1]; // Because this is optional, do not .pop() unless you're sure it's a message link indicator. const URLPattern = /^(?:https:\/\/discord.com\/channels\/(\d{17,19})\/(\d{17,19})\/(\d{17,19}))$/; const copyIDPattern = /^(?:(\d{17,19})-(\d{17,19}))$/; @@ -21,66 +21,65 @@ export default new Command({ const guildID = match[1]; const channelID = match[2]; const messageID = match[3]; - let guild = $.guild; - let channel: Channel | undefined = $.channel; + let tmpChannel: Channel | undefined = channel; if (guild?.id !== guildID) { try { - guild = await $.client.guilds.fetch(guildID); + guild = await client.guilds.fetch(guildID); } catch { - return $.channel.send(`\`${guildID}\` is an invalid guild ID!`); + return channel.send(`\`${guildID}\` is an invalid guild ID!`); } } - if (channel.id !== channelID) channel = guild.channels.cache.get(channelID); - if (!channel) return $.channel.send(`\`${channelID}\` is an invalid channel ID!`); + if (tmpChannel.id !== channelID) tmpChannel = guild.channels.cache.get(channelID); + if (!tmpChannel) return channel.send(`\`${channelID}\` is an invalid channel ID!`); - if ($.message.id !== messageID) { + if (message.id !== messageID) { try { - target = await (channel as TextChannel).messages.fetch(messageID); + target = await (tmpChannel as TextChannel).messages.fetch(messageID); } catch { - return $.channel.send(`\`${messageID}\` is an invalid message ID!`); + return channel.send(`\`${messageID}\` is an invalid message ID!`); } } - $.args.pop(); + args.pop(); } // - ("Copy ID" Button) else if (copyIDPattern.test(last)) { const match = copyIDPattern.exec(last)!; const channelID = match[1]; const messageID = match[2]; - let channel: Channel | undefined = $.channel; + let tmpChannel: Channel | undefined = channel; - if (channel.id !== channelID) channel = $.guild?.channels.cache.get(channelID); - if (!channel) return $.channel.send(`\`${channelID}\` is an invalid channel ID!`); + if (tmpChannel.id !== channelID) tmpChannel = guild?.channels.cache.get(channelID); + if (!tmpChannel) return channel.send(`\`${channelID}\` is an invalid channel ID!`); - if ($.message.id !== messageID) { + if (message.id !== messageID) { try { - target = await (channel as TextChannel).messages.fetch(messageID); + target = await (tmpChannel as TextChannel).messages.fetch(messageID); } catch { - return $.channel.send(`\`${messageID}\` is an invalid message ID!`); + return channel.send(`\`${messageID}\` is an invalid message ID!`); } } - $.args.pop(); + args.pop(); } // else if (/^\d{17,19}$/.test(last)) { try { - target = await $.channel.messages.fetch(last); + target = await channel.messages.fetch(last); } catch { - return $.channel.send(`No valid message found by the ID \`${last}\`!`); + return channel.send(`No valid message found by the ID \`${last}\`!`); } - $.args.pop(); + args.pop(); } // The entire string has to be a number for this to match. Prevents leaCheeseAmerican1 from triggering this. else if (/^\d+$/.test(last)) { distance = parseInt(last); - if (distance >= 0 && distance <= 99) $.args.pop(); - else return $.channel.send("Your distance must be between 0 and 99!"); + if (distance >= 0 && distance <= 99) args.pop(); + else return channel.send("Your distance must be between 0 and 99!"); } } @@ -88,13 +87,13 @@ export default new Command({ // Messages are ordered from latest to earliest. // You also have to add 1 as well because fetchMessages includes your own message. target = ( - await $.message.channel.messages.fetch({ + await message.channel.messages.fetch({ limit: distance + 1 }) ).last(); } - for (const search of $.args) { + for (const search of args) { // Even though the bot will always grab *some* emote, the user can choose not to keep that emote there if it isn't what they want const emote = queryClosestEmoteByName(search); const reaction = await target!.react(emote); @@ -102,7 +101,7 @@ export default new Command({ // This part is called with a promise because you don't want to wait 5 seconds between each reaction. setTimeout(() => { // This reason for this null assertion is that by the time you use this command, the client is going to be loaded. - reaction.users.remove($.client.user!); + reaction.users.remove(client.user!); }, 5000); } diff --git a/src/commands/utility/say.ts b/src/commands/utility/say.ts index e58b90e..6c70dfb 100644 --- a/src/commands/utility/say.ts +++ b/src/commands/utility/say.ts @@ -1,13 +1,13 @@ -import Command from "../../core/command"; +import {Command, NamedCommand} from "../../core"; -export default new Command({ +export default new NamedCommand({ description: "Repeats your message.", usage: "", run: "Please provide a message for me to say!", any: new Command({ description: "Message to repeat.", - async run($) { - $.channel.send(`*${$.author} says:*\n${$.args.join(" ")}`); + async run({message, channel, guild, author, member, client, args}) { + channel.send(`*${author} says:*\n${args.join(" ")}`); } }) }); diff --git a/src/commands/utility/scanemotes.ts b/src/commands/utility/scanemotes.ts index b087793..41d5115 100644 --- a/src/commands/utility/scanemotes.ts +++ b/src/commands/utility/scanemotes.ts @@ -1,33 +1,33 @@ -import Command, {handler} from "../../core/command"; -import {pluralise} from "../../core/lib"; +import {Command, NamedCommand} from "../../core"; +import {pluralise} from "../../lib"; import moment from "moment"; import {Collection, TextChannel} from "discord.js"; const lastUsedTimestamps: {[id: string]: number} = {}; -export default new Command({ +export default new NamedCommand({ description: "Scans all text channels in the current guild and returns the number of times each emoji specific to the guild has been used. Has a cooldown of 24 hours per guild.", - async run($) { - if (!$.guild) { - $.channel.send(`You must use this command on a server!`); + async run({message, channel, guild, author, member, client, args}) { + if (!guild) { + channel.send(`You must use this command on a server!`); return; } // Test if the command is on cooldown. This isn't the strictest cooldown possible, because in the event that the bot crashes, the cooldown will be reset. But for all intends and purposes, it's a good enough cooldown. It's a per-server cooldown. const startTime = Date.now(); const cooldown = 86400000; // 24 hours - const lastUsedTimestamp = lastUsedTimestamps[$.guild.id] ?? 0; + const lastUsedTimestamp = lastUsedTimestamps[guild.id] ?? 0; const difference = startTime - lastUsedTimestamp; const howLong = moment(startTime).to(lastUsedTimestamp + cooldown); // If it's been less than an hour since the command was last used, prevent it from executing. if (difference < cooldown) { - $.channel.send( + channel.send( `This command requires a day to cooldown. You'll be able to activate this command ${howLong}.` ); return; - } else lastUsedTimestamps[$.guild.id] = startTime; + } else lastUsedTimestamps[guild.id] = startTime; const stats: { [id: string]: { @@ -39,20 +39,20 @@ export default new Command({ } = {}; let totalUserEmoteUsage = 0; // IMPORTANT: You MUST check if the bot actually has access to the channel in the first place. It will get the list of all channels, but that doesn't mean it has access to every channel. Without this, it'll require admin access and throw an annoying unhelpful DiscordAPIError: Missing Access otherwise. - const allTextChannelsInCurrentGuild = $.guild.channels.cache.filter( + const allTextChannelsInCurrentGuild = guild.channels.cache.filter( (channel) => channel.type === "text" && channel.viewable ) as Collection; let messagesSearched = 0; let channelsSearched = 0; let currentChannelName = ""; const totalChannels = allTextChannelsInCurrentGuild.size; - const statusMessage = await $.channel.send("Gathering emotes..."); + const statusMessage = await channel.send("Gathering emotes..."); let warnings = 0; - $.channel.startTyping(); + channel.startTyping(); // Initialize the emote stats object with every emote in the current guild. // The goal here is to cut the need to access guild.emojis.get() which'll make it faster and easier to work with. - for (let emote of $.guild.emojis.cache.values()) { + for (let emote of guild.emojis.cache.values()) { // If you don't include the "a" for animated emotes, it'll not only not show up, but also cause all other emotes in the same message to not show up. The emote name is self-correcting but it's better to keep the right value since it'll be used to calculate message lengths that fit. stats[emote.id] = { name: emote.name, @@ -70,7 +70,7 @@ export default new Command({ for (const channel of allTextChannelsInCurrentGuild.values()) { currentChannelName = channel.name; - let selected = channel.lastMessageID ?? $.message.id; + let selected = channel.lastMessageID ?? message.id; let continueLoop = true; while (continueLoop) { @@ -167,7 +167,7 @@ export default new Command({ )}.` ); console.log(`Finished operation in ${finishTime - startTime} ms.`); - $.channel.stopTyping(); + channel.stopTyping(); // Display stats on emote usage. // This can work outside the loop now that it's synchronous, and now it's clearer what code is meant to execute at the end. @@ -186,6 +186,6 @@ export default new Command({ ); } - $.channel.send(lines, {split: true}).catch(handler.bind($)); + await channel.send(lines, {split: true}); } }); diff --git a/src/commands/utility/shorten.ts b/src/commands/utility/shorten.ts index 79961f3..a14c460 100644 --- a/src/commands/utility/shorten.ts +++ b/src/commands/utility/shorten.ts @@ -1,18 +1,18 @@ -import Command from "../../core/command"; +import {Command, NamedCommand} from "../../core"; import * as https from "https"; -export default new Command({ +export default new NamedCommand({ description: "Shortens a given URL.", run: "Please provide a URL.", any: new Command({ - async run($) { - https.get("https://is.gd/create.php?format=simple&url=" + encodeURIComponent($.args[0]), function (res) { + async run({message, channel, guild, author, member, client, args}) { + https.get("https://is.gd/create.php?format=simple&url=" + encodeURIComponent(args[0]), function (res) { var body = ""; res.on("data", function (chunk) { body += chunk; }); res.on("end", function () { - $.channel.send(`<${body}>`); + channel.send(`<${body}>`); }); }); } diff --git a/src/commands/utility/time.ts b/src/commands/utility/time.ts index d520271..c51552a 100644 --- a/src/commands/utility/time.ts +++ b/src/commands/utility/time.ts @@ -1,6 +1,5 @@ -import Command from "../../core/command"; -import {ask, askYesOrNo, askMultipleChoice, prompt, callMemberByUsername} from "../../core/libd"; -import {Storage} from "../../core/structures"; +import {Command, NamedCommand, ask, askYesOrNo, askMultipleChoice, prompt, callMemberByUsername} from "../../core"; +import {Storage} from "../../structures"; import {User} from "discord.js"; import moment from "moment"; @@ -167,7 +166,7 @@ function getTimeEmbed(user: User) { return embed; } -export default new Command({ +export default new NamedCommand({ description: "Show others what time it is for you.", aliases: ["tz"], async run({channel, author}) { @@ -175,7 +174,7 @@ export default new Command({ }, subcommands: { // Welcome to callback hell. We hope you enjoy your stay here! - setup: new Command({ + setup: new NamedCommand({ description: "Registers your timezone information for the bot.", async run({author, channel}) { const profile = Storage.getUser(author.id); @@ -327,7 +326,7 @@ export default new Command({ ); } }), - delete: new Command({ + delete: new NamedCommand({ description: "Delete your timezone information.", async run({channel, author}) { prompt( @@ -344,7 +343,7 @@ export default new Command({ ); } }), - utc: new Command({ + utc: new NamedCommand({ description: "Displays UTC time.", async run({channel}) { const time = moment().utc(); @@ -370,7 +369,7 @@ export default new Command({ }); } }), - daylight: new Command({ + daylight: new NamedCommand({ description: "Provides information on the daylight savings region", run: DST_NOTE_INFO }) diff --git a/src/core/command.ts b/src/core/command.ts index 79b1693..becc5f6 100644 --- a/src/core/command.ts +++ b/src/core/command.ts @@ -1,19 +1,21 @@ -import {parseVars} from "./lib"; -import {Collection} from "discord.js"; -import {Client, Message, TextChannel, DMChannel, NewsChannel, Guild, User, GuildMember, GuildChannel} from "discord.js"; -import {getPrefix} from "../core/structures"; +import {parseVars} from "../lib"; +import { + Collection, + Client, + Message, + TextChannel, + DMChannel, + NewsChannel, + Guild, + User, + GuildMember, + GuildChannel +} from "discord.js"; +import {getPrefix} from "../structures"; import {SingleMessageOptions} from "./libd"; import {hasPermission, getPermissionLevel, getPermissionName} from "./permissions"; import {client} from "../index"; -export enum TYPES { - SUBCOMMAND, - USER, - NUMBER, - ANY, - NONE -} - // RegEx patterns used for identifying/extracting each type from a string argument. const patterns = { channel: /^<#(\d{17,19})>$/, @@ -30,7 +32,7 @@ const patterns = { // Therefore, there won't by any type narrowing on channel or guild of CommandMenu until this is fixed. // Otherwise, you'd have to define channelType for every single subcommand, which would get very tedious. // Just use type assertions when you specify a channel type. -export enum CHANNEL_TYPE { +enum CHANNEL_TYPE { ANY, GUILD, DM diff --git a/src/core/handler.ts b/src/core/handler.ts index 9a7582d..0646394 100644 --- a/src/core/handler.ts +++ b/src/core/handler.ts @@ -1,8 +1,7 @@ import {client} from "../index"; import {loadableCommands} from "./loader"; import {Permissions, Message} from "discord.js"; -import {getPrefix} from "./structures"; -import {Config} from "./structures"; +import {getPrefix} from "../structures"; import {defaultMetadata} from "./command"; // For custom message events that want to cancel the command handler on certain conditions. @@ -99,13 +98,3 @@ client.on("message", async (message) => { ); } }); - -client.once("ready", () => { - if (client.user) { - console.ready(`Logged in as ${client.user.tag}.`); - client.user.setActivity({ - type: "LISTENING", - name: `${Config.prefix}help` - }); - } -}); diff --git a/src/core/index.ts b/src/core/index.ts new file mode 100644 index 0000000..16b06e0 --- /dev/null +++ b/src/core/index.ts @@ -0,0 +1,15 @@ +export {Command, NamedCommand} from "./command"; +export {addInterceptRule} from "./handler"; +export { + SingleMessageOptions, + botHasPermission, + paginate, + prompt, + ask, + askYesOrNo, + askMultipleChoice, + getMemberByUsername, + callMemberByUsername +} from "./libd"; +export {loadableCommands, categories} from "./loader"; +export {hasPermission, getPermissionLevel, getPermissionName} from "./permissions"; diff --git a/src/core/permissions.ts b/src/core/permissions.ts index 9798a2a..a251a64 100644 --- a/src/core/permissions.ts +++ b/src/core/permissions.ts @@ -1,5 +1,5 @@ import {User, GuildMember, Permissions} from "discord.js"; -import {Config} from "./structures"; +import {Config} from "../structures"; interface PermissionLevel { name: string; diff --git a/src/index.ts b/src/index.ts index 5d1f52c..7280c76 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,7 +2,7 @@ import "./modules/globals"; import {Client} from "discord.js"; import setup from "./modules/setup"; -import {Config} from "./core/structures"; +import {Config} from "./structures"; // This is here in order to make it much less of a headache to access the client from other files. // This of course won't actually do anything until the setup process is complete and it logs in. @@ -16,6 +16,7 @@ setup.init().then(() => { // Initialize Modules // import "./core/handler"; // Command loading will start as soon as an instance of "core/command" is loaded, which is loaded in "core/handler". import "./core/eventListeners"; +import "./modules/ready"; import "./modules/presence"; import "./modules/lavalink"; import "./modules/emoteRegistry"; diff --git a/src/core/lib.test.ts b/src/lib.test.ts similarity index 100% rename from src/core/lib.test.ts rename to src/lib.test.ts diff --git a/src/core/lib.ts b/src/lib.ts similarity index 99% rename from src/core/lib.ts rename to src/lib.ts index 477089f..8036a96 100644 --- a/src/core/lib.ts +++ b/src/lib.ts @@ -1,6 +1,6 @@ // Library for pure functions import {get} from "https"; -import FileManager from "./storage"; +import FileManager from "./modules/storage"; /** * Splits a command by spaces while accounting for quotes which capture string arguments. diff --git a/src/modules/emoteRegistry.ts b/src/modules/emoteRegistry.ts index bcc7ed0..a3de8bc 100644 --- a/src/modules/emoteRegistry.ts +++ b/src/modules/emoteRegistry.ts @@ -1,6 +1,6 @@ import {client} from "../index"; -import FileManager from "../core/storage"; -import {EmoteRegistryDump} from "../core/structures"; +import FileManager from "./storage"; +import {EmoteRegistryDump} from "../structures"; function updateGlobalEmoteRegistry(): void { const data: EmoteRegistryDump = {version: 1, list: []}; diff --git a/src/modules/lavalink.ts b/src/modules/lavalink.ts index 92d6ee9..41a24fb 100644 --- a/src/modules/lavalink.ts +++ b/src/modules/lavalink.ts @@ -1,5 +1,5 @@ import attachClientToLavalink from "discord.js-lavalink-lib"; -import {Config} from "../core/structures"; +import {Config} from "../structures"; import {client} from "../index"; // Although the example showed to do "client.music = LavaLink(...)" and "(client as any).music = Lavalink(...)" was done to match that, nowhere in the library is client.music ever actually used nor does the function return anything. In other words, client.music is undefined and is never used. diff --git a/src/modules/messageEmbed.ts b/src/modules/messageEmbed.ts index 1d5c1c5..5018ef2 100644 --- a/src/modules/messageEmbed.ts +++ b/src/modules/messageEmbed.ts @@ -1,6 +1,6 @@ import {client} from "../index"; import {TextChannel, APIMessage, MessageEmbed} from "discord.js"; -import {getPrefix} from "../core/structures"; +import {getPrefix} from "../structures"; import {DiscordAPIError} from "discord.js"; client.on("message", async (message) => { diff --git a/src/modules/ready.ts b/src/modules/ready.ts new file mode 100644 index 0000000..9c3c86b --- /dev/null +++ b/src/modules/ready.ts @@ -0,0 +1,12 @@ +import {client} from "../index"; +import {Config} from "../structures"; + +client.once("ready", () => { + if (client.user) { + console.ready(`Logged in as ${client.user.tag}.`); + client.user.setActivity({ + type: "LISTENING", + name: `${Config.prefix}help` + }); + } +}); diff --git a/src/modules/setup.ts b/src/modules/setup.ts index 99ca135..f146dad 100644 --- a/src/modules/setup.ts +++ b/src/modules/setup.ts @@ -1,7 +1,7 @@ import {existsSync as exists, readFileSync as read, writeFile as write} from "fs"; import inquirer from "inquirer"; -import Storage, {generateHandler} from "../core/storage"; -import {Config} from "../core/structures"; +import Storage, {generateHandler} from "./storage"; +import {Config} from "../structures"; // The template should be built with a reductionist mentality. // Provide everything the user needs and then let them remove whatever they want. diff --git a/src/core/storage.ts b/src/modules/storage.ts similarity index 100% rename from src/core/storage.ts rename to src/modules/storage.ts diff --git a/src/core/structures.ts b/src/structures.ts similarity index 99% rename from src/core/structures.ts rename to src/structures.ts index 74e5adf..401c4d6 100644 --- a/src/core/structures.ts +++ b/src/structures.ts @@ -1,4 +1,4 @@ -import FileManager from "./storage"; +import FileManager from "./modules/storage"; import {select, GenericJSON, GenericStructure} from "./lib"; import {watch} from "fs"; import {Guild as DiscordGuild, Snowflake} from "discord.js"; From 44cae5c0cba0be0209ff8700d56b4410af38a8ce Mon Sep 17 00:00:00 2001 From: WatDuhHekBro <44940783+WatDuhHekBro@users.noreply.github.com> Date: Mon, 5 Apr 2021 03:46:50 -0500 Subject: [PATCH 132/178] Fixed some bugs and added proper event handler --- src/commands/system/help.ts | 32 +++++++++++++++++++++++++++----- src/core/command.ts | 31 +++++++++++++++++-------------- src/core/handler.ts | 31 ++++++++++++++++++++++++++++++- src/core/index.ts | 2 +- src/modules/lavalink.ts | 24 ++++++++++++++++++++++++ 5 files changed, 99 insertions(+), 21 deletions(-) diff --git a/src/commands/system/help.ts b/src/commands/system/help.ts index e3b160c..d205a10 100644 --- a/src/commands/system/help.ts +++ b/src/commands/system/help.ts @@ -1,5 +1,5 @@ -import {Command, NamedCommand, loadableCommands, categories, getPermissionName} from "../../core"; -import {toTitleCase} from "../../lib"; +import {Command, NamedCommand, loadableCommands, categories, getPermissionName, CHANNEL_TYPE} from "../../core"; +import {toTitleCase, requireAllCasesHandledFor} from "../../lib"; export default new NamedCommand({ description: "Lists all commands. If a command is specified, their arguments are listed as well.", @@ -10,14 +10,19 @@ export default new NamedCommand({ let output = `Legend: \`\`, \`[list/of/stuff]\`, \`(optional)\`, \`()\`, \`([optional/list/...])\``; for (const [category, headers] of categories) { - output += `\n\n===[ ${toTitleCase(category)} ]===`; + let tmp = `\n\n===[ ${toTitleCase(category)} ]===`; + // Ignore empty categories, including ["test"]. + let hasActualCommands = false; for (const header of headers) { if (header !== "test") { const command = commands.get(header)!; - output += `\n- \`${header}\`: ${command.description}`; + tmp += `\n- \`${header}\`: ${command.description}`; + hasActualCommands = true; } } + + if (hasActualCommands) output += tmp; } channel.send(output, {split: true}); @@ -50,6 +55,8 @@ export default new NamedCommand({ let append = ""; command = result.command; + if (result.args.length > 0) header += " " + result.args.join(" "); + if (command.usage === "") { const list: string[] = []; @@ -80,9 +87,24 @@ export default new NamedCommand({ return channel.send( `Command: \`${header}\`\nAliases: ${aliases}\nCategory: \`${category}\`\nPermission Required: \`${getPermissionName( result.permission - )}\` (${result.permission})\nDescription: ${command.description}\n${append}`, + )}\` (${result.permission})\nChannel Type: ${getChannelTypeName(result.channelType)}\nNSFW Only: ${ + result.nsfw ? "Yes" : "No" + }\nDescription: ${command.description}\n${append}`, {split: true} ); } }) }); + +function getChannelTypeName(type: CHANNEL_TYPE): string { + switch (type) { + case CHANNEL_TYPE.ANY: + return "Any"; + case CHANNEL_TYPE.GUILD: + return "Guild Only"; + case CHANNEL_TYPE.DM: + return "DM Only"; + default: + requireAllCasesHandledFor(type); + } +} diff --git a/src/core/command.ts b/src/core/command.ts index becc5f6..d389619 100644 --- a/src/core/command.ts +++ b/src/core/command.ts @@ -32,7 +32,7 @@ const patterns = { // Therefore, there won't by any type narrowing on channel or guild of CommandMenu until this is fixed. // Otherwise, you'd have to define channelType for every single subcommand, which would get very tedious. // Just use type assertions when you specify a channel type. -enum CHANNEL_TYPE { +export enum CHANNEL_TYPE { ANY, GUILD, DM @@ -126,7 +126,6 @@ export class Command { protected user: Command | null; protected number: Command | null; protected any: Command | null; - public static readonly CHANNEL_TYPE = CHANNEL_TYPE; constructor(options?: CommandOptions) { this.description = options?.description || "No description."; @@ -182,6 +181,13 @@ export class Command { menu: CommandMenu, metadata: ExecuteCommandMetadata ): Promise { + // Update inherited properties if the current command specifies a property. + // In case there are no initial arguments, these should go first so that it can register. + if (this.permission !== -1) metadata.permission = this.permission; + if (this.nsfw !== null) metadata.nsfw = this.nsfw; + if (this.channelType !== null) metadata.channelType = this.channelType; + + // Take off the leftmost argument from the list. const param = args.shift(); // If there are no arguments left, execute the current command. Otherwise, continue on. @@ -239,7 +245,7 @@ export class Command { return null; } catch (error) { const errorMessage = error.stack ?? error; - console.error(`Command Error: ${metadata.header} (${metadata.args})\n${errorMessage}`); + console.error(`Command Error: ${metadata.header} (${metadata.args.join(", ")})\n${errorMessage}`); return { content: `There was an error while trying to execute that command!\`\`\`${errorMessage}\`\`\`` @@ -250,11 +256,6 @@ export class Command { // If the current command is an endpoint but there are still some arguments left, don't continue. if (this.endpoint) return {content: "Too many arguments!"}; - // Update inherited properties if the current command specifies a property. - if (this.permission !== -1) metadata.permission = this.permission; - if (this.nsfw !== null) metadata.nsfw = this.nsfw; - if (this.channelType !== null) metadata.channelType = this.channelType; - // Resolve the value of the current command's argument (adding it to the resolved args), // then pass the thread of execution to whichever subcommand is valid (if any). if (this.subcommands.has(param)) { @@ -295,6 +296,14 @@ export class Command { args: string[], metadata: CommandInfoMetadata ): Promise { + // Update inherited properties if the current command specifies a property. + // In case there are no initial arguments, these should go first so that it can register. + if (this.permission !== -1) metadata.permission = this.permission; + if (this.nsfw !== null) metadata.nsfw = this.nsfw; + if (this.channelType !== null) metadata.channelType = this.channelType; + if (this.usage !== "") metadata.usage = this.usage; + + // Take off the leftmost argument from the list. const param = args.shift(); // If there are no arguments left, return the data or an error message. @@ -324,12 +333,6 @@ export class Command { }; } - // Update inherited properties if the current command specifies a property. - if (this.permission !== -1) metadata.permission = this.permission; - if (this.nsfw !== null) metadata.nsfw = this.nsfw; - if (this.channelType !== null) metadata.channelType = this.channelType; - if (this.usage !== "") metadata.usage = this.usage; - // Then test if anything fits any hardcoded values, otherwise check if it's a valid keyed subcommand. if (param === "") { if (this.user) { diff --git a/src/core/handler.ts b/src/core/handler.ts index 0646394..10bea9e 100644 --- a/src/core/handler.ts +++ b/src/core/handler.ts @@ -1,6 +1,6 @@ import {client} from "../index"; import {loadableCommands} from "./loader"; -import {Permissions, Message} from "discord.js"; +import {Permissions, Message, TextChannel, DMChannel, NewsChannel} from "discord.js"; import {getPrefix} from "../structures"; import {defaultMetadata} from "./command"; @@ -11,6 +11,16 @@ export function addInterceptRule(handler: (message: Message) => boolean) { interceptRules.push(handler); } +const lastCommandInfo: { + header: string; + args: string[]; + channel: TextChannel | DMChannel | NewsChannel | null; +} = { + header: "N/A", + args: [], + channel: null +}; + // Note: client.user is only undefined before the bot logs in, so by this point, client.user cannot be undefined. // Note: guild.available will never need to be checked because the message starts in either a DM channel or an already-available guild. client.on("message", async (message) => { @@ -41,6 +51,11 @@ client.on("message", async (message) => { if (commands.has(header)) { const command = commands.get(header)!; + // Set last command info in case of unhandled rejections. + lastCommandInfo.header = header; + lastCommandInfo.args = [...args]; + lastCommandInfo.channel = channel; + // Send the arguments to the command to resolve and execute. const result = await command.execute(args, menu, { header, @@ -73,6 +88,11 @@ client.on("message", async (message) => { if (commands.has(header)) { const command = commands.get(header)!; + // Set last command info in case of unhandled rejections. + lastCommandInfo.header = header; + lastCommandInfo.args = [...args]; + lastCommandInfo.channel = channel; + // Send the arguments to the command to resolve and execute. const result = await command.execute(args, menu, { header, @@ -98,3 +118,12 @@ client.on("message", async (message) => { ); } }); + +process.on("unhandledRejection", (reason: any) => { + if (reason?.name === "DiscordAPIError") { + console.error(`Command Error: ${lastCommandInfo.header} (${lastCommandInfo.args.join(", ")})\n${reason.stack}`); + lastCommandInfo.channel?.send( + `There was an error while trying to execute that command!\`\`\`${reason.stack}\`\`\`` + ); + } +}); diff --git a/src/core/index.ts b/src/core/index.ts index 16b06e0..047e6f5 100644 --- a/src/core/index.ts +++ b/src/core/index.ts @@ -1,4 +1,4 @@ -export {Command, NamedCommand} from "./command"; +export {Command, NamedCommand, CHANNEL_TYPE} from "./command"; export {addInterceptRule} from "./handler"; export { SingleMessageOptions, diff --git a/src/modules/lavalink.ts b/src/modules/lavalink.ts index 41a24fb..8b18a7a 100644 --- a/src/modules/lavalink.ts +++ b/src/modules/lavalink.ts @@ -22,3 +22,27 @@ attachClientToLavalink(client, { helpCmd: "mhelp", admins: ["717352467280691331"] }); + +// Disable the unhandledRejection listener by Lavalink because it captures every single unhandled +// rejection and adds its message with it. Then replace it with a better, more selective error handler. +for (const listener of process.listeners("unhandledRejection")) { + if (listener.toString().includes("discord.js-lavalink-musicbot")) { + process.off("unhandledRejection", listener); + } +} + +process.on("unhandledRejection", (reason: any) => { + if (reason?.code === "ECONNREFUSED") { + console.error( + `[discord.js-lavalink-musicbot] Caught unhandled rejection: ${reason.stack}\nIf this is causing issues, head to the support server at https://discord.gg/dNN4azK` + ); + } +}); + +// It's unsafe to process uncaughtException because after an uncaught exception, the system +// becomes corrupted. So disable Lavalink from adding a hook to it. +for (const listener of process.listeners("uncaughtException")) { + if (listener.toString().includes("discord.js-lavalink-musicbot")) { + process.off("uncaughtException", listener); + } +} From 03f37680e7e1cb1e8bb3965aa9168e32d65df6a6 Mon Sep 17 00:00:00 2001 From: WatDuhHekBro <44940783+WatDuhHekBro@users.noreply.github.com> Date: Mon, 5 Apr 2021 04:26:33 -0500 Subject: [PATCH 133/178] Fully isolated command handler from rest of code --- src/commands/fun/modules/eco-extras.ts | 2 +- src/core/command.ts | 21 +++- src/core/eventListeners.ts | 39 +++---- src/core/handler.ts | 147 +++++++++++++------------ src/core/index.ts | 1 + src/core/interface.ts | 23 ++++ src/core/loader.ts | 22 ---- src/core/permissions.ts | 64 ++--------- src/index.ts | 54 ++++++++- 9 files changed, 193 insertions(+), 180 deletions(-) create mode 100644 src/core/interface.ts diff --git a/src/commands/fun/modules/eco-extras.ts b/src/commands/fun/modules/eco-extras.ts index f8de393..f2760dd 100644 --- a/src/commands/fun/modules/eco-extras.ts +++ b/src/commands/fun/modules/eco-extras.ts @@ -1,4 +1,4 @@ -import {Command, NamedCommand} from "../../../core/command"; +import {Command, NamedCommand} from "../../../core"; import {Storage} from "../../../structures"; import {isAuthorized, getMoneyEmbed} from "./eco-utils"; diff --git a/src/core/command.ts b/src/core/command.ts index d389619..f1a06ce 100644 --- a/src/core/command.ts +++ b/src/core/command.ts @@ -1,4 +1,3 @@ -import {parseVars} from "../lib"; import { Collection, Client, @@ -11,10 +10,24 @@ import { GuildMember, GuildChannel } from "discord.js"; -import {getPrefix} from "../structures"; import {SingleMessageOptions} from "./libd"; import {hasPermission, getPermissionLevel, getPermissionName} from "./permissions"; -import {client} from "../index"; +import {getPrefix} from "./interface"; +import {parseVars} from "../lib"; + +/** + * ===[ Command Types ]=== + * SUBCOMMAND - Any specifically-defined keywords / string literals. + * CHANNEL - <#...> + * ROLE - <@&...> + * EMOTE - <::ID> (The previous two values, animated and emote name respectively, do not matter at all for finding the emote.) + * MESSAGE - Available by using the built-in "Copy Message Link" or "Copy ID" buttons. https://discordapp.com/channels/// or - (automatically searches all guilds for the channel ID). + * USER - <@...> and <@!...> + * ID - Any number with 17-19 digits. Only used as a redirect to another subcommand type. + * NUMBER - Any valid number via the Number() function, except for NaN and Infinity (because those can really mess with the program). + * ANY - Generic argument case. + * NONE - No subcommands exist. + */ // RegEx patterns used for identifying/extracting each type from a string argument. const patterns = { @@ -264,7 +277,7 @@ export class Command { const id = patterns.user.exec(param)![1]; try { - menu.args.push(await client.users.fetch(id)); + menu.args.push(await menu.client.users.fetch(id)); return this.user.execute(args, menu, metadata); } catch { return { diff --git a/src/core/eventListeners.ts b/src/core/eventListeners.ts index e32db03..705647e 100644 --- a/src/core/eventListeners.ts +++ b/src/core/eventListeners.ts @@ -1,28 +1,29 @@ -import {client} from "../index"; +import {Client, Permissions, Message} from "discord.js"; import {botHasPermission} from "./libd"; -import {Permissions, Message} from "discord.js"; // A list of message ID and callback pairs. You get the emote name and ID of the user reacting. export const unreactEventListeners: Map void> = new Map(); -// Attached to the client, there can be one event listener attached to a message ID which is executed if present. -client.on("messageReactionRemove", (reaction, user) => { - const canDeleteEmotes = botHasPermission(reaction.message.guild, Permissions.FLAGS.MANAGE_MESSAGES); - - if (!canDeleteEmotes) { - const callback = unreactEventListeners.get(reaction.message.id); - callback && callback(reaction.emoji.name, user.id); - } -}); - // A list of "channel-message" and callback pairs. Also, I imagine that the callback will be much more maintainable when discord.js v13 comes out with a dedicated message.referencedMessage property. // Also, I'm defining it here instead of the message event because the load order screws up if you export it from there. Yeah... I'm starting to notice just how much technical debt has been built up. The command handler needs to be modularized and refactored sooner rather than later. Define all constants in one area then grab from there. export const replyEventListeners = new Map void>(); -client.on("message", (message) => { - // If there's an inline reply, fire off that event listener (if it exists). - if (message.reference) { - const reference = message.reference; - replyEventListeners.get(`${reference.channelID}-${reference.messageID}`)?.(message); - } -}); +export function attachEventListenersToClient(client: Client) { + // Attached to the client, there can be one event listener attached to a message ID which is executed if present. + client.on("messageReactionRemove", (reaction, user) => { + const canDeleteEmotes = botHasPermission(reaction.message.guild, Permissions.FLAGS.MANAGE_MESSAGES); + + if (!canDeleteEmotes) { + const callback = unreactEventListeners.get(reaction.message.id); + callback && callback(reaction.emoji.name, user.id); + } + }); + + client.on("message", (message) => { + // If there's an inline reply, fire off that event listener (if it exists). + if (message.reference) { + const reference = message.reference; + replyEventListeners.get(`${reference.channelID}-${reference.messageID}`)?.(message); + } + }); +} diff --git a/src/core/handler.ts b/src/core/handler.ts index 10bea9e..54f8f1b 100644 --- a/src/core/handler.ts +++ b/src/core/handler.ts @@ -1,8 +1,7 @@ -import {client} from "../index"; +import {Client, Permissions, Message, TextChannel, DMChannel, NewsChannel} from "discord.js"; import {loadableCommands} from "./loader"; -import {Permissions, Message, TextChannel, DMChannel, NewsChannel} from "discord.js"; -import {getPrefix} from "../structures"; import {defaultMetadata} from "./command"; +import {getPrefix} from "./interface"; // For custom message events that want to cancel the command handler on certain conditions. const interceptRules: ((message: Message) => boolean)[] = [(message) => message.author.bot]; @@ -23,67 +22,31 @@ const lastCommandInfo: { // Note: client.user is only undefined before the bot logs in, so by this point, client.user cannot be undefined. // Note: guild.available will never need to be checked because the message starts in either a DM channel or an already-available guild. -client.on("message", async (message) => { - for (const shouldIntercept of interceptRules) { - if (shouldIntercept(message)) { - return; - } - } - - const commands = await loadableCommands; - const {author, channel, content, guild, member} = message; - const text = content; - const menu = { - author, - channel, - client, - guild, - member, - message, - args: [] - }; - - // Execute a dedicated block for messages in DM channels. - if (channel.type === "dm") { - // In a DM channel, simply forget about the prefix and execute any message as a command. - const [header, ...args] = text.split(/ +/); - - if (commands.has(header)) { - const command = commands.get(header)!; - - // Set last command info in case of unhandled rejections. - lastCommandInfo.header = header; - lastCommandInfo.args = [...args]; - lastCommandInfo.channel = channel; - - // Send the arguments to the command to resolve and execute. - const result = await command.execute(args, menu, { - header, - args: [...args], - ...defaultMetadata - }); - - // If something went wrong, let the user know (like if they don't have permission to use a command). - if (result) { - channel.send(result); +export function attachMessageHandlerToClient(client: Client) { + client.on("message", async (message) => { + for (const shouldIntercept of interceptRules) { + if (shouldIntercept(message)) { + return; } - } else { - channel.send( - `I couldn't find the command or alias that starts with \`${header}\`. To see the list of commands, type \`help\`` - ); } - } - // Continue if the bot has permission to send messages in this channel. - else if (channel.permissionsFor(client.user!)!.has(Permissions.FLAGS.SEND_MESSAGES)) { - const prefix = getPrefix(guild); - // First, test if the message is just a ping to the bot. - if (new RegExp(`^<@!?${client.user!.id}>$`).test(text)) { - channel.send(`${author}, my prefix on this server is \`${prefix}\`.`); - } - // Then check if it's a normal command. - else if (text.startsWith(prefix)) { - const [header, ...args] = text.substring(prefix.length).split(/ +/); + const commands = await loadableCommands; + const {author, channel, content, guild, member} = message; + const text = content; + const menu = { + author, + channel, + client, + guild, + member, + message, + args: [] + }; + + // Execute a dedicated block for messages in DM channels. + if (channel.type === "dm") { + // In a DM channel, simply forget about the prefix and execute any message as a command. + const [header, ...args] = text.split(/ +/); if (commands.has(header)) { const command = commands.get(header)!; @@ -104,20 +67,58 @@ client.on("message", async (message) => { if (result) { channel.send(result); } + } else { + channel.send( + `I couldn't find the command or alias that starts with \`${header}\`. To see the list of commands, type \`help\`` + ); } } - } - // Otherwise, let the sender know that the bot doesn't have permission to send messages. - else { - author.send( - `I don't have permission to send messages in ${channel}. ${ - member!.hasPermission(Permissions.FLAGS.ADMINISTRATOR) - ? "Because you're a server admin, you have the ability to change that channel's permissions to match if that's what you intended." - : "Try using a different channel or contacting a server admin to change permissions of that channel if you think something's wrong." - }` - ); - } -}); + // Continue if the bot has permission to send messages in this channel. + else if (channel.permissionsFor(client.user!)!.has(Permissions.FLAGS.SEND_MESSAGES)) { + const prefix = getPrefix(guild); + + // First, test if the message is just a ping to the bot. + if (new RegExp(`^<@!?${client.user!.id}>$`).test(text)) { + channel.send(`${author}, my prefix on this server is \`${prefix}\`.`); + } + // Then check if it's a normal command. + else if (text.startsWith(prefix)) { + const [header, ...args] = text.substring(prefix.length).split(/ +/); + + if (commands.has(header)) { + const command = commands.get(header)!; + + // Set last command info in case of unhandled rejections. + lastCommandInfo.header = header; + lastCommandInfo.args = [...args]; + lastCommandInfo.channel = channel; + + // Send the arguments to the command to resolve and execute. + const result = await command.execute(args, menu, { + header, + args: [...args], + ...defaultMetadata + }); + + // If something went wrong, let the user know (like if they don't have permission to use a command). + if (result) { + channel.send(result); + } + } + } + } + // Otherwise, let the sender know that the bot doesn't have permission to send messages. + else { + author.send( + `I don't have permission to send messages in ${channel}. ${ + member!.hasPermission(Permissions.FLAGS.ADMINISTRATOR) + ? "Because you're a server admin, you have the ability to change that channel's permissions to match if that's what you intended." + : "Try using a different channel or contacting a server admin to change permissions of that channel if you think something's wrong." + }` + ); + } + }); +} process.on("unhandledRejection", (reason: any) => { if (reason?.name === "DiscordAPIError") { diff --git a/src/core/index.ts b/src/core/index.ts index 047e6f5..0b1d56f 100644 --- a/src/core/index.ts +++ b/src/core/index.ts @@ -1,5 +1,6 @@ export {Command, NamedCommand, CHANNEL_TYPE} from "./command"; export {addInterceptRule} from "./handler"; +export {launch} from "./interface"; export { SingleMessageOptions, botHasPermission, diff --git a/src/core/interface.ts b/src/core/interface.ts new file mode 100644 index 0000000..b04d2af --- /dev/null +++ b/src/core/interface.ts @@ -0,0 +1,23 @@ +import {Client, User, GuildMember, Guild} from "discord.js"; +import {attachMessageHandlerToClient} from "./handler"; +import {attachEventListenersToClient} from "./eventListeners"; + +interface LaunchSettings { + permissionLevels: PermissionLevel[]; + getPrefix: (guild: Guild | null) => string; +} + +export async function launch(client: Client, settings: LaunchSettings) { + attachMessageHandlerToClient(client); + attachEventListenersToClient(client); + permissionLevels = settings.permissionLevels; + getPrefix = settings.getPrefix; +} + +interface PermissionLevel { + name: string; + check: (user: User, member: GuildMember | null) => boolean; +} + +export let permissionLevels: PermissionLevel[] = []; +export let getPrefix: (guild: Guild | null) => string = () => "."; diff --git a/src/core/loader.ts b/src/core/loader.ts index 6c6fa7f..d5ba5c1 100644 --- a/src/core/loader.ts +++ b/src/core/loader.ts @@ -79,25 +79,3 @@ function globP(path: string) { }); }); } - -// Gathers a list of categories and top-level commands. -// Returns: new Collection() -/*export async function getCommandList(): Promise> { - const categorizedCommands = new Collection(); - const commands = await loadableCommands; - - for (const [category, headers] of categories) { - const commandList: Command[] = []; - - for (const header of headers) { - if (header !== "test") { - // If this is somehow undefined, it'll show up as an error when implementing a help command. - commandList.push(commands.get(header)!); - } - } - - categorizedCommands.set(toTitleCase(category), commandList); - } - - return categorizedCommands; -}*/ diff --git a/src/core/permissions.ts b/src/core/permissions.ts index a251a64..fb430c8 100644 --- a/src/core/permissions.ts +++ b/src/core/permissions.ts @@ -1,68 +1,18 @@ -import {User, GuildMember, Permissions} from "discord.js"; -import {Config} from "../structures"; - -interface PermissionLevel { - name: string; - check: (user: User, member: GuildMember | null) => boolean; -} - -export const PermissionLevels: PermissionLevel[] = [ - { - // NONE // - name: "User", - check: () => true - }, - { - // MOD // - name: "Moderator", - check: (_user, member) => - !!member && - (member.hasPermission(Permissions.FLAGS.MANAGE_ROLES) || - member.hasPermission(Permissions.FLAGS.MANAGE_MESSAGES) || - member.hasPermission(Permissions.FLAGS.KICK_MEMBERS) || - member.hasPermission(Permissions.FLAGS.BAN_MEMBERS)) - }, - { - // ADMIN // - name: "Administrator", - check: (_user, member) => !!member && member.hasPermission(Permissions.FLAGS.ADMINISTRATOR) - }, - { - // OWNER // - name: "Server Owner", - check: (_user, member) => !!member && member.guild.ownerID === member.id - }, - { - // BOT_SUPPORT // - name: "Bot Support", - check: (user) => Config.support.includes(user.id) - }, - { - // BOT_ADMIN // - name: "Bot Admin", - check: (user) => Config.admins.includes(user.id) - }, - { - // BOT_OWNER // - name: "Bot Owner", - check: (user) => Config.owner === user.id - } -]; - -// After checking the lengths of these three objects, use this as the length for consistency. -const length = PermissionLevels.length; +import {User, GuildMember} from "discord.js"; +import {permissionLevels} from "./interface"; export function hasPermission(user: User, member: GuildMember | null, permission: number): boolean { - for (let i = length - 1; i >= permission; i--) if (PermissionLevels[i].check(user, member)) return true; + for (let i = permissionLevels.length - 1; i >= permission; i--) + if (permissionLevels[i].check(user, member)) return true; return false; } export function getPermissionLevel(user: User, member: GuildMember | null): number { - for (let i = length - 1; i >= 0; i--) if (PermissionLevels[i].check(user, member)) return i; + for (let i = permissionLevels.length - 1; i >= 0; i--) if (permissionLevels[i].check(user, member)) return i; return 0; } export function getPermissionName(level: number) { - if (level > length || level < 0) return "N/A"; - else return PermissionLevels[level].name; + if (level > permissionLevels.length || level < 0) return "N/A"; + else return permissionLevels[level].name; } diff --git a/src/index.ts b/src/index.ts index 7280c76..21633ce 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,8 +1,9 @@ // Bootstrapping Section // import "./modules/globals"; -import {Client} from "discord.js"; +import {Client, Permissions} from "discord.js"; +import {launch} from "./core"; import setup from "./modules/setup"; -import {Config} from "./structures"; +import {Config, getPrefix} from "./structures"; // This is here in order to make it much less of a headache to access the client from other files. // This of course won't actually do anything until the setup process is complete and it logs in. @@ -13,9 +14,54 @@ setup.init().then(() => { client.login(Config.token).catch(setup.again); }); +// Setup the command handler. +launch(client, { + permissionLevels: [ + { + // NONE // + name: "User", + check: () => true + }, + { + // MOD // + name: "Moderator", + check: (_user, member) => + !!member && + (member.hasPermission(Permissions.FLAGS.MANAGE_ROLES) || + member.hasPermission(Permissions.FLAGS.MANAGE_MESSAGES) || + member.hasPermission(Permissions.FLAGS.KICK_MEMBERS) || + member.hasPermission(Permissions.FLAGS.BAN_MEMBERS)) + }, + { + // ADMIN // + name: "Administrator", + check: (_user, member) => !!member && member.hasPermission(Permissions.FLAGS.ADMINISTRATOR) + }, + { + // OWNER // + name: "Server Owner", + check: (_user, member) => !!member && member.guild.ownerID === member.id + }, + { + // BOT_SUPPORT // + name: "Bot Support", + check: (user) => Config.support.includes(user.id) + }, + { + // BOT_ADMIN // + name: "Bot Admin", + check: (user) => Config.admins.includes(user.id) + }, + { + // BOT_OWNER // + name: "Bot Owner", + check: (user) => Config.owner === user.id + } + ], + getPrefix: getPrefix +}); + // Initialize Modules // -import "./core/handler"; // Command loading will start as soon as an instance of "core/command" is loaded, which is loaded in "core/handler". -import "./core/eventListeners"; import "./modules/ready"; import "./modules/presence"; import "./modules/lavalink"; From 4a78ce808becae3f0c4ff60208d65e219549ec3d Mon Sep 17 00:00:00 2001 From: WatDuhHekBro <44940783+WatDuhHekBro@users.noreply.github.com> Date: Mon, 5 Apr 2021 06:45:28 -0500 Subject: [PATCH 134/178] Added more subcommand types --- src/core/command.ts | 274 +++++++++++++++++++++++++++++++++++++++---- src/modules/setup.ts | 10 ++ 2 files changed, 261 insertions(+), 23 deletions(-) diff --git a/src/core/command.ts b/src/core/command.ts index f1a06ce..ba89c71 100644 --- a/src/core/command.ts +++ b/src/core/command.ts @@ -13,7 +13,7 @@ import { import {SingleMessageOptions} from "./libd"; import {hasPermission, getPermissionLevel, getPermissionName} from "./permissions"; import {getPrefix} from "./interface"; -import {parseVars} from "../lib"; +import {parseVars, requireAllCasesHandledFor} from "../lib"; /** * ===[ Command Types ]=== @@ -34,11 +34,15 @@ const patterns = { channel: /^<#(\d{17,19})>$/, role: /^<@&(\d{17,19})>$/, emote: /^$/, - message: /(?:\d{17,19}\/(\d{17,19})\/(\d{17,19})$)|(?:^(\d{17,19})-(\d{17,19})$)/, + messageLink: /^https?:\/\/(?:ptb\.|canary\.)?discord(?:app)?\.com\/channels\/(?:\d{17,19}|@me)\/(\d{17,19})\/(\d{17,19})$/, + messagePair: /^(\d{17,19})-(\d{17,19})$/, user: /^<@!?(\d{17,19})>$/, id: /^(\d{17,19})$/ }; +// Maybe add a guild redirect... somehow? +type ID = "channel" | "role" | "emote" | "message" | "user"; + // Callbacks don't work with discriminated unions: // - https://github.com/microsoft/TypeScript/issues/41759 // - https://github.com/microsoft/TypeScript/issues/35769 @@ -78,10 +82,17 @@ interface CommandOptionsEndpoint { } // Prevents subcommands from being added by compile-time. +// Also, contrary to what you might think, channel pings do still work in DM channels. +// Role pings, maybe not, but it's not a big deal. interface CommandOptionsNonEndpoint { readonly endpoint?: false; readonly subcommands?: {[key: string]: NamedCommand}; + readonly channel?: Command; + readonly role?: Command; + readonly emote?: Command; + readonly message?: Command; readonly user?: Command; + readonly id?: ID; readonly number?: Command; readonly any?: Command; } @@ -119,6 +130,7 @@ interface CommandInfoMetadata { channelType: CHANNEL_TYPE; args: string[]; usage: string; + readonly originalArgs: string[]; } export const defaultMetadata = { @@ -136,7 +148,13 @@ export class Command { public readonly channelType: CHANNEL_TYPE | null; // null (default) indicates to inherit protected run: (($: CommandMenu) => Promise) | string; protected readonly subcommands: Collection; // This is the final data structure you'll actually use to work with the commands the aliases point to. + protected channel: Command | null; + protected role: Command | null; + protected emote: Command | null; + protected message: Command | null; protected user: Command | null; + protected id: Command | null; + protected idType: ID | null; protected number: Command | null; protected any: Command | null; @@ -149,14 +167,47 @@ export class Command { this.channelType = options?.channelType ?? null; this.run = options?.run || "No action was set on this command!"; this.subcommands = new Collection(); // Populate this collection after setting subcommands. + this.channel = null; + this.role = null; + this.emote = null; + this.message = null; this.user = null; + this.id = null; + this.idType = null; this.number = null; this.any = null; if (options && !options.endpoint) { - this.user = options?.user || null; - this.number = options?.number || null; - this.any = options?.any || null; + if (options?.channel) this.channel = options.channel; + if (options?.role) this.role = options.role; + if (options?.emote) this.emote = options.emote; + if (options?.message) this.message = options.message; + if (options?.user) this.user = options.user; + if (options?.number) this.number = options.number; + if (options?.any) this.any = options.any; + if (options?.id) this.idType = options.id; + + if (options?.id) { + switch (options.id) { + case "channel": + this.id = this.channel; + break; + case "role": + this.id = this.role; + break; + case "emote": + this.id = this.emote; + break; + case "message": + this.id = this.message; + break; + case "user": + this.id = this.user; + break; + default: + requireAllCasesHandledFor(options.id); + } + } if (options?.subcommands) { const baseSubcommands = Object.keys(options.subcommands); @@ -271,8 +322,85 @@ export class Command { // Resolve the value of the current command's argument (adding it to the resolved args), // then pass the thread of execution to whichever subcommand is valid (if any). + const isMessageLink = patterns.messageLink.test(param); + const isMessagePair = patterns.messagePair.test(param); + if (this.subcommands.has(param)) { return this.subcommands.get(param)!.execute(args, menu, metadata); + } else if (this.channel && patterns.channel.test(param)) { + const id = patterns.channel.exec(param)![1]; + const channel = menu.client.channels.cache.get(id); + + // Users can only enter in this format for text channels, so this restricts it to that. + if (channel instanceof TextChannel) { + menu.args.push(channel); + return this.channel.execute(args, menu, metadata); + } else { + return { + content: `\`${id}\` is not a valid text channel!` + }; + } + } else if (this.role && patterns.role.test(param)) { + const id = patterns.role.exec(param)![1]; + + if (!menu.guild) { + return { + content: "You can't use role parameters in DM channels!" + }; + } + + const role = menu.guild.roles.cache.get(id); + + if (role) { + menu.args.push(role); + return this.role.execute(args, menu, metadata); + } else { + return { + content: `\`${id}\` is not a valid role in this server!` + }; + } + } else if (this.emote && patterns.emote.test(param)) { + const id = patterns.emote.exec(param)![1]; + const emote = menu.client.emojis.cache.get(id); + + if (emote) { + menu.args.push(emote); + return this.emote.execute(args, menu, metadata); + } else { + return { + content: `\`${id}\` isn't a valid emote!` + }; + } + } else if (this.message && (isMessageLink || isMessagePair)) { + let channelID = ""; + let messageID = ""; + + if (isMessageLink) { + const result = patterns.messageLink.exec(param)!; + channelID = result[1]; + messageID = result[2]; + } else if (isMessagePair) { + const result = patterns.messagePair.exec(param)!; + channelID = result[1]; + messageID = result[2]; + } + + const channel = menu.client.channels.cache.get(channelID); + + if (channel instanceof TextChannel || channel instanceof DMChannel) { + try { + menu.args.push(await channel.messages.fetch(messageID)); + return this.message.execute(args, menu, metadata); + } catch { + return { + content: `\`${messageID}\` isn't a valid message of channel ${channel}!` + }; + } + } else { + return { + content: `\`${channelID}\` is not a valid text channel!` + }; + } } else if (this.user && patterns.user.test(param)) { const id = patterns.user.exec(param)![1]; @@ -284,6 +412,73 @@ export class Command { content: `No user found by the ID \`${id}\`!` }; } + } else if (this.id && this.idType && patterns.id.test(param)) { + const id = patterns.id.exec(param)![1]; + + // Probably modularize the findXByY code in general in libd. + // Because this part is pretty much a whole bunch of copy pastes. + switch (this.idType) { + case "channel": + const channel = menu.client.channels.cache.get(id); + + // Users can only enter in this format for text channels, so this restricts it to that. + if (channel instanceof TextChannel) { + menu.args.push(channel); + return this.id.execute(args, menu, metadata); + } else { + return { + content: `\`${id}\` isn't a valid text channel!` + }; + } + case "role": + if (!menu.guild) { + return { + content: "You can't use role parameters in DM channels!" + }; + } + + const role = menu.guild.roles.cache.get(id); + + if (role) { + menu.args.push(role); + return this.id.execute(args, menu, metadata); + } else { + return { + content: `\`${id}\` isn't a valid role in this server!` + }; + } + case "emote": + const emote = menu.client.emojis.cache.get(id); + + if (emote) { + menu.args.push(emote); + return this.id.execute(args, menu, metadata); + } else { + return { + content: `\`${id}\` isn't a valid emote!` + }; + } + case "message": + try { + menu.args.push(await menu.channel.messages.fetch(id)); + return this.id.execute(args, menu, metadata); + } catch { + return { + content: `\`${id}\` isn't a valid message of channel ${menu.channel}!` + }; + } + case "user": + try { + menu.args.push(await menu.client.users.fetch(id)); + return this.id.execute(args, menu, metadata); + } catch { + return { + content: `No user found by the ID \`${id}\`!` + }; + } + default: + requireAllCasesHandledFor(this.idType); + } } else if (this.number && !Number.isNaN(Number(param)) && param !== "Infinity" && param !== "-Infinity") { menu.args.push(Number(param)); return this.number.execute(args, menu, metadata); @@ -302,7 +497,7 @@ export class Command { // What this does is resolve the resulting subcommand as well as the inherited properties and the available subcommands. public async resolveInfo(args: string[]): Promise { - return this.resolveInfoInternal(args, {...defaultMetadata, args: [], usage: ""}); + return this.resolveInfoInternal(args, {...defaultMetadata, args: [], usage: "", originalArgs: [...args]}); } private async resolveInfoInternal( @@ -333,7 +528,12 @@ export class Command { } // Then get all the generic subcommands. + if (this.channel) subcommandInfo.set("", this.channel); + if (this.role) subcommandInfo.set("", this.role); + if (this.emote) subcommandInfo.set("", this.emote); + if (this.message) subcommandInfo.set("", this.message); if (this.user) subcommandInfo.set("", this.user); + if (this.id) subcommandInfo.set(`>`, this.id); if (this.number) subcommandInfo.set("", this.number); if (this.any) subcommandInfo.set("", this.any); @@ -346,45 +546,73 @@ export class Command { }; } + const invalidSubcommandGenerator: () => CommandInfoError = () => ({ + type: "error", + message: `No subcommand found by the argument list: \`${metadata.originalArgs.join(" ")}\`` + }); + // Then test if anything fits any hardcoded values, otherwise check if it's a valid keyed subcommand. - if (param === "") { + if (param === "") { + if (this.channel) { + metadata.args.push(""); + return this.channel.resolveInfoInternal(args, metadata); + } else { + return invalidSubcommandGenerator(); + } + } else if (param === "") { + if (this.role) { + metadata.args.push(""); + return this.role.resolveInfoInternal(args, metadata); + } else { + return invalidSubcommandGenerator(); + } + } else if (param === "") { + if (this.emote) { + metadata.args.push(""); + return this.emote.resolveInfoInternal(args, metadata); + } else { + return invalidSubcommandGenerator(); + } + } else if (param === "") { + if (this.message) { + metadata.args.push(""); + return this.message.resolveInfoInternal(args, metadata); + } else { + return invalidSubcommandGenerator(); + } + } else if (param === "") { if (this.user) { metadata.args.push(""); return this.user.resolveInfoInternal(args, metadata); } else { - return { - type: "error", - message: `No subcommand found by the argument list: \`${metadata.args.join(" ")}\`` - }; + return invalidSubcommandGenerator(); + } + } else if (param === "") { + if (this.id) { + metadata.args.push(`>`); + return this.id.resolveInfoInternal(args, metadata); + } else { + return invalidSubcommandGenerator(); } } else if (param === "") { if (this.number) { metadata.args.push(""); return this.number.resolveInfoInternal(args, metadata); } else { - return { - type: "error", - message: `No subcommand found by the argument list: \`${metadata.args.join(" ")}\`` - }; + return invalidSubcommandGenerator(); } } else if (param === "") { if (this.any) { metadata.args.push(""); return this.any.resolveInfoInternal(args, metadata); } else { - return { - type: "error", - message: `No subcommand found by the argument list: \`${metadata.args.join(" ")}\`` - }; + return invalidSubcommandGenerator(); } } else if (this.subcommands?.has(param)) { metadata.args.push(param); return this.subcommands.get(param)!.resolveInfoInternal(args, metadata); } else { - return { - type: "error", - message: `No subcommand found by the argument list: \`${metadata.args.join(" ")}\`` - }; + return invalidSubcommandGenerator(); } } } diff --git a/src/modules/setup.ts b/src/modules/setup.ts index f146dad..9b44eda 100644 --- a/src/modules/setup.ts +++ b/src/modules/setup.ts @@ -14,6 +14,16 @@ if (IS_DEV_MODE && !exists("src/commands/test.ts")) { ); } +// A generic process handler is set to catch unhandled rejections other than the ones from Lavalink and Discord. +process.on("unhandledRejection", (reason: any) => { + const isLavalinkError = reason?.code === "ECONNREFUSED"; + const isDiscordError = reason?.name === "DiscordAPIError"; + + if (!isLavalinkError && !isDiscordError) { + console.error(reason.stack); + } +}); + // This file is called (or at least should be called) automatically as long as a config file doesn't exist yet. // And that file won't be written until the data is successfully initialized. const prompts = [ From 5402883a2f4c245cbc602b7dc92e87164ab96753 Mon Sep 17 00:00:00 2001 From: WatDuhHekBro <44940783+WatDuhHekBro@users.noreply.github.com> Date: Mon, 5 Apr 2021 07:21:27 -0500 Subject: [PATCH 135/178] Resolved all lingering post-merge errors --- package-lock.json | 13 -- package.json | 1 - src/commands/admin.ts | 329 ------------------------------ src/commands/fun/figlet.ts | 14 +- src/commands/fun/insult.ts | 14 +- src/commands/fun/love.ts | 14 +- src/commands/fun/ravi.ts | 18 +- src/commands/fun/thonk.ts | 10 +- src/commands/fun/urban.ts | 16 +- src/commands/fun/weather.ts | 16 +- src/commands/fun/whois.ts | 36 ++-- src/commands/system/admin.ts | 120 +++++++++++ src/commands/utility/calc.ts | 18 +- src/commands/utility/code.ts | 4 +- src/commands/utility/invite.ts | 12 +- src/commands/utility/todo.ts | 44 ++-- src/commands/utility/translate.ts | 14 +- src/events/guildCreate.ts | 32 --- src/events/guildDelete.ts | 24 --- src/events/guildMemberAdd.ts | 77 ------- src/events/ready.ts | 20 -- src/index.ts | 1 + src/modules/emoteRegistry.ts | 41 +++- src/modules/guildMemberAdd.ts | 74 +++++++ src/modules/ready.ts | 4 +- 25 files changed, 349 insertions(+), 617 deletions(-) delete mode 100644 src/commands/admin.ts delete mode 100644 src/events/guildCreate.ts delete mode 100644 src/events/guildDelete.ts delete mode 100644 src/events/guildMemberAdd.ts delete mode 100644 src/events/ready.ts create mode 100644 src/modules/guildMemberAdd.ts diff --git a/package-lock.json b/package-lock.json index 86853ab..80b5169 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30,7 +30,6 @@ "@types/inquirer": "^6.5.0", "@types/jest": "^26.0.20", "@types/mathjs": "^6.0.11", - "@types/mocha": "^8.2.0", "@types/ms": "^0.7.31", "@types/node": "^14.14.20", "@types/ws": "^7.4.0", @@ -1083,12 +1082,6 @@ "integrity": "sha512-1z8k4wzFnNjVK/tlxvrWuK5WMt6mydWWP7+zvH5eFep4oj+UkrfiJTRtjCeBXNpwaA/FYqqtb4/QS4ianFpIRA==", "dev": true }, - "node_modules/@types/mocha": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-8.2.2.tgz", - "integrity": "sha512-Lwh0lzzqT5Pqh6z61P3c3P5nm6fzQK/MMHl9UKeneAeInVflBSz1O2EkX6gM6xfJd7FBXBY5purtLx7fUiZ7Hw==", - "dev": true - }, "node_modules/@types/ms": { "version": "0.7.31", "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz", @@ -8966,12 +8959,6 @@ "integrity": "sha512-1z8k4wzFnNjVK/tlxvrWuK5WMt6mydWWP7+zvH5eFep4oj+UkrfiJTRtjCeBXNpwaA/FYqqtb4/QS4ianFpIRA==", "dev": true }, - "@types/mocha": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-8.2.2.tgz", - "integrity": "sha512-Lwh0lzzqT5Pqh6z61P3c3P5nm6fzQK/MMHl9UKeneAeInVflBSz1O2EkX6gM6xfJd7FBXBY5purtLx7fUiZ7Hw==", - "dev": true - }, "@types/ms": { "version": "0.7.31", "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz", diff --git a/package.json b/package.json index e38dcab..a48dcd5 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,6 @@ "@types/inquirer": "^6.5.0", "@types/jest": "^26.0.20", "@types/mathjs": "^6.0.11", - "@types/mocha": "^8.2.0", "@types/ms": "^0.7.31", "@types/node": "^14.14.20", "@types/ws": "^7.4.0", diff --git a/src/commands/admin.ts b/src/commands/admin.ts deleted file mode 100644 index 2622087..0000000 --- a/src/commands/admin.ts +++ /dev/null @@ -1,329 +0,0 @@ -import Command from "../core/command"; -import {CommonLibrary, logs, botHasPermission, clean} from "../core/lib"; -import {Config, Storage} from "../core/structures"; -import {PermissionNames, getPermissionLevel} from "../core/permissions"; -import {Permissions} from "discord.js"; -import * as discord from "discord.js"; - -function getLogBuffer(type: string) { - return { - files: [ - { - attachment: Buffer.alloc(logs[type].length, logs[type]), - name: `${Date.now()}.${type}.log` - } - ] - }; -} - -const activities = ["playing", "listening", "streaming", "watching"]; -const statuses = ["online", "idle", "dnd", "invisible"]; - -export default new Command({ - description: - "An all-in-one command to do admin stuff. You need to be either an admin of the server or one of the bot's mechanics to use this command.", - async run($: CommonLibrary): Promise { - if (!$.member) - return $.channel.send( - "Couldn't find a member object for you! Did you make sure you used this in a server?" - ); - const permLevel = getPermissionLevel($.member); - $.channel.send( - `${$.author.toString()}, your permission level is \`${PermissionNames[permLevel]}\` (${permLevel}).` - ); - }, - subcommands: { - set: new Command({ - description: "Set different per-guild settings for the bot.", - run: "You have to specify the option you want to set.", - permission: Command.PERMISSIONS.ADMIN, - subcommands: { - prefix: new Command({ - description: "Set a custom prefix for your guild. Removes your custom prefix if none is provided.", - usage: "()", - async run($: CommonLibrary): Promise { - Storage.getGuild($.guild?.id || "N/A").prefix = null; - Storage.save(); - $.channel.send( - `The custom prefix for this guild has been removed. My prefix is now back to \`${Config.prefix}\`.` - ); - }, - any: new Command({ - async run($: CommonLibrary): Promise { - Storage.getGuild($.guild?.id || "N/A").prefix = $.args[0]; - Storage.save(); - $.channel.send(`The custom prefix for this guild is now \`${$.args[0]}\`.`); - } - }) - }), - welcome: new Command({ - description: "Configure your server's welcome settings for the bot.", - usage: "type/channel <...>", - run: "You need to specify which part to modify, `type`/`channel`.", - subcommands: { - type: new Command({ - description: - "Sets how welcome messages are displayed for your server. Removes welcome messages if unspecified.", - usage: "`none`/`text`/`graphical`", - async run($) { - if ($.guild) { - Storage.getGuild($.guild.id).welcomeType = "none"; - Storage.save(); - $.channel.send("Set this server's welcome type to `none`."); - } else { - $.channel.send("You must use this command in a server."); - } - }, - // I should probably make this a bit more dynamic... Oh well. - subcommands: { - text: new Command({ - async run($) { - if ($.guild) { - Storage.getGuild($.guild.id).welcomeType = "text"; - Storage.save(); - $.channel.send("Set this server's welcome type to `text`."); - } else { - $.channel.send("You must use this command in a server."); - } - } - }), - graphical: new Command({ - async run($) { - if ($.guild) { - Storage.getGuild($.guild.id).welcomeType = "graphical"; - Storage.save(); - $.channel.send("Set this server's welcome type to `graphical`."); - } else { - $.channel.send("You must use this command in a server."); - } - } - }) - } - }), - channel: new Command({ - description: "Sets the welcome channel for your server. Type `#` to reference the channel.", - usage: "()", - async run($) { - if ($.guild) { - Storage.getGuild($.guild.id).welcomeChannel = $.channel.id; - Storage.save(); - $.channel.send( - `Successfully set ${$.channel} as the welcome channel for this server.` - ); - } else { - $.channel.send("You must use this command in a server."); - } - }, - // If/when channel types come out, this will be the perfect candidate to test it. - any: new Command({ - async run($) { - if ($.guild) { - const match = $.args[0].match(/^<#(\d{17,19})>$/); - - if (match) { - Storage.getGuild($.guild.id).welcomeChannel = match[1]; - Storage.save(); - $.channel.send( - `Successfully set this server's welcome channel to ${match[0]}.` - ); - } else { - $.channel.send( - "You must provide a reference channel. You can do this by typing `#` then searching for the proper channel." - ); - } - } else { - $.channel.send("You must use this command in a server."); - } - } - }) - }), - message: new Command({ - description: - "Sets a custom welcome message for your server. Use `%user%` as the placeholder for the user.", - usage: "()", - async run($) { - if ($.guild) { - Storage.getGuild($.guild.id).welcomeMessage = null; - Storage.save(); - $.channel.send("Reset your server's welcome message to the default."); - } else { - $.channel.send("You must use this command in a server."); - } - }, - any: new Command({ - async run($) { - if ($.guild) { - const message = $.args.join(" "); - Storage.getGuild($.guild.id).welcomeMessage = message; - Storage.save(); - $.channel.send(`Set your server's welcome message to \`${message}\`.`); - } else { - $.channel.send("You must use this command in a server."); - } - } - }) - }) - } - }) - } - }), - diag: new Command({ - description: 'Requests a debug log with the "info" verbosity level.', - permission: Command.PERMISSIONS.BOT_SUPPORT, - async run($: CommonLibrary): Promise { - $.channel.send(getLogBuffer("info")); - }, - any: new Command({ - description: `Select a verbosity to listen to. Available levels: \`[${Object.keys(logs).join(", ")}]\``, - async run($: CommonLibrary): Promise { - const type = $.args[0]; - - if (type in logs) $.channel.send(getLogBuffer(type)); - else - $.channel.send( - `Couldn't find a verbosity level named \`${type}\`! The available types are \`[${Object.keys( - logs - ).join(", ")}]\`.` - ); - } - }) - }), - status: new Command({ - description: "Changes the bot's status.", - permission: Command.PERMISSIONS.BOT_SUPPORT, - async run($: CommonLibrary): Promise { - $.channel.send("Setting status to `online`..."); - }, - any: new Command({ - description: `Select a status to set to. Available statuses: \`[${statuses.join(", ")}]\`.`, - async run($: CommonLibrary): Promise { - if (!statuses.includes($.args[0])) return $.channel.send("That status doesn't exist!"); - else { - $.client.user?.setStatus($.args[0]); - $.channel.send(`Setting status to \`${$.args[0]}\`...`); - } - } - }) - }), - purge: new Command({ - description: "Purges bot messages.", - permission: Command.PERMISSIONS.BOT_SUPPORT, - async run($: CommonLibrary): Promise { - if ($.message.channel instanceof discord.DMChannel) { - return; - } - $.message.delete(); - const msgs = await $.channel.messages.fetch({ - limit: 100 - }); - const travMessages = msgs.filter((m) => m.author.id === $.client.user?.id); - - await $.message.channel.send(`Found ${travMessages.size} messages to delete.`).then((m) => - m.delete({ - timeout: 5000 - }) - ); - await $.message.channel.bulkDelete(travMessages); - } - }), - clear: new Command({ - description: "Clears a given amount of messages.", - usage: "", - run: "A number was not provided.", - number: new Command({ - description: "Amount of messages to delete.", - async run($: CommonLibrary): Promise { - if ($.channel.type === "dm") { - await $.channel.send("Can't clear messages in the DMs!"); - return; - } - $.message.delete(); - const fetched = await $.channel.messages.fetch({ - limit: $.args[0] - }); - await $.channel.bulkDelete(fetched); - } - }) - }), - eval: new Command({ - description: "Evaluate code.", - usage: "", - permission: Command.PERMISSIONS.BOT_OWNER, - // You have to bring everything into scope to use them. AFAIK, there isn't a more maintainable way to do this, but at least TS will let you know if anything gets removed. - async run({args, author, channel, client, guild, member, message}): Promise { - try { - const code = args.join(" "); - let evaled = eval(code); - - if (typeof evaled !== "string") evaled = require("util").inspect(evaled); - channel.send(clean(evaled), {code: "js", split: true}); - } catch (err) { - channel.send(`\`ERROR\` \`\`\`js\n${clean(err)}\n\`\`\``); - } - } - }), - nick: new Command({ - description: "Change the bot's nickname.", - permission: Command.PERMISSIONS.BOT_SUPPORT, - async run($: CommonLibrary): Promise { - const nickName = $.args.join(" "); - await $.guild?.me?.setNickname(nickName); - if (botHasPermission($.guild, Permissions.FLAGS.MANAGE_MESSAGES)) - $.message.delete({timeout: 5000}).catch($.handler.bind($)); - $.channel.send(`Nickname set to \`${nickName}\``).then((m) => m.delete({timeout: 5000})); - } - }), - guilds: new Command({ - description: "Shows a list of all guilds the bot is a member of.", - permission: Command.PERMISSIONS.BOT_SUPPORT, - async run($: CommonLibrary): Promise { - const guildList = $.client.guilds.cache.array().map((e) => e.name); - $.channel.send(guildList); - } - }), - activity: new Command({ - description: "Set the activity of the bot.", - permission: Command.PERMISSIONS.BOT_SUPPORT, - usage: " ", - async run($: CommonLibrary): Promise { - $.client.user?.setActivity(".help", { - type: "LISTENING" - }); - $.channel.send("Activity set to default."); - }, - any: new Command({ - description: `Select an activity type to set. Available levels: \`[${activities.join(", ")}]\``, - async run($: CommonLibrary): Promise { - const type = $.args[0]; - - if (activities.includes(type)) { - $.client.user?.setActivity($.args.slice(1).join(" "), { - type: $.args[0].toUpperCase() - }); - $.channel.send( - `Set activity to \`${$.args[0].toUpperCase()}\` \`${$.args.slice(1).join(" ")}\`.` - ); - } else - $.channel.send( - `Couldn't find an activity type named \`${type}\`! The available types are \`[${activities.join( - ", " - )}]\`.` - ); - } - }) - }), - syslog: new Command({ - description: "Sets up the current channel to receive system logs.", - permission: Command.PERMISSIONS.BOT_ADMIN, - async run($) { - if ($.guild) { - Config.systemLogsChannel = $.channel.id; - Config.save(); - $.channel.send(`Successfully set ${$.channel} as the system logs channel.`); - } else { - $.channel.send("DM system log channels aren't supported."); - } - } - }) - } -}); diff --git a/src/commands/fun/figlet.ts b/src/commands/fun/figlet.ts index ded3d94..b78ba89 100644 --- a/src/commands/fun/figlet.ts +++ b/src/commands/fun/figlet.ts @@ -1,15 +1,15 @@ -import Command from "../../core/command"; +import {Command, NamedCommand} from "../../core"; import figlet from "figlet"; -export default new Command({ +export default new NamedCommand({ description: "Generates a figlet of your input.", - async run($) { - const input = $.args.join(" "); - if (!$.args[0]) { - $.channel.send("You have to provide input for me to create a figlet!"); + async run({message, channel, guild, author, member, client, args}) { + const input = args.join(" "); + if (!args[0]) { + channel.send("You have to provide input for me to create a figlet!"); return; } - $.channel.send( + channel.send( "```" + figlet.textSync(`${input}`, { horizontalLayout: "full" diff --git a/src/commands/fun/insult.ts b/src/commands/fun/insult.ts index c2e0260..63847fd 100644 --- a/src/commands/fun/insult.ts +++ b/src/commands/fun/insult.ts @@ -1,14 +1,14 @@ -import Command from "../../core/command"; +import {Command, NamedCommand} from "../../core"; -export default new Command({ +export default new NamedCommand({ description: "Insult TravBot! >:D", - async run($) { - $.channel.startTyping(); + async run({message, channel, guild, author, member, client, args}) { + channel.startTyping(); setTimeout(() => { - $.channel.send( - `${$.author.toString()} What the fuck did you just fucking say about me, you little bitch? I'll have you know I graduated top of my class in the Navy Seals, and I've been involved in numerous secret raids on Al-Quaeda, and I have over 300 confirmed kills. I am trained in gorilla warfare and I'm the top sniper in the entire US armed forces. You are nothing to me but just another target. I will wipe you the fuck out with precision the likes of which has never been seen before on this Earth, mark my fucking words. You think you can get away with saying that shit to me over the Internet? Think again, fucker. As we speak I am contacting my secret network of spies across the USA and your IP is being traced right now so you better prepare for the storm, maggot. The storm that wipes out the pathetic little thing you call your life. You're fucking dead, kid. I can be anywhere, anytime, and I can kill you in over seven hundred ways, and that's just with my bare hands. Not only am I extensively trained in unarmed combat, but I have access to the entire arsenal of the United States Marine Corps and I will use it to its full extent to wipe your miserable ass off the face of the continent, you little shit. If only you could have known what unholy retribution your little "clever" comment was about to bring down upon you, maybe you would have held your fucking tongue. But you couldn't, you didn't, and now you're paying the price, you goddamn idiot. I will shit fury all over you and you will drown in it. You're fucking dead, kiddo.` + channel.send( + `${author} What the fuck did you just fucking say about me, you little bitch? I'll have you know I graduated top of my class in the Navy Seals, and I've been involved in numerous secret raids on Al-Quaeda, and I have over 300 confirmed kills. I am trained in gorilla warfare and I'm the top sniper in the entire US armed forces. You are nothing to me but just another target. I will wipe you the fuck out with precision the likes of which has never been seen before on this Earth, mark my fucking words. You think you can get away with saying that shit to me over the Internet? Think again, fucker. As we speak I am contacting my secret network of spies across the USA and your IP is being traced right now so you better prepare for the storm, maggot. The storm that wipes out the pathetic little thing you call your life. You're fucking dead, kid. I can be anywhere, anytime, and I can kill you in over seven hundred ways, and that's just with my bare hands. Not only am I extensively trained in unarmed combat, but I have access to the entire arsenal of the United States Marine Corps and I will use it to its full extent to wipe your miserable ass off the face of the continent, you little shit. If only you could have known what unholy retribution your little "clever" comment was about to bring down upon you, maybe you would have held your fucking tongue. But you couldn't, you didn't, and now you're paying the price, you goddamn idiot. I will shit fury all over you and you will drown in it. You're fucking dead, kiddo.` ); - $.channel.stopTyping(); + channel.stopTyping(); }, 60000); } }); diff --git a/src/commands/fun/love.ts b/src/commands/fun/love.ts index d0f9a5a..94467c5 100644 --- a/src/commands/fun/love.ts +++ b/src/commands/fun/love.ts @@ -1,13 +1,13 @@ -import Command from "../../core/command"; +import {Command, NamedCommand} from "../../core"; -export default new Command({ +export default new NamedCommand({ description: "Chooses someone to love.", - async run($) { - if ($.guild) { - const member = $.guild.members.cache.random(); - $.channel.send(`I love ${member.user.username}!`); + async run({message, channel, guild, author, member, client, args}) { + if (guild) { + const member = guild.members.cache.random(); + channel.send(`I love ${member.user.username}!`); } else { - $.channel.send("You must use this command in a guild!"); + channel.send("You must use this command in a guild!"); } } }); diff --git a/src/commands/fun/ravi.ts b/src/commands/fun/ravi.ts index fbcf14e..7b408f6 100644 --- a/src/commands/fun/ravi.ts +++ b/src/commands/fun/ravi.ts @@ -1,11 +1,11 @@ -import Command from "../../core/command"; -import {Random} from "../../core/lib"; +import {Command, NamedCommand} from "../../core"; +import {Random} from "../../lib"; -export default new Command({ +export default new NamedCommand({ description: "Ravioli ravioli...", usage: "[number from 1 to 9]", - async run($) { - $.channel.send({ + async run({message, channel, guild, author, member, client, args}) { + channel.send({ embed: { title: "Ravioli ravioli...", image: { @@ -18,11 +18,11 @@ export default new Command({ }); }, number: new Command({ - async run($) { - const arg: number = $.args[0]; + async run({message, channel, guild, author, member, client, args}) { + const arg: number = args[0]; if (arg >= 1 && arg <= 9) { - $.channel.send({ + channel.send({ embed: { title: "Ravioli ravioli...", image: { @@ -31,7 +31,7 @@ export default new Command({ } }); } else { - $.channel.send("Please provide a number between 1 and 9."); + channel.send("Please provide a number between 1 and 9."); } } }) diff --git a/src/commands/fun/thonk.ts b/src/commands/fun/thonk.ts index a09c6e4..762549e 100644 --- a/src/commands/fun/thonk.ts +++ b/src/commands/fun/thonk.ts @@ -1,4 +1,4 @@ -import Command from "../../core/command"; +import {Command, NamedCommand} from "../../core"; const letters: {[letter: string]: string[]} = { a: "aáàảãạâấầẩẫậăắằẳẵặ".split(""), @@ -31,11 +31,11 @@ function transform(str: string) { let phrase = "I have no currently set phrase!"; -export default new Command({ +export default new NamedCommand({ description: "Transforms your text into vietnamese.", usage: "thonk ([text])", - async run($) { - if ($.args.length > 0) phrase = $.args.join(" "); - $.channel.send(transform(phrase)); + async run({message, channel, guild, author, member, client, args}) { + if (args.length > 0) phrase = args.join(" "); + channel.send(transform(phrase)); } }); diff --git a/src/commands/fun/urban.ts b/src/commands/fun/urban.ts index 1f21c2e..2901433 100644 --- a/src/commands/fun/urban.ts +++ b/src/commands/fun/urban.ts @@ -1,16 +1,16 @@ -import Command from "../../core/command"; +import {Command, NamedCommand} from "../../core"; import {MessageEmbed} from "discord.js"; // Anycasting Alert const urban = require("relevant-urban"); -export default new Command({ +export default new NamedCommand({ description: "Gives you a definition of the inputted word.", - async run($) { - if (!$.args[0]) { - $.channel.send("Please input a word."); + async run({message, channel, guild, author, member, client, args}) { + if (!args[0]) { + channel.send("Please input a word."); } - const res = await urban($.args.join(" ")).catch((e: Error) => { - return $.channel.send("Sorry, that word was not found."); + const res = await urban(args.join(" ")).catch((e: Error) => { + return channel.send("Sorry, that word was not found."); }); const embed = new MessageEmbed() .setColor(0x1d2439) @@ -22,6 +22,6 @@ export default new Command({ if (res.tags.length > 0 && res.tags.join(" ").length < 1024) { embed.addField("Tags", res.tags.join(", "), true); } - $.channel.send(embed); + channel.send(embed); } }); diff --git a/src/commands/fun/weather.ts b/src/commands/fun/weather.ts index aaac509..d232ea3 100644 --- a/src/commands/fun/weather.ts +++ b/src/commands/fun/weather.ts @@ -1,22 +1,22 @@ -import Command from "../../core/command"; +import {Command, NamedCommand} from "../../core"; import {MessageEmbed} from "discord.js"; // Anycasting Alert const weather = require("weather-js"); -export default new Command({ +export default new NamedCommand({ description: "Shows weather info of specified location.", - async run($) { - if ($.args.length == 0) { - $.channel.send("You need to provide a city."); + async run({message, channel, guild, author, member, client, args}) { + if (args.length == 0) { + channel.send("You need to provide a city."); return; } weather.find( { - search: $.args.join(" "), + search: args.join(" "), degreeType: "C" }, function (err: any, result: any) { - if (err) $.channel.send(err); + if (err) channel.send(err); var current = result[0].current; var location = result[0].location; const embed = new MessageEmbed() @@ -30,7 +30,7 @@ export default new Command({ .addField("Feels like", `${current.feelslike} Degrees`, true) .addField("Winds", current.winddisplay, true) .addField("Humidity", `${current.humidity}%`, true); - $.channel.send({ + channel.send({ embed }); } diff --git a/src/commands/fun/whois.ts b/src/commands/fun/whois.ts index ed7e07e..c8daff2 100644 --- a/src/commands/fun/whois.ts +++ b/src/commands/fun/whois.ts @@ -1,5 +1,5 @@ import {User} from "discord.js"; -import Command from "../../core/command"; +import {Command, NamedCommand, getMemberByUsername} from "../../core"; // Quotes must be used here or the numbers will change const registry: {[id: string]: string} = { @@ -34,49 +34,49 @@ const registry: {[id: string]: string} = { "Some random conlanger, worldbuilder and programmer doofus. ~~May also secretly be a nyan. :3~~" }; -export default new Command({ +export default new NamedCommand({ description: "Tells you who you or the specified user is.", aliases: ["whoami"], - async run($) { - const id = $.author.id; + async run({message, channel, guild, author, member, client, args}) { + const id = author.id; if (id in registry) { - $.channel.send(registry[id]); + channel.send(registry[id]); } else { - $.channel.send("You haven't been added to the registry yet!"); + channel.send("You haven't been added to the registry yet!"); } }, user: new Command({ - async run($) { - const user: User = $.args[0]; + async run({message, channel, guild, author, member, client, args}) { + const user: User = args[0]; const id = user.id; if (id in registry) { - $.channel.send(`\`${user.username}\` - ${registry[id]}`); + channel.send(`\`${user.username}\` - ${registry[id]}`); } else { - $.channel.send(`\`${user.username}#${user.discriminator}\` hasn't been added to the registry yet!`); + channel.send(`\`${user.username}#${user.discriminator}\` hasn't been added to the registry yet!`); } } }), any: new Command({ - async run($) { - if ($.guild) { - const query: string = $.args.join(" "); - const member = await $.getMemberByUsername($.guild, query); + async run({message, channel, guild, author, member, client, args}) { + if (guild) { + const query: string = args.join(" "); + const member = await getMemberByUsername(guild, query); if (member && member.id in registry) { const id = member.id; if (id in registry) { - $.channel.send(`\`${member.user.username}\` - ${registry[member.id]}`); + channel.send(`\`${member.user.username}\` - ${registry[member.id]}`); } else { - $.channel.send(`\`${member.user.username}\` hasn't been added to the registry yet!`); + channel.send(`\`${member.user.username}\` hasn't been added to the registry yet!`); } } else { - $.channel.send(`Couldn't find a user by the name of \`${query}\`!`); + channel.send(`Couldn't find a user by the name of \`${query}\`!`); } } else { - $.channel.send( + channel.send( "You must run this in a guild! (*If you have the user's ID, you don't have to be in a guild.*)" ); } diff --git a/src/commands/system/admin.ts b/src/commands/system/admin.ts index b2da0bb..7aeebc2 100644 --- a/src/commands/system/admin.ts +++ b/src/commands/system/admin.ts @@ -50,6 +50,113 @@ export default new NamedCommand({ channel.send(`The custom prefix for this guild is now \`${args[0]}\`.`); } }) + }), + welcome: new NamedCommand({ + description: "Configure your server's welcome settings for the bot.", + usage: "type/channel <...>", + run: "You need to specify which part to modify, `type`/`channel`.", + subcommands: { + type: new NamedCommand({ + description: + "Sets how welcome messages are displayed for your server. Removes welcome messages if unspecified.", + usage: "`none`/`text`/`graphical`", + async run({message, channel, guild, author, member, client, args}) { + if (guild) { + Storage.getGuild(guild.id).welcomeType = "none"; + Storage.save(); + channel.send("Set this server's welcome type to `none`."); + } else { + channel.send("You must use this command in a server."); + } + }, + // I should probably make this a bit more dynamic... Oh well. + subcommands: { + text: new NamedCommand({ + async run({message, channel, guild, author, member, client, args}) { + if (guild) { + Storage.getGuild(guild.id).welcomeType = "text"; + Storage.save(); + channel.send("Set this server's welcome type to `text`."); + } else { + channel.send("You must use this command in a server."); + } + } + }), + graphical: new NamedCommand({ + async run({message, channel, guild, author, member, client, args}) { + if (guild) { + Storage.getGuild(guild.id).welcomeType = "graphical"; + Storage.save(); + channel.send("Set this server's welcome type to `graphical`."); + } else { + channel.send("You must use this command in a server."); + } + } + }) + } + }), + channel: new NamedCommand({ + description: "Sets the welcome channel for your server. Type `#` to reference the channel.", + usage: "()", + async run({message, channel, guild, author, member, client, args}) { + if (guild) { + Storage.getGuild(guild.id).welcomeChannel = channel.id; + Storage.save(); + channel.send(`Successfully set ${channel} as the welcome channel for this server.`); + } else { + channel.send("You must use this command in a server."); + } + }, + // If/when channel types come out, this will be the perfect candidate to test it. + any: new Command({ + async run({message, channel, guild, author, member, client, args}) { + if (guild) { + const match = args[0].match(/^<#(\d{17,19})>$/); + + if (match) { + Storage.getGuild(guild.id).welcomeChannel = match[1]; + Storage.save(); + channel.send( + `Successfully set this server's welcome channel to ${match[0]}.` + ); + } else { + channel.send( + "You must provide a reference channel. You can do this by typing `#` then searching for the proper channel." + ); + } + } else { + channel.send("You must use this command in a server."); + } + } + }) + }), + message: new NamedCommand({ + description: + "Sets a custom welcome message for your server. Use `%user%` as the placeholder for the user.", + usage: "()", + async run({message, channel, guild, author, member, client, args}) { + if (guild) { + Storage.getGuild(guild.id).welcomeMessage = null; + Storage.save(); + channel.send("Reset your server's welcome message to the default."); + } else { + channel.send("You must use this command in a server."); + } + }, + any: new Command({ + async run({message, channel, guild, author, member, client, args}) { + if (guild) { + const message = args.join(" "); + Storage.getGuild(guild.id).welcomeMessage = message; + Storage.save(); + channel.send(`Set your server's welcome message to \`${message}\`.`); + } else { + channel.send("You must use this command in a server."); + } + } + }) + }) + } }) } }), @@ -197,6 +304,19 @@ export default new NamedCommand({ ); } }) + }), + syslog: new NamedCommand({ + description: "Sets up the current channel to receive system logs.", + permission: PERMISSIONS.BOT_ADMIN, + async run({message, channel, guild, author, member, client, args}) { + if (guild) { + Config.systemLogsChannel = channel.id; + Config.save(); + channel.send(`Successfully set ${channel} as the system logs channel.`); + } else { + channel.send("DM system log channels aren't supported."); + } + } }) } }); diff --git a/src/commands/utility/calc.ts b/src/commands/utility/calc.ts index a3fe45e..2303351 100644 --- a/src/commands/utility/calc.ts +++ b/src/commands/utility/calc.ts @@ -1,26 +1,26 @@ -import Command from "../../core/command"; +import {Command, NamedCommand} from "../../core"; import * as math from "mathjs"; import {MessageEmbed} from "discord.js"; -export default new Command({ +export default new NamedCommand({ description: "Calculates a specified math expression.", - async run($) { - if (!$.args[0]) { - $.channel.send("Please provide a calculation."); + async run({message, channel, guild, author, member, client, args}) { + if (!args[0]) { + channel.send("Please provide a calculation."); return; } let resp; try { - resp = math.evaluate($.args.join(" ")); + resp = math.evaluate(args.join(" ")); } catch (e) { - $.channel.send("Please provide a *valid* calculation."); + channel.send("Please provide a *valid* calculation."); return; } const embed = new MessageEmbed() .setColor(0xffffff) .setTitle("Math Calculation") - .addField("Input", `\`\`\`js\n${$.args.join("")}\`\`\``) + .addField("Input", `\`\`\`js\n${args.join("")}\`\`\``) .addField("Output", `\`\`\`js\n${resp}\`\`\``); - $.channel.send(embed); + channel.send(embed); } }); diff --git a/src/commands/utility/code.ts b/src/commands/utility/code.ts index b473b57..39e41b7 100644 --- a/src/commands/utility/code.ts +++ b/src/commands/utility/code.ts @@ -1,6 +1,6 @@ -import Command from "../../core/command"; +import {Command, NamedCommand} from "../../core"; -export default new Command({ +export default new NamedCommand({ description: "Gives you the Github link.", run: "https://github.com/keanuplayz/TravBot-v3" }); diff --git a/src/commands/utility/invite.ts b/src/commands/utility/invite.ts index 7f1fc94..5d5657c 100644 --- a/src/commands/utility/invite.ts +++ b/src/commands/utility/invite.ts @@ -1,11 +1,11 @@ -import Command from "../../core/command"; +import {Command, NamedCommand} from "../../core"; -export default new Command({ +export default new NamedCommand({ description: "Gives you the invite link.", - async run($) { - $.channel.send( - `https://discordapp.com/api/oauth2/authorize?client_id=${$.client.user!.id}&permissions=${ - $.args[0] || 8 + async run({message, channel, guild, author, member, client, args}) { + channel.send( + `https://discordapp.com/api/oauth2/authorize?client_id=${client.user!.id}&permissions=${ + args[0] || 8 }&scope=bot` ); } diff --git a/src/commands/utility/todo.ts b/src/commands/utility/todo.ts index 1f6c639..594610b 100644 --- a/src/commands/utility/todo.ts +++ b/src/commands/utility/todo.ts @@ -1,13 +1,13 @@ -import Command from "../../core/command"; +import {Command, NamedCommand} from "../../core"; import moment from "moment"; -import {Storage} from "../../core/structures"; +import {Storage} from "../../structures"; import {MessageEmbed} from "discord.js"; -export default new Command({ +export default new NamedCommand({ description: "Keep and edit your personal todo list.", - async run($) { - const user = Storage.getUser($.author.id); - const embed = new MessageEmbed().setTitle(`Todo list for ${$.author.tag}`).setColor("BLUE"); + async run({message, channel, guild, author, member, client, args}) { + const user = Storage.getUser(author.id); + const embed = new MessageEmbed().setTitle(`Todo list for ${author.tag}`).setColor("BLUE"); for (const timestamp in user.todoList) { const date = new Date(Number(timestamp)); @@ -17,23 +17,23 @@ export default new Command({ ); } - $.channel.send(embed); + channel.send(embed); }, subcommands: { - add: new Command({ - async run($) { - const user = Storage.getUser($.author.id); - const note = $.args.join(" "); + add: new NamedCommand({ + async run({message, channel, guild, author, member, client, args}) { + const user = Storage.getUser(author.id); + const note = args.join(" "); user.todoList[Date.now().toString()] = note; console.debug(user.todoList); Storage.save(); - $.channel.send(`Successfully added \`${note}\` to your todo list.`); + channel.send(`Successfully added \`${note}\` to your todo list.`); } }), - remove: new Command({ - async run($) { - const user = Storage.getUser($.author.id); - const note = $.args.join(" "); + remove: new NamedCommand({ + async run({message, channel, guild, author, member, client, args}) { + const user = Storage.getUser(author.id); + const note = args.join(" "); let isFound = false; for (const timestamp in user.todoList) { @@ -43,19 +43,19 @@ export default new Command({ delete user.todoList[timestamp]; Storage.save(); isFound = true; - $.channel.send(`Removed \`${note}\` from your todo list.`); + channel.send(`Removed \`${note}\` from your todo list.`); } } - if (!isFound) $.channel.send("That item couldn't be found."); + if (!isFound) channel.send("That item couldn't be found."); } }), - clear: new Command({ - async run($) { - const user = Storage.getUser($.author.id); + clear: new NamedCommand({ + async run({message, channel, guild, author, member, client, args}) { + const user = Storage.getUser(author.id); user.todoList = {}; Storage.save(); - $.channel.send("Cleared todo list."); + channel.send("Cleared todo list."); } }) } diff --git a/src/commands/utility/translate.ts b/src/commands/utility/translate.ts index 2e56087..b697855 100644 --- a/src/commands/utility/translate.ts +++ b/src/commands/utility/translate.ts @@ -1,18 +1,18 @@ -import Command from "../../core/command"; +import {Command, NamedCommand} from "../../core"; // Anycasting Alert const translate = require("translate-google"); -export default new Command({ +export default new NamedCommand({ description: "Translates your input.", usage: " ", - async run($) { - const lang = $.args[0]; - const input = $.args.slice(1).join(" "); + async run({message, channel, guild, author, member, client, args}) { + const lang = args[0]; + const input = args.slice(1).join(" "); translate(input, { to: lang }) .then((res: any) => { - $.channel.send({ + channel.send({ embed: { title: "Translation", fields: [ @@ -30,7 +30,7 @@ export default new Command({ }) .catch((err: any) => { console.error(err); - $.channel.send( + channel.send( `${err}\nPlease use the following list: https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes` ); }); diff --git a/src/events/guildCreate.ts b/src/events/guildCreate.ts deleted file mode 100644 index 02189b3..0000000 --- a/src/events/guildCreate.ts +++ /dev/null @@ -1,32 +0,0 @@ -import Event from "../core/event"; -import $ from "../core/lib"; -import {updateGlobalEmoteRegistry} from "../core/lib"; -import {client} from "../index"; -import {Config} from "../core/structures"; -import {TextChannel} from "discord.js"; - -export default new Event<"guildCreate">({ - on(guild) { - $.log( - `[GUILD JOIN] ${guild.name} (${guild.id}) added the bot. Owner: ${guild.owner!.user.tag} (${ - guild.owner!.user.id - }). Updated emote registry.` - ); - - if (Config.systemLogsChannel) { - const channel = client.channels.cache.get(Config.systemLogsChannel); - - if (channel && channel.type === "text") { - (channel as TextChannel).send( - `TravBot joined: \`${guild.name}\`. The owner of this guild is: \`${guild.owner!.user.tag}\` (\`${ - guild.owner!.user.id - }\`)` - ); - } else { - console.warn(`${Config.systemLogsChannel} is not a valid text channel for system logs!`); - } - } - - updateGlobalEmoteRegistry(); - } -}); diff --git a/src/events/guildDelete.ts b/src/events/guildDelete.ts deleted file mode 100644 index 41c656f..0000000 --- a/src/events/guildDelete.ts +++ /dev/null @@ -1,24 +0,0 @@ -import Event from "../core/event"; -import $ from "../core/lib"; -import {updateGlobalEmoteRegistry} from "../core/lib"; -import {client} from "../index"; -import {Config} from "../core/structures"; -import {TextChannel} from "discord.js"; - -export default new Event<"guildDelete">({ - on(guild) { - $.log(`[GUILD LEAVE] ${guild.name} (${guild.id}) removed the bot. Updated emote registry.`); - - if (Config.systemLogsChannel) { - const channel = client.channels.cache.get(Config.systemLogsChannel); - - if (channel && channel.type === "text") { - (channel as TextChannel).send(`\`${guild.name}\` (\`${guild.id}\`) removed the bot.`); - } else { - console.warn(`${Config.systemLogsChannel} is not a valid text channel for system logs!`); - } - } - - updateGlobalEmoteRegistry(); - } -}); diff --git a/src/events/guildMemberAdd.ts b/src/events/guildMemberAdd.ts deleted file mode 100644 index 1855094..0000000 --- a/src/events/guildMemberAdd.ts +++ /dev/null @@ -1,77 +0,0 @@ -import Event from "../core/event"; -import $, {parseVars} from "../core/lib"; -import {createCanvas, loadImage, Canvas} from "canvas"; -import {Storage} from "../core/structures"; -import {TextChannel, MessageAttachment} from "discord.js"; - -function applyText(canvas: Canvas, text: string) { - const ctx = canvas.getContext("2d"); - let fontSize = 70; - - do { - ctx.font = `${(fontSize -= 10)}px sans-serif`; - } while (ctx.measureText(text).width > canvas.width - 300); - - return ctx.font; -} - -export default new Event<"guildMemberAdd">({ - async on(member) { - const {welcomeType, welcomeChannel, welcomeMessage} = Storage.getGuild(member.guild.id); - - if (welcomeChannel) { - const channel = member.guild.channels.cache.get(welcomeChannel); - - if (channel && channel.type === "text") { - if (welcomeType === "graphical") { - const canvas = createCanvas(700, 250); - const ctx = canvas.getContext("2d"); - const background = await loadImage( - "https://raw.githubusercontent.com/keanuplayz/TravBot/dev/assets/wallpaper.png" - ); - ctx.drawImage(background, 0, 0, canvas.width, canvas.height); - - ctx.strokeStyle = "#74037b"; - ctx.strokeRect(0, 0, canvas.width, canvas.height); - - ctx.font = "28px sans-serif"; - ctx.fillStyle = "#ffffff"; - ctx.fillText("Welcome to the server,", canvas.width / 2.5, canvas.height / 3.5); - - ctx.font = applyText(canvas, member.displayName); - ctx.fillStyle = "#ffffff"; - ctx.fillText(`${member.displayName}!`, canvas.width / 2.5, canvas.height / 1.5); - - ctx.beginPath(); - ctx.arc(125, 125, 100, 0, Math.PI * 2, true); - ctx.closePath(); - ctx.clip(); - - const avatarURL = - member.user.avatarURL({ - dynamic: true, - size: 2048, - format: "png" - }) ?? member.user.defaultAvatarURL; - const avatar = await loadImage(avatarURL); - ctx.drawImage(avatar, 25, 25, 200, 200); - - const attachment = new MessageAttachment(canvas.toBuffer("image/png"), "welcome-image.png"); - (channel as TextChannel).send(`Welcome \`${member.user.tag}\`!`, attachment); - } else if (welcomeType === "text") { - (channel as TextChannel).send( - parseVars( - welcomeMessage || - "Say hello to `%user%`, everyone! We all need a warm welcome sometimes :D", - { - user: member.user.tag - } - ) - ); - } - } else { - $.error(`"${welcomeChannel}" is not a valid text channel ID!`); - } - } - } -}); diff --git a/src/events/ready.ts b/src/events/ready.ts deleted file mode 100644 index b8467aa..0000000 --- a/src/events/ready.ts +++ /dev/null @@ -1,20 +0,0 @@ -import Event from "../core/event"; -import {client} from "../index"; -import $ from "../core/lib"; -import {Config} from "../core/structures"; -import {updateGlobalEmoteRegistry} from "../core/lib"; - -export default new Event<"ready">({ - once() { - if (client.user) { - $.ready( - `Logged in as ${client.user.tag}, ready to serve ${client.users.cache.size} users in ${client.guilds.cache.size} servers..` - ); - client.user.setActivity({ - type: "LISTENING", - name: `${Config.prefix}help` - }); - } - updateGlobalEmoteRegistry(); - } -}); diff --git a/src/index.ts b/src/index.ts index 21633ce..67cacb5 100644 --- a/src/index.ts +++ b/src/index.ts @@ -69,3 +69,4 @@ import "./modules/emoteRegistry"; import "./modules/channelListener"; import "./modules/intercept"; import "./modules/messageEmbed"; +import "./modules/guildMemberAdd"; diff --git a/src/modules/emoteRegistry.ts b/src/modules/emoteRegistry.ts index a3de8bc..88ab099 100644 --- a/src/modules/emoteRegistry.ts +++ b/src/modules/emoteRegistry.ts @@ -1,6 +1,7 @@ import {client} from "../index"; import FileManager from "./storage"; -import {EmoteRegistryDump} from "../structures"; +import {EmoteRegistryDump, Config} from "../structures"; +import {TextChannel} from "discord.js"; function updateGlobalEmoteRegistry(): void { const data: EmoteRegistryDump = {version: 1, list: []}; @@ -38,13 +39,43 @@ client.on("emojiUpdate", () => { updateGlobalEmoteRegistry(); }); -client.on("guildCreate", () => { - console.log("Updated emote registry."); +client.on("guildCreate", (guild) => { + console.log( + `[GUILD JOIN] ${guild.name} (${guild.id}) added the bot. Owner: ${guild.owner!.user.tag} (${ + guild.owner!.user.id + }). Updated emote registry.` + ); + + if (Config.systemLogsChannel) { + const channel = client.channels.cache.get(Config.systemLogsChannel); + + if (channel && channel.type === "text") { + (channel as TextChannel).send( + `TravBot joined: \`${guild.name}\`. The owner of this guild is: \`${guild.owner!.user.tag}\` (\`${ + guild.owner!.user.id + }\`)` + ); + } else { + console.warn(`${Config.systemLogsChannel} is not a valid text channel for system logs!`); + } + } + updateGlobalEmoteRegistry(); }); -client.on("guildDelete", () => { - console.log("Updated emote registry."); +client.on("guildDelete", (guild) => { + console.log(`[GUILD LEAVE] ${guild.name} (${guild.id}) removed the bot. Updated emote registry.`); + + if (Config.systemLogsChannel) { + const channel = client.channels.cache.get(Config.systemLogsChannel); + + if (channel && channel.type === "text") { + (channel as TextChannel).send(`\`${guild.name}\` (\`${guild.id}\`) removed the bot.`); + } else { + console.warn(`${Config.systemLogsChannel} is not a valid text channel for system logs!`); + } + } + updateGlobalEmoteRegistry(); }); diff --git a/src/modules/guildMemberAdd.ts b/src/modules/guildMemberAdd.ts new file mode 100644 index 0000000..9a26ffd --- /dev/null +++ b/src/modules/guildMemberAdd.ts @@ -0,0 +1,74 @@ +import {createCanvas, loadImage, Canvas} from "canvas"; +import {TextChannel, MessageAttachment} from "discord.js"; +import {parseVars} from "../lib"; +import {Storage} from "../structures"; +import {client} from "../index"; + +function applyText(canvas: Canvas, text: string) { + const ctx = canvas.getContext("2d"); + let fontSize = 70; + + do { + ctx.font = `${(fontSize -= 10)}px sans-serif`; + } while (ctx.measureText(text).width > canvas.width - 300); + + return ctx.font; +} + +client.on("guildMemberAdd", async (member) => { + const {welcomeType, welcomeChannel, welcomeMessage} = Storage.getGuild(member.guild.id); + + if (welcomeChannel) { + const channel = member.guild.channels.cache.get(welcomeChannel); + + if (channel && channel.type === "text") { + if (welcomeType === "graphical") { + const canvas = createCanvas(700, 250); + const ctx = canvas.getContext("2d"); + const background = await loadImage( + "https://raw.githubusercontent.com/keanuplayz/TravBot/dev/assets/wallpaper.png" + ); + ctx.drawImage(background, 0, 0, canvas.width, canvas.height); + + ctx.strokeStyle = "#74037b"; + ctx.strokeRect(0, 0, canvas.width, canvas.height); + + ctx.font = "28px sans-serif"; + ctx.fillStyle = "#ffffff"; + ctx.fillText("Welcome to the server,", canvas.width / 2.5, canvas.height / 3.5); + + ctx.font = applyText(canvas, member.displayName); + ctx.fillStyle = "#ffffff"; + ctx.fillText(`${member.displayName}!`, canvas.width / 2.5, canvas.height / 1.5); + + ctx.beginPath(); + ctx.arc(125, 125, 100, 0, Math.PI * 2, true); + ctx.closePath(); + ctx.clip(); + + const avatarURL = + member.user.avatarURL({ + dynamic: true, + size: 2048, + format: "png" + }) ?? member.user.defaultAvatarURL; + const avatar = await loadImage(avatarURL); + ctx.drawImage(avatar, 25, 25, 200, 200); + + const attachment = new MessageAttachment(canvas.toBuffer("image/png"), "welcome-image.png"); + (channel as TextChannel).send(`Welcome \`${member.user.tag}\`!`, attachment); + } else if (welcomeType === "text") { + (channel as TextChannel).send( + parseVars( + welcomeMessage || "Say hello to `%user%`, everyone! We all need a warm welcome sometimes :D", + { + user: member.user.tag + } + ) + ); + } + } else { + console.error(`"${welcomeChannel}" is not a valid text channel ID!`); + } + } +}); diff --git a/src/modules/ready.ts b/src/modules/ready.ts index 9c3c86b..eac1fa0 100644 --- a/src/modules/ready.ts +++ b/src/modules/ready.ts @@ -3,7 +3,9 @@ import {Config} from "../structures"; client.once("ready", () => { if (client.user) { - console.ready(`Logged in as ${client.user.tag}.`); + console.ready( + `Logged in as ${client.user.tag}, ready to serve ${client.users.cache.size} users in ${client.guilds.cache.size} servers..` + ); client.user.setActivity({ type: "LISTENING", name: `${Config.prefix}help` From 937e6f52f51f40460c0982889259ab187e88ad65 Mon Sep 17 00:00:00 2001 From: EL2020 Date: Mon, 5 Apr 2021 20:33:34 -0400 Subject: [PATCH 136/178] added a few names properly this time --- src/commands/fun/whois.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/commands/fun/whois.ts b/src/commands/fun/whois.ts index 056f1b1..d37e674 100644 --- a/src/commands/fun/whois.ts +++ b/src/commands/fun/whois.ts @@ -30,7 +30,11 @@ const registry: {[id: string]: string} = { "219661798742163467": "An extremely talented artist and modder.", "440399719076855818": "You are, uhh, Stay Put, Soft Puppy, Es-Pee, Swift Pacemaker, Smug Poyo, and many more.\n...Seriously, this woman has too many names.", - "243061915281129472": "Some random conlanger, worldbuilder and programmer doofus. ~~May also secretly be a nyan. :3~~" + "243061915281129472": "Some random conlanger, worldbuilder and programmer doofus. ~~May also secretly be a nyan. :3~~", + "367439475153829892": "A weeb.", + "760375501775700038": "˙qǝǝʍ ∀", + "389178357302034442": "In his dreams, he is the star. its him. <:itsMe:808174425253871657>", + "606395763404046349": "Me." }; export default new Command({ From 678485160e3f42052a5d2ba7c6c9daf94b6fc5a4 Mon Sep 17 00:00:00 2001 From: WatDuhHekBro <44940783+WatDuhHekBro@users.noreply.github.com> Date: Mon, 5 Apr 2021 20:55:21 -0500 Subject: [PATCH 137/178] Added optional channel target for setting channel --- src/commands/admin.ts | 25 +++++++++++++++++++++++-- src/commands/utilities/streaminfo.ts | 6 +++--- src/events/channelUpdate.ts | 8 ++++---- src/events/voiceStateUpdate.ts | 10 +++++++--- 4 files changed, 37 insertions(+), 12 deletions(-) diff --git a/src/commands/admin.ts b/src/commands/admin.ts index e78068f..40c2242 100644 --- a/src/commands/admin.ts +++ b/src/commands/admin.ts @@ -166,7 +166,8 @@ export default new Command({ } }), stream: new Command({ - description: "Set a channel to send stream notifications.", + description: "Set a channel to send stream notifications. Type `#` to reference the channel.", + usage: "()", async run($) { if ($.guild) { const guild = Storage.getGuild($.guild.id); @@ -183,7 +184,27 @@ export default new Command({ } else { $.channel.send("You must use this command in a server."); } - } + }, + // If/when channel types come out, this will be the perfect candidate to test it. + any: new Command({ + async run($) { + if ($.guild) { + const match = $.args[0].match(/^<#(\d{17,19})>$/); + + if (match) { + Storage.getGuild($.guild.id).streamingChannel = match[1]; + Storage.save(); + $.channel.send(`Successfully set this server's welcome channel to ${match[0]}.`); + } else { + $.channel.send( + "You must provide a reference channel. You can do this by typing `#` then searching for the proper channel." + ); + } + } else { + $.channel.send("You must use this command in a server."); + } + } + }) }) } }), diff --git a/src/commands/utilities/streaminfo.ts b/src/commands/utilities/streaminfo.ts index e4ea9aa..7d90785 100644 --- a/src/commands/utilities/streaminfo.ts +++ b/src/commands/utilities/streaminfo.ts @@ -1,5 +1,5 @@ import Command from "../../core/command"; -import {streamList, getStreamEmbed} from "../../events/voiceStateUpdate"; +import {streamList} from "../../events/voiceStateUpdate"; export default new Command({ description: "Sets the description of your stream. You can embed links by writing `[some name](some link)`", @@ -8,8 +8,8 @@ export default new Command({ if (streamList.has(userID)) { const stream = streamList.get(userID)!; - stream.description = $.args.join(" ") || undefined; - stream.message.edit(getStreamEmbed(stream.streamer, stream.channel, stream.description)); + stream.description = $.args.join(" ") || "No description set."; + stream.update(); } else { // Alternatively, I could make descriptions last outside of just one stream. $.channel.send("You can only use this command when streaming."); diff --git a/src/events/channelUpdate.ts b/src/events/channelUpdate.ts index 0b325c9..7d49789 100644 --- a/src/events/channelUpdate.ts +++ b/src/events/channelUpdate.ts @@ -1,12 +1,12 @@ import Event from "../core/event"; -import {streamList, getStreamEmbed} from "./voiceStateUpdate"; +import {streamList} from "./voiceStateUpdate"; export default new Event<"channelUpdate">({ async on(before, after) { if (before.type === "voice" && after.type === "voice") { - for (const {streamer, channel, description, message} of streamList.values()) { - if (after.id === channel.id) { - message.edit(getStreamEmbed(streamer, channel, description)); + for (const stream of streamList.values()) { + if (after.id === stream.channel.id) { + stream.update(); } } } diff --git a/src/events/voiceStateUpdate.ts b/src/events/voiceStateUpdate.ts index ded1a5a..54b90d4 100644 --- a/src/events/voiceStateUpdate.ts +++ b/src/events/voiceStateUpdate.ts @@ -9,13 +9,14 @@ type Stream = { channel: VoiceChannel; description?: string; message: Message; + update: () => void; }; // A list of user IDs and message embeds. export const streamList = new Collection(); // Probably find a better, DRY way of doing this. -export function getStreamEmbed(streamer: GuildMember, channel: VoiceChannel, description?: string): MessageEmbed { +function getStreamEmbed(streamer: GuildMember, channel: VoiceChannel, description?: string): MessageEmbed { const user = streamer.user; const embed = new MessageEmbed() .setTitle(`Stream: \`#${channel.name}\``) @@ -49,12 +50,15 @@ export default new Event<"voiceStateUpdate">({ const voiceChannel = after.channel!; const textChannel = client.channels.cache.get(streamingChannel); - if (textChannel && textChannel instanceof TextChannel) { + if (textChannel instanceof TextChannel) { if (isStartStreamEvent) { streamList.set(member.id, { streamer: member, channel: voiceChannel, - message: await textChannel.send(getStreamEmbed(member, voiceChannel)) + message: await textChannel.send(getStreamEmbed(member, voiceChannel)), + update(this: Stream) { + this.message.edit(getStreamEmbed(this.streamer, this.channel, this.description)); + } }); } else if (isStopStreamEvent) { if (streamList.has(member.id)) { From 1351f3250bf9845532d7a44ca5be4ffbeb15f9d3 Mon Sep 17 00:00:00 2001 From: WatDuhHekBro <44940783+WatDuhHekBro@users.noreply.github.com> Date: Mon, 5 Apr 2021 22:57:03 -0500 Subject: [PATCH 138/178] Added hacky persistence and bug fixes on eco bet --- src/commands/fun/subcommands/eco-bet.ts | 33 ++++++++++++++++--------- src/core/structures.ts | 2 ++ src/events/ready.ts | 14 ++++++++++- 3 files changed, 37 insertions(+), 12 deletions(-) diff --git a/src/commands/fun/subcommands/eco-bet.ts b/src/commands/fun/subcommands/eco-bet.ts index edce702..68087c2 100644 --- a/src/commands/fun/subcommands/eco-bet.ts +++ b/src/commands/fun/subcommands/eco-bet.ts @@ -2,6 +2,7 @@ import Command from "../../../core/command"; import $ from "../../../core/lib"; import {Storage} from "../../../core/structures"; import {isAuthorized, getMoneyEmbed, getSendEmbed, ECO_EMBED_COLOR} from "./eco-utils"; +import {User} from "discord.js"; export const BetCommand = new Command({ description: "Bet your Mons with other people [TBD]", @@ -27,8 +28,8 @@ export const BetCommand = new Command({ async run({args, author, channel, guild}): Promise { if (isAuthorized(guild, channel)) { const sender = Storage.getUser(author.id); - const target = args[0]; - const receiver = Storage.getUser(target); + const target = args[0] as User; + const receiver = Storage.getUser(target.id); const amount = Math.floor(args[1]); // handle invalid target @@ -49,15 +50,15 @@ export const BetCommand = new Command({ } }, any: new Command({ - async run({client, args, author, message, channel, guild}): Promise { + async run({client, args, author, message, channel, guild, askYesOrNo}): Promise { if (isAuthorized(guild, channel)) { // [Pertinence to make configurable on the fly.] // Lower and upper bounds for bet const durationBounds = { min:"1m", max:"1d" }; const sender = Storage.getUser(author.id); - const target = args[0]; - const receiver = Storage.getUser(target); + const target = args[0] as User; + const receiver = Storage.getUser(target.id); const amount = Math.floor(args[1]); const duration = parseDuration(args[2].trim()); @@ -94,16 +95,22 @@ export const BetCommand = new Command({ // Remove amount money from both parts at the start to avoid duplication of money. sender.money -= amount; receiver.money -= amount; + // Very hacky solution for persistence but better than no solution, backup returns runs during the bot's setup code. + sender.quoteUnquoteSoCalledInsuranceForEcoBetIfItEvenCanBeSoCalled += amount; + receiver.quoteUnquoteSoCalledInsuranceForEcoBetIfItEvenCanBeSoCalled += amount; Storage.save(); // Notify both users. await channel.send(`<@${target.id}> has taken <@${author.id}>'s bet, the bet amount of ${$(amount).pluralise("Mon", "s")} has been deducted from each of them.`); - // Wait for the duration of the bet. + // Wait for the duration of the bet. client.setTimeout(async () => { + // In debug mode, saving the storage will break the references, so you have to redeclare sender and receiver for it to actually save. + const sender = Storage.getUser(author.id); + const receiver = Storage.getUser(target.id); // [TODO: when D.JSv13 comes out, inline reply to clean up.] // When bet is over, give a vote to ask people their thoughts. - const voteMsg = await channel.send(`VOTE: do you think that <@${target.id}> has won the bet?\nhttps://discord.com/channels/${guild.id}/${channel.id}/${message.id}`); + const voteMsg = await channel.send(`VOTE: do you think that <@${target.id}> has won the bet?\nhttps://discord.com/channels/${guild!.id}/${channel.id}/${message.id}`); await voteMsg.react("✅"); await voteMsg.react("❌"); @@ -115,9 +122,11 @@ export const BetCommand = new Command({ // [Pertinence to make configurable on the fly.] { time: parseDuration("2m") } ).then(reactions => { - // Count votes - const ok = reactions.filter(reaction => reaction.emoji.name === "✅").size; - const no = reactions.filter(reaction => reaction.emoji.name === "❌").size; + // Count votes + const okReaction = reactions.get("✅"); + const noReaction = reactions.get("❌"); + const ok = okReaction ? (okReaction.count ?? 1) - 1 : 0; + const no = noReaction ? (noReaction.count ?? 1) - 1 : 0; if (ok > no) { receiver.money += amount * 2; @@ -132,11 +141,13 @@ export const BetCommand = new Command({ receiver.money += amount; channel.send(`By the people's votes, <@${target.id}> couldn't be determined to have won or lost the bet that <@${author.id}> had sent them.`); } + sender.quoteUnquoteSoCalledInsuranceForEcoBetIfItEvenCanBeSoCalled -= amount; + receiver.quoteUnquoteSoCalledInsuranceForEcoBetIfItEvenCanBeSoCalled -= amount; Storage.save(); }); }, duration); } - else + else await channel.send(`<@${target.id}> has rejected your bet, <@${author.id}>`); } } diff --git a/src/core/structures.ts b/src/core/structures.ts index 37b2194..6a4aa94 100644 --- a/src/core/structures.ts +++ b/src/core/structures.ts @@ -26,6 +26,7 @@ class User { public lastMonday: number; public timezone: number | null; // This is for the standard timezone only, not the daylight savings timezone public daylightSavingsRegion: "na" | "eu" | "sh" | null; + public quoteUnquoteSoCalledInsuranceForEcoBetIfItEvenCanBeSoCalled: number; constructor(data?: GenericJSON) { this.money = select(data?.money, 0, Number); @@ -35,6 +36,7 @@ class User { this.daylightSavingsRegion = /^((na)|(eu)|(sh))$/.test(data?.daylightSavingsRegion) ? data?.daylightSavingsRegion : null; + this.quoteUnquoteSoCalledInsuranceForEcoBetIfItEvenCanBeSoCalled = select(data?.quoteUnquoteSoCalledInsuranceForEcoBetIfItEvenCanBeSoCalled, 0, Number); } } diff --git a/src/events/ready.ts b/src/events/ready.ts index e3fe021..561d33a 100644 --- a/src/events/ready.ts +++ b/src/events/ready.ts @@ -1,7 +1,7 @@ import Event from "../core/event"; import {client} from "../index"; import $ from "../core/lib"; -import {Config} from "../core/structures"; +import {Config, Storage} from "../core/structures"; import {updateGlobalEmoteRegistry} from "../core/lib"; export default new Event<"ready">({ @@ -14,5 +14,17 @@ export default new Event<"ready">({ }); } updateGlobalEmoteRegistry(); + + // Run this setup block once to restore eco bet money in case the bot went down. (And I guess search the client for those users to let them know too.) + for (const id in Storage.users) { + const user = Storage.users[id]; + + if(user.quoteUnquoteSoCalledInsuranceForEcoBetIfItEvenCanBeSoCalled > 0) { + client.users.cache.get(id)?.send(`Because my system either crashed or restarted while you had a pending bet, the total amount of money that you bet, which was \`${user.quoteUnquoteSoCalledInsuranceForEcoBetIfItEvenCanBeSoCalled}\`, has been restored.`); + user.money += user.quoteUnquoteSoCalledInsuranceForEcoBetIfItEvenCanBeSoCalled; + user.quoteUnquoteSoCalledInsuranceForEcoBetIfItEvenCanBeSoCalled = 0; + } + } + Storage.save(); } }); From dc33dbd180f80bed551d6f0ef0abed8d70cd1a89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=83=95=E3=82=BA=E3=82=AD?= Date: Tue, 6 Apr 2021 08:02:52 +0200 Subject: [PATCH 139/178] Rename insurance field --- src/commands/fun/subcommands/eco-bet.ts | 8 ++++---- src/core/structures.ts | 4 ++-- src/events/ready.ts | 8 ++++---- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/commands/fun/subcommands/eco-bet.ts b/src/commands/fun/subcommands/eco-bet.ts index 68087c2..ca8d455 100644 --- a/src/commands/fun/subcommands/eco-bet.ts +++ b/src/commands/fun/subcommands/eco-bet.ts @@ -96,8 +96,8 @@ export const BetCommand = new Command({ sender.money -= amount; receiver.money -= amount; // Very hacky solution for persistence but better than no solution, backup returns runs during the bot's setup code. - sender.quoteUnquoteSoCalledInsuranceForEcoBetIfItEvenCanBeSoCalled += amount; - receiver.quoteUnquoteSoCalledInsuranceForEcoBetIfItEvenCanBeSoCalled += amount; + sender.ecoBetInsurance += amount; + receiver.ecoBetInsurance += amount; Storage.save(); // Notify both users. @@ -141,8 +141,8 @@ export const BetCommand = new Command({ receiver.money += amount; channel.send(`By the people's votes, <@${target.id}> couldn't be determined to have won or lost the bet that <@${author.id}> had sent them.`); } - sender.quoteUnquoteSoCalledInsuranceForEcoBetIfItEvenCanBeSoCalled -= amount; - receiver.quoteUnquoteSoCalledInsuranceForEcoBetIfItEvenCanBeSoCalled -= amount; + sender.ecoBetInsurance -= amount; + receiver.ecoBetInsurance -= amount; Storage.save(); }); }, duration); diff --git a/src/core/structures.ts b/src/core/structures.ts index 6a4aa94..e302196 100644 --- a/src/core/structures.ts +++ b/src/core/structures.ts @@ -26,7 +26,7 @@ class User { public lastMonday: number; public timezone: number | null; // This is for the standard timezone only, not the daylight savings timezone public daylightSavingsRegion: "na" | "eu" | "sh" | null; - public quoteUnquoteSoCalledInsuranceForEcoBetIfItEvenCanBeSoCalled: number; + public ecoBetInsurance: number; constructor(data?: GenericJSON) { this.money = select(data?.money, 0, Number); @@ -36,7 +36,7 @@ class User { this.daylightSavingsRegion = /^((na)|(eu)|(sh))$/.test(data?.daylightSavingsRegion) ? data?.daylightSavingsRegion : null; - this.quoteUnquoteSoCalledInsuranceForEcoBetIfItEvenCanBeSoCalled = select(data?.quoteUnquoteSoCalledInsuranceForEcoBetIfItEvenCanBeSoCalled, 0, Number); + this.ecoBetInsurance = select(data?.ecoBetInsurance, 0, Number); } } diff --git a/src/events/ready.ts b/src/events/ready.ts index 561d33a..e74d3fe 100644 --- a/src/events/ready.ts +++ b/src/events/ready.ts @@ -19,10 +19,10 @@ export default new Event<"ready">({ for (const id in Storage.users) { const user = Storage.users[id]; - if(user.quoteUnquoteSoCalledInsuranceForEcoBetIfItEvenCanBeSoCalled > 0) { - client.users.cache.get(id)?.send(`Because my system either crashed or restarted while you had a pending bet, the total amount of money that you bet, which was \`${user.quoteUnquoteSoCalledInsuranceForEcoBetIfItEvenCanBeSoCalled}\`, has been restored.`); - user.money += user.quoteUnquoteSoCalledInsuranceForEcoBetIfItEvenCanBeSoCalled; - user.quoteUnquoteSoCalledInsuranceForEcoBetIfItEvenCanBeSoCalled = 0; + if(user.ecoBetInsurance > 0) { + client.users.cache.get(id)?.send(`Because my system either crashed or restarted while you had a pending bet, the total amount of money that you bet, which was \`${user.ecoBetInsurance}\`, has been restored.`); + user.money += user.ecoBetInsurance; + user.ecoBetInsurance = 0; } } Storage.save(); From db3320365711808c3bfb6427ea0f14db50e38316 Mon Sep 17 00:00:00 2001 From: WatDuhHekBro <44940783+WatDuhHekBro@users.noreply.github.com> Date: Tue, 6 Apr 2021 01:15:17 -0500 Subject: [PATCH 140/178] Cleaned up guild checks and return statements --- src/commands/fun/cookie.ts | 8 +- src/commands/fun/eco.ts | 1 + src/commands/fun/figlet.ts | 7 +- src/commands/fun/love.ts | 13 +-- src/commands/fun/modules/eco-core.ts | 1 + src/commands/fun/neko.ts | 9 +- src/commands/fun/weather.ts | 7 +- src/commands/fun/whois.ts | 32 +++--- src/commands/system/admin.ts | 128 +++++++++-------------- src/commands/utility/calc.ts | 10 +- src/commands/utility/desc.ts | 20 +--- src/commands/utility/info.ts | 146 ++++++++++++++------------- src/commands/utility/scanemotes.ts | 23 ++--- src/commands/utility/time.ts | 1 + 14 files changed, 174 insertions(+), 232 deletions(-) diff --git a/src/commands/fun/cookie.ts b/src/commands/fun/cookie.ts index 50b1b3d..69a624a 100644 --- a/src/commands/fun/cookie.ts +++ b/src/commands/fun/cookie.ts @@ -36,18 +36,16 @@ export default new NamedCommand({ } }) }, + id: "user", user: new Command({ description: "User to give cookie to.", async run({message, channel, guild, author, member, client, args}) { const sender = author; const mention: User = args[0]; - if (mention.id == sender.id) { - channel.send("You can't give yourself cookies!"); - return; - } + if (mention.id == sender.id) return channel.send("You can't give yourself cookies!"); - channel.send( + return channel.send( `:cookie: <@${sender.id}> ${parseVars(random(cookies), { target: mention.toString() })}` diff --git a/src/commands/fun/eco.ts b/src/commands/fun/eco.ts index 37c288e..da7d97a 100644 --- a/src/commands/fun/eco.ts +++ b/src/commands/fun/eco.ts @@ -18,6 +18,7 @@ export default new NamedCommand({ shop: ShopCommand, monday: MondayCommand }, + id: "user", user: new Command({ description: "See how much money someone else has by using their user ID or pinging them.", async run({guild, channel, args}) { diff --git a/src/commands/fun/figlet.ts b/src/commands/fun/figlet.ts index b78ba89..439ce00 100644 --- a/src/commands/fun/figlet.ts +++ b/src/commands/fun/figlet.ts @@ -5,11 +5,8 @@ export default new NamedCommand({ description: "Generates a figlet of your input.", async run({message, channel, guild, author, member, client, args}) { const input = args.join(" "); - if (!args[0]) { - channel.send("You have to provide input for me to create a figlet!"); - return; - } - channel.send( + if (!args[0]) return channel.send("You have to provide input for me to create a figlet!"); + return channel.send( "```" + figlet.textSync(`${input}`, { horizontalLayout: "full" diff --git a/src/commands/fun/love.ts b/src/commands/fun/love.ts index 94467c5..bf42d0d 100644 --- a/src/commands/fun/love.ts +++ b/src/commands/fun/love.ts @@ -1,13 +1,10 @@ -import {Command, NamedCommand} from "../../core"; +import {Command, NamedCommand, CHANNEL_TYPE} from "../../core"; export default new NamedCommand({ description: "Chooses someone to love.", - async run({message, channel, guild, author, member, client, args}) { - if (guild) { - const member = guild.members.cache.random(); - channel.send(`I love ${member.user.username}!`); - } else { - channel.send("You must use this command in a guild!"); - } + channelType: CHANNEL_TYPE.GUILD, + async run({message, channel, guild, author, client, args}) { + const member = guild!.members.cache.random(); + channel.send(`I love ${member.nickname ?? member.user.username}!`); } }); diff --git a/src/commands/fun/modules/eco-core.ts b/src/commands/fun/modules/eco-core.ts index b1966f2..6ed9628 100644 --- a/src/commands/fun/modules/eco-core.ts +++ b/src/commands/fun/modules/eco-core.ts @@ -112,6 +112,7 @@ export const PayCommand = new NamedCommand({ description: "Send money to someone.", usage: " ", run: "Who are you sending this money to?", + id: "user", user: new Command({ run: "You need to enter an amount you're sending!", number: new Command({ diff --git a/src/commands/fun/neko.ts b/src/commands/fun/neko.ts index 46d0bfc..18cfe76 100644 --- a/src/commands/fun/neko.ts +++ b/src/commands/fun/neko.ts @@ -45,15 +45,10 @@ export default new NamedCommand({ description: "Image type to send.", async run({message, channel, guild, author, member, client, args}) { const arg = args[0]; - - if (!(arg in endpoints.sfw)) { - channel.send("Couldn't find that endpoint!"); - return; - } - + if (!(arg in endpoints.sfw)) return channel.send("Couldn't find that endpoint!"); let url = new URL(`https://nekos.life/api/v2${endpoints.sfw[arg]}`); const content = await getContent(url.toString()); - channel.send(content.url); + return channel.send(content.url); } }) }); diff --git a/src/commands/fun/weather.ts b/src/commands/fun/weather.ts index d232ea3..590755f 100644 --- a/src/commands/fun/weather.ts +++ b/src/commands/fun/weather.ts @@ -6,11 +6,8 @@ const weather = require("weather-js"); export default new NamedCommand({ description: "Shows weather info of specified location.", async run({message, channel, guild, author, member, client, args}) { - if (args.length == 0) { - channel.send("You need to provide a city."); - return; - } - weather.find( + if (args.length == 0) return channel.send("You need to provide a city."); + return weather.find( { search: args.join(" "), degreeType: "C" diff --git a/src/commands/fun/whois.ts b/src/commands/fun/whois.ts index c8daff2..a65a064 100644 --- a/src/commands/fun/whois.ts +++ b/src/commands/fun/whois.ts @@ -1,5 +1,5 @@ import {User} from "discord.js"; -import {Command, NamedCommand, getMemberByUsername} from "../../core"; +import {Command, NamedCommand, getMemberByUsername, CHANNEL_TYPE} from "../../core"; // Quotes must be used here or the numbers will change const registry: {[id: string]: string} = { @@ -46,6 +46,7 @@ export default new NamedCommand({ channel.send("You haven't been added to the registry yet!"); } }, + id: "user", user: new Command({ async run({message, channel, guild, author, member, client, args}) { const user: User = args[0]; @@ -54,31 +55,28 @@ export default new NamedCommand({ if (id in registry) { channel.send(`\`${user.username}\` - ${registry[id]}`); } else { - channel.send(`\`${user.username}#${user.discriminator}\` hasn't been added to the registry yet!`); + channel.send(`\`${user.tag}\` hasn't been added to the registry yet!`); } } }), any: new Command({ - async run({message, channel, guild, author, member, client, args}) { - if (guild) { - const query: string = args.join(" "); - const member = await getMemberByUsername(guild, query); + channelType: CHANNEL_TYPE.GUILD, + async run({message, channel, guild, author, client, args}) { + const query = args.join(" ") as string; + const member = await getMemberByUsername(guild!, query); - if (member && member.id in registry) { - const id = member.id; + if (member && member.id in registry) { + const id = member.id; - if (id in registry) { - channel.send(`\`${member.user.username}\` - ${registry[member.id]}`); - } else { - channel.send(`\`${member.user.username}\` hasn't been added to the registry yet!`); - } + if (id in registry) { + channel.send(`\`${member.nickname ?? member.user.username}\` - ${registry[member.id]}`); } else { - channel.send(`Couldn't find a user by the name of \`${query}\`!`); + channel.send( + `\`${member.nickname ?? member.user.username}\` hasn't been added to the registry yet!` + ); } } else { - channel.send( - "You must run this in a guild! (*If you have the user's ID, you don't have to be in a guild.*)" - ); + channel.send(`Couldn't find a user by the name of \`${query}\`!`); } } }) diff --git a/src/commands/system/admin.ts b/src/commands/system/admin.ts index 7aeebc2..ac0e9e0 100644 --- a/src/commands/system/admin.ts +++ b/src/commands/system/admin.ts @@ -1,7 +1,7 @@ -import {Command, NamedCommand, botHasPermission, getPermissionLevel, getPermissionName} from "../../core"; +import {Command, NamedCommand, botHasPermission, getPermissionLevel, getPermissionName, CHANNEL_TYPE} from "../../core"; import {clean} from "../../lib"; import {Config, Storage} from "../../structures"; -import {Permissions} from "discord.js"; +import {Permissions, TextChannel} from "discord.js"; import {logs} from "../../modules/globals"; function getLogBuffer(type: string) { @@ -22,8 +22,6 @@ export default new NamedCommand({ description: "An all-in-one command to do admin stuff. You need to be either an admin of the server or one of the bot's mechanics to use this command.", async run({message, channel, guild, author, member, client, args}) { - if (!member) - return channel.send("Couldn't find a member object for you! Did you make sure you used this in a server?"); const permLevel = getPermissionLevel(author, member); return channel.send(`${author}, your permission level is \`${getPermissionName(permLevel)}\` (${permLevel}).`); }, @@ -32,12 +30,13 @@ export default new NamedCommand({ description: "Set different per-guild settings for the bot.", run: "You have to specify the option you want to set.", permission: PERMISSIONS.ADMIN, + channelType: CHANNEL_TYPE.GUILD, subcommands: { prefix: new NamedCommand({ description: "Set a custom prefix for your guild. Removes your custom prefix if none is provided.", usage: "()", async run({message, channel, guild, author, member, client, args}) { - Storage.getGuild(guild?.id || "N/A").prefix = null; + Storage.getGuild(guild!.id).prefix = null; Storage.save(); channel.send( `The custom prefix for this guild has been removed. My prefix is now back to \`${Config.prefix}\`.` @@ -45,7 +44,7 @@ export default new NamedCommand({ }, any: new Command({ async run({message, channel, guild, author, member, client, args}) { - Storage.getGuild(guild?.id || "N/A").prefix = args[0]; + Storage.getGuild(guild!.id).prefix = args[0]; Storage.save(); channel.send(`The custom prefix for this guild is now \`${args[0]}\`.`); } @@ -61,36 +60,24 @@ export default new NamedCommand({ "Sets how welcome messages are displayed for your server. Removes welcome messages if unspecified.", usage: "`none`/`text`/`graphical`", async run({message, channel, guild, author, member, client, args}) { - if (guild) { - Storage.getGuild(guild.id).welcomeType = "none"; - Storage.save(); - channel.send("Set this server's welcome type to `none`."); - } else { - channel.send("You must use this command in a server."); - } + Storage.getGuild(guild!.id).welcomeType = "none"; + Storage.save(); + channel.send("Set this server's welcome type to `none`."); }, // I should probably make this a bit more dynamic... Oh well. subcommands: { text: new NamedCommand({ async run({message, channel, guild, author, member, client, args}) { - if (guild) { - Storage.getGuild(guild.id).welcomeType = "text"; - Storage.save(); - channel.send("Set this server's welcome type to `text`."); - } else { - channel.send("You must use this command in a server."); - } + Storage.getGuild(guild!.id).welcomeType = "text"; + Storage.save(); + channel.send("Set this server's welcome type to `text`."); } }), graphical: new NamedCommand({ async run({message, channel, guild, author, member, client, args}) { - if (guild) { - Storage.getGuild(guild.id).welcomeType = "graphical"; - Storage.save(); - channel.send("Set this server's welcome type to `graphical`."); - } else { - channel.send("You must use this command in a server."); - } + Storage.getGuild(guild!.id).welcomeType = "graphical"; + Storage.save(); + channel.send("Set this server's welcome type to `graphical`."); } }) } @@ -99,34 +86,17 @@ export default new NamedCommand({ description: "Sets the welcome channel for your server. Type `#` to reference the channel.", usage: "()", async run({message, channel, guild, author, member, client, args}) { - if (guild) { - Storage.getGuild(guild.id).welcomeChannel = channel.id; - Storage.save(); - channel.send(`Successfully set ${channel} as the welcome channel for this server.`); - } else { - channel.send("You must use this command in a server."); - } + Storage.getGuild(guild!.id).welcomeChannel = channel.id; + Storage.save(); + channel.send(`Successfully set ${channel} as the welcome channel for this server.`); }, - // If/when channel types come out, this will be the perfect candidate to test it. - any: new Command({ + id: "channel", + channel: new Command({ async run({message, channel, guild, author, member, client, args}) { - if (guild) { - const match = args[0].match(/^<#(\d{17,19})>$/); - - if (match) { - Storage.getGuild(guild.id).welcomeChannel = match[1]; - Storage.save(); - channel.send( - `Successfully set this server's welcome channel to ${match[0]}.` - ); - } else { - channel.send( - "You must provide a reference channel. You can do this by typing `#` then searching for the proper channel." - ); - } - } else { - channel.send("You must use this command in a server."); - } + const result = args[0] as TextChannel; + Storage.getGuild(guild!.id).welcomeChannel = result.id; + Storage.save(); + channel.send(`Successfully set this server's welcome channel to ${result}.`); } }) }), @@ -135,24 +105,16 @@ export default new NamedCommand({ "Sets a custom welcome message for your server. Use `%user%` as the placeholder for the user.", usage: "()", async run({message, channel, guild, author, member, client, args}) { - if (guild) { - Storage.getGuild(guild.id).welcomeMessage = null; - Storage.save(); - channel.send("Reset your server's welcome message to the default."); - } else { - channel.send("You must use this command in a server."); - } + Storage.getGuild(guild!.id).welcomeMessage = null; + Storage.save(); + channel.send("Reset your server's welcome message to the default."); }, any: new Command({ async run({message, channel, guild, author, member, client, args}) { - if (guild) { - const message = args.join(" "); - Storage.getGuild(guild.id).welcomeMessage = message; - Storage.save(); - channel.send(`Set your server's welcome message to \`${message}\`.`); - } else { - channel.send("You must use this command in a server."); - } + const newMessage = args.join(" "); + Storage.getGuild(guild!.id).welcomeMessage = newMessage; + Storage.save(); + channel.send(`Set your server's welcome message to \`${newMessage}\`.`); } }) }) @@ -202,9 +164,10 @@ export default new NamedCommand({ purge: new NamedCommand({ description: "Purges the bot's own messages.", permission: PERMISSIONS.BOT_SUPPORT, + channelType: CHANNEL_TYPE.GUILD, async run({message, channel, guild, author, member, client, args}) { // It's probably better to go through the bot's own messages instead of calling bulkDelete which requires MANAGE_MESSAGES. - if (botHasPermission(guild, Permissions.FLAGS.MANAGE_MESSAGES) && channel.type !== "dm") { + if (botHasPermission(guild, Permissions.FLAGS.MANAGE_MESSAGES)) { message.delete(); const msgs = await channel.messages.fetch({ limit: 100 @@ -216,7 +179,7 @@ export default new NamedCommand({ timeout: 5000 }) ); - await channel.bulkDelete(travMessages); + await (channel as TextChannel).bulkDelete(travMessages); } else { channel.send( "This command must be executed in a guild where I have the `MANAGE_MESSAGES` permission." @@ -227,17 +190,16 @@ export default new NamedCommand({ clear: new NamedCommand({ description: "Clears a given amount of messages.", usage: "", + channelType: CHANNEL_TYPE.GUILD, run: "A number was not provided.", number: new Command({ description: "Amount of messages to delete.", async run({message, channel, guild, author, member, client, args}) { - if (channel.type === "dm") return channel.send("Can't clear messages in the DMs!"); message.delete(); const fetched = await channel.messages.fetch({ limit: args[0] }); - await channel.bulkDelete(fetched); - return; + return await (channel as TextChannel).bulkDelete(fetched); } }) }), @@ -261,9 +223,10 @@ export default new NamedCommand({ nick: new NamedCommand({ description: "Change the bot's nickname.", permission: PERMISSIONS.BOT_SUPPORT, + channelType: CHANNEL_TYPE.GUILD, async run({message, channel, guild, author, member, client, args}) { const nickName = args.join(" "); - await guild?.me?.setNickname(nickName); + await guild!.me?.setNickname(nickName); if (botHasPermission(guild, Permissions.FLAGS.MANAGE_MESSAGES)) message.delete({timeout: 5000}); channel.send(`Nickname set to \`${nickName}\``).then((m) => m.delete({timeout: 5000})); } @@ -308,15 +271,20 @@ export default new NamedCommand({ syslog: new NamedCommand({ description: "Sets up the current channel to receive system logs.", permission: PERMISSIONS.BOT_ADMIN, + channelType: CHANNEL_TYPE.GUILD, async run({message, channel, guild, author, member, client, args}) { - if (guild) { - Config.systemLogsChannel = channel.id; + Config.systemLogsChannel = channel.id; + Config.save(); + channel.send(`Successfully set ${channel} as the system logs channel.`); + }, + channel: new Command({ + async run({message, channel, guild, author, member, client, args}) { + const targetChannel = args[0] as TextChannel; + Config.systemLogsChannel = targetChannel.id; Config.save(); - channel.send(`Successfully set ${channel} as the system logs channel.`); - } else { - channel.send("DM system log channels aren't supported."); + channel.send(`Successfully set ${targetChannel} as the system logs channel.`); } - } + }) }) } }); diff --git a/src/commands/utility/calc.ts b/src/commands/utility/calc.ts index 2303351..dad3e88 100644 --- a/src/commands/utility/calc.ts +++ b/src/commands/utility/calc.ts @@ -5,22 +5,18 @@ import {MessageEmbed} from "discord.js"; export default new NamedCommand({ description: "Calculates a specified math expression.", async run({message, channel, guild, author, member, client, args}) { - if (!args[0]) { - channel.send("Please provide a calculation."); - return; - } + if (!args[0]) return channel.send("Please provide a calculation."); let resp; try { resp = math.evaluate(args.join(" ")); } catch (e) { - channel.send("Please provide a *valid* calculation."); - return; + return channel.send("Please provide a *valid* calculation."); } const embed = new MessageEmbed() .setColor(0xffffff) .setTitle("Math Calculation") .addField("Input", `\`\`\`js\n${args.join("")}\`\`\``) .addField("Output", `\`\`\`js\n${resp}\`\`\``); - channel.send(embed); + return channel.send(embed); } }); diff --git a/src/commands/utility/desc.ts b/src/commands/utility/desc.ts index ed0b87c..b3c3d57 100644 --- a/src/commands/utility/desc.ts +++ b/src/commands/utility/desc.ts @@ -6,24 +6,14 @@ export default new NamedCommand({ async run({message, channel, guild, author, member, client, args}) { const voiceChannel = message.member?.voice.channel; - if (!voiceChannel) { - channel.send("You are not in a voice channel."); - return; - } - - if (!voiceChannel.guild.me?.hasPermission("MANAGE_CHANNELS")) { - channel.send("I am lacking the required permissions to perform this action."); - return; - } - - if (args.length === 0) { - channel.send("Please provide a new voice channel name."); - return; - } + if (!voiceChannel) return channel.send("You are not in a voice channel."); + if (!voiceChannel.guild.me?.hasPermission("MANAGE_CHANNELS")) + return channel.send("I am lacking the required permissions to perform this action."); + if (args.length === 0) return channel.send("Please provide a new voice channel name."); const prevName = voiceChannel.name; const newName = args.join(" "); await voiceChannel.setName(newName); - await channel.send(`Changed channel name from "${prevName}" to "${newName}".`); + return await channel.send(`Changed channel name from "${prevName}" to "${newName}".`); } }); diff --git a/src/commands/utility/info.ts b/src/commands/utility/info.ts index 68b4d35..7006a0e 100644 --- a/src/commands/utility/info.ts +++ b/src/commands/utility/info.ts @@ -1,14 +1,16 @@ -import {MessageEmbed, version as djsversion, Guild} from "discord.js"; +import {MessageEmbed, version as djsversion, Guild, User, GuildMember} from "discord.js"; import ms from "ms"; import os from "os"; -import {Command, NamedCommand, getMemberByUsername} from "../../core"; +import {Command, NamedCommand, getMemberByUsername, CHANNEL_TYPE} from "../../core"; import {formatBytes, trimArray} from "../../lib"; import {verificationLevels, filterLevels, regions} from "../../defs/info"; import moment, {utc} from "moment"; export default new NamedCommand({ description: "Command to provide all sorts of info about the current server, a user, etc.", - run: "Please provide an argument.\nFor help, run `%prefix%help info`.", + async run({message, channel, guild, author, member, client, args}) { + channel.send(await getUserInfo(author, member)); + }, subcommands: { avatar: new NamedCommand({ description: "Shows your own, or another user's avatar.", @@ -16,6 +18,7 @@ export default new NamedCommand({ async run({message, channel, guild, author, member, client, args}) { channel.send(author.displayAvatarURL({dynamic: true, size: 2048})); }, + id: "user", user: new Command({ description: "Shows your own, or another user's avatar.", async run({message, channel, guild, author, member, client, args}) { @@ -29,21 +32,20 @@ export default new NamedCommand({ }), any: new Command({ description: "Shows another user's avatar by searching their name", - async run({message, channel, guild, author, member, client, args}) { - if (guild) { - const name = args.join(" "); - const member = await getMemberByUsername(guild, name); + channelType: CHANNEL_TYPE.GUILD, + async run({message, channel, guild, author, client, args}) { + const name = args.join(" "); + const member = await getMemberByUsername(guild!, name); - if (member) { - channel.send( - member.user.displayAvatarURL({ - dynamic: true, - size: 2048 - }) - ); - } else { - channel.send(`No user found by the name \`${name}\`!`); - } + if (member) { + channel.send( + member.user.displayAvatarURL({ + dynamic: true, + size: 2048 + }) + ); + } else { + channel.send(`No user found by the name \`${name}\`!`); } } }) @@ -92,12 +94,9 @@ export default new NamedCommand({ guild: new NamedCommand({ description: "Displays info about the current guild or another guild.", usage: "(/)", + channelType: CHANNEL_TYPE.GUILD, async run({message, channel, guild, author, member, client, args}) { - if (guild) { - channel.send(await getGuildInfo(guild, guild)); - } else { - channel.send("Please execute this command in a guild."); - } + channel.send(await getGuildInfo(guild!, guild)); }, any: new Command({ description: "Display info about a guild by finding its name or ID.", @@ -105,19 +104,21 @@ export default new NamedCommand({ // If a guild ID is provided (avoid the "number" subcommand because of inaccuracies), search for that guild if (args.length === 1 && /^\d{17,19}$/.test(args[0])) { const id = args[0]; - const guild = client.guilds.cache.get(id); + const targetGuild = client.guilds.cache.get(id); - if (guild) { - channel.send(await getGuildInfo(guild, guild)); + if (targetGuild) { + channel.send(await getGuildInfo(targetGuild, guild)); } else { channel.send(`None of the servers I'm in matches the guild ID \`${id}\`!`); } } else { const query: string = args.join(" ").toLowerCase(); - const guild = client.guilds.cache.find((guild) => guild.name.toLowerCase().includes(query)); + const targetGuild = client.guilds.cache.find((guild) => + guild.name.toLowerCase().includes(query) + ); - if (guild) { - channel.send(await getGuildInfo(guild, guild)); + if (targetGuild) { + channel.send(await getGuildInfo(targetGuild, guild)); } else { channel.send(`None of the servers I'm in matches the query \`${query}\`!`); } @@ -126,55 +127,62 @@ export default new NamedCommand({ }) }) }, + id: "user", user: new Command({ description: "Displays info about mentioned user.", async run({message, channel, guild, author, client, args}) { + const user = args[0] as User; // Transforms the User object into a GuildMember object of the current guild. - const member = await guild?.members.fetch(args[0]); - - if (!member) { - channel.send( - "No member object was found by that user! Are you sure you used this command in a server?" - ); - return; - } - - const roles = member.roles.cache - .sort((a: {position: number}, b: {position: number}) => b.position - a.position) - .map((role: {toString: () => any}) => role.toString()) - .slice(0, -1); - const userFlags = (await member.user.fetchFlags()).toArray(); - - const embed = new MessageEmbed() - .setThumbnail(member.user.displayAvatarURL({dynamic: true, size: 512})) - .setColor(member.displayHexColor || "BLUE") - .addField("User", [ - `**❯ Username:** ${member.user.username}`, - `**❯ Discriminator:** ${member.user.discriminator}`, - `**❯ ID:** ${member.id}`, - `**❯ Flags:** ${userFlags.length ? userFlags.join(", ") : "None"}`, - `**❯ Avatar:** [Link to avatar](${member.user.displayAvatarURL({ - dynamic: true - })})`, - `**❯ Time Created:** ${moment(member.user.createdTimestamp).format("LT")} ${moment( - member.user.createdTimestamp - ).format("LL")} ${moment(member.user.createdTimestamp).fromNow()}`, - `**❯ Status:** ${member.user.presence.status}`, - `**❯ Game:** ${member.user.presence.activities || "Not playing a game."}` - ]) - .addField("Member", [ - `**❯ Highest Role:** ${member.roles.highest.id === guild?.id ? "None" : member.roles.highest.name}`, - `**❯ Server Join Date:** ${moment(member.joinedAt).format("LL LTS")}`, - `**❯ Hoist Role:** ${member.roles.hoist ? member.roles.hoist.name : "None"}`, - `**❯ Roles:** [${roles.length}]: ${ - roles.length == 0 ? "None" : roles.length <= 10 ? roles.join(", ") : trimArray(roles).join(", ") - }` - ]); - channel.send(embed); + const member = guild?.members.resolve(args[0]); + channel.send(await getUserInfo(user, member)); } }) }); +async function getUserInfo(user: User, member: GuildMember | null | undefined): Promise { + const userFlags = (await user.fetchFlags()).toArray(); + + const embed = new MessageEmbed() + .setThumbnail(user.displayAvatarURL({dynamic: true, size: 512})) + .setColor("BLUE") + .addField("User", [ + `**❯ Username:** ${user.username}`, + `**❯ Discriminator:** ${user.discriminator}`, + `**❯ ID:** ${user.id}`, + `**❯ Flags:** ${userFlags.length ? userFlags.join(", ") : "None"}`, + `**❯ Avatar:** [Link to avatar](${user.displayAvatarURL({ + dynamic: true + })})`, + `**❯ Time Created:** ${moment(user.createdTimestamp).format("LT")} ${moment(user.createdTimestamp).format( + "LL" + )} ${moment(user.createdTimestamp).fromNow()}`, + `**❯ Status:** ${user.presence.status}`, + `**❯ Game:** ${user.presence.activities || "Not playing a game."}` + ]); + + if (member) { + const roles = member.roles.cache + .sort((a: {position: number}, b: {position: number}) => b.position - a.position) + .map((role: {toString: () => any}) => role.toString()) + .slice(0, -1); + + embed + .setColor(member.displayHexColor) + .addField("Member", [ + `**❯ Highest Role:** ${ + member.roles.highest.id === member.guild.id ? "None" : member.roles.highest.name + }`, + `**❯ Server Join Date:** ${moment(member.joinedAt).format("LL LTS")}`, + `**❯ Hoist Role:** ${member.roles.hoist ? member.roles.hoist.name : "None"}`, + `**❯ Roles:** [${roles.length}]: ${ + roles.length == 0 ? "None" : roles.length <= 10 ? roles.join(", ") : trimArray(roles).join(", ") + }` + ]); + } + + return embed; +} + async function getGuildInfo(guild: Guild, currentGuild: Guild | null) { const members = await guild.members.fetch({ withPresences: true, diff --git a/src/commands/utility/scanemotes.ts b/src/commands/utility/scanemotes.ts index 41d5115..a60d861 100644 --- a/src/commands/utility/scanemotes.ts +++ b/src/commands/utility/scanemotes.ts @@ -1,4 +1,4 @@ -import {Command, NamedCommand} from "../../core"; +import {Command, NamedCommand, CHANNEL_TYPE} from "../../core"; import {pluralise} from "../../lib"; import moment from "moment"; import {Collection, TextChannel} from "discord.js"; @@ -8,26 +8,21 @@ const lastUsedTimestamps: {[id: string]: number} = {}; export default new NamedCommand({ description: "Scans all text channels in the current guild and returns the number of times each emoji specific to the guild has been used. Has a cooldown of 24 hours per guild.", + channelType: CHANNEL_TYPE.GUILD, async run({message, channel, guild, author, member, client, args}) { - if (!guild) { - channel.send(`You must use this command on a server!`); - return; - } - // Test if the command is on cooldown. This isn't the strictest cooldown possible, because in the event that the bot crashes, the cooldown will be reset. But for all intends and purposes, it's a good enough cooldown. It's a per-server cooldown. const startTime = Date.now(); const cooldown = 86400000; // 24 hours - const lastUsedTimestamp = lastUsedTimestamps[guild.id] ?? 0; + const lastUsedTimestamp = lastUsedTimestamps[guild!.id] ?? 0; const difference = startTime - lastUsedTimestamp; const howLong = moment(startTime).to(lastUsedTimestamp + cooldown); // If it's been less than an hour since the command was last used, prevent it from executing. - if (difference < cooldown) { - channel.send( + if (difference < cooldown) + return channel.send( `This command requires a day to cooldown. You'll be able to activate this command ${howLong}.` ); - return; - } else lastUsedTimestamps[guild.id] = startTime; + else lastUsedTimestamps[guild!.id] = startTime; const stats: { [id: string]: { @@ -39,7 +34,7 @@ export default new NamedCommand({ } = {}; let totalUserEmoteUsage = 0; // IMPORTANT: You MUST check if the bot actually has access to the channel in the first place. It will get the list of all channels, but that doesn't mean it has access to every channel. Without this, it'll require admin access and throw an annoying unhelpful DiscordAPIError: Missing Access otherwise. - const allTextChannelsInCurrentGuild = guild.channels.cache.filter( + const allTextChannelsInCurrentGuild = guild!.channels.cache.filter( (channel) => channel.type === "text" && channel.viewable ) as Collection; let messagesSearched = 0; @@ -52,7 +47,7 @@ export default new NamedCommand({ // Initialize the emote stats object with every emote in the current guild. // The goal here is to cut the need to access guild.emojis.get() which'll make it faster and easier to work with. - for (let emote of guild.emojis.cache.values()) { + for (let emote of guild!.emojis.cache.values()) { // If you don't include the "a" for animated emotes, it'll not only not show up, but also cause all other emotes in the same message to not show up. The emote name is self-correcting but it's better to keep the right value since it'll be used to calculate message lengths that fit. stats[emote.id] = { name: emote.name, @@ -186,6 +181,6 @@ export default new NamedCommand({ ); } - await channel.send(lines, {split: true}); + return await channel.send(lines, {split: true}); } }); diff --git a/src/commands/utility/time.ts b/src/commands/utility/time.ts index c51552a..23250ba 100644 --- a/src/commands/utility/time.ts +++ b/src/commands/utility/time.ts @@ -374,6 +374,7 @@ export default new NamedCommand({ run: DST_NOTE_INFO }) }, + id: "user", user: new Command({ description: "See what time it is for someone else.", async run({channel, args}) { From fd136dc34f57ad5c483ccbe77d06c9877553de9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=83=95=E3=82=BA=E3=82=AD?= Date: Tue, 6 Apr 2021 08:39:11 +0200 Subject: [PATCH 141/178] Finally added description fields --- src/commands/fun/subcommands/eco-bet.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/commands/fun/subcommands/eco-bet.ts b/src/commands/fun/subcommands/eco-bet.ts index ca8d455..01c6c96 100644 --- a/src/commands/fun/subcommands/eco-bet.ts +++ b/src/commands/fun/subcommands/eco-bet.ts @@ -5,10 +5,11 @@ import {isAuthorized, getMoneyEmbed, getSendEmbed, ECO_EMBED_COLOR} from "./eco- import {User} from "discord.js"; export const BetCommand = new Command({ - description: "Bet your Mons with other people [TBD]", + description: "Bet your Mons with other people.", usage: " ", run: "Who are you betting with?", user: new Command({ + description: "User to bet with.", // handles missing amount argument async run({args, author, channel, guild}): Promise { if (isAuthorized(guild, channel)) { @@ -24,6 +25,7 @@ export const BetCommand = new Command({ } }, number: new Command({ + description: "Amount of Mons to bet.", // handles missing duration argument async run({args, author, channel, guild}): Promise { if (isAuthorized(guild, channel)) { @@ -50,6 +52,7 @@ export const BetCommand = new Command({ } }, any: new Command({ + description: "Duration of the bet.", async run({client, args, author, message, channel, guild, askYesOrNo}): Promise { if (isAuthorized(guild, channel)) { // [Pertinence to make configurable on the fly.] From cd9aaa5f4b2cc331cb22b8f0ed6efb143e642911 Mon Sep 17 00:00:00 2001 From: WatDuhHekBro <44940783+WatDuhHekBro@users.noreply.github.com> Date: Tue, 6 Apr 2021 01:48:17 -0500 Subject: [PATCH 142/178] Added comma --- src/commands/fun/whois.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands/fun/whois.ts b/src/commands/fun/whois.ts index 86ba4a0..cee90c0 100644 --- a/src/commands/fun/whois.ts +++ b/src/commands/fun/whois.ts @@ -31,7 +31,7 @@ const registry: {[id: string]: string} = { "440399719076855818": "You are, uhh, Stay Put, Soft Puppy, Es-Pee, Swift Pacemaker, Smug Poyo, and many more.\n...Seriously, this woman has too many names.", "243061915281129472": - "Some random conlanger, worldbuilder and programmer doofus. ~~May also secretly be a nyan. :3~~" + "Some random conlanger, worldbuilder and programmer doofus. ~~May also secretly be a nyan. :3~~", "367439475153829892": "A weeb.", "760375501775700038": "˙qǝǝʍ ∀", "389178357302034442": "In his dreams, he is the star. its him. <:itsMe:808174425253871657>", From 728434251448b10b363b57654c2c0432383184e6 Mon Sep 17 00:00:00 2001 From: WatDuhHekBro <44940783+WatDuhHekBro@users.noreply.github.com> Date: Tue, 6 Apr 2021 08:19:41 -0500 Subject: [PATCH 143/178] Began revising documentation and copied changelog --- .prettierignore | 1 + CHANGELOG.md | 109 +++++++++++++++++++ README.md | 41 +++++-- docs/CHANGELOG.md | 0 docs/DesignDecisions.md | 1 + docs/Documentation.md | 202 ++--------------------------------- docs/GettingStarted.md | 20 ---- docs/Overview.md | 230 ++++++++++++++++++++++++++++++++++++++++ docs/Specifications.md | 48 --------- package-lock.json | 4 +- package.json | 28 ++--- 11 files changed, 400 insertions(+), 284 deletions(-) create mode 100644 CHANGELOG.md delete mode 100644 docs/CHANGELOG.md create mode 100644 docs/DesignDecisions.md delete mode 100644 docs/GettingStarted.md create mode 100644 docs/Overview.md delete mode 100644 docs/Specifications.md diff --git a/.prettierignore b/.prettierignore index 40343cc..28e6c5e 100644 --- a/.prettierignore +++ b/.prettierignore @@ -10,6 +10,7 @@ LICENSE dist/ data/ docs/ +*.md tmp/ test* !test/ diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..f6bdb65 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,109 @@ +# 2.8.4 - Reworked the react command +- `react` is now a fully versatile command for helping you react to other messages with non-server emotes. + - Now properly reacts to the previous message (bug fix). + - Provides you the option to react to any number of messages before your message (3 messages above yours for example). + - Renamed guild ID to message ID for clarity's sake. + - Now removes the bot's own reaction after a few seconds to make the reaction count more accurate. + - Now lets you react with multiple messages in a row. + - Now reacts with ❓ if no reactions were found at all (see below). +- `emote`: + - Is now case-sensitive again (because there are too many name conflicts). + - Accepts multiple emotes for tiled emotes. + - Now reacts to your message with ❓ instead of `None of those emote names were valid!` so that the bot doesn't spam the chat if you can't find the right emote (because you'll still be able to delete your messages). +- `thonk` now stores the last specified phrase so you can repeat a phrase with different diacritics. + +# 2.8.3 - The ultimate meme + +# 2.8.2 +- Added a changelog. +- Added an extra instruction to the readme's installation. +- Made commands utilize the existing `Array.random()` function. +- Removed concatenation when using template strings. +- Added `Number.pluralise()` for convenient pluralization. +- Reworked the `neko` command. +- Made `whoami` sync up with `whois` by using the same config. +- Fixed a bug with `emote` where it wouldn't find any upper case emotes and made it more lenient to just include any emote (so you don't have to remember the exact emote name). +- Moved lists and gathering shop items outside of `exports.run()` so that it initializes once during the bot's initialization (or when reloaded) rather than every time the command is called. + +# 2.8.1 - Modularized eco shop and eco buy +- Fixed scanemotes sometimes not displaying all emotes. This was an issue of not accounting for whether an emote was animated or not. +- Modularized `eco shop` and `eco buy`. Shop items are now in the `shop` subfolder and `eco shop` now separates every 5 shop items into separate pages automatically. + +# 2.8.0 - Added graphical welcome setting +- Adds a new option to the `set` and `conf` commands, allowing you to enable an image being sent as a welcome. + +# 2.7.1 - Added eco buy laser bridge and reworked scanemotes +- `eco buy laser bridge` - Added a shop item. Buy what is technically a laser bridge. Costs 3 Mons. +- `insult` - Now pings the user who activated it. +- `scanemotes` - Reworked the command after a test run in a big server. + - Merged the unsorted and sorted emote listings into one section. The unsorted emotes pretty much had a random order as it was pretty much which emote was added first as the search went on, so nothing's gone there. `#1 :emote: x 20 - 30.532% (Bots: 132)` + - Bumped the cooldown from 1 hour to 1 day. + - An updated progress meter which now stays on a single channel at a time because it's no longer asynchronous. This progress bar also works with Discord's rate limits. `Searching channel ___... (___ messages scanned, x/y channels scanned)` + - Now includes all emotes in a server, even if they haven't been used. + - Now properly counts emote usage for reactions (whether or not a bot reacted to a message) + +# 2.7.0 - Added percentages to scanemotes +## Major Changes +- Added an hour long cooldown to `scanemotes` per server because it's a very memory-intensive task to search through every single message. +- Added a second list of emotes to `scanemotes`, sorting by percentage of users-only usage. +- Added a function to the client's common functions to generate a page users can turn. +## Minor Changes +- `avatar` + - Now has proper error handling when searching by mention and ID. + - No longer pings the user, it just sends the image link by itself. +- `eco` + - Merges `sender.id + message.guild.id` into `compositeID` since it's so frequently used. + - Bug Fix: If you have exactly 1 Mon and you pay someone 1 Mon, they'll get 1 more Mon and you'll still have 1 Mon because the 0 coerces to false resetting your money, because JS soft comparison. Fixed by using the "in" operator instead. + - Uses else ifs to make the command marginally faster. + - Now properly handles mentions and extracting the user ID from them in the `pay` subcommand. + - Added a message that occurs when the user tries to buy an item that doesn't exist. +- Added an `insult` command which will have the bot type out the navy seals copypasta for a minute. +- Modified the `invite` command to auto-generate a link based on the current bot client ID rather than having it be hardcoded to TravBot specifically. +- Added error checking to `scanemotes` so users aren't left in the dark if something happens. +- On big servers, `scanemotes` should now have emotes actually show up. + +# 2.6.1 - Hotfix: Scan emotes no longer requires admin +- Fixed the `scanemotes` command to no longer require admin permissions. This was due to an oversight: There can be channels which the bot doesn't have access to, ie private channels. You have to check if the bot has access to a channel because the filter will gather all text-based channels regardless. Admin permissions overrides all restrictions, which is why it only worked with admin permissions. +- Entering a username in the `avatar` command unsuccessfully will now send a message in chat. + +# 2.6.0 - Added the ability to get other users' avatars and see emote usage +- You can now scan the current guild for emote usage, collecting all emotes used in messages and reactions. (example below) +![2020-06-19 04_08_22-Window](https://user-images.githubusercontent.com/44940783/85116219-98a69280-b1e2-11ea-9246-b8f5ff2537ea.png) +- You can now get other avatars by providing an ID (works even when the bot doesn't share the same server as that user), username, or by pinging them. +- Included the fix for `serverinfo`. + +# 2.5.3 +- Changed default prefix for setup. +- Enhanced `react` command. New optional guildID arg. +- Added message logger. +- Fixed calc permission error. +- Removed `delete` command. +- Added ignored and notified channels to logger. +- Added images to logger. Added author to logger. +- Emote command is now not case-sensitive. + +# 2.5.2 - Bug fixes to the "eco" command +- Now prevents users from sending negative amounts of money to others (minimum of 1 Mon). +- Also prevents users from sending decimal amounts. +- Fixes a potentially wrong substring for user IDs. +- Now requires an argument when using the "desc" command. + +# 2.5.1 +- Added shop functionality to eco. +- Fixed faulty guild check. +- Attempt at fixing emote for eco cute. +- Pluralised "mon" for eco handhold. +- Added `translate` command. + +# 2.5.0 - Added the "pay" sub-command to "eco" + +# 2.4.1 +- Added Procfile. +- Updated whoami's keys. +- Rewrote `desc` command. + +# 2.4.0 - Implemented music system +- VC Rename command +- Travis CI configuration +- Music system +- Dependency updates diff --git a/README.md b/README.md index b099c29..094666e 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,24 @@ # TravBot-v3 -Fourth revision of TravBot, version number 3.0.0. +

+ +

+

+ + License + + + Version + + + Version (Dev) + + + Discord.js Version + +

+ +Fourth revision of [TravBot](https://github.com/keanuplayz/TravBot), version number 3.0.0. This is the repo belonging to the code of TravBot v3. @@ -8,14 +26,19 @@ This version will be the final revision of TravBot, this being the final structu Thank you for coming on this journey with me, but it is time to put the big changes to an end. +## Installation + +1. `npm install` +2. `npm run build` +3. `npm start` + +## Contributing + +To get information on how to contribute to this project, see the [overview](docs/Overview.md) as well as other files in the `docs` folder meant for developers. + ## Special Thanks Special thanks to: - -- Lexi Sother (TravBot v2, structural overhaul. Reviewing PRs.) -- WatDuhHekBro (a _lot_ of contributions to TravBot v2) -- Zeehondie (Ideas for various commands.) - -### License - -Refer to the [LICENSE](https://github.com/keanuplayz/TravBot-v3/tree/master/LICENSE) file. +- Lexi Sother (TravBot v2, structural overhaul. Reviewing PRs.) +- WatDuhHekBro (a *lot* of contributions to TravBot v2) +- Zeehondie (Ideas for various commands.) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md deleted file mode 100644 index e69de29..0000000 diff --git a/docs/DesignDecisions.md b/docs/DesignDecisions.md new file mode 100644 index 0000000..27306bf --- /dev/null +++ b/docs/DesignDecisions.md @@ -0,0 +1 @@ +Coming Soon™ diff --git a/docs/Documentation.md b/docs/Documentation.md index c720942..cf91fb2 100644 --- a/docs/Documentation.md +++ b/docs/Documentation.md @@ -1,199 +1,19 @@ -# What this is +# Table of Contents -This is a user-friendly version of the project's structure (compared to the amalgamation that has become [specifications](Specifications.md) which is just a list of design decisions and isn't actually helpful at all for understanding the code). This will follow the line of logic that the program would run through. - -# Building/Setup - -- `npm run dev` runs the TypeScript compiler in watch mode, meaning that any changes you make to the code will automatically reload the bot. -- This will take all the files in `src` (where all the code is) and compile it into `dist` which is what the program actually uses. - - If there's a runtime error, `dist\commands\test.js:25:30` for example, then you have to into `dist` instead of `src`, then find the line that corresponds. - -# Launching - -When you start the program, it'll run the code in `index` (meaning both `src/index.ts` and `dist/index.js`, they're the same except that `dist/<...>.js` is compiled). The code in `index` will call `setup` and check if `data/config.json` exists, prompting you if it doesn't. It'll then run initialization code. +... # Structure -- `commands` contains all the commands. -- `defs` contains static definitions. -- `core` contains the foundation of the program. You won't need to worry about this unless you're modifying pre-existing behavior of the `Command` class for example or add a function to the library. -- `events` contains all the events. Again, you generally won't need to touch this unless you're listening for a new Discord event. +- `src`: Contains all the code for the bot itself. Code directly in this folder is for the starting index file as well as commonly accessed utility files. + - `core`: This is currently where the command handler is. Try to keep it as isolated as possible, it might split off to become its own module. + - `commands`: Where all the dynamically loaded commands are stored. You can use a subfolder to specify the command category. Specify a `modules` folder to create files that are ignored by the command loader. + - `modules`: This is where mostly single-purpose blocks of code go. (This is **not** the same as a `modules` folder under `commands`.) + - `defs`: Contains static definitions. +- `dist`: This is where the runnable code in `src` compiles to. (The directory structure mirrors `src`.) +- `data`: Holds all the dynamic/private data used by the bot. This folder is not meant to hold definitions. +- `docs`: Information for developers who want to contribute. -# The Command Class - -A valid command file must be located in `commands` and export a default `Command` instance. Assume that we're working with `commands/money.ts`. - -```js -import Command from '../core/command'; - -export default new Command({ - //... -}); -``` - -The `run` property can be either a function or a string. If it's a function, you get one parameter, `$` which represents the common library (see below). If it's a string, it's a variable string. - -- `%author%` pings the person who sent the message. -- `%prefix%` gets the bot's current prefix in the selected server. - -```js -import Command from '../core/command'; -import { CommonLibrary } from '../core/lib'; - -export default new Command({ - run: '%author%, make sure to use the prefix! (%prefix)', -}); -``` - -...is equal to... - -```js -import Command from '../core/command'; -import { CommonLibrary } from '../core/lib'; -import { getPrefix } from '../core/structures'; - -export default new Command({ - async run($: CommonLibrary): Promise { - $.channel.send( - `${$.author.toString()}, make sure to use the prefix! (${getPrefix( - $.guild, - )})`, - ); - }, -}); -``` - -Now here's where it gets fun. The `Command` class is a recursive structure, containing other `Command` instances as properties. - -- `subcommands` is used for specific keywords for accessing a certain command. For example, `$eco pay` has a subcommand of `pay`. - -```js -import Command from '../core/command'; -import { CommonLibrary } from '../core/lib'; - -export default new Command({ - subcommands: { - pay: new Command({ - //... - }), - }, -}); -``` - -There's also `user` which listens for a ping or a Discord ID, `<@237359961842253835>` and `237359961842253835` respectively. The argument will be a `User` object. - -```js -import Command from '../core/command'; -import { CommonLibrary } from '../core/lib'; - -export default new Command({ - user: new Command({ - async run($: CommonLibrary): Promise { - $.debug($.args[0].username); // "WatDuhHekBro" - }, - }), -}); -``` - -There's also `number` which checks for any number type except `Infinity`, converting the argument to a `number` type. - -```js -import Command from '../core/command'; -import { CommonLibrary } from '../core/lib'; - -export default new Command({ - number: new Command({ - async run($: CommonLibrary): Promise { - $.debug($.args[0] + 5); - }, - }), -}); -``` - -And then there's `any` which catches everything else that doesn't fall into the above categories. The argument will be a `string`. - -```js -import Command from '../core/command'; -import { CommonLibrary } from '../core/lib'; - -export default new Command({ - any: new Command({ - async run($: CommonLibrary): Promise { - $.debug($.args[0].toUpperCase()); - }, - }), -}); -``` - -Of course, maybe you just want to get string arguments regardless, and since everything is an optional property, so you'd then just include `any` and not `subcommands`, `user`, or `number`. - -## Other Properties - -- `description`: The description for that specific command. -- `endpoint`: A `boolean` determining whether or not to prevent any further arguments. For example, you could prevent `$money daily too many arguments`. -- `usage`: Provide a custom usage for the help menu. Do note that this is relative to the subcommand, so the below will result in `$money pay `. - -```js -import Command from '../core/command'; -import { CommonLibrary } from '../core/lib'; - -export default new Command({ - subcommands: { - pay: new Command({ - usage: ' ', - }), - }, -}); -``` - -- `permission`: The permission to restrict the current command to. You can specify it for certain subcommands, useful for having `$money` be open to anyone but not `$money admin`. If it's `null` (default), the permission will inherit whatever was declared before (if any). The default value is NOT the same as `Command.PERMISSIONS.NONE`. -- `aliases`: A list of aliases (if any). - -## Alternatives to Nesting - -For a lot of the metadata properties like `description`, you must provide them when creating a new `Command` instance. However, you can freely modify and attach subcommands, useful for splitting a command into multiple files. - -```js -import pay from "./subcommands/pay"; - -const cmd = new Command({ - description: "Handle your money." -}); -cmd.subcommands.set("pay", pay); -cmd.run = async($: CommonLibrary): Promise { - $.debug($.args); -}; -cmd.any = new Command({ - //... -}); - -export default cmd; -``` - -## Error Handling - -Any errors caused when using `await` or just regular synchronous functions will be automatically caught, you don't need to worry about those. However, promises must be caught manually. For example, `$.channel.send("")` will throw an error because you can't send empty messages to Discord, but since it's a promise, it'll just fade without throwing an error. There are two ways to do this: - -- `$.channel.send("").catch($.handler.bind($))` -- `$.channel.send("").catch(error => $.handler(error))` - -# The Common Library - -This is the container of functions available without having to import `core/lib`, usually as `$`. When accessing this from a command's `run` function, it'll also come with shortcuts to other properties. - -## Custom Wrappers - -- `$(5)` = `new NumberWrapper(5)` -- `$("text")` = `new StringWrapper("text")` -- `$([1,2,3])` = `new ArrayWrapper([1,2,3])` - -## Custom Logger - -- `$.log(...)` -- `$.warn(...)` -- `$.error(...)` -- `$.debug(...)` -- `$.ready(...)` (only meant to be used once at the start of the program) +# Cleanup is Soon™ ## Convenience Functions diff --git a/docs/GettingStarted.md b/docs/GettingStarted.md deleted file mode 100644 index a003a81..0000000 --- a/docs/GettingStarted.md +++ /dev/null @@ -1,20 +0,0 @@ -# Getting Started - -1. `npm install` -2. `npm run build` -3. `npm start` - -# Getting Started (Developers) - -1. `npm install` -2. `npm run dev` -3. Familiarize yourself with the [project's structure](Documentation.md). -4. Make sure to avoid using `npm run build`! This will remove all your dev dependencies (in order to reduce space used). Instead, use `npm run once` to compile and build in non-dev mode. -5. Begin developing. - -## Don't forget to... - -- ...update the [changelog](CHANGELOG.md) and any other necessary docs. -- ...update the version numbers in `package.json` and `package-lock.json`. -- ...make sure the test suite passes by running `npm test`. -- ...format the code by running `npm run format`. diff --git a/docs/Overview.md b/docs/Overview.md new file mode 100644 index 0000000..19e0981 --- /dev/null +++ b/docs/Overview.md @@ -0,0 +1,230 @@ +# Table of Contents + +- [Introduction](#introduction) +- [Setting up the development environment](#setting-up-the-development-environment) +- [Adding a new command](#adding-a-new-command) +- [Adding a new non-command feature](#adding-a-new-non-command-feature) + +# Introduction + +This is a brief overview that'll describe where and how to add new features to TravBot. For more details on specific functions, head over to the [documentation](Documentation.md). Assume the prefix for all of these examples is `$`. + +# Setting up the development environment + +1. `npm install` +2. `npm run dev` *(runs the TypeScript compiler in watch mode, meaning any changes you make to the code will automatically reload the bot)* + +*Note: Make sure to avoid using `npm run build`! This will remove all your dev dependencies (in order to reduce space used). Instead, use `npm run once` to compile and build in non-dev mode.* + +## Don't forget to... + +- ...update the [changelog](../CHANGELOG.md) and any other necessary docs. +- ...update the version numbers in [package.json](../package.json) and [package-lock.json](../package-lock.json). + +# Adding a new command + +To add a new command, go to `src/commands` and either copy the [template](../src/commands/template.ts) or rename the auto-generated test file (`../src/commands/test.ts`). For reference, this is the barebones requirement for a command file. + +## The very basics of a command + +```ts +import {NamedCommand} from "../core"; + +export default new NamedCommand(); +``` + +To make something actually happen when the command is run however, you implement the `run` property. + +```ts +import {Command, NamedCommand} from "../core"; + +export default new NamedCommand({ + async run({message, channel, guild, author, member, client, args}) { + channel.send("test"); + } +}); +``` + +### Quick note on the run property + +You can also enter a string for the `run` property which will send a message with that string specified ([you can also specify some variables in that string](Documentation.md#)). The above is functionally equivalent to the below. + +```ts +import {Command, NamedCommand} from "../core"; + +export default new NamedCommand({ + run: "test" +}); +``` + +## Introducing subcommands + +Where this command handler really shines though is from its subcommands feature. You can filter and parse argument lists in a declarative manner. + +```ts +import {Command, NamedCommand} from "../core"; + +export default new NamedCommand({ + user: new Command({ + async run({message, channel, guild, author, member, client, args}) { + const user = args[0]; + } + }) +}); +``` + +Here, . For example, if this file was named `test.ts`, `$test <@237359961842253835>` would get the user by the ID `237359961842253835` into `args[0]` as a [User](https://discord.js.org/#/docs/main/stable/class/User) object. `$test experiment` would run as if you just called `$test` *(given that [endpoint](Documentation.md#) isn't set to `true`)*. + +If you want, you can typecast the argument to be more strongly typed, because the type of `args` is `any[]`. *([See why if you're curious.](DesignDecisions.md#))* + +```ts +import {Command, NamedCommand} from "../core"; +import {User} from "discord.js"; + +export default new NamedCommand({ + user: new Command({ + async run({message, channel, guild, author, member, client, args}) { + const user = args[0] as User; + } + }) +}); +``` + +## Keyed subcommands + +For keyed subcommands, you would instead use a `NamedCommand`. + +```ts +import {Command, NamedCommand} from "../core"; + +export default new NamedCommand({ + run: "one", + subcommands: { + bread: new NamedCommand({ + run: "two" + }) + } +}); +``` + +If the file was named `cat.ts`: +- `$cat` would output `one` +- `$cat bread` would output `two` + +Only `bread` in this case would lead to `two` being the output, which is different from the generic subcommand types in previous examples. + +You get an additional property with `NamedCommand`s: `aliases`. That means you can define aliases not only for top-level commands, but also every layer of subcommands. + +```ts +import {Command, NamedCommand} from "../core"; + +export default new NamedCommand({ + aliases: ["potato"], + subcommands: { + slice: new NamedCommand({ + aliases: ["pear"] + }) + } +}); +``` + +For example, if this file was named `plant.ts`, the following would work: +- `$plant` +- `$potato` +- `$plant slice` +- `$plant pear` +- `$potato slice` +- `$potato pear` + +## Metadata / Command Properties + +You can also specify metadata for commands by adding additional properties. Some of these properties are per-command while others are inherited. + +```ts +import {Command, NamedCommand} from "../core"; + +export default new NamedCommand({ + description: "desc one", + subcommands: { + pineapple: new NamedCommand({ + //... + }) + } +}); +``` + +`description` is an example of a per-command property (which is used in a help command). If the file was named `siege.ts`: +- The description of `$siege` would be `desc one`. +- There wouldn't be a description for `$siege pineapple`. + +This is in contrast to inherited properties. + +```ts +import {Command, NamedCommand, CHANNEL_TYPE} from "../core"; + +export default new NamedCommand({ + channelType: CHANNEL_TYPE.GUILD, + subcommands: { + pineapple: new NamedCommand({ + //... + }) + } +}); +``` + +Here, the property `channelType` would spread to all subcommands unless a subcommand defines it. Using the above example, the `channelType` for both `$siege` and `$siege pineapple` would be `CHANNEL_TYPE.GUILD`. + +*To get a full list of metadata properties, see the [documentation](Documentation.md#).* + +## Utility Functions + +You'll have to import these manually, however it's in the same import list as `Command` and `NamedCommand`. + +```ts +import {Command, NamedCommand, paginate} from "../core"; + +export default new NamedCommand({ + async run({message, channel, guild, author, member, client, args}) { + paginate(/* enter your code here */); + } +}); +``` + +*To get a full list of utility functions, see the [documentation](Documentation.md#).* + +# Adding a new non-command feature + +If the feature you want to add isn't specifically a command, or the change you're making involves adding event listeners, go to `src/modules` and create a file. Code written here won't be automatically loaded, so make sure to open [src/index.ts](../src/index.ts) and add an import statement at the bottom. + +```ts +import "./modules/myModule"; +``` + +This will just run whatever code is in there. + +## Listening for events + +Rather than have an `events` folder which contains dynamically loaded events, you add an event listener directly via `client.on("...", () => {})`. *([See why if you're curious.](DesignDecisions.md#))* The client can be imported from the index file. + +```ts +import {client} from ".."; + +client.on("message", (message) => { + //... +}); +``` + +As long as you make sure to add that import statement in the index file itself, the event(s) will load. + +**Just make sure you instantiate the client *before* you import a module or you'll get a runtime error.** + +`index.ts` +```ts +import {Client} from "discord.js"; + +export const client = new Client(); + +//... + +import "./modules/myModule"; +``` diff --git a/docs/Specifications.md b/docs/Specifications.md deleted file mode 100644 index 49bef9d..0000000 --- a/docs/Specifications.md +++ /dev/null @@ -1,48 +0,0 @@ -# Structure - -The top-level directory is reserved for files that have to be there for it to work as well as configuration files. - -- `src`: Contains all the code for the bot itself. Code in this directory is for independent tasks keeping the initialization out of the subdirectories. - - `core`: This is where core structures and critical functions for the bot go. - - `modules`: This is where modules go that accomplish one specific purpose but isn't so necessary for the bot to function. The goal is to be able to safely remove these without too much trouble. - - `commands`: Here's the place to store commands. The file name determines the command name. - - `subcommands/`: All commands here are ignored by the category loader. Here is where you can split commands into different files. Also works per directory, for example, `utility/subcommands/` is ignored. - - `/`: Specify a directory which'll group commands into a category. For example, a `utility` folder would make all commands inside have the `Utility` category. - - `.ts`: All commands at this level will have the `Miscellaneous` category. - - `events`: Here's the place to store events. The file name determines the event type. -- `dist`: This is where the runnable code in `src` compiles to. (The directory structure mirrors `src`.) -- `test`: Used for all the unit tests. -- `data`: Holds all the dynamic data used by the bot. This is what you modify if you want to change stuff for just your instance of the bot. -- `standard`: Contains all the standard data to be used with the project itself. It's part of the code and will not be checked for inaccuracies because it's not meant to be easily modified. -- `docs`: Used for information about the design of the project. - -# Specific Files - -This list starts from `src`/`dist`. - -- `index`: This is the entry point of the bot. Here is where all the initialization is done, because the idea is to keep repeatable code in separate modules while having code that runs only once here designating this is **the** starting point. -- `setup`: Used for the first time the bot is loaded, walking the user through setting up the bot. -- `core/lib`: Exports a function object which lets you wrap values letting you call special functions as well as calling utility functions common to all commands. -- `core/structures`: Contains all the structures that the dynamic data read from JSON files should follow. This exports instances of these classes. -- `core/command`: Contains the class used to instantiate commands. -- `core/event`: Contains the class used to instantiate events. -- `core/storage`: Exports an object which handles everything related to files. -- `core/wrappers`: Contains classes that wrap around values and provide extra functionality. -- `core/permissions`: The file containing everything related to permissions. - -# Design Decisions - -- All top-level files (relative to `src`/`dist`) should ideally be independent, one-time use scripts. This helps separate code that just initializes once and reusable code that forms the bulk of the main program itself. That's why all the file searching and loading commands/events will be done in `index`. -- Wrapper objects were designed with the idea of letting you assign functions directly to native objects [without the baggage of actually doing so](https://developer.mozilla.org/en-US/docs/Web/JavaScript/The_performance_hazards_of__%5B%5BPrototype%5D%5D_mutation). -- `test` should be a keyword for any file not tracked by git and generally be more flexible to play around with. It should also be automatically generated during initialization in `commands` so you can have templates ready for new commands. -- The storage module should not provide an auto-write feature. This would actually end up overcomplicating things especially when code isn't fully blocking. -- I think it's much easier to make a template system within the code itself. After all, the templates only change when the code changes to use new keys or remove old ones. You'll also be able to dedicate specific classes for the task rather than attaching meta tags to arrays and objects. -- I decided to forget about implementing dynamic events. I don't think it'll work with this setup. After all, there are only so many events you can use, whereas commands can have any number of ones, more suitable for dynamic loading. The main reasons were unsecure types and no easy way to access variables like the config or client. -- I want to make attaching subcommands more flexible, so you can either add subcommands in the constructor or by using a method. However, you have to add all other properties when instantiating a command. -- All commands should have only one parameter. This parameter is meant to be flexible so you can add properties without making a laundry list of parameters. It also has convenience functions too so you don't have to import the library for each command. -- The objects in `core/structures` are initialized into a special object once and then cached into memory automatically due to an import system. This means you don't have to keep loading JSON files over and over again even without the stack storage system. Because a JSON file resolves into an object, any extra keys are removed automatically (as it isn't initialized into the data) and any property that doesn't yet exist on the JSON object will be initialized into something. You can then have specific functions like `addUser` onto objects with a specific structure. -- There were several possible ways to go about implementing aliases and subaliases. - - Two properties on the `Command` class, `aliases: string[]` and `subaliases: {[key: string]: string[]}`. - - Exporting a const named `aliases` which would handle top-level aliases. - - For `subaliases`, either making subaliases work like redirects (Instead of doing `new Command(...)`, you'd do `"nameOfSubcommand"`), or define properties on `Command`. -- What I ended up doing for aliases is making an `aliases` property on `Command` and then converting those string arrays to a more usable structure with strings pointing to the original commands. `aliases` at the very top will determine global aliases and is pretty much the exception in the program's logic. `aliases` elsewhere will provide aliases per subcommand. For anything other than the top-level or `subcommands`, `aliases` does nothing (plus you'll get a warning about it). diff --git a/package-lock.json b/package-lock.json index 80b5169..c4d3d3a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,11 @@ { - "name": "d.js-v12-bot", + "name": "travebot", "version": "0.0.1", "lockfileVersion": 2, "requires": true, "packages": { "": { - "name": "d.js-v12-bot", + "name": "travebot", "version": "0.0.1", "hasInstallScript": true, "license": "MIT", diff --git a/package.json b/package.json index a48dcd5..ab53c23 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,17 @@ { - "name": "d.js-v12-bot", + "name": "travebot", "version": "0.0.1", - "description": "A Discord bot built on Discord.JS v12", + "description": "TravBot Discord bot.", "main": "dist/index.js", + "scripts": { + "build": "tsc --project tsconfig.prod.json && npm prune --production", + "start": "node .", + "once": "tsc && npm start", + "dev": "tsc-watch --onSuccess \"node . dev\"", + "test": "jest", + "format": "prettier --write **/*", + "postinstall": "husky install" + }, "dependencies": { "canvas": "^2.7.0", "chalk": "^4.1.0", @@ -34,19 +43,10 @@ "tsc-watch": "^4.2.9", "typescript": "^3.9.7" }, - "scripts": { - "build": "tsc --project tsconfig.prod.json && npm prune --production", - "start": "node .", - "once": "tsc && npm start", - "dev": "tsc-watch --onSuccess \"node . dev\"", - "test": "jest", - "format": "prettier --write **/*", - "postinstall": "husky install" - }, + "author": "Keanu Timmermans", + "license": "MIT", "keywords": [ "discord.js", "bot" - ], - "author": "Keanu Timmermans", - "license": "MIT" + ] } From c82083af5d1e604c8b50ded467dceefec8674270 Mon Sep 17 00:00:00 2001 From: Mijyuoon Date: Tue, 6 Apr 2021 18:24:42 +0300 Subject: [PATCH 144/178] Added an entry that should've been added --- src/commands/fun/whois.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/commands/fun/whois.ts b/src/commands/fun/whois.ts index d37e674..a218d97 100644 --- a/src/commands/fun/whois.ts +++ b/src/commands/fun/whois.ts @@ -31,6 +31,7 @@ const registry: {[id: string]: string} = { "440399719076855818": "You are, uhh, Stay Put, Soft Puppy, Es-Pee, Swift Pacemaker, Smug Poyo, and many more.\n...Seriously, this woman has too many names.", "243061915281129472": "Some random conlanger, worldbuilder and programmer doofus. ~~May also secretly be a nyan. :3~~", + "792751612904603668": "Some random nyan. :3 ~~May also secretly be a conlanger, worldbuilder and programmer doofus.~~", "367439475153829892": "A weeb.", "760375501775700038": "˙qǝǝʍ ∀", "389178357302034442": "In his dreams, he is the star. its him. <:itsMe:808174425253871657>", From fae06f66d4ebcb1a0630e0742059a74e436499b1 Mon Sep 17 00:00:00 2001 From: EL2020 Date: Tue, 6 Apr 2021 21:04:27 -0400 Subject: [PATCH 145/178] added functionality for reacting to in-line replies --- src/commands/utilities/react.ts | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/commands/utilities/react.ts b/src/commands/utilities/react.ts index ceef800..49648aa 100644 --- a/src/commands/utilities/react.ts +++ b/src/commands/utilities/react.ts @@ -10,8 +10,19 @@ export default new Command({ async run($: CommonLibrary): Promise { let target: Message | undefined; let distance = 1; - - if ($.args.length >= 2) { + + // allows reactions by using an in-line reply + if($.message.reference){ + const messageID = $.message.reference.messageID; + try{ + target = await $.channel.messages.fetch(messageID!) + }catch{ + return $.channel.send("Unknown error occurred!") + } + } + + // handles reacts by message id/distance + else if ($.args.length >= 2) { const last = $.args[$.args.length - 1]; // Because this is optional, do not .pop() unless you're sure it's a message link indicator. const URLPattern = /^(?:https:\/\/discord.com\/channels\/(\d{17,19})\/(\d{17,19})\/(\d{17,19}))$/; const copyIDPattern = /^(?:(\d{17,19})-(\d{17,19}))$/; From 397287ec3c148478dfe50ae41dfece9e0b00f0ac Mon Sep 17 00:00:00 2001 From: WatDuhHekBro <44940783+WatDuhHekBro@users.noreply.github.com> Date: Wed, 7 Apr 2021 01:00:57 -0500 Subject: [PATCH 146/178] Retroactively added version history --- CHANGELOG.md | 159 ++++++++++++++++++++++++++++++++++++++---- docs/Documentation.md | 18 +++++ docs/Overview.md | 5 +- package-lock.json | 4 +- package.json | 2 +- 5 files changed, 166 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f6bdb65..fa8c546 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,133 @@ -# 2.8.4 - Reworked the react command +# 3.2.0 - Internal refactor, more subcommand types, and more command type guards (2021-??-??) +- The custom logger changed: `$.log` no longer exists, it's just `console.log`. Now you don't have to do `import $ from "../core/lib"` at the top of every file that uses the custom logger. +- Utility functions are no longer attached to the command menu. Stuff like `$.paginate()` and `$(5).pluralise()` instead need to be imported and used as regular functions. +- The `paginate` function was reworked to reduce the amount of repetition you had to do. +- Events are no longer loaded dynamically. What you do is `import "./some-file"` which will run the code in there, attaching the event to the client. Since events are no longer bound to certain files, you can keep them more organized: + - Since there can be multiple listeners per event, large event files can be split up into more organized blocks. + - You can also group together related events like `channelCreate` and `channelDelete` and show the relation in one single file rather than splitting them up just because they're two different events. +- Lots of files were moved around: + - The `core` folder represents the command handler and is pretty much treated as if it was an external module. That means that instead of importing different items from each file, you'd import it from its index file (which is shortened to `import {} from ../core`). My hope is to move this section to its own module eventually™. + - Other `core` files that were more or less specific to the bot were moved outside, either at the top-level or into `modules`. This includes stuff like the library file containing utility functions as well as structures for storing/loading data. Since they're at the top now, there's less typing involved in importing them (`../lib` instead of `../core/lib` and so on). + - Commands are still dynamically loaded. This won't change. +- Added more type guards to the `Command` class, reducing the amount of unused properties there are. + - If a command has `endpoint: true` specified, it'll now prevent adding subcommands at compile-time rather than relying on runtime warnings. + - Added a `NamedCommand` subclass on top-level commands (default exports) as well as keyed subcommands (basically the ones with a hardcoded value). `NamedCommand`s have access to `aliases`. Having `aliases` on something like a numeric subcommand (ie `$test 5`) doesn't really make sense. +- Added more features to the `Command` class as well: + - You can now restrict certain commands to Guild-only channels or DM-only channels. Unfortunately, there's a bug in TypeScript where callbacks don't get affected by discriminated unions. So for now, if you set a command's channel type, just do a non-null assertion on `guild` and a `TextChannel` assertion for `channel` (and vice versa). + - A command can now be designated as NSFW-only. + - Added more subcommand types: + - Channel: `<#...>` + - Role: `<@&...>` + - Emote: `` + - Message: `https://discordapp.com/channels///` or `-` from the "Copy Message Link" and "Copy ID" (shift) buttons. + - ID: Any Discord ID. In order to use this, you have to specify which subcommand type you want to redirect it to. For example, to replicate the old behavior with plain IDs being converted to user IDs, you first implement user `user: new Command(...)` then do `id: "user"`. + - Some changes to subcommands: + - User: `<@...>` and `<@!...>` - Its default state is more restricted. It no longer accepts standalone IDs by default. +- You'll notice in a lot of commands as well as the template that properties are destructured. While using `$` will work just fine, having `{message, channel, guild}` will let you access properties using `channel` instead of `$.channel` and so on. +- Direct messaging the bot now listens for commands. You don't need a prefix when doing this, it's assumed you're running a command. +- Command invocations are no longer logged every single time. Now the catch block shows the command used and the arguments, and unhandled rejections related to Discord are captured too, showing the same information. +- I added Husky and I think I've got its pre-commit hook to work. If this goes as expected, the formatter should be called every commit so there aren't any more formatting commits. +- Internally, the core message handler and the `Command` class(es) are very de-spaghettified compared to before. Its methods are a lot more modular now. +- Retroactively added version history for TravBot-v3. +- Revised documentation. + +# 3.1.10 (2021-04-06) +- Ported the rest of features from TravBot-v2 +- Prototyped stream notifications +- Added eco bet command +- Added several entries to the `whois` list +- Added functionality for reacting to in-line replies + +# 3.1.9 (2021-03-28) +- Stops deleting the `emote` invocation +- Added channel lock for `eco` +- Listens for CheeseBot's "Remember to drink water!" message and reacts with 🚱 +- Added message quoting +- Added sandboxed regex query to `lsemotes` with timeout +- Added `info guild` for other guilds + +# 3.1.8 - Introduce a terrible hack to reduce memory usage and a few other less significant changes (2021-02-16) +- Add the titular hack™️ aka "pulling CC modding on discord.js". +- Reduce the usage of caches where possible (don't remember whether I eliminated all of them or not; note that guild members, roles and emojis can be assumed to be always cached if the guild object is available), especially in the info command (because I have effectively broken the automatic members cache with the titular hack). Also get rid of calls to `BaseManager#resolve` to make cache lookups explicit. +- Get rid of usages of `@ts-ignore` (never do this, or I'll kill you!!!). +- Enable sourcemaps for seeing the source code lines in the error stack traces. +- Raise the target JS edition to ES2019 since Node.js installed on the deployment machine is version 15.x anyway. + +# 3.1.7 - Added time command for user-submitted timezones (2021-01-25) + +# 3.1.6 - Added emote dumper (2021-01-03) + +# 3.1.5 - Attempting to fix the emote name resolution (2020-12-20) +This is an attempt at fixing some notable problems with the `emote` and `react` commands, including the following: +- `leaSMUG` would resolve to `leaSmug` (should be fixed by taking capitalization into account and making an offset number from each difference) +- `leaCheese` would resolve to `leaCheeseAngry` (should be fixed by taking length into account in its heuristics) + +# 3.1.4 - New formatter settings, hotfix, and feature (2020-12-16) +- Added `eco monday` +- Fixed `eval` command +- Ported `eco guild` +- Added more message linking options to `react` +- Added formatter +- Fixed error prevention in `neko` command + +# 3.1.3 - Ported the eco command (2020-12-15) +- Ported the `eco` command, as well as `eco shop` and `eco buy` +- Public Rollout + +# 3.1.2 - Added music functionality and ported more commands (2020-10-24) +- Added music functionality via Lavalink +- Ported the following commands: + - `lsemotes` + - `shorten` + - `eval` + - `info bot` + - `admin clear` + - `cookie` + - `neko` + - `ok` + - `owoify` + - `desc` + - `react` + - `say` +- Bug fixes to `info guild` + +# 3.1.1 - Began the porting process (2020-09-11) +- Ported the following commands: + - `info` + - `8ball` + - `poll` + - `lsemotes` + - `scanemotes` +- Ported the following commands to `admin`: + - `status` + - `purge` + - `nick` + - `guilds` + - `activity` +- Added pinging bot for prefix and var string prefix +- Added removing emotes in paginate if possible +- Added command aliases +- Added CodeQL +- Modularized finding members by their username +- Added documentation +- Added Docker support + +# 3.1.0 - Restructured the project according to CrossExchange (2020-07-26) +Ported over CrossExchange v1.0.1 and added several additional features: +- Command Categories: This follows suit of [the pre-rework command structure](https://github.com/keanuplayz/TravBot-v3/tree/pre-typescript/src/Commands), where you have categories (now `utility` which gets capitalized to `Utility` for example) and miscellaneous commands. + - `subcommands` will be a reserved directory name to allow you to split up big command files into smaller ones. `commands/subcommands` is ignored as does `commands/utility/subcommands`. + - The way you'd work with splitting up these commands is that instead of doing `export default new Command(...)`, you would instead do: + - `subcommands/part1.ts`: `export default new Command(...)` + - `main.ts`: `import sub from "./subcommands/part1"; const a = new Command(...); a.attach("layer", sub); export default a;` +- Command Permissions: These permissions will work with the recursive structure as well because it'd be useful to section off different subcommands into different permissions. For example, everyone has access to `.money` but if you want to add `.money set ` (better for organization), you'd simply assign a property to `.money set` and it'd affect everything below it unless overridden. See `admin.ts` for an example on how this works. +- Dynamically-Loaded Events: All events now read from the `events` folder. If you want to access the client, you can do so by importing it from the index. (`import {client} from "../index";`) + +# 3.0.0 - Brainstormed first structure (2020-07-08) +- Adds folder-separated command categories. +- Adding commands now involves instantiating classes rather than exporting a function with some settings. +- Adds structures for better organization of commonly used classes like `Command` and `Event`. + +# 2.8.4 - Reworked the react command (2020-09-05) - `react` is now a fully versatile command for helping you react to other messages with non-server emotes. - Now properly reacts to the previous message (bug fix). - Provides you the option to react to any number of messages before your message (3 messages above yours for example). @@ -12,9 +141,9 @@ - Now reacts to your message with ❓ instead of `None of those emote names were valid!` so that the bot doesn't spam the chat if you can't find the right emote (because you'll still be able to delete your messages). - `thonk` now stores the last specified phrase so you can repeat a phrase with different diacritics. -# 2.8.3 - The ultimate meme +# 2.8.3 - The ultimate meme (2020-08-08) -# 2.8.2 +# 2.8.2 (2020-07-01) - Added a changelog. - Added an extra instruction to the readme's installation. - Made commands utilize the existing `Array.random()` function. @@ -25,14 +154,14 @@ - Fixed a bug with `emote` where it wouldn't find any upper case emotes and made it more lenient to just include any emote (so you don't have to remember the exact emote name). - Moved lists and gathering shop items outside of `exports.run()` so that it initializes once during the bot's initialization (or when reloaded) rather than every time the command is called. -# 2.8.1 - Modularized eco shop and eco buy +# 2.8.1 - Modularized eco shop and eco buy (2020-06-30) - Fixed scanemotes sometimes not displaying all emotes. This was an issue of not accounting for whether an emote was animated or not. - Modularized `eco shop` and `eco buy`. Shop items are now in the `shop` subfolder and `eco shop` now separates every 5 shop items into separate pages automatically. -# 2.8.0 - Added graphical welcome setting +# 2.8.0 - Added graphical welcome setting (2020-06-29) - Adds a new option to the `set` and `conf` commands, allowing you to enable an image being sent as a welcome. -# 2.7.1 - Added eco buy laser bridge and reworked scanemotes +# 2.7.1 - Added eco buy laser bridge and reworked scanemotes (2020-06-28) - `eco buy laser bridge` - Added a shop item. Buy what is technically a laser bridge. Costs 3 Mons. - `insult` - Now pings the user who activated it. - `scanemotes` - Reworked the command after a test run in a big server. @@ -42,7 +171,7 @@ - Now includes all emotes in a server, even if they haven't been used. - Now properly counts emote usage for reactions (whether or not a bot reacted to a message) -# 2.7.0 - Added percentages to scanemotes +# 2.7.0 - Added percentages to scanemotes (2020-06-26) ## Major Changes - Added an hour long cooldown to `scanemotes` per server because it's a very memory-intensive task to search through every single message. - Added a second list of emotes to `scanemotes`, sorting by percentage of users-only usage. @@ -62,17 +191,17 @@ - Added error checking to `scanemotes` so users aren't left in the dark if something happens. - On big servers, `scanemotes` should now have emotes actually show up. -# 2.6.1 - Hotfix: Scan emotes no longer requires admin +# 2.6.1 - Hotfix: Scan emotes no longer requires admin (2020-06-22) - Fixed the `scanemotes` command to no longer require admin permissions. This was due to an oversight: There can be channels which the bot doesn't have access to, ie private channels. You have to check if the bot has access to a channel because the filter will gather all text-based channels regardless. Admin permissions overrides all restrictions, which is why it only worked with admin permissions. - Entering a username in the `avatar` command unsuccessfully will now send a message in chat. -# 2.6.0 - Added the ability to get other users' avatars and see emote usage +# 2.6.0 - Added the ability to get other users' avatars and see emote usage (2020-06-19) - You can now scan the current guild for emote usage, collecting all emotes used in messages and reactions. (example below) ![2020-06-19 04_08_22-Window](https://user-images.githubusercontent.com/44940783/85116219-98a69280-b1e2-11ea-9246-b8f5ff2537ea.png) - You can now get other avatars by providing an ID (works even when the bot doesn't share the same server as that user), username, or by pinging them. - Included the fix for `serverinfo`. -# 2.5.3 +# 2.5.3 (2020-06-16) - Changed default prefix for setup. - Enhanced `react` command. New optional guildID arg. - Added message logger. @@ -82,27 +211,27 @@ - Added images to logger. Added author to logger. - Emote command is now not case-sensitive. -# 2.5.2 - Bug fixes to the "eco" command +# 2.5.2 - Bug fixes to the "eco" command (2020-06-01) - Now prevents users from sending negative amounts of money to others (minimum of 1 Mon). - Also prevents users from sending decimal amounts. - Fixes a potentially wrong substring for user IDs. - Now requires an argument when using the "desc" command. -# 2.5.1 +# 2.5.1 (2020-05-18) - Added shop functionality to eco. - Fixed faulty guild check. - Attempt at fixing emote for eco cute. - Pluralised "mon" for eco handhold. - Added `translate` command. -# 2.5.0 - Added the "pay" sub-command to "eco" +# 2.5.0 - Added the "pay" sub-command to "eco" (2020-05-09) -# 2.4.1 +# 2.4.1 (2020-04-18) - Added Procfile. - Updated whoami's keys. - Rewrote `desc` command. -# 2.4.0 - Implemented music system +# 2.4.0 - Implemented music system (2020-04-11) - VC Rename command - Travis CI configuration - Music system diff --git a/docs/Documentation.md b/docs/Documentation.md index cf91fb2..8f45a7a 100644 --- a/docs/Documentation.md +++ b/docs/Documentation.md @@ -13,6 +13,24 @@ - `data`: Holds all the dynamic/private data used by the bot. This folder is not meant to hold definitions. - `docs`: Information for developers who want to contribute. +# Version Numbers + +When a new version is ready to be declared... +- ...update the [changelog](../CHANGELOG.md). +- ...update the version numbers in [package.json](../package.json) and [package-lock.json](../package-lock.json). + +## Naming Versions + +Because versions are assigned to batches of changes rather than single changes (or even single commits), versioning is used a bit differently in order to avoid wasting version numbers. + +`..-` +- `` is a defined as the overarching version group of TravBot. TravBot-v2 went by `2.x.x` and all versions of TravBot-v3 will go by `3.x.x`. +- `` includes any big overhauls or revisions of the entire codebase. +- `` includes any feature additions in a specific area of the codebase. +- `` will be pretty much for any very small changes like a quick bug fix or typos. *Note: Normally, these would probably get grouped up, but if there hasn't been a proper version in a while, this will get pushed as a patch.* + +*Note: This system doesn't retroactively apply to TravBot-v2, which is why this version naming system won't make sense for v2's changelog.* + # Cleanup is Soon™ ## Convenience Functions diff --git a/docs/Overview.md b/docs/Overview.md index 19e0981..f6854c4 100644 --- a/docs/Overview.md +++ b/docs/Overview.md @@ -16,10 +16,7 @@ This is a brief overview that'll describe where and how to add new features to T *Note: Make sure to avoid using `npm run build`! This will remove all your dev dependencies (in order to reduce space used). Instead, use `npm run once` to compile and build in non-dev mode.* -## Don't forget to... - -- ...update the [changelog](../CHANGELOG.md) and any other necessary docs. -- ...update the version numbers in [package.json](../package.json) and [package-lock.json](../package-lock.json). +*Note: If you update one of the APIs or utility functions, make sure to update the [documentation](Documentation.md).* # Adding a new command diff --git a/package-lock.json b/package-lock.json index c4d3d3a..a2b61d6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "travebot", - "version": "0.0.1", + "version": "3.2.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "travebot", - "version": "0.0.1", + "version": "3.2.0", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index ab53c23..4903443 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "travebot", - "version": "0.0.1", + "version": "3.2.0", "description": "TravBot Discord bot.", "main": "dist/index.js", "scripts": { From 5a64aed45d2c76bb559179ffbc7d3f975291d760 Mon Sep 17 00:00:00 2001 From: WatDuhHekBro <44940783+WatDuhHekBro@users.noreply.github.com> Date: Wed, 7 Apr 2021 04:58:13 -0500 Subject: [PATCH 147/178] Filled out design decisions doc --- docs/DesignDecisions.md | 126 +++++++++++++++++++++++++++++++++++++++- docs/Documentation.md | 7 +++ docs/Overview.md | 4 +- src/core/command.ts | 32 +++++----- src/core/libd.ts | 11 ++++ 5 files changed, 164 insertions(+), 16 deletions(-) diff --git a/docs/DesignDecisions.md b/docs/DesignDecisions.md index 27306bf..4a2bbd5 100644 --- a/docs/DesignDecisions.md +++ b/docs/DesignDecisions.md @@ -1 +1,125 @@ -Coming Soon™ +# Using the Command Class + +## any[] Parameters For Subcommand Run + +Unless there's some sort of TypeScript wizardry to solve this, the `args` parameter in the subcommand type will have to be `any[]` because it's simply too context-dependent to statically figure it out. +- Each subcommand is its own layer which doesn't know about parent commands at compile-time. +- Subcommands can be split into different files for code maintainability. +- Even though the last argument is able to be strongly-typed, if you have multiple parameters, you'd essentially only get static benefits for one of the arguments, and you wouldn't even know the location of that one argument. +- Overall, it's just easier to use your best judgement then use type assertions. + +## Channel Type Type Guards + +As of right now, it's currently not feasible to implement type guards for channel types. [Discriminated unions with a default parameter don't work with callbacks.](https://github.com/microsoft/TypeScript/issues/41759) In order to implement type guards, the `channelType` parameter would have to be required, making each command layer quite tedious. + +So instead, use non-null assertions when setting the `channelType`. For example: + +```ts +import {Command, NamedCommand, CHANNEL_TYPE} from "../core"; +import {TextChannel} from "discord.js"; + +export default new NamedCommand({ + channelType: CHANNEL_TYPE.GUILD, + async run({message, channel, guild, author, member, client, args}) { + console.log(guild!.name); + console.log(member!.nickname); + console.log((channel as TextChannel).name !== "dm"); + } +}); +``` + +```ts +import {Command, NamedCommand, CHANNEL_TYPE} from "../core"; +import {DMChannel} from "discord.js"; + +export default new NamedCommand({ + channelType: CHANNEL_TYPE.DM, + async run({message, channel, guild, author, member, client, args}) { + console.log(guild === null); + console.log(member === null); + console.log((channel as DMChannel).type === "dm"); + } +}); +``` + +The three guarantees are whether or not `guild` will be `null`, whether or not `member` will be `null`, and the type of `channel`. + +*Take note that `member` can still be `null` even in a guild (for example, if you target a message by someone who left), `member` cannot be `null` here because the `message` being sent must be by someone who is in the guild by this point.* + +## Uneven Return Paths + +`Command.run` doesn't use the return values for anything, so it's safe to do `return channel.send(...)` to merge those two statements. However, you'll come across an error: `Not all code paths return a value.` + +There are several ways to resolve this issue: +- Split all `return channel.send(...)` statements to `{channel.send(...); return;}` +- Set an explicit any return type in the function header: `async run(...): Promise {` +- Add an extra `return` statement at the end of each path + +## Type Guards + +The `Command` class is implemented in a certain way to provide type guards which reduce unnecessary properties at compile-time rather than warning the user at runtime. +- The reason `NamedCommand` (which extends `Command`) exists is to provide a type guard for `aliases`. After all, `aliases` doesn't really make sense for generic subcommand types - how would you handle an alias for a type that accepts a number for example? +- The `endpoint` property changes what other properties are available via a discriminated union. If `endpoint` is `true`, no subcommands of any type can be defined. After all, it wouldn't make sense logically. + +## Boolean Types + +Boolean subcommand types won't be implemented: +- Since there are only two values, why not just put it under `subcommands`? +- If boolean types were to be implemented, how many different types of input would have to be considered? `yes`/`no`, `y`/`n`, `true`/`false`, `1`/`0`, etc. + +## Hex and Octal Number Types + +For common use cases, there wouldn't be a need to go accept numbers of different bases. The only time it would be applicable is if there was some sort of base converter command, and even then, it'd be better to just implement custom logic. + +# The Command Handler + +## The Scope of the Command Handler + +What this does: +- Provides the `Command`/`NamedCommand` classes. +- Dynamically loads commands and attaches runtime metadata. +- Provides utility functions specific to Discord to make certain patterns of commands less tedious to implement. + +What this doesn't do: +- Manage the general file system or serialization/deserialization of data. +- Provide general utility functions. +- Provide any Discord-related functionality besides strictly command handling. + +## Client Creation + +Creating the client is beyond the scope of the command handler and will not be abstracted away. Instead, the user will simply attach the command handler to the client to initialize it. +- This makes it so if a user wants to specify their own `ClientOptions` when instantiating the client, it's less troublesome to implement. +- The user can export the client and use it throughout different parts of their code. + +## Bot-Specific Mentions + +Pinging the bot will display the current guild prefix. The bot mention will not serve as an alternate prefix. +- When talking about a bot, the bot might be pinged to show who it is. It could be in the middle (so don't listen for a prefix anywhere) or it could be at the start (so only listen to a standalone ping). +- It likely isn't a common use case to ping the bot. The only time it would really shine is in the event two bots have a prefix conflict, but the command that changes prefixes can simply add a parameter to deal with that case. For example, instead of `@bot set prefix `, you'd use `set prefix @bot`. + +## Direct Messages + +When direct messaging a bot, no prefixes will be used at all because it's assumed that you're executing a command. Because the only people allowed is the user and the bot, NSFW-only commands can also be executed here. + +## Permission Setup + +Because the command handler provides no specific permission set, it's up to the user to come up with functions to add permissions as well as create the enum that assigns permissions. +- The `permission` property of a `Command` instance is `-1` by default, which means to inherit the permission level from the parent command. If you want, you can create your enum like this: `enum Permissions {INHERIT = -1, USER, ADMIN}`, where `Permissions.USER = 0` and `Permissions.ADMIN = 1`. + +# Miscellaneous + +## Static Event Loading + +While dynamic loading fits very well with commands, it was more or less clunky design to try and make events fit the same model: +- There are no restrictions when it comes to command names, and the name of the file will determine the name of the command, which avoids repetition. Events on the other hand involved lots of boilerplate to get static types back. +- Since there can be multiple listeners per event, large event files can be split up into more organized blocks. +- Likewise, small event listeners which span multiple events can be grouped together like `channelCreate` and `channelDelete`, showing the relation in one single file rather than splitting them up just because they're two different events. + +## Testing + +For TravBot, there'll be two types of tests: standard unit tests and manual integration tests. +- Standard unit tests are executed only on isolated functions and are part of the pre-commit hook. +- Somehow, including the bot in an import chain will cause the system to crash (same error message as [this](https://stackoverflow.com/questions/66102858/discord-clientuser-is-not-a-constructor)). That's why the integration tests are manually done. There would be a list of inputs and outputs to check of each command for tests while simultaneously serving as a help menu with examples of all possible inputs/outputs for others to see. +- An idea which will not be implemented is prompting the user for inputs during the tests. This is no better than manual tests, worse actually, because if this had to run before each commit, it'd quickly become a nightmare. +- Maybe take some ideas from something like [this](https://github.com/stuyy/jest-unit-tests-demo) in the future to get tests to properly work. +- Another possibility is to use `client.emit(...)` then mock the `message.channel.send(...)` function which would listen if the input is correct. diff --git a/docs/Documentation.md b/docs/Documentation.md index 8f45a7a..b61381c 100644 --- a/docs/Documentation.md +++ b/docs/Documentation.md @@ -31,6 +31,13 @@ Because versions are assigned to batches of changes rather than single changes ( *Note: This system doesn't retroactively apply to TravBot-v2, which is why this version naming system won't make sense for v2's changelog.* +# Message Subcommand Type + +- `https://discord.com/channels///` comes from the `Copy Message Link` button. +- `-` comes from holding `Shift` on the desktop application and clicking on the `Copy ID` button. + + + # Cleanup is Soon™ ## Convenience Functions diff --git a/docs/Overview.md b/docs/Overview.md index f6854c4..5b57397 100644 --- a/docs/Overview.md +++ b/docs/Overview.md @@ -72,7 +72,7 @@ export default new NamedCommand({ Here, . For example, if this file was named `test.ts`, `$test <@237359961842253835>` would get the user by the ID `237359961842253835` into `args[0]` as a [User](https://discord.js.org/#/docs/main/stable/class/User) object. `$test experiment` would run as if you just called `$test` *(given that [endpoint](Documentation.md#) isn't set to `true`)*. -If you want, you can typecast the argument to be more strongly typed, because the type of `args` is `any[]`. *([See why if you're curious.](DesignDecisions.md#))* +If you want, you can typecast the argument to be more strongly typed, because the type of `args` is `any[]`. *([See why if you're curious.](DesignDecisions.md#any[]-parameters-for-subcommand-run))* ```ts import {Command, NamedCommand} from "../core"; @@ -201,7 +201,7 @@ This will just run whatever code is in there. ## Listening for events -Rather than have an `events` folder which contains dynamically loaded events, you add an event listener directly via `client.on("...", () => {})`. *([See why if you're curious.](DesignDecisions.md#))* The client can be imported from the index file. +Rather than have an `events` folder which contains dynamically loaded events, you add an event listener directly via `client.on("...", () => {})`. *([See why if you're curious.](DesignDecisions.md#static-event-loading))* The client can be imported from the index file. ```ts import {client} from ".."; diff --git a/src/core/command.ts b/src/core/command.ts index ba89c71..0835da9 100644 --- a/src/core/command.ts +++ b/src/core/command.ts @@ -139,6 +139,7 @@ export const defaultMetadata = { channelType: CHANNEL_TYPE.ANY }; +// Each Command instance represents a block that links other Command instances under it. export class Command { public readonly description: string; public readonly endpoint: boolean; @@ -146,17 +147,19 @@ export class Command { public readonly permission: number; // -1 (default) indicates to inherit, 0 is the lowest rank, 1 is second lowest rank, and so on. public readonly nsfw: boolean | null; // null (default) indicates to inherit public readonly channelType: CHANNEL_TYPE | null; // null (default) indicates to inherit - protected run: (($: CommandMenu) => Promise) | string; - protected readonly subcommands: Collection; // This is the final data structure you'll actually use to work with the commands the aliases point to. - protected channel: Command | null; - protected role: Command | null; - protected emote: Command | null; - protected message: Command | null; - protected user: Command | null; - protected id: Command | null; - protected idType: ID | null; - protected number: Command | null; - protected any: Command | null; + // The execute and subcommand properties are restricted to the class because subcommand recursion could easily break when manually handled. + // The class will handle checking for null fields. + private run: (($: CommandMenu) => Promise) | string; + private readonly subcommands: Collection; // This is the final data structure you'll actually use to work with the commands the aliases point to. + private channel: Command | null; + private role: Command | null; + private emote: Command | null; + private message: Command | null; + private user: Command | null; + private id: Command | null; + private idType: ID | null; + private number: Command | null; + private any: Command | null; constructor(options?: CommandOptions) { this.description = options?.description || "No description."; @@ -240,6 +243,9 @@ export class Command { // Go through the arguments provided and find the right subcommand, then execute with the given arguments. // Will return null if it successfully executes, SingleMessageOptions if there's an error (to let the user know what it is). + // + // Calls the resulting subcommand's execute method in order to make more modular code, basically pushing the chain of execution to the subcommand. + // For example, a numeric subcommand would accept args of [4] then execute on it. public async execute( args: string[], menu: CommandMenu, @@ -261,12 +267,12 @@ export class Command { // 1. Does this command specify a required channel type? If so, does the channel type match? if ( metadata.channelType === CHANNEL_TYPE.GUILD && - (!(menu.channel instanceof GuildChannel) || menu.guild === null) + (!(menu.channel instanceof GuildChannel) || menu.guild === null || menu.member === null) ) { return {content: "This command must be executed in a server."}; } else if ( metadata.channelType === CHANNEL_TYPE.DM && - (menu.channel.type !== "dm" || menu.guild !== null) + (menu.channel.type !== "dm" || menu.guild !== null || menu.member !== null) ) { return {content: "This command must be executed as a direct message."}; } diff --git a/src/core/libd.ts b/src/core/libd.ts index 3746b6a..a8df915 100644 --- a/src/core/libd.ts +++ b/src/core/libd.ts @@ -247,3 +247,14 @@ export async function callMemberByUsername( else send(`Couldn't find a user by the name of \`${username}\`!`); } else send("You must execute this command in a server!"); } + +// TO DO Section // + +// getGuildByID() - checks for guild.available (boolean) +// getGuildByName() +// findMemberByNickname() - gets a member by their nickname or their username +// findUserByUsername() + +// For "get x by y" methods: +// Caching: All guilds, channels, and roles are fully cached, while the caches for messages, users, and members aren't complete. +// It's more reliable to get users/members by fetching their IDs. fetch() will searching through the cache anyway. From e7dfab75927bc6599207b7c4e49162c20edf7007 Mon Sep 17 00:00:00 2001 From: WatDuhHekBro <44940783+WatDuhHekBro@users.noreply.github.com> Date: Wed, 7 Apr 2021 05:58:09 -0500 Subject: [PATCH 148/178] Updated the documentation --- docs/Documentation.md | 120 +++++++++++++++++----------------------- docs/Overview.md | 8 +-- src/core/libd.ts | 17 +++++- src/core/permissions.ts | 7 +++ src/modules/storage.ts | 1 + src/structures.ts | 4 ++ 6 files changed, 82 insertions(+), 75 deletions(-) diff --git a/docs/Documentation.md b/docs/Documentation.md index b61381c..d035587 100644 --- a/docs/Documentation.md +++ b/docs/Documentation.md @@ -1,6 +1,12 @@ # Table of Contents -... +- [Structure](#structure) +- [Version Numbers](#version-numbers) +- [Message Subcommand Type](#message-subcommand-type) +- [Command Menu](#command-menu) +- [Command Metadata](#command-metadata) +- [Command Var String](#command-var-string) +- [Utility Functions](#utility-functions) # Structure @@ -36,96 +42,70 @@ Because versions are assigned to batches of changes rather than single changes ( - `https://discord.com/channels///` comes from the `Copy Message Link` button. - `-` comes from holding `Shift` on the desktop application and clicking on the `Copy ID` button. +# Command Menu +- `args`: A list of arguments in the command. It's relative to the subcommand, so if you do `$test this 5`, `5` becomes `$.args[0]` if `this` is a subcommand. Args are already converted, so a `number` subcommand would return a number rather than a string. +- `client`: `message.client` +- `message`: `message` +- `channel`: `message.channel` +- `guild`: `message.guild` +- `author`: `message.author` +- `member`: `message.member` -# Cleanup is Soon™ +# Command Metadata -## Convenience Functions +- `description`: The command description that'll appear in the help menu. +- `endpoint`: Whether or not any arguments are allowed after the command. +- `usage`: Defines a custom usage when showing the command in the help menu. +- `permission`: *(Inherits)* -1 (default) indicates to inherit, 0 is the lowest rank, 1 is second lowest rank, and so on. +- `nsfw`: *(Inherits)* Whether or not the command is restricted to NSFW channels and DM channels. +- `channelType`: *(Inherits)* Whether the command is restricted to guild channels, DM channels, or has no restriction. Uses the `CHANNEL_TYPE` enum provided by the command handler. -This modularizes certain patterns of code to make things easier. +# Command Var String -- `$.paginate()`: Takes a message and some additional parameters and makes a reaction page with it. All the pagination logic is taken care of but nothing more, the page index is returned and you have to send a callback to do something with it. +- `%author%` - A user mention of the person who called the command. +- `%prefix%` - The prefix of the current guild. -```js +# Utility Functions + +## [src/core (libd)](../src/core/libd.ts) - Utility functions specific for working with Discord + +`paginate()` +```ts const pages = ['one', 'two', 'three']; -const msg = await $.channel.send(pages[0]); +const msg = await channel.send(pages[0]); -$.paginate(msg, $.author.id, pages.length, (page) => { - msg.edit(pages[page]); +paginate(msg, author.id, pages.length, (page) => { + msg.edit(pages[page]); }); ``` -- `$.prompt()`: Prompts the user about a decision before following through. +`prompt()` +```ts +const msg = await channel.send('Are you sure you want to delete this?'); -```js -const msg = await $.channel.send('Are you sure you want to delete this?'); - -$.prompt(msg, $.author.id, () => { - delete this; // Replace this with actual code. +prompt(msg, author.id, () => { + //... }); ``` -- `$.getMemberByUsername()`: Gets a user by their username. Gets the first one then rolls with it. -- `$.callMemberByUsername()`: Convenience function to handle cases where someone isn't found by a username automatically. - -```js -$.callMemberByUsername($.message, $.args.join(' '), (member) => { - $.channel.send(`Your nickname is ${member.nickname}.`); +`callMemberByUsername()` +```ts +callMemberByUsername(message, args.join(" "), (member) => { + channel.send(`Your nickname is ${member.nickname}.`); }); ``` -## Dynamic Properties +## [src/lib](../src/lib.ts) - General utility functions -These will be accessible only inside a `Command` and will change per message. - -- `$.args`: A list of arguments in the command. It's relative to the subcommand, so if you do `$test this 5`, `5` becomes `$.args[0]` if `this` is a subcommand. Args are already converted, so a `number` subcommand would return a number rather than a string. -- `$.client`: `message.client` -- `$.message`: `message` -- `$.channel`: `message.channel` -- `$.guild`: `message.guild` -- `$.author`: `message.author` -- `$.member`: `message.member` - -# Wrappers - -This is similar to modifying a primitive object's `prototype` without actually doing so. - -## NumberWrapper - -- `.pluralise()`: A substitute for not having to do `amount === 1 ? "singular" : "plural"`. For example, `$(x).pluralise("credit", "s")` will return `"1 credit"` and/or `"5 credits"` respectively. -- `.pluraliseSigned()`: This builds on `.pluralise()` and adds a sign at the beginning for marking changes/differences. `$(0).pluraliseSigned("credit", "s")` will return `"+0 credits"`. - -## StringWrapper - -- `.replaceAll()`: A non-regex alternative to replacing everything in a string. `$("test").replaceAll('t', 'z')` = `"zesz"`. -- `.toTitleCase()`: Capitalizes the first letter of each word. `$("this is some text").toTitleCase()` = `"This Is Some Text"`. - -## ArrayWrapper - -- `.random()`: Returns a random element from an array. `$([1,2,3]).random()` could be any one of those elements. -- `.split()`: Splits an array into different arrays by a specified length. `$([1,2,3,4,5,6,7,8,9,10]).split(3)` = `[[1,2,3],[4,5,6],[7,8,9],[10]]`. - -# Other Library Functions - -These do have to be manually imported, which are used more on a case-by-case basis. - -- `formatTimestamp()`: Formats a `Date` object into your system's time. `YYYY-MM-DD HH:MM:SS` -- `formatUTCTimestamp()`: Formats a `Date` object into UTC time. `YYYY-MM-DD HH:MM:SS` -- `botHasPermission()`: Tests if a bot has a certain permission in a specified guild. - `parseArgs()`: Turns `call test "args with spaces" "even more spaces"` into `["call", "test", "args with spaces", "even more spaces"]`, inspired by the command line. - `parseVars()`: Replaces all `%` args in a string with stuff you specify. For example, you can replace all `nop` with `asm`, and `register %nop%` will turn into `register asm`. Useful for storing strings with variables in one place them accessing them in another place. - `isType()`: Used for type-checking. Useful for testing `any` types. - `select()`: Checks if a variable matches a certain type and uses the fallback value if not. (Warning: Type checking is based on the fallback's type. Be sure that the "type" parameter is accurate to this!) - `Random`: An object of functions containing stuff related to randomness. `Random.num` is a random decimal, `Random.int` is a random integer, `Random.chance` takes a number ranging from `0` to `1` as a percentage. `Random.sign` takes a number and has a 50-50 chance to be negative or positive. `Random.deviation` takes a number and a magnitude and produces a random number within those confines. `(5, 2)` would produce any number between `3` and `7`. - -# Other Core Functions - -- `permissions::hasPermission()`: Checks if a `Member` has a certain permission. -- `permissions::getPermissionLevel()`: Gets a `Member`'s permission level according to the permissions enum defined in the file. -- `structures::getPrefix()`: Get the current prefix of the guild or the bot's prefix if none is found. - -# The other core files - -- `core/permissions`: Contains all the permission roles and checking functions. -- `core/structures`: Contains all the code handling dynamic JSON data. Has a one-to-one connection with each file generated, for example, `Config` which calls `super("config")` meaning it writes to `data/config.json`. -- `core/storage`: Handles most of the file system operations, all of the ones related to `data` at least. +- `pluralise()`: A substitute for not having to do `amount === 1 ? "singular" : "plural"`. For example, `pluralise(x, "credit", "s")` will return `"1 credit"` and/or `"5 credits"` respectively. +- `pluraliseSigned()`: This builds on `pluralise()` and adds a sign at the beginning for marking changes/differences. `pluraliseSigned(0, "credit", "s")` will return `"+0 credits"`. +- `replaceAll()`: A non-regex alternative to replacing everything in a string. `replaceAll("test", "t", "z")` = `"zesz"`. +- `toTitleCase()`: Capitalizes the first letter of each word. `toTitleCase("this is some text")` = `"This Is Some Text"`. +- `random()`: Returns a random element from an array. `random([1,2,3])` could be any one of those elements. +- `split()`: Splits an array into different arrays by a specified length. `split([1,2,3,4,5,6,7,8,9,10], 3)` = `[[1,2,3],[4,5,6],[7,8,9],[10]]`. diff --git a/docs/Overview.md b/docs/Overview.md index 5b57397..31935f1 100644 --- a/docs/Overview.md +++ b/docs/Overview.md @@ -44,7 +44,7 @@ export default new NamedCommand({ ### Quick note on the run property -You can also enter a string for the `run` property which will send a message with that string specified ([you can also specify some variables in that string](Documentation.md#)). The above is functionally equivalent to the below. +You can also enter a string for the `run` property which will send a message with that string specified ([you can also specify some variables in that string](Documentation.md#command-var-string)). The above is functionally equivalent to the below. ```ts import {Command, NamedCommand} from "../core"; @@ -70,7 +70,7 @@ export default new NamedCommand({ }); ``` -Here, . For example, if this file was named `test.ts`, `$test <@237359961842253835>` would get the user by the ID `237359961842253835` into `args[0]` as a [User](https://discord.js.org/#/docs/main/stable/class/User) object. `$test experiment` would run as if you just called `$test` *(given that [endpoint](Documentation.md#) isn't set to `true`)*. +Here, . For example, if this file was named `test.ts`, `$test <@237359961842253835>` would get the user by the ID `237359961842253835` into `args[0]` as a [User](https://discord.js.org/#/docs/main/stable/class/User) object. `$test experiment` would run as if you just called `$test` *(given that [endpoint](Documentation.md#command-metadata) isn't set to `true`)*. If you want, you can typecast the argument to be more strongly typed, because the type of `args` is `any[]`. *([See why if you're curious.](DesignDecisions.md#any[]-parameters-for-subcommand-run))* @@ -171,7 +171,7 @@ export default new NamedCommand({ Here, the property `channelType` would spread to all subcommands unless a subcommand defines it. Using the above example, the `channelType` for both `$siege` and `$siege pineapple` would be `CHANNEL_TYPE.GUILD`. -*To get a full list of metadata properties, see the [documentation](Documentation.md#).* +*To get a full list of metadata properties, see the [documentation](Documentation.md#command-menu).* ## Utility Functions @@ -187,7 +187,7 @@ export default new NamedCommand({ }); ``` -*To get a full list of utility functions, see the [documentation](Documentation.md#).* +*To get a full list of utility functions, see the [documentation](Documentation.md#utility-functions).* # Adding a new non-command feature diff --git a/src/core/libd.ts b/src/core/libd.ts index a8df915..84d6ea2 100644 --- a/src/core/libd.ts +++ b/src/core/libd.ts @@ -13,6 +13,9 @@ import {unreactEventListeners, replyEventListeners} from "./eventListeners"; export type SingleMessageOptions = MessageOptions & {split?: false}; +/** + * Tests if a bot has a certain permission in a specified guild. + */ export function botHasPermission(guild: Guild | null, permission: number): boolean { return !!guild?.me?.hasPermission(permission); } @@ -21,6 +24,10 @@ export function botHasPermission(guild: Guild | null, permission: number): boole // Pagination function that allows for customization via a callback. // Define your own pages outside the function because this only manages the actual turning of pages. + +/** + * Takes a message and some additional parameters and makes a reaction page with it. All the pagination logic is taken care of but nothing more, the page index is returned and you have to send a callback to do something with it. + */ export async function paginate( channel: TextChannel | DMChannel | NewsChannel, senderID: string, @@ -90,6 +97,9 @@ export async function paginate( // Waits for the sender to either confirm an action or let it pass (and delete the message). // This should probably be renamed to "confirm" now that I think of it, "prompt" is better used elsewhere. // Append "\n*(This message will automatically be deleted after 10 seconds.)*" in the future? +/** + * Prompts the user about a decision before following through. + */ export async function prompt(message: Message, senderID: string, onConfirm: () => void, duration = 10000) { let isDeleted = false; @@ -222,6 +232,9 @@ export async function askMultipleChoice( if (!isDeleted) message.delete(); } +/** + * Gets a user by their username. Gets the first one then rolls with it. + */ export async function getMemberByUsername(guild: Guild, username: string) { return ( await guild.members.fetch({ @@ -231,7 +244,9 @@ export async function getMemberByUsername(guild: Guild, username: string) { ).first(); } -/** Convenience function to handle false cases automatically. */ +/** + * Convenience function to handle cases where someone isn't found by a username automatically. + */ export async function callMemberByUsername( message: Message, username: string, diff --git a/src/core/permissions.ts b/src/core/permissions.ts index fb430c8..ba3be33 100644 --- a/src/core/permissions.ts +++ b/src/core/permissions.ts @@ -1,12 +1,19 @@ +// Contains all the permission roles and checking functions. import {User, GuildMember} from "discord.js"; import {permissionLevels} from "./interface"; +/** + * Checks if a `Member` has a certain permission. + */ export function hasPermission(user: User, member: GuildMember | null, permission: number): boolean { for (let i = permissionLevels.length - 1; i >= permission; i--) if (permissionLevels[i].check(user, member)) return true; return false; } +/** + * Gets a `Member`'s permission level according to the permissions enum defined in the file. + */ export function getPermissionLevel(user: User, member: GuildMember | null): number { for (let i = permissionLevels.length - 1; i >= 0; i--) if (permissionLevels[i].check(user, member)) return i; return 0; diff --git a/src/modules/storage.ts b/src/modules/storage.ts index 8553fc6..c38ddc6 100644 --- a/src/modules/storage.ts +++ b/src/modules/storage.ts @@ -1,3 +1,4 @@ +// Handles most of the file system operations, all of the ones related to `data` at least. import fs from "fs"; const Storage = { diff --git a/src/structures.ts b/src/structures.ts index f764d5a..351bbb6 100644 --- a/src/structures.ts +++ b/src/structures.ts @@ -1,3 +1,4 @@ +// Contains all the code handling dynamic JSON data. Has a one-to-one connection with each file generated, for example, `Config` which calls `super("config")` meaning it writes to `data/config.json`. import FileManager from "./modules/storage"; import {select, GenericJSON, GenericStructure} from "./lib"; import {watch} from "fs"; @@ -145,6 +146,9 @@ if (IS_DEV_MODE) { }); } +/** + * Get the current prefix of the guild or the bot's prefix if none is found. + */ export function getPrefix(guild: DiscordGuild | null): string { let prefix = Config.prefix; From 053b835e89ea7148172ee1e04f46164af712fddf Mon Sep 17 00:00:00 2001 From: Mijyuoon Date: Wed, 7 Apr 2021 17:01:44 +0300 Subject: [PATCH 149/178] Improve emote resolving for .emote and .react commands (#33) --- src/commands/utilities/emote.ts | 7 +- src/commands/utilities/react.ts | 19 +-- .../utilities/subcommands/emote-utils.ts | 127 ++++++++++++++---- 3 files changed, 113 insertions(+), 40 deletions(-) diff --git a/src/commands/utilities/emote.ts b/src/commands/utilities/emote.ts index 912774c..d62a3d0 100644 --- a/src/commands/utilities/emote.ts +++ b/src/commands/utilities/emote.ts @@ -1,5 +1,5 @@ import Command from "../../core/command"; -import {queryClosestEmoteByName} from "./subcommands/emote-utils"; +import {processEmoteQueryFormatted} from "./subcommands/emote-utils"; import {botHasPermission} from "../../core/lib"; import {Permissions} from "discord.js"; @@ -10,9 +10,8 @@ export default new Command({ description: "The emote(s) to send.", usage: "", async run({guild, channel, message, args}) { - let output = ""; - for (const query of args) output += queryClosestEmoteByName(query).toString(); - channel.send(output); + const output = processEmoteQueryFormatted(args); + if (output.length > 0) channel.send(output); } }) }); diff --git a/src/commands/utilities/react.ts b/src/commands/utilities/react.ts index 49648aa..13fabd3 100644 --- a/src/commands/utilities/react.ts +++ b/src/commands/utilities/react.ts @@ -1,7 +1,7 @@ import Command from "../../core/command"; import {CommonLibrary} from "../../core/lib"; import {Message, Channel, TextChannel} from "discord.js"; -import {queryClosestEmoteByName} from "./subcommands/emote-utils"; +import {processEmoteQueryArray} from "./subcommands/emote-utils"; export default new Command({ description: @@ -10,17 +10,11 @@ export default new Command({ async run($: CommonLibrary): Promise { let target: Message | undefined; let distance = 1; - - // allows reactions by using an in-line reply - if($.message.reference){ - const messageID = $.message.reference.messageID; - try{ - target = await $.channel.messages.fetch(messageID!) - }catch{ - return $.channel.send("Unknown error occurred!") - } + + if ($.message.reference) { + // If the command message is a reply to another message, use that as the react target. + target = await $.channel.messages.fetch($.message.reference.messageID!); } - // handles reacts by message id/distance else if ($.args.length >= 2) { const last = $.args[$.args.length - 1]; // Because this is optional, do not .pop() unless you're sure it's a message link indicator. @@ -106,9 +100,8 @@ export default new Command({ ).last(); } - for (const search of $.args) { + for (const emote of processEmoteQueryArray($.args)) { // Even though the bot will always grab *some* emote, the user can choose not to keep that emote there if it isn't what they want - const emote = queryClosestEmoteByName(search); const reaction = await target!.react(emote); // This part is called with a promise because you don't want to wait 5 seconds between each reaction. diff --git a/src/commands/utilities/subcommands/emote-utils.ts b/src/commands/utilities/subcommands/emote-utils.ts index 0e18652..16ca2eb 100644 --- a/src/commands/utilities/subcommands/emote-utils.ts +++ b/src/commands/utilities/subcommands/emote-utils.ts @@ -1,33 +1,114 @@ +import {GuildEmoji} from "discord.js"; import {client} from "../../../index"; -// Calculate and match the list of emotes against the queried emote, then sort the IDs based on calculated priority. -export function queryClosestEmoteByName(query: string) { - const priorityTable: {[id: string]: number} = {}; +// Levenshtein distance coefficients for all transformation types. +// TODO: Investigate what values result in the most optimal matching strategy. +const directMatchWeight = 0.0; +const uppercaseWeight = 0.2; +const lowercaseWeight = 0.5; +const substitutionWeight = 1.0; +const deletionWeight = 1.5; +const insertionWeight = 1.5; - for (const emote of client.emojis.cache.values()) priorityTable[emote.id] = compareEmoteNames(emote.name, query); +// Maximum Levenshtein distance for an emote to be considered a suitable match candidate. +const maxAcceptedDistance = 3.0; - const resultingIDs = Object.keys(priorityTable).sort((a, b) => priorityTable[b] - priorityTable[a]); - return client.emojis.cache.get(resultingIDs[0])!; -} +// Algorithm taken from https://en.wikipedia.org/wiki/Levenshtein_distance#Iterative_with_two_matrix_rows +// Modified for separate handling of uppercasing and lowercasing transformations. +function levenshtein(s: string, t: string): number { + const m = s.length; + const n = t.length; -// Compare an emote's name against a query to see how alike the two are. The higher the number, the closer they are. Takes into account length and capitalization. -function compareEmoteNames(emote: string, query: string) { - let likeness = -Math.abs(emote.length - query.length); - const isQueryLonger = query.length > emote.length; + let v0 = new Array(n + 1); + let v1 = new Array(n + 1); - // Loop through all indexes that the two strings share then compare each letter. - for (let i = 0; i < (isQueryLonger ? emote.length : query.length); i++) { - const c = emote[i]; - const q = query[i]; + let i, j; - // If they're the exact same character - if (c === q) likeness += 1.5; - // If the emote is uppercase but the query is lowercase - else if (c === q.toUpperCase()) likeness += 1; - // If the emote is lowercase but the query is uppercase - else if (c === q.toLowerCase()) likeness += 0.5; - // Otherwise, if they're different characters, don't add anything (this isn't a spellchecker) + for (i = 0; i <= n; i++) v0[i] = i; + + for (i = 0; i < m; i++) { + v1[0] = i + 1; + + for (j = 0; j < n; j++) { + let r; + + if (s[i] === t[j]) r = directMatchWeight; + else if (s[i] === t[j].toUpperCase()) r = uppercaseWeight; + else if (s[i] === t[j].toLowerCase()) r = lowercaseWeight; + else r = substitutionWeight; + + v1[j + 1] = Math.min( + v0[j + 1] + deletionWeight, + v1[j] + insertionWeight, + v0[j] + r); + } + + const tmp = v1; + v1 = v0, v0 = tmp; } - return likeness; + return v0[n]; } + +function searchSimilarEmotes(query: string): GuildEmoji[] { + const emoteCandidates: {emote: GuildEmoji, dist: number}[] = []; + + for (const emote of client.emojis.cache.values()) { + const dist = levenshtein(emote.name, query); + if (dist <= maxAcceptedDistance) { + emoteCandidates.push({ emote, dist }); + } + } + + emoteCandidates.sort((b, a) => b.dist - a.dist); + return emoteCandidates.map(em => em.emote); +} + +const unicodeEmojiRegex = /^(?:[\u2700-\u27bf]|(?:\ud83c[\udde6-\uddff]){2}|[\ud800-\udbff][\udc00-\udfff]|[\u0023-\u0039]\ufe0f?\u20e3|\u3299|\u3297|\u303d|\u3030|\u24c2|\ud83c[\udd70-\udd71]|\ud83c[\udd7e-\udd7f]|\ud83c\udd8e|\ud83c[\udd91-\udd9a]|\ud83c[\udde6-\uddff]|\ud83c[\ude01-\ude02]|\ud83c\ude1a|\ud83c\ude2f|\ud83c[\ude32-\ude3a]|\ud83c[\ude50-\ude51]|\u203c|\u2049|[\u25aa-\u25ab]|\u25b6|\u25c0|[\u25fb-\u25fe]|\u00a9|\u00ae|\u2122|\u2139|\ud83c\udc04|[\u2600-\u26FF]|\u2b05|\u2b06|\u2b07|\u2b1b|\u2b1c|\u2b50|\u2b55|\u231a|\u231b|\u2328|\u23cf|[\u23e9-\u23f3]|[\u23f8-\u23fa]|\ud83c\udccf|\u2934|\u2935|[\u2190-\u21ff])[\ufe00-\ufe0f]?$/; +const discordEmoteMentionRegex = /^$/; +const emoteNameWithSelectorRegex = /^(.+)~(\d+)$/; + +function processEmoteQuery(query: string[], isFormatted: boolean): string[] { + return query.map(emote => { + emote = emote.trim(); + + // If the query directly matches a Unicode emoji or a Discord custom emote mention, pass it as-is. + if (discordEmoteMentionRegex.test(emote) + || unicodeEmojiRegex.test(emote)) return emote; + + // If formatted mode is enabled, parse whitespace and newline elements. + if (isFormatted) { + if (emote == "-") return " "; + if (emote == "+") return "\n"; + } + + // Selector number used for disambiguating multiple emotes with same name. + let selector = 0; + + // If the query has emoteName~123 format, extract the actual name and the selector number. + const queryWithSelector = emote.match(emoteNameWithSelectorRegex); + if (queryWithSelector) { + emote = queryWithSelector[1]; + selector = +queryWithSelector[2]; + } + + // Try to match an emote name directly if the selector is for the closest match. + if (selector == 0) { + const directMatchEmote = client.emojis.cache.find(em => em.name === emote); + if (directMatchEmote) return directMatchEmote.toString(); + } + + // Find all similar emote candidates within certian threshold and select Nth top one according to the selector. + const similarEmotes = searchSimilarEmotes(emote); + if (similarEmotes.length > 0) { + selector = Math.min(selector, similarEmotes.length); + return similarEmotes[selector].toString(); + } + + // Return some "missing/invalid emote" indicator. + return "❓"; + }); +} + +export const processEmoteQueryArray = (query: string[]): string[] => processEmoteQuery(query, false); +export const processEmoteQueryFormatted = (query: string[]): string => processEmoteQuery(query, true).join(""); \ No newline at end of file From 066d210107ab4669d751cb7ed875e05eaa40d231 Mon Sep 17 00:00:00 2001 From: Mijyuoon Date: Thu, 8 Apr 2021 02:41:16 +0300 Subject: [PATCH 150/178] Fixed a bug in .emote command when the selector number is too large --- src/commands/utilities/subcommands/emote-utils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/commands/utilities/subcommands/emote-utils.ts b/src/commands/utilities/subcommands/emote-utils.ts index 16ca2eb..0fdbf9c 100644 --- a/src/commands/utilities/subcommands/emote-utils.ts +++ b/src/commands/utilities/subcommands/emote-utils.ts @@ -101,7 +101,7 @@ function processEmoteQuery(query: string[], isFormatted: boolean): string[] { // Find all similar emote candidates within certian threshold and select Nth top one according to the selector. const similarEmotes = searchSimilarEmotes(emote); if (similarEmotes.length > 0) { - selector = Math.min(selector, similarEmotes.length); + selector = Math.min(selector, similarEmotes.length - 1); return similarEmotes[selector].toString(); } @@ -111,4 +111,4 @@ function processEmoteQuery(query: string[], isFormatted: boolean): string[] { } export const processEmoteQueryArray = (query: string[]): string[] => processEmoteQuery(query, false); -export const processEmoteQueryFormatted = (query: string[]): string => processEmoteQuery(query, true).join(""); \ No newline at end of file +export const processEmoteQueryFormatted = (query: string[]): string => processEmoteQuery(query, true).join(""); From 20fb2135c77bf36879b56494b204d4d531c646e0 Mon Sep 17 00:00:00 2001 From: WatDuhHekBro <44940783+WatDuhHekBro@users.noreply.github.com> Date: Thu, 8 Apr 2021 06:37:49 -0500 Subject: [PATCH 151/178] Implemented various ideas from backlog --- CHANGELOG.md | 10 +++ src/commands/fun/eco.ts | 9 +- src/commands/fun/modules/eco-extras.ts | 44 ++++++++++ src/commands/fun/party.ts | 13 +++ src/commands/fun/thonk.ts | 9 +- src/commands/fun/urban.ts | 44 +++++----- src/commands/fun/vaporwave.ts | 34 ++++++++ src/commands/fun/weather.ts | 62 +++++++------- src/commands/system/admin.ts | 16 +++- src/commands/utility/emote.ts | 5 +- src/commands/utility/modules/emote-utils.ts | 1 + src/commands/utility/scanemotes.ts | 10 +++ src/commands/utility/translate.ts | 11 ++- src/defs/translate.d.ts | 9 ++ src/defs/urban.d.ts | 17 ++++ src/defs/weather.d.ts | 92 +++++++++++++++++++++ 16 files changed, 322 insertions(+), 64 deletions(-) create mode 100644 src/commands/fun/party.ts create mode 100644 src/commands/fun/vaporwave.ts create mode 100644 src/defs/translate.d.ts create mode 100644 src/defs/urban.d.ts create mode 100644 src/defs/weather.d.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index fa8c546..4142c6c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ +# ??? +- `vaporwave`: Transforms input into full-width text +- `eco post`: A play on `eco get` +- `admin set prefix (<@bot>)`: Allows you to target a bot when setting a prefix if two bots have conflicting prefixes +- `party`: Sets the bot's status to streaming with a certain URL +- `eco award`: Awards users with Mons, only accessible by that person +- `thonk`: A result can now be discarded if the person who called the command reacts with ❌ +- `scanemotes forcereset`: Removes the cooldown on `scanemotes`, only accessible by bot support and up +- `urban`: Bug fixes + # 3.2.0 - Internal refactor, more subcommand types, and more command type guards (2021-??-??) - The custom logger changed: `$.log` no longer exists, it's just `console.log`. Now you don't have to do `import $ from "../core/lib"` at the top of every file that uses the custom logger. - Utility functions are no longer attached to the command menu. Stuff like `$.paginate()` and `$(5).pluralise()` instead need to be imported and used as regular functions. diff --git a/src/commands/fun/eco.ts b/src/commands/fun/eco.ts index 022383a..c87241a 100644 --- a/src/commands/fun/eco.ts +++ b/src/commands/fun/eco.ts @@ -2,7 +2,7 @@ import {Command, NamedCommand, callMemberByUsername} from "../../core"; import {isAuthorized, getMoneyEmbed} from "./modules/eco-utils"; import {DailyCommand, PayCommand, GuildCommand, LeaderboardCommand} from "./modules/eco-core"; import {BuyCommand, ShopCommand} from "./modules/eco-shop"; -import {MondayCommand} from "./modules/eco-extras"; +import {MondayCommand, AwardCommand} from "./modules/eco-extras"; import {BetCommand} from "./modules/eco-bet"; export default new NamedCommand({ @@ -18,7 +18,12 @@ export default new NamedCommand({ buy: BuyCommand, shop: ShopCommand, monday: MondayCommand, - bet: BetCommand + bet: BetCommand, + award: AwardCommand, + post: new NamedCommand({ + description: "A play on `eco get`", + run: "`405 Method Not Allowed`" + }) }, id: "user", user: new Command({ diff --git a/src/commands/fun/modules/eco-extras.ts b/src/commands/fun/modules/eco-extras.ts index f2760dd..2d69f29 100644 --- a/src/commands/fun/modules/eco-extras.ts +++ b/src/commands/fun/modules/eco-extras.ts @@ -1,6 +1,8 @@ import {Command, NamedCommand} from "../../../core"; import {Storage} from "../../../structures"; import {isAuthorized, getMoneyEmbed} from "./eco-utils"; +import {User} from "discord.js"; +import {pluralise} from "../../../lib"; const WEEKDAY = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]; @@ -32,3 +34,45 @@ export const MondayCommand = new NamedCommand({ } } }); + +export const AwardCommand = new NamedCommand({ + description: "Only usable by Mon, awards one or a specified amount of Mons to the user.", + usage: " ()", + aliases: ["give"], + run: "You need to specify a user!", + user: new Command({ + async run({message, channel, guild, author, member, client, args}) { + if (author.id === "394808963356688394" || IS_DEV_MODE) { + const target = args[0] as User; + const user = Storage.getUser(target.id); + user.money++; + Storage.save(); + channel.send(`1 Mon given to ${target.username}.`, getMoneyEmbed(target)); + } else { + channel.send("This command is restricted to the bean."); + } + }, + number: new Command({ + async run({message, channel, guild, author, member, client, args}) { + if (author.id === "394808963356688394" || IS_DEV_MODE) { + const target = args[0] as User; + const amount = Math.floor(args[1]); + + if (amount > 0) { + const user = Storage.getUser(target.id); + user.money += amount; + Storage.save(); + channel.send( + `${pluralise(amount, "Mon", "s")} given to ${target.username}.`, + getMoneyEmbed(target) + ); + } else { + channel.send("You need to enter a number greater than 0."); + } + } else { + channel.send("This command is restricted to the bean."); + } + } + }) + }) +}); diff --git a/src/commands/fun/party.ts b/src/commands/fun/party.ts new file mode 100644 index 0000000..90e62c5 --- /dev/null +++ b/src/commands/fun/party.ts @@ -0,0 +1,13 @@ +import {Command, NamedCommand} from "../../core"; + +export default new NamedCommand({ + description: "Initiates a celebratory stream from the bot.", + async run({message, channel, guild, author, member, client, args}) { + channel.send("This calls for a celebration!"); + client.user!.setActivity({ + type: "STREAMING", + url: "https://www.youtube.com/watch?v=dQw4w9WgXcQ", + name: "Celebration!" + }); + } +}); diff --git a/src/commands/fun/thonk.ts b/src/commands/fun/thonk.ts index 762549e..35e4d4a 100644 --- a/src/commands/fun/thonk.ts +++ b/src/commands/fun/thonk.ts @@ -36,6 +36,13 @@ export default new NamedCommand({ usage: "thonk ([text])", async run({message, channel, guild, author, member, client, args}) { if (args.length > 0) phrase = args.join(" "); - channel.send(transform(phrase)); + const msg = await channel.send(transform(phrase)); + msg.createReactionCollector( + (reaction, user) => { + if (user.id === author.id && reaction.emoji.name === "❌") msg.delete(); + return false; + }, + {time: 60000} + ); } }); diff --git a/src/commands/fun/urban.ts b/src/commands/fun/urban.ts index 2901433..212a2a5 100644 --- a/src/commands/fun/urban.ts +++ b/src/commands/fun/urban.ts @@ -1,27 +1,31 @@ import {Command, NamedCommand} from "../../core"; import {MessageEmbed} from "discord.js"; -// Anycasting Alert -const urban = require("relevant-urban"); +import urban from "relevant-urban"; export default new NamedCommand({ description: "Gives you a definition of the inputted word.", - async run({message, channel, guild, author, member, client, args}) { - if (!args[0]) { - channel.send("Please input a word."); + run: "Please input a word.", + any: new Command({ + async run({message, channel, guild, author, member, client, args}) { + // [Bug Fix]: Use encodeURIComponent() when emojis are used: "TypeError [ERR_UNESCAPED_CHARACTERS]: Request path contains unescaped characters" + urban(encodeURIComponent(args.join(" "))) + .then((res) => { + const embed = new MessageEmbed() + .setColor(0x1d2439) + .setTitle(res.word) + .setURL(res.urbanURL) + .setDescription(`**Definition:**\n*${res.definition}*\n\n**Example:**\n*${res.example}*`) + // [Bug Fix] When an embed field is empty (if the author field is missing, like the top entry for "british"): "RangeError [EMBED_FIELD_VALUE]: MessageEmbed field values may not be empty." + .addField("Author", res.author || "N/A", true) + .addField("Rating", `**\`Upvotes: ${res.thumbsUp} | Downvotes: ${res.thumbsDown}\`**`); + if (res.tags && res.tags.length > 0 && res.tags.join(" ").length < 1024) + embed.addField("Tags", res.tags.join(", "), true); + + channel.send(embed); + }) + .catch(() => { + channel.send("Sorry, that word was not found."); + }); } - const res = await urban(args.join(" ")).catch((e: Error) => { - return channel.send("Sorry, that word was not found."); - }); - const embed = new MessageEmbed() - .setColor(0x1d2439) - .setTitle(res.word) - .setURL(res.urbanURL) - .setDescription(`**Definition:**\n*${res.definition}*\n\n**Example:**\n*${res.example}*`) - .addField("Author", res.author, true) - .addField("Rating", `**\`Upvotes: ${res.thumbsUp} | Downvotes: ${res.thumbsDown}\`**`); - if (res.tags.length > 0 && res.tags.join(" ").length < 1024) { - embed.addField("Tags", res.tags.join(", "), true); - } - channel.send(embed); - } + }) }); diff --git a/src/commands/fun/vaporwave.ts b/src/commands/fun/vaporwave.ts new file mode 100644 index 0000000..ad6b945 --- /dev/null +++ b/src/commands/fun/vaporwave.ts @@ -0,0 +1,34 @@ +import {Command, NamedCommand} from "../../core"; + +const vaporwave = (() => { + const map = new Map(); + const vaporwave = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!"#$%&'()*+,-./0123456789:;<=>?@[\]^_`{|}~ "; + const normal = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!\"#$%&'()*+,-./0123456789:;<=>?@[\\]^_`{|}~ "; + if (vaporwave.length !== normal.length) console.error("Vaporwave text failed to load properly!"); + for (let i = 0; i < vaporwave.length; i++) map.set(normal[i], vaporwave[i]); + return map; +})(); + +function getVaporwaveText(text: string): string { + let output = ""; + + for (const c of text) { + const transformed = vaporwave.get(c); + if (transformed) output += transformed; + } + + return output; +} + +export default new NamedCommand({ + description: "Transforms your text into vaporwave.", + run: "You need to enter some text!", + any: new Command({ + async run({message, channel, guild, author, member, client, args}) { + const text = getVaporwaveText(args.join(" ")); + if (text !== "") channel.send(text); + else channel.send("Make sure to enter at least one valid character."); + } + }) +}); diff --git a/src/commands/fun/weather.ts b/src/commands/fun/weather.ts index 590755f..07c44f5 100644 --- a/src/commands/fun/weather.ts +++ b/src/commands/fun/weather.ts @@ -1,36 +1,38 @@ import {Command, NamedCommand} from "../../core"; import {MessageEmbed} from "discord.js"; -// Anycasting Alert -const weather = require("weather-js"); +import {find} from "weather-js"; export default new NamedCommand({ description: "Shows weather info of specified location.", - async run({message, channel, guild, author, member, client, args}) { - if (args.length == 0) return channel.send("You need to provide a city."); - return weather.find( - { - search: args.join(" "), - degreeType: "C" - }, - function (err: any, result: any) { - if (err) channel.send(err); - var current = result[0].current; - var location = result[0].location; - const embed = new MessageEmbed() - .setDescription(`**${current.skytext}**`) - .setAuthor(`Weather for ${current.observationpoint}`) - .setThumbnail(current.imageUrl) - .setColor(0x00ae86) - .addField("Timezone", `UTC${location.timezone}`, true) - .addField("Degree Type", "C", true) - .addField("Temperature", `${current.temperature} Degrees`, true) - .addField("Feels like", `${current.feelslike} Degrees`, true) - .addField("Winds", current.winddisplay, true) - .addField("Humidity", `${current.humidity}%`, true); - channel.send({ - embed - }); - } - ); - } + run: "You need to provide a city.", + any: new Command({ + async run({message, channel, guild, author, member, client, args}) { + find( + { + search: args.join(" "), + degreeType: "C" + }, + function (error, result) { + if (error) return channel.send(error.toString()); + if (result.length === 0) return channel.send("No city found by that name."); + var current = result[0].current; + var location = result[0].location; + const embed = new MessageEmbed() + .setDescription(`**${current.skytext}**`) + .setAuthor(`Weather for ${current.observationpoint}`) + .setThumbnail(current.imageUrl) + .setColor(0x00ae86) + .addField("Timezone", `UTC${location.timezone}`, true) + .addField("Degree Type", "C", true) + .addField("Temperature", `${current.temperature} Degrees`, true) + .addField("Feels like", `${current.feelslike} Degrees`, true) + .addField("Winds", current.winddisplay, true) + .addField("Humidity", `${current.humidity}%`, true); + return channel.send({ + embed + }); + } + ); + } + }) }); diff --git a/src/commands/system/admin.ts b/src/commands/system/admin.ts index 5e0c225..f487e07 100644 --- a/src/commands/system/admin.ts +++ b/src/commands/system/admin.ts @@ -1,7 +1,7 @@ import {Command, NamedCommand, botHasPermission, getPermissionLevel, getPermissionName, CHANNEL_TYPE} from "../../core"; import {clean} from "../../lib"; import {Config, Storage} from "../../structures"; -import {Permissions, TextChannel} from "discord.js"; +import {Permissions, TextChannel, User} from "discord.js"; import {logs} from "../../modules/globals"; function getLogBuffer(type: string) { @@ -34,7 +34,7 @@ export default new NamedCommand({ subcommands: { prefix: new NamedCommand({ description: "Set a custom prefix for your guild. Removes your custom prefix if none is provided.", - usage: "()", + usage: "() (<@bot>)", async run({message, channel, guild, author, member, client, args}) { Storage.getGuild(guild!.id).prefix = null; Storage.save(); @@ -47,7 +47,17 @@ export default new NamedCommand({ Storage.getGuild(guild!.id).prefix = args[0]; Storage.save(); channel.send(`The custom prefix for this guild is now \`${args[0]}\`.`); - } + }, + user: new Command({ + description: "Specifies the bot in case of conflicting prefixes.", + async run({message, channel, guild, author, member, client, args}) { + if ((args[1] as User).id === client.user!.id) { + Storage.getGuild(guild!.id).prefix = args[0]; + Storage.save(); + channel.send(`The custom prefix for this guild is now \`${args[0]}\`.`); + } + } + }) }) }), welcome: new NamedCommand({ diff --git a/src/commands/utility/emote.ts b/src/commands/utility/emote.ts index 22bc97b..1b8aa07 100644 --- a/src/commands/utility/emote.ts +++ b/src/commands/utility/emote.ts @@ -2,8 +2,9 @@ import {Command, NamedCommand} from "../../core"; import {processEmoteQueryFormatted} from "./modules/emote-utils"; export default new NamedCommand({ - description: "Send the specified emote.", - run: "Please provide a command name.", + description: + "Send the specified emote list. Enter + to move an emote list to the next line, - to add a space, and _ to add a zero-width space.", + run: "Please provide a list of emotes.", any: new Command({ description: "The emote(s) to send.", usage: "", diff --git a/src/commands/utility/modules/emote-utils.ts b/src/commands/utility/modules/emote-utils.ts index f766cba..2c6df68 100644 --- a/src/commands/utility/modules/emote-utils.ts +++ b/src/commands/utility/modules/emote-utils.ts @@ -76,6 +76,7 @@ function processEmoteQuery(query: string[], isFormatted: boolean): string[] { if (isFormatted) { if (emote == "-") return " "; if (emote == "+") return "\n"; + if (emote == "_") return "\u200b"; } // Selector number used for disambiguating multiple emotes with same name. diff --git a/src/commands/utility/scanemotes.ts b/src/commands/utility/scanemotes.ts index a60d861..3662c57 100644 --- a/src/commands/utility/scanemotes.ts +++ b/src/commands/utility/scanemotes.ts @@ -182,5 +182,15 @@ export default new NamedCommand({ } return await channel.send(lines, {split: true}); + }, + subcommands: { + forcereset: new NamedCommand({ + description: "Forces the cooldown timer to reset.", + permission: PERMISSIONS.BOT_SUPPORT, + async run({message, channel, guild, author, member, client, args}) { + lastUsedTimestamps[guild!.id] = 0; + channel.send("Reset the cooldown on `scanemotes`."); + } + }) } }); diff --git a/src/commands/utility/translate.ts b/src/commands/utility/translate.ts index b697855..e0e9250 100644 --- a/src/commands/utility/translate.ts +++ b/src/commands/utility/translate.ts @@ -1,6 +1,5 @@ import {Command, NamedCommand} from "../../core"; -// Anycasting Alert -const translate = require("translate-google"); +import translate from "translate-google"; export default new NamedCommand({ description: "Translates your input.", @@ -11,7 +10,7 @@ export default new NamedCommand({ translate(input, { to: lang }) - .then((res: any) => { + .then((res) => { channel.send({ embed: { title: "Translation", @@ -28,10 +27,10 @@ export default new NamedCommand({ } }); }) - .catch((err: any) => { - console.error(err); + .catch((error) => { + console.error(error); channel.send( - `${err}\nPlease use the following list: https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes` + `${error}\nPlease use the following list: https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes` ); }); } diff --git a/src/defs/translate.d.ts b/src/defs/translate.d.ts new file mode 100644 index 0000000..15e022b --- /dev/null +++ b/src/defs/translate.d.ts @@ -0,0 +1,9 @@ +interface TranslateOptions { + from?: string; + to?: string; +} + +declare module "translate-google" { + function translate(input: string, options: TranslateOptions): Promise; + export = translate; +} diff --git a/src/defs/urban.d.ts b/src/defs/urban.d.ts new file mode 100644 index 0000000..cc97806 --- /dev/null +++ b/src/defs/urban.d.ts @@ -0,0 +1,17 @@ +interface Definition { + id: number; + word: string; + thumbsUp: number; + thumbsDown: number; + author: string; + urbanURL: string; + example: string; + definition: string; + tags: string[] | null; + sounds: string[] | null; +} + +declare module "relevant-urban" { + function urban(query: string): Promise; + export = urban; +} diff --git a/src/defs/weather.d.ts b/src/defs/weather.d.ts new file mode 100644 index 0000000..972de8c --- /dev/null +++ b/src/defs/weather.d.ts @@ -0,0 +1,92 @@ +interface WeatherJSOptions { + search: string; + lang?: string; + degreeType?: string; + timeout?: number; +} + +interface WeatherJSResult { + location: { + name: string; + lat: string; + long: string; + timezone: string; + alert: string; + degreetype: string; + imagerelativeurl: string; + }; + current: { + temperature: string; + skycode: string; + skytext: string; + date: string; + observationtime: string; + observationpoint: string; + feelslike: string; + humidity: string; + winddisplay: string; + day: string; + shortday: string; + windspeed: string; + imageUrl: string; + }; + forecast: [ + { + low: string; + high: string; + skycodeday: string; + skytextday: string; + date: string; + day: string; + shortday: string; + precip: string; + }, + { + low: string; + high: string; + skycodeday: string; + skytextday: string; + date: string; + day: string; + shortday: string; + precip: string; + }, + { + low: string; + high: string; + skycodeday: string; + skytextday: string; + date: string; + day: string; + shortday: string; + precip: string; + }, + { + low: string; + high: string; + skycodeday: string; + skytextday: string; + date: string; + day: string; + shortday: string; + precip: string; + }, + { + low: string; + high: string; + skycodeday: string; + skytextday: string; + date: string; + day: string; + shortday: string; + precip: string; + } + ]; +} + +declare module "weather-js" { + const find: ( + options: WeatherJSOptions, + callback: (error: Error | string | null, result: WeatherJSResult[]) => any + ) => void; +} From a7aea6a28e49697c6a97f64865626ab430495fff Mon Sep 17 00:00:00 2001 From: WatDuhHekBro <44940783+WatDuhHekBro@users.noreply.github.com> Date: Thu, 8 Apr 2021 16:43:58 -0500 Subject: [PATCH 152/178] Added streaminfo message and system info module --- src/commands/utility/streaminfo.ts | 9 ++++- src/index.ts | 2 +- src/modules/channelListener.ts | 20 ----------- src/modules/emoteRegistry.ts | 41 +++------------------ src/modules/systemInfo.ts | 57 ++++++++++++++++++++++++++++++ 5 files changed, 71 insertions(+), 58 deletions(-) delete mode 100644 src/modules/channelListener.ts create mode 100644 src/modules/systemInfo.ts diff --git a/src/commands/utility/streaminfo.ts b/src/commands/utility/streaminfo.ts index 969e668..c1a0c9d 100644 --- a/src/commands/utility/streaminfo.ts +++ b/src/commands/utility/streaminfo.ts @@ -8,8 +8,15 @@ export default new NamedCommand({ if (streamList.has(userID)) { const stream = streamList.get(userID)!; - stream.description = args.join(" ") || "No description set."; + const description = args.join(" ") || "No description set."; + stream.description = description; stream.update(); + channel.send(`Successfully set the stream description to:`, { + embed: { + description, + color: member!.displayColor + } + }); } else { // Alternatively, I could make descriptions last outside of just one stream. channel.send("You can only use this command when streaming."); diff --git a/src/index.ts b/src/index.ts index 6c917d7..d1c4736 100644 --- a/src/index.ts +++ b/src/index.ts @@ -66,7 +66,7 @@ import "./modules/ready"; import "./modules/presence"; import "./modules/lavalink"; import "./modules/emoteRegistry"; -import "./modules/channelListener"; +import "./modules/systemInfo"; import "./modules/intercept"; import "./modules/messageEmbed"; import "./modules/guildMemberAdd"; diff --git a/src/modules/channelListener.ts b/src/modules/channelListener.ts deleted file mode 100644 index 910d1ab..0000000 --- a/src/modules/channelListener.ts +++ /dev/null @@ -1,20 +0,0 @@ -import {client} from "../index"; -import {GuildChannel} from "discord.js"; - -client.on("channelCreate", async (channel) => { - const botGuilds = client.guilds; - - if (channel instanceof GuildChannel) { - const createdGuild = await botGuilds.fetch(channel.guild.id); - console.log(`Channel created in '${createdGuild.name}' called '#${channel.name}'`); - } -}); - -client.on("channelDelete", async (channel) => { - const botGuilds = client.guilds; - - if (channel instanceof GuildChannel) { - const createdGuild = await botGuilds.fetch(channel.guild.id); - console.log(`Channel deleted in '${createdGuild.name}' called '#${channel.name}'`); - } -}); diff --git a/src/modules/emoteRegistry.ts b/src/modules/emoteRegistry.ts index 88ab099..a3de8bc 100644 --- a/src/modules/emoteRegistry.ts +++ b/src/modules/emoteRegistry.ts @@ -1,7 +1,6 @@ import {client} from "../index"; import FileManager from "./storage"; -import {EmoteRegistryDump, Config} from "../structures"; -import {TextChannel} from "discord.js"; +import {EmoteRegistryDump} from "../structures"; function updateGlobalEmoteRegistry(): void { const data: EmoteRegistryDump = {version: 1, list: []}; @@ -39,43 +38,13 @@ client.on("emojiUpdate", () => { updateGlobalEmoteRegistry(); }); -client.on("guildCreate", (guild) => { - console.log( - `[GUILD JOIN] ${guild.name} (${guild.id}) added the bot. Owner: ${guild.owner!.user.tag} (${ - guild.owner!.user.id - }). Updated emote registry.` - ); - - if (Config.systemLogsChannel) { - const channel = client.channels.cache.get(Config.systemLogsChannel); - - if (channel && channel.type === "text") { - (channel as TextChannel).send( - `TravBot joined: \`${guild.name}\`. The owner of this guild is: \`${guild.owner!.user.tag}\` (\`${ - guild.owner!.user.id - }\`)` - ); - } else { - console.warn(`${Config.systemLogsChannel} is not a valid text channel for system logs!`); - } - } - +client.on("guildCreate", () => { + console.log("Updated emote registry."); updateGlobalEmoteRegistry(); }); -client.on("guildDelete", (guild) => { - console.log(`[GUILD LEAVE] ${guild.name} (${guild.id}) removed the bot. Updated emote registry.`); - - if (Config.systemLogsChannel) { - const channel = client.channels.cache.get(Config.systemLogsChannel); - - if (channel && channel.type === "text") { - (channel as TextChannel).send(`\`${guild.name}\` (\`${guild.id}\`) removed the bot.`); - } else { - console.warn(`${Config.systemLogsChannel} is not a valid text channel for system logs!`); - } - } - +client.on("guildDelete", () => { + console.log("Updated emote registry."); updateGlobalEmoteRegistry(); }); diff --git a/src/modules/systemInfo.ts b/src/modules/systemInfo.ts new file mode 100644 index 0000000..f9a0b64 --- /dev/null +++ b/src/modules/systemInfo.ts @@ -0,0 +1,57 @@ +import {client} from "../index"; +import {GuildChannel, TextChannel} from "discord.js"; +import {Config} from "../structures"; + +client.on("channelCreate", async (channel) => { + const botGuilds = client.guilds; + + if (channel instanceof GuildChannel) { + const createdGuild = await botGuilds.fetch(channel.guild.id); + console.log(`Channel created in '${createdGuild.name}' called '#${channel.name}'`); + } +}); + +client.on("channelDelete", async (channel) => { + const botGuilds = client.guilds; + + if (channel instanceof GuildChannel) { + const createdGuild = await botGuilds.fetch(channel.guild.id); + console.log(`Channel deleted in '${createdGuild.name}' called '#${channel.name}'`); + } +}); + +client.on("guildCreate", (guild) => { + console.log( + `[GUILD JOIN] ${guild.name} (${guild.id}) added the bot. Owner: ${guild.owner!.user.tag} (${ + guild.owner!.user.id + }).` + ); + + if (Config.systemLogsChannel) { + const channel = client.channels.cache.get(Config.systemLogsChannel); + + if (channel && channel.type === "text") { + (channel as TextChannel).send( + `TravBot joined: \`${guild.name}\`. The owner of this guild is: \`${guild.owner!.user.tag}\` (\`${ + guild.owner!.user.id + }\`)` + ); + } else { + console.warn(`${Config.systemLogsChannel} is not a valid text channel for system logs!`); + } + } +}); + +client.on("guildDelete", (guild) => { + console.log(`[GUILD LEAVE] ${guild.name} (${guild.id}) removed the bot.`); + + if (Config.systemLogsChannel) { + const channel = client.channels.cache.get(Config.systemLogsChannel); + + if (channel && channel.type === "text") { + (channel as TextChannel).send(`\`${guild.name}\` (\`${guild.id}\`) removed the bot.`); + } else { + console.warn(`${Config.systemLogsChannel} is not a valid text channel for system logs!`); + } + } +}); From da62401f59b2374b834198935f2eec89823669ee Mon Sep 17 00:00:00 2001 From: Keanu Date: Fri, 9 Apr 2021 17:17:25 +0200 Subject: [PATCH 153/178] Fixed messageEmbed test, added parseVars lib test. --- .husky/pre-commit | 0 src/lib.test.ts | 8 +++- src/modules/messageEmbed.test.ts | 80 +++++++++++++++----------------- 3 files changed, 44 insertions(+), 44 deletions(-) mode change 100644 => 100755 .husky/pre-commit diff --git a/.husky/pre-commit b/.husky/pre-commit old mode 100644 new mode 100755 diff --git a/src/lib.test.ts b/src/lib.test.ts index 0cd4cc9..b9990b3 100644 --- a/src/lib.test.ts +++ b/src/lib.test.ts @@ -1,5 +1,5 @@ import {strict as assert} from "assert"; -import {pluralise, pluraliseSigned, replaceAll, toTitleCase, split} from "./lib"; +import {pluralise, pluraliseSigned, replaceAll, toTitleCase, split, parseVars} from "./lib"; // I can't figure out a way to run the test suite while running the bot. describe("Wrappers", () => { @@ -52,6 +52,12 @@ describe("Wrappers", () => { }); }); + describe("#parseVars()", () => { + it('should replace %test% with "yeet"', () => { + assert.strictEqual(parseVars("ya %test%", {test: "yeet"}), "ya yeet"); + }); + }); + describe("#toTitleCase()", () => { it("should capitalize the first letter of each word", () => { assert.strictEqual( diff --git a/src/modules/messageEmbed.test.ts b/src/modules/messageEmbed.test.ts index 3508de2..8d70866 100644 --- a/src/modules/messageEmbed.test.ts +++ b/src/modules/messageEmbed.test.ts @@ -1,55 +1,49 @@ jest.useFakeTimers(); import {strict as assert} from "assert"; -//import {extractFirstMessageLink} from "./messageEmbed"; +import {extractFirstMessageLink} from "./messageEmbed"; -/*describe("modules/messageEmbed", () => { - describe("extractFirstMessageLink()", () => { - const guildID = "802906483866631183"; - const channelID = "681747101169682147" - const messageID = "996363055050949479"; - const post = `channels/${guildID}/${channelID}/${messageID}`; - const commonUrl = `https://discord.com/channels/${post}`; - const combined = [guildID, channelID, messageID]; +describe("modules/messageEmbed", () => { + describe("extractFirstMessageLink()", () => { + const guildID = "802906483866631183"; + const channelID = "681747101169682147"; + const messageID = "996363055050949479"; + const post = `channels/${guildID}/${channelID}/${messageID}`; + const commonUrl = `https://discord.com/${post}`; + const combined = [guildID, channelID, messageID]; - it('should return work and extract correctly on an isolated link', () => { - const result = extractFirstMessageLink(commonUrl); - assert.deepStrictEqual(result, combined); - }) + it("should return work and extract correctly on an isolated link", () => { + const result = extractFirstMessageLink(commonUrl); + assert.deepStrictEqual(result, combined); + }); - it('should return work and extract correctly on a link within a message', () => { - const result = extractFirstMessageLink(`sample text${commonUrl}, more sample text`); - assert.deepStrictEqual(result, combined); - }) + it("should return work and extract correctly on a link within a message", () => { + const result = extractFirstMessageLink(`sample text${commonUrl}, more sample text`); + assert.deepStrictEqual(result, combined); + }); - it('should return null on "!link"', () => { - const result = extractFirstMessageLink(`just some !${commonUrl} text`); - assert.strictEqual(result, null); - }) + it('should return null on "!link"', () => { + const result = extractFirstMessageLink(`just some !${commonUrl} text`); + assert.strictEqual(result, null); + }); - it('should return null on ""', () => { - const result = extractFirstMessageLink(`just some <${commonUrl}> text`); - assert.strictEqual(result, null); - }) + it('should return null on ""', () => { + const result = extractFirstMessageLink(`just some <${commonUrl}> text`); + assert.strictEqual(result, null); + }); - it('should return work and extract correctly on " { - const result = extractFirstMessageLink(`just some <${commonUrl} text`); - assert.deepStrictEqual(result, combined); - }) + it('should return work and extract correctly on " { + const result = extractFirstMessageLink(`just some <${commonUrl} text`); + assert.deepStrictEqual(result, combined); + }); - it('should return work and extract correctly on "link>"', () => { - const result = extractFirstMessageLink(`just some ${commonUrl}> text`); - assert.deepStrictEqual(result, combined); - }) + it('should return work and extract correctly on "link>"', () => { + const result = extractFirstMessageLink(`just some ${commonUrl}> text`); + assert.deepStrictEqual(result, combined); + }); - it('should return work and extract correctly on a canary link', () => { - const result = extractFirstMessageLink(`https://canary.discord.com/${post}`); - assert.deepStrictEqual(result, combined); - }) - }) -});*/ - -describe("placeholder", () => { - it("placeholder", async () => { - assert.strictEqual(1, 1); + it("should return work and extract correctly on a canary link", () => { + const result = extractFirstMessageLink(`https://canary.discord.com/${post}`); + assert.deepStrictEqual(result, combined); + }); }); }); From 72ff144cc0323d8770a009f0020b82bed1f8842b Mon Sep 17 00:00:00 2001 From: WatDuhHekBro <44940783+WatDuhHekBro@users.noreply.github.com> Date: Fri, 9 Apr 2021 23:06:16 -0500 Subject: [PATCH 154/178] Split command resolution part of help command --- src/commands/system/help.ts | 53 ++++++----------------------- src/commands/utility/info.ts | 2 +- src/commands/utility/lsemotes.ts | 2 +- src/commands/utility/react.ts | 6 ++-- src/core/command.ts | 53 +++++++++++++++++++---------- src/core/handler.ts | 8 ++++- src/core/index.ts | 3 +- src/core/libd.ts | 27 ++++++++++++--- src/core/loader.ts | 57 ++++++++++++++++++++++++++++++-- src/lib.ts | 9 +++-- src/modules/messageEmbed.ts | 2 +- src/structures.ts | 10 +++--- 12 files changed, 146 insertions(+), 86 deletions(-) diff --git a/src/commands/system/help.ts b/src/commands/system/help.ts index d205a10..53a701c 100644 --- a/src/commands/system/help.ts +++ b/src/commands/system/help.ts @@ -1,61 +1,28 @@ -import {Command, NamedCommand, loadableCommands, categories, getPermissionName, CHANNEL_TYPE} from "../../core"; -import {toTitleCase, requireAllCasesHandledFor} from "../../lib"; +import {Command, NamedCommand, CHANNEL_TYPE, getPermissionName, getCommandList, getCommandInfo} from "../../core"; +import {requireAllCasesHandledFor} from "../../lib"; export default new NamedCommand({ description: "Lists all commands. If a command is specified, their arguments are listed as well.", usage: "([command, [subcommand/type], ...])", aliases: ["h"], async run({message, channel, guild, author, member, client, args}) { - const commands = await loadableCommands; + const commands = await getCommandList(); let output = `Legend: \`\`, \`[list/of/stuff]\`, \`(optional)\`, \`()\`, \`([optional/list/...])\``; - for (const [category, headers] of categories) { - let tmp = `\n\n===[ ${toTitleCase(category)} ]===`; - // Ignore empty categories, including ["test"]. - let hasActualCommands = false; - - for (const header of headers) { - if (header !== "test") { - const command = commands.get(header)!; - tmp += `\n- \`${header}\`: ${command.description}`; - hasActualCommands = true; - } - } - - if (hasActualCommands) output += tmp; + for (const [category, commandList] of commands) { + output += `\n\n===[ ${category} ]===`; + for (const command of commandList) output += `\n- \`${command.name}\`: ${command.description}`; } channel.send(output, {split: true}); }, any: new Command({ async run({message, channel, guild, author, member, client, args}) { - // Setup the root command - const commands = await loadableCommands; - let header = args.shift() as string; - let command = commands.get(header); - if (!command || header === "test") return channel.send(`No command found by the name \`${header}\`.`); - if (!(command instanceof NamedCommand)) - return channel.send(`Command is not a proper instance of NamedCommand.`); - if (command.name) header = command.name; - - // Search categories - let category = "Unknown"; - for (const [referenceCategory, headers] of categories) { - if (headers.includes(header)) { - category = toTitleCase(referenceCategory); - break; - } - } - - // Gather info - const result = await command.resolveInfo(args); - - if (result.type === "error") return channel.send(result.message); - + const [result, category] = await getCommandInfo(args); + if (typeof result === "string") return channel.send(result); let append = ""; - command = result.command; - - if (result.args.length > 0) header += " " + result.args.join(" "); + const command = result.command; + const header = result.args.length > 0 ? `${result.header} ${result.args.join(" ")}` : result.header; if (command.usage === "") { const list: string[] = []; diff --git a/src/commands/utility/info.ts b/src/commands/utility/info.ts index 7006a0e..6b7ca82 100644 --- a/src/commands/utility/info.ts +++ b/src/commands/utility/info.ts @@ -102,7 +102,7 @@ export default new NamedCommand({ description: "Display info about a guild by finding its name or ID.", async run({message, channel, guild, author, member, client, args}) { // If a guild ID is provided (avoid the "number" subcommand because of inaccuracies), search for that guild - if (args.length === 1 && /^\d{17,19}$/.test(args[0])) { + if (args.length === 1 && /^\d{17,}$/.test(args[0])) { const id = args[0]; const targetGuild = client.guilds.cache.get(id); diff --git a/src/commands/utility/lsemotes.ts b/src/commands/utility/lsemotes.ts index a04f044..fef7813 100644 --- a/src/commands/utility/lsemotes.ts +++ b/src/commands/utility/lsemotes.ts @@ -16,7 +16,7 @@ export default new NamedCommand({ "Filters emotes by via a regular expression. Flags can be added by adding a dash at the end. For example, to do a case-insensitive search, do %prefix%lsemotes somepattern -i", async run({message, channel, guild, author, member, client, args}) { // If a guild ID is provided, filter all emotes by that guild (but only if there aren't any arguments afterward) - if (args.length === 1 && /^\d{17,19}$/.test(args[0])) { + if (args.length === 1 && /^\d{17,}$/.test(args[0])) { const guildID: string = args[0]; displayEmoteList( diff --git a/src/commands/utility/react.ts b/src/commands/utility/react.ts index 5bb3eb0..42e8227 100644 --- a/src/commands/utility/react.ts +++ b/src/commands/utility/react.ts @@ -17,8 +17,8 @@ export default new NamedCommand({ // handles reacts by message id/distance else if (args.length >= 2) { const last = args[args.length - 1]; // Because this is optional, do not .pop() unless you're sure it's a message link indicator. - const URLPattern = /^(?:https:\/\/discord.com\/channels\/(\d{17,19})\/(\d{17,19})\/(\d{17,19}))$/; - const copyIDPattern = /^(?:(\d{17,19})-(\d{17,19}))$/; + const URLPattern = /^(?:https:\/\/discord.com\/channels\/(\d{17,})\/(\d{17,})\/(\d{17,}))$/; + const copyIDPattern = /^(?:(\d{17,})-(\d{17,}))$/; // https://discord.com/channels/// ("Copy Message Link" Button) if (URLPattern.test(last)) { @@ -70,7 +70,7 @@ export default new NamedCommand({ args.pop(); } // - else if (/^\d{17,19}$/.test(last)) { + else if (/^\d{17,}$/.test(last)) { try { target = await channel.messages.fetch(last); } catch { diff --git a/src/core/command.ts b/src/core/command.ts index 0835da9..a89449b 100644 --- a/src/core/command.ts +++ b/src/core/command.ts @@ -30,14 +30,16 @@ import {parseVars, requireAllCasesHandledFor} from "../lib"; */ // RegEx patterns used for identifying/extracting each type from a string argument. +// The reason why \d{17,} is used is because the max safe number for JS numbers is 16 characters when stringified (decimal). Beyond that are IDs. const patterns = { - channel: /^<#(\d{17,19})>$/, - role: /^<@&(\d{17,19})>$/, - emote: /^$/, - messageLink: /^https?:\/\/(?:ptb\.|canary\.)?discord(?:app)?\.com\/channels\/(?:\d{17,19}|@me)\/(\d{17,19})\/(\d{17,19})$/, - messagePair: /^(\d{17,19})-(\d{17,19})$/, - user: /^<@!?(\d{17,19})>$/, - id: /^(\d{17,19})$/ + channel: /^<#(\d{17,})>$/, + role: /^<@&(\d{17,})>$/, + emote: /^$/, + // The message type won't include #. At that point, you may as well just use a search usernames function. Even then, tags would only be taken into account to differentiate different users with identical usernames. + messageLink: /^https?:\/\/(?:ptb\.|canary\.)?discord(?:app)?\.com\/channels\/(?:\d{17,}|@me)\/(\d{17,})\/(\d{17,})$/, + messagePair: /^(\d{17,})-(\d{17,})$/, + user: /^<@!?(\d{17,})>$/, + id: /^(\d{17,})$/ }; // Maybe add a guild redirect... somehow? @@ -106,9 +108,10 @@ interface ExecuteCommandMetadata { permission: number; nsfw: boolean; channelType: CHANNEL_TYPE; + symbolicArgs: string[]; // i.e. instead of <#...> } -interface CommandInfo { +export interface CommandInfo { readonly type: "info"; readonly command: Command; readonly subcommandInfo: Collection; @@ -117,6 +120,7 @@ interface CommandInfo { readonly nsfw: boolean; readonly channelType: CHANNEL_TYPE; readonly args: string[]; + readonly header: string; } interface CommandInfoError { @@ -131,14 +135,9 @@ interface CommandInfoMetadata { args: string[]; usage: string; readonly originalArgs: string[]; + readonly header: string; } -export const defaultMetadata = { - permission: 0, - nsfw: false, - channelType: CHANNEL_TYPE.ANY -}; - // Each Command instance represents a block that links other Command instances under it. export class Command { public readonly description: string; @@ -298,12 +297,15 @@ export class Command { // Then capture any potential errors. try { if (typeof this.run === "string") { + // Although I *could* add an option in the launcher to attach arbitrary variables to this var string... + // I'll just leave it like this, because instead of using var strings for user stuff, you could just make "run" a template string. await menu.channel.send( parseVars( this.run, { author: menu.author.toString(), - prefix: getPrefix(menu.guild) + prefix: getPrefix(menu.guild), + command: `${metadata.header} ${metadata.symbolicArgs.join(", ")}` }, "???" ) @@ -332,6 +334,7 @@ export class Command { const isMessagePair = patterns.messagePair.test(param); if (this.subcommands.has(param)) { + metadata.symbolicArgs.push(param); return this.subcommands.get(param)!.execute(args, menu, metadata); } else if (this.channel && patterns.channel.test(param)) { const id = patterns.channel.exec(param)![1]; @@ -339,6 +342,7 @@ export class Command { // Users can only enter in this format for text channels, so this restricts it to that. if (channel instanceof TextChannel) { + metadata.symbolicArgs.push(""); menu.args.push(channel); return this.channel.execute(args, menu, metadata); } else { @@ -358,6 +362,7 @@ export class Command { const role = menu.guild.roles.cache.get(id); if (role) { + metadata.symbolicArgs.push(""); menu.args.push(role); return this.role.execute(args, menu, metadata); } else { @@ -370,6 +375,7 @@ export class Command { const emote = menu.client.emojis.cache.get(id); if (emote) { + metadata.symbolicArgs.push(""); menu.args.push(emote); return this.emote.execute(args, menu, metadata); } else { @@ -395,6 +401,7 @@ export class Command { if (channel instanceof TextChannel || channel instanceof DMChannel) { try { + metadata.symbolicArgs.push(""); menu.args.push(await channel.messages.fetch(messageID)); return this.message.execute(args, menu, metadata); } catch { @@ -411,6 +418,7 @@ export class Command { const id = patterns.user.exec(param)![1]; try { + metadata.symbolicArgs.push(""); menu.args.push(await menu.client.users.fetch(id)); return this.user.execute(args, menu, metadata); } catch { @@ -419,6 +427,7 @@ export class Command { }; } } else if (this.id && this.idType && patterns.id.test(param)) { + metadata.symbolicArgs.push(""); const id = patterns.id.exec(param)![1]; // Probably modularize the findXByY code in general in libd. @@ -486,9 +495,11 @@ export class Command { requireAllCasesHandledFor(this.idType); } } else if (this.number && !Number.isNaN(Number(param)) && param !== "Infinity" && param !== "-Infinity") { + metadata.symbolicArgs.push(""); menu.args.push(Number(param)); return this.number.execute(args, menu, metadata); } else if (this.any) { + metadata.symbolicArgs.push(""); menu.args.push(param); return this.any.execute(args, menu, metadata); } else { @@ -502,8 +513,16 @@ export class Command { } // What this does is resolve the resulting subcommand as well as the inherited properties and the available subcommands. - public async resolveInfo(args: string[]): Promise { - return this.resolveInfoInternal(args, {...defaultMetadata, args: [], usage: "", originalArgs: [...args]}); + public async resolveInfo(args: string[], header: string): Promise { + return this.resolveInfoInternal(args, { + permission: 0, + nsfw: false, + channelType: CHANNEL_TYPE.ANY, + header, + args: [], + usage: "", + originalArgs: [...args] + }); } private async resolveInfoInternal( diff --git a/src/core/handler.ts b/src/core/handler.ts index 54f8f1b..9503b3b 100644 --- a/src/core/handler.ts +++ b/src/core/handler.ts @@ -1,6 +1,5 @@ import {Client, Permissions, Message, TextChannel, DMChannel, NewsChannel} from "discord.js"; import {loadableCommands} from "./loader"; -import {defaultMetadata} from "./command"; import {getPrefix} from "./interface"; // For custom message events that want to cancel the command handler on certain conditions. @@ -20,6 +19,13 @@ const lastCommandInfo: { channel: null }; +const defaultMetadata = { + permission: 0, + nsfw: false, + channelType: 0, // CHANNEL_TYPE.ANY, apparently isn't initialized at this point yet + symbolicArgs: [] +}; + // Note: client.user is only undefined before the bot logs in, so by this point, client.user cannot be undefined. // Note: guild.available will never need to be checked because the message starts in either a DM channel or an already-available guild. export function attachMessageHandlerToClient(client: Client) { diff --git a/src/core/index.ts b/src/core/index.ts index 0b1d56f..5d75c14 100644 --- a/src/core/index.ts +++ b/src/core/index.ts @@ -1,3 +1,4 @@ +// Onion Lasers Command Handler // export {Command, NamedCommand, CHANNEL_TYPE} from "./command"; export {addInterceptRule} from "./handler"; export {launch} from "./interface"; @@ -12,5 +13,5 @@ export { getMemberByUsername, callMemberByUsername } from "./libd"; -export {loadableCommands, categories} from "./loader"; +export {getCommandList, getCommandInfo} from "./loader"; export {hasPermission, getPermissionLevel, getPermissionName} from "./permissions"; diff --git a/src/core/libd.ts b/src/core/libd.ts index 84d6ea2..7f52c75 100644 --- a/src/core/libd.ts +++ b/src/core/libd.ts @@ -25,6 +25,11 @@ export function botHasPermission(guild: Guild | null, permission: number): boole // Pagination function that allows for customization via a callback. // Define your own pages outside the function because this only manages the actual turning of pages. +const FIVE_BACKWARDS_EMOJI = "⏪"; +const BACKWARDS_EMOJI = "⬅️"; +const FORWARDS_EMOJI = "➡️"; +const FIVE_FORWARDS_EMOJI = "⏩"; + /** * Takes a message and some additional parameters and makes a reaction page with it. All the pagination logic is taken care of but nothing more, the page index is returned and you have to send a callback to do something with it. */ @@ -43,30 +48,42 @@ export async function paginate( const turn = (amount: number) => { page += amount; - if (page < 0) page += total; - else if (page >= total) page -= total; + if (page >= total) { + page %= total; + } else if (page < 0) { + // Assuming 3 total pages, it's a bit tricker, but if we just take the modulo of the absolute value (|page| % total), we get (1 2 0 ...), and we just need the pattern (2 1 0 ...). It needs to reverse order except for when it's 0. I want to find a better solution, but for the time being... total - (|page| % total) unless (|page| % total) = 0, then return 0. + const flattened = Math.abs(page) % total; + if (flattened !== 0) page = total - flattened; + } message.edit(callback(page, true)); }; - const BACKWARDS_EMOJI = "⬅️"; - const FORWARDS_EMOJI = "➡️"; const handle = (emote: string, reacterID: string) => { if (senderID === reacterID) { switch (emote) { + case FIVE_BACKWARDS_EMOJI: + if (total > 5) turn(-5); + break; case BACKWARDS_EMOJI: turn(-1); break; case FORWARDS_EMOJI: turn(1); break; + case FIVE_FORWARDS_EMOJI: + if (total > 5) turn(5); + break; } } }; // Listen for reactions and call the handler. + let backwardsReactionFive = total > 5 ? await message.react(FIVE_BACKWARDS_EMOJI) : null; let backwardsReaction = await message.react(BACKWARDS_EMOJI); let forwardsReaction = await message.react(FORWARDS_EMOJI); + let forwardsReactionFive = total > 5 ? await message.react(FIVE_FORWARDS_EMOJI) : null; unreactEventListeners.set(message.id, handle); + const collector = message.createReactionCollector( (reaction, user) => { if (user.id === senderID) { @@ -88,8 +105,10 @@ export async function paginate( // When time's up, remove the bot's own reactions. collector.on("end", () => { unreactEventListeners.delete(message.id); + backwardsReactionFive?.users.remove(message.author); backwardsReaction.users.remove(message.author); forwardsReaction.users.remove(message.author); + forwardsReactionFive?.users.remove(message.author); }); } } diff --git a/src/core/loader.ts b/src/core/loader.ts index d5ba5c1..8b20ad7 100644 --- a/src/core/loader.ts +++ b/src/core/loader.ts @@ -1,13 +1,14 @@ import {Collection} from "discord.js"; import glob from "glob"; -import {Command, NamedCommand} from "./command"; +import {NamedCommand, CommandInfo} from "./command"; +import {toTitleCase} from "../lib"; // Internally, it'll keep its original capitalization. It's up to you to convert it to title case when you make a help command. -export const categories = new Collection(); +const categories = new Collection(); /** Returns the cache of the commands if it exists and searches the directory if not. */ export const loadableCommands = (async () => { - const commands = new Collection(); + const commands = new Collection(); // Include all .ts files recursively in "src/commands/". const files = await globP("src/commands/**/*.ts"); // Extract the usable parts from "src/commands/" if: @@ -79,3 +80,53 @@ function globP(path: string) { }); }); } + +/** + * Returns a list of categories and their associated commands. + */ +export async function getCommandList(): Promise> { + const list = new Collection(); + const commands = await loadableCommands; + + for (const [category, headers] of categories) { + const commandList: NamedCommand[] = []; + for (const header of headers.filter((header) => header !== "test")) commandList.push(commands.get(header)!); + // Ignore empty categories like "miscellaneous" (if it's empty). + if (commandList.length > 0) list.set(toTitleCase(category), commandList); + } + + return list; +} + +/** + * Resolves a command based on the arguments given. + * - Returns a string if there was an error. + * - Returns a CommandInfo/category tuple if it was a success. + */ +export async function getCommandInfo(args: string[]): Promise<[CommandInfo, string] | string> { + // Use getCommandList() instead if you're just getting the list of all commands. + if (args.length === 0) return "No arguments were provided!"; + + // Setup the root command + const commands = await loadableCommands; + let header = args.shift()!; + const command = commands.get(header); + if (!command || header === "test") return `No command found by the name \`${header}\`.`; + if (!(command instanceof NamedCommand)) return "Command is not a proper instance of NamedCommand."; + // If it's an alias, set the header to the original command name. + if (command.name) header = command.name; + + // Search categories + let category = "Unknown"; + for (const [referenceCategory, headers] of categories) { + if (headers.includes(header)) { + category = toTitleCase(referenceCategory); + break; + } + } + + // Gather info + const result = await command.resolveInfo(args, header); + if (result.type === "error") return result.message; + else return [result, category]; +} diff --git a/src/lib.ts b/src/lib.ts index 8036a96..490dd13 100644 --- a/src/lib.ts +++ b/src/lib.ts @@ -144,14 +144,13 @@ export abstract class GenericStructure { constructor(tag?: string) { this.__meta__ = tag || this.__meta__; + Object.defineProperty(this, "__meta__", { + enumerable: false + }); } public save(asynchronous = true) { - const tag = this.__meta__; - /// @ts-ignore - delete this.__meta__; - FileManager.write(tag, this, asynchronous); - this.__meta__ = tag; + FileManager.write(this.__meta__, this, asynchronous); } } diff --git a/src/modules/messageEmbed.ts b/src/modules/messageEmbed.ts index 5018ef2..7ad000c 100644 --- a/src/modules/messageEmbed.ts +++ b/src/modules/messageEmbed.ts @@ -57,7 +57,7 @@ client.on("message", async (message) => { export function extractFirstMessageLink(message: string): [string, string, string] | null { const messageLinkMatch = message.match( - /([!<])?https?:\/\/(?:ptb\.|canary\.)?discord(?:app)?\.com\/channels\/(\d{17,19})\/(\d{17,19})\/(\d{17,19})(>)?/ + /([!<])?https?:\/\/(?:ptb\.|canary\.)?discord(?:app)?\.com\/channels\/(\d{17,})\/(\d{17,})\/(\d{17,})(>)?/ ); if (messageLinkMatch === null) return null; const [, leftToken, guildID, channelID, messageID, rightToken] = messageLinkMatch; diff --git a/src/structures.ts b/src/structures.ts index 351bbb6..f9065b9 100644 --- a/src/structures.ts +++ b/src/structures.ts @@ -91,15 +91,13 @@ class StorageStructure extends GenericStructure { super("storage"); this.users = {}; this.guilds = {}; - - for (let id in data.users) if (/\d{17,19}/g.test(id)) this.users[id] = new User(data.users[id]); - - for (let id in data.guilds) if (/\d{17,19}/g.test(id)) this.guilds[id] = new Guild(data.guilds[id]); + for (let id in data.users) if (/\d{17,}/g.test(id)) this.users[id] = new User(data.users[id]); + for (let id in data.guilds) if (/\d{17,}/g.test(id)) this.guilds[id] = new Guild(data.guilds[id]); } /** Gets a user's profile if they exist and generate one if not. */ public getUser(id: string): User { - if (!/\d{17,19}/g.test(id)) + if (!/\d{17,}/g.test(id)) console.warn(`"${id}" is not a valid user ID! It will be erased when the data loads again.`); if (id in this.users) return this.users[id]; @@ -112,7 +110,7 @@ class StorageStructure extends GenericStructure { /** Gets a guild's settings if they exist and generate one if not. */ public getGuild(id: string): Guild { - if (!/\d{17,19}/g.test(id)) + if (!/\d{17,}/g.test(id)) console.warn(`"${id}" is not a valid guild ID! It will be erased when the data loads again.`); if (id in this.guilds) return this.guilds[id]; From 653cc6f8a64934a4bb5cd4ac174d6c79971e4c9a Mon Sep 17 00:00:00 2001 From: WatDuhHekBro <44940783+WatDuhHekBro@users.noreply.github.com> Date: Fri, 9 Apr 2021 23:33:22 -0500 Subject: [PATCH 155/178] Turned the help command into a paginated embed --- CHANGELOG.md | 6 ++- docs/Documentation.md | 10 +++-- package-lock.json | 4 +- package.json | 2 +- src/commands/system/help.ts | 80 ++++++++++++++++++++++++++++--------- 5 files changed, 75 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4142c6c..cefffe1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -# ??? +# 3.2.1 - `vaporwave`: Transforms input into full-width text - `eco post`: A play on `eco get` - `admin set prefix (<@bot>)`: Allows you to target a bot when setting a prefix if two bots have conflicting prefixes @@ -7,8 +7,10 @@ - `thonk`: A result can now be discarded if the person who called the command reacts with ❌ - `scanemotes forcereset`: Removes the cooldown on `scanemotes`, only accessible by bot support and up - `urban`: Bug fixes +- Changed `help` to display a paginated embed +- Various changes to core -# 3.2.0 - Internal refactor, more subcommand types, and more command type guards (2021-??-??) +# 3.2.0 - Internal refactor, more subcommand types, and more command type guards (2021-04-09) - The custom logger changed: `$.log` no longer exists, it's just `console.log`. Now you don't have to do `import $ from "../core/lib"` at the top of every file that uses the custom logger. - Utility functions are no longer attached to the command menu. Stuff like `$.paginate()` and `$(5).pluralise()` instead need to be imported and used as regular functions. - The `paginate` function was reworked to reduce the amount of repetition you had to do. diff --git a/docs/Documentation.md b/docs/Documentation.md index d035587..1907bc9 100644 --- a/docs/Documentation.md +++ b/docs/Documentation.md @@ -65,6 +65,7 @@ Because versions are assigned to batches of changes rather than single changes ( - `%author%` - A user mention of the person who called the command. - `%prefix%` - The prefix of the current guild. +- `%command%` - The command's execution path up to the current subcommand. # Utility Functions @@ -72,11 +73,12 @@ Because versions are assigned to batches of changes rather than single changes ( `paginate()` ```ts -const pages = ['one', 'two', 'three']; -const msg = await channel.send(pages[0]); +const pages = ["one", "two", "three"]; -paginate(msg, author.id, pages.length, (page) => { - msg.edit(pages[page]); +paginate(channel, author.id, pages.length, (page) => { + return { + content: pages[page] + }; }); ``` diff --git a/package-lock.json b/package-lock.json index a2b61d6..344651c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "travebot", - "version": "3.2.0", + "version": "3.2.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "travebot", - "version": "3.2.0", + "version": "3.2.1", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 4903443..74c35eb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "travebot", - "version": "3.2.0", + "version": "3.2.1", "description": "TravBot Discord bot.", "main": "dist/index.js", "scripts": { diff --git a/src/commands/system/help.ts b/src/commands/system/help.ts index 53a701c..180819d 100644 --- a/src/commands/system/help.ts +++ b/src/commands/system/help.ts @@ -1,5 +1,16 @@ -import {Command, NamedCommand, CHANNEL_TYPE, getPermissionName, getCommandList, getCommandInfo} from "../../core"; +import { + Command, + NamedCommand, + CHANNEL_TYPE, + getPermissionName, + getCommandList, + getCommandInfo, + paginate +} from "../../core"; import {requireAllCasesHandledFor} from "../../lib"; +import {MessageEmbed} from "discord.js"; + +const EMBED_COLOR = "#158a28"; export default new NamedCommand({ description: "Lists all commands. If a command is specified, their arguments are listed as well.", @@ -7,14 +18,18 @@ export default new NamedCommand({ aliases: ["h"], async run({message, channel, guild, author, member, client, args}) { const commands = await getCommandList(); - let output = `Legend: \`\`, \`[list/of/stuff]\`, \`(optional)\`, \`()\`, \`([optional/list/...])\``; + const categoryArray = commands.keyArray(); - for (const [category, commandList] of commands) { - output += `\n\n===[ ${category} ]===`; - for (const command of commandList) output += `\n- \`${command.name}\`: ${command.description}`; - } - - channel.send(output, {split: true}); + paginate(channel, author.id, categoryArray.length, (page, hasMultiplePages) => { + const category = categoryArray[page]; + const commandList = commands.get(category)!; + let output = `Legend: \`\`, \`[list/of/stuff]\`, \`(optional)\`, \`()\`, \`([optional/list/...])\`\n`; + for (const command of commandList) output += `\n❯ \`${command.name}\`: ${command.description}`; + return new MessageEmbed() + .setTitle(hasMultiplePages ? `${category} (Page ${page + 1} of ${categoryArray.length})` : category) + .setDescription(output) + .setColor(EMBED_COLOR); + }); }, any: new Command({ async run({message, channel, guild, author, member, client, args}) { @@ -29,17 +44,17 @@ export default new NamedCommand({ for (const [tag, subcommand] of result.keyedSubcommandInfo) { const customUsage = subcommand.usage ? ` ${subcommand.usage}` : ""; - list.push(`- \`${header} ${tag}${customUsage}\` - ${subcommand.description}`); + list.push(`❯ \`${header} ${tag}${customUsage}\` - ${subcommand.description}`); } for (const [type, subcommand] of result.subcommandInfo) { const customUsage = subcommand.usage ? ` ${subcommand.usage}` : ""; - list.push(`- \`${header} ${type}${customUsage}\` - ${subcommand.description}`); + list.push(`❯ \`${header} ${type}${customUsage}\` - ${subcommand.description}`); } - append = "Usages:" + (list.length > 0 ? `\n${list.join("\n")}` : " None."); + append = list.length > 0 ? list.join("\n") : "None"; } else { - append = `Usage: \`${header} ${command.usage}\``; + append = `\`${header} ${command.usage}\``; } let aliases = "N/A"; @@ -52,12 +67,41 @@ export default new NamedCommand({ } return channel.send( - `Command: \`${header}\`\nAliases: ${aliases}\nCategory: \`${category}\`\nPermission Required: \`${getPermissionName( - result.permission - )}\` (${result.permission})\nChannel Type: ${getChannelTypeName(result.channelType)}\nNSFW Only: ${ - result.nsfw ? "Yes" : "No" - }\nDescription: ${command.description}\n${append}`, - {split: true} + new MessageEmbed() + .setTitle(header) + .setDescription(command.description) + .setColor(EMBED_COLOR) + .addFields( + { + name: "Aliases", + value: aliases, + inline: true + }, + { + name: "Category", + value: category, + inline: true + }, + { + name: "Permission Required", + value: `\`${getPermissionName(result.permission)}\` (Level ${result.permission})`, + inline: true + }, + { + name: "Channel Type", + value: getChannelTypeName(result.channelType), + inline: true + }, + { + name: "NSFW Only?", + value: result.nsfw ? "Yes" : "No", + inline: true + }, + { + name: "Usages", + value: append + } + ) ); } }) From 4c3437a1775472dd83d9ca2e9897936871f37fe4 Mon Sep 17 00:00:00 2001 From: WatDuhHekBro <44940783+WatDuhHekBro@users.noreply.github.com> Date: Sat, 10 Apr 2021 02:38:46 -0500 Subject: [PATCH 156/178] Finally made the commands directory configurable --- src/core/handler.ts | 3 +-- src/core/interface.ts | 59 ++++++++++++++++++++++++++++++----------- src/core/loader.ts | 55 +++++++++++++++++++++++--------------- src/core/permissions.ts | 3 ++- src/index.ts | 17 +++++++----- 5 files changed, 91 insertions(+), 46 deletions(-) diff --git a/src/core/handler.ts b/src/core/handler.ts index 9503b3b..560ca77 100644 --- a/src/core/handler.ts +++ b/src/core/handler.ts @@ -1,6 +1,5 @@ import {Client, Permissions, Message, TextChannel, DMChannel, NewsChannel} from "discord.js"; -import {loadableCommands} from "./loader"; -import {getPrefix} from "./interface"; +import {getPrefix, loadableCommands} from "./interface"; // For custom message events that want to cancel the command handler on certain conditions. const interceptRules: ((message: Message) => boolean)[] = [(message) => message.author.bot]; diff --git a/src/core/interface.ts b/src/core/interface.ts index b04d2af..1e1df4b 100644 --- a/src/core/interface.ts +++ b/src/core/interface.ts @@ -1,23 +1,52 @@ -import {Client, User, GuildMember, Guild} from "discord.js"; +import {Collection, Client, User, GuildMember, Guild} from "discord.js"; import {attachMessageHandlerToClient} from "./handler"; import {attachEventListenersToClient} from "./eventListeners"; - -interface LaunchSettings { - permissionLevels: PermissionLevel[]; - getPrefix: (guild: Guild | null) => string; -} - -export async function launch(client: Client, settings: LaunchSettings) { - attachMessageHandlerToClient(client); - attachEventListenersToClient(client); - permissionLevels = settings.permissionLevels; - getPrefix = settings.getPrefix; -} +import {NamedCommand} from "./command"; +import {loadCommands} from "./loader"; interface PermissionLevel { name: string; check: (user: User, member: GuildMember | null) => boolean; } -export let permissionLevels: PermissionLevel[] = []; -export let getPrefix: (guild: Guild | null) => string = () => "."; +type PrefixResolver = (guild: Guild | null) => string; +type CategoryTransformer = (text: string) => string; + +// One potential option is to let the user customize system messages such as "This command must be executed in a guild." +// I decided not to do that because I don't think it'll be worth the trouble. +interface LaunchSettings { + permissionLevels?: PermissionLevel[]; + getPrefix?: PrefixResolver; + categoryTransformer?: CategoryTransformer; +} + +// One alternative to putting everything in launch(client, ...) is to create an object then set each individual aspect, such as OnionCore.setPermissions(...). +// That way, you can split different pieces of logic into different files, then do OnionCore.launch(client). +// Additionally, each method would return the object so multiple methods could be chained, such as OnionCore.setPermissions(...).setPrefixResolver(...).launch(client). +// I decided to not do this because creating a class then having a bunch of boilerplate around it just wouldn't really be worth it. +// commandsDirectory requires an absolute path to work, so use __dirname. +export async function launch(client: Client, commandsDirectory: string, settings?: LaunchSettings) { + // Core Launch Parameters // + loadableCommands = loadCommands(commandsDirectory); + attachMessageHandlerToClient(client); + attachEventListenersToClient(client); + + // Additional Configuration // + if (settings?.permissionLevels) { + if (settings.permissionLevels.length > 0) permissionLevels = settings.permissionLevels; + else console.warn("permissionLevels must have at least one element to work!"); + } + if (settings?.getPrefix) getPrefix = settings.getPrefix; + if (settings?.categoryTransformer) categoryTransformer = settings.categoryTransformer; +} + +// Placeholder until properly loaded by the user. +export let loadableCommands = (async () => new Collection())(); +export let permissionLevels: PermissionLevel[] = [ + { + name: "User", + check: () => true + } +]; +export let getPrefix: PrefixResolver = () => "."; +export let categoryTransformer: CategoryTransformer = (text) => text; diff --git a/src/core/loader.ts b/src/core/loader.ts index 8b20ad7..da22a4a 100644 --- a/src/core/loader.ts +++ b/src/core/loader.ts @@ -1,35 +1,46 @@ import {Collection} from "discord.js"; import glob from "glob"; +import path from "path"; import {NamedCommand, CommandInfo} from "./command"; -import {toTitleCase} from "../lib"; +import {loadableCommands, categoryTransformer} from "./interface"; // Internally, it'll keep its original capitalization. It's up to you to convert it to title case when you make a help command. const categories = new Collection(); -/** Returns the cache of the commands if it exists and searches the directory if not. */ -export const loadableCommands = (async () => { +// This will go through all the .js files and import them. Because the import has to be .js (and cannot be .ts), there's no need for a custom filename checker in the launch settings. +// This will avoid the problems of being a node module by requiring absolute imports, which the user will pass in as a launch parameter. +export async function loadCommands(commandsDir: string): Promise> { + // Add a trailing separator so that the reduced filename list will reliably cut off the starting part. + // "C:/some/path/to/commands" --> "C:/some/path/to/commands/" (and likewise for \) + commandsDir = path.normalize(commandsDir); + if (!commandsDir.endsWith(path.sep)) commandsDir += path.sep; + const commands = new Collection(); // Include all .ts files recursively in "src/commands/". - const files = await globP("src/commands/**/*.ts"); - // Extract the usable parts from "src/commands/" if: + const files = await globP(path.join(commandsDir, "**", "*.js")); // This stage filters out source maps (.js.map). + // Because glob will use / regardless of platform, the following regex pattern can rely on / being the case. + const filesClean = files.map((filename) => filename.substring(commandsDir.length)); + // Extract the usable parts from commands directory if: // - The path is 1 to 2 subdirectories (a or a/b, not a/b/c) // - Any leading directory isn't "modules" - // - The filename doesn't end in .test.ts (for jest testing) - // - The filename cannot be the hardcoded top-level "template.ts", reserved for generating templates - const pattern = /src\/commands\/(?!template\.ts)(?!modules\/)(\w+(?:\/\w+)?)(?:test\.)?\.ts/; + // - The filename doesn't end in .test.js (for jest testing) + // - The filename cannot be the hardcoded top-level "template.js", reserved for generating templates + const pattern = /^(?!template\.js)(?!modules\/)(\w+(?:\/\w+)?)(?:test\.)?\.js$/; const lists: {[category: string]: string[]} = {}; - for (const path of files) { - const match = pattern.exec(path); + for (let i = 0; i < files.length; i++) { + const match = pattern.exec(filesClean[i]); + if (!match) continue; + const commandID = match[1]; // e.g. "utilities/info" + const slashIndex = commandID.indexOf("/"); + const isMiscCommand = slashIndex !== -1; + const category = isMiscCommand ? commandID.substring(0, slashIndex) : "miscellaneous"; + const commandName = isMiscCommand ? commandID.substring(slashIndex + 1) : commandID; // e.g. "info" - if (match) { - const commandID = match[1]; // e.g. "utilities/info" - const slashIndex = commandID.indexOf("/"); - const isMiscCommand = slashIndex !== -1; - const category = isMiscCommand ? commandID.substring(0, slashIndex) : "miscellaneous"; - const commandName = isMiscCommand ? commandID.substring(slashIndex + 1) : commandID; // e.g. "info" + // This try-catch block MUST be here or Node.js' dynamic require() will silently fail. + try { // If the dynamic import works, it must be an object at the very least. Then, just test to see if it's a proper instance. - const command = (await import(`../commands/${commandID}`)).default as unknown; + const command = (await import(files[i])).default as unknown; if (command instanceof NamedCommand) { command.name = commandName; @@ -55,10 +66,12 @@ export const loadableCommands = (async () => { if (!(category in lists)) lists[category] = []; lists[category].push(commandName); - console.log(`Loading Command: ${commandID}`); + console.log(`Loaded Command: ${commandID}`); } else { console.warn(`Command "${commandID}" has no default export which is a NamedCommand instance!`); } + } catch (error) { + console.log(error); } } @@ -67,7 +80,7 @@ export const loadableCommands = (async () => { } return commands; -})(); +} function globP(path: string) { return new Promise((resolve, reject) => { @@ -92,7 +105,7 @@ export async function getCommandList(): Promise header !== "test")) commandList.push(commands.get(header)!); // Ignore empty categories like "miscellaneous" (if it's empty). - if (commandList.length > 0) list.set(toTitleCase(category), commandList); + if (commandList.length > 0) list.set(categoryTransformer(category), commandList); } return list; @@ -120,7 +133,7 @@ export async function getCommandInfo(args: string[]): Promise<[CommandInfo, stri let category = "Unknown"; for (const [referenceCategory, headers] of categories) { if (headers.includes(header)) { - category = toTitleCase(referenceCategory); + category = categoryTransformer(referenceCategory); break; } } diff --git a/src/core/permissions.ts b/src/core/permissions.ts index ba3be33..f6b515e 100644 --- a/src/core/permissions.ts +++ b/src/core/permissions.ts @@ -6,6 +6,7 @@ import {permissionLevels} from "./interface"; * Checks if a `Member` has a certain permission. */ export function hasPermission(user: User, member: GuildMember | null, permission: number): boolean { + if (permissionLevels.length === 0) return true; for (let i = permissionLevels.length - 1; i >= permission; i--) if (permissionLevels[i].check(user, member)) return true; return false; @@ -20,6 +21,6 @@ export function getPermissionLevel(user: User, member: GuildMember | null): numb } export function getPermissionName(level: number) { - if (level > permissionLevels.length || level < 0) return "N/A"; + if (level > permissionLevels.length || level < 0 || permissionLevels.length === 0) return "N/A"; else return permissionLevels[level].name; } diff --git a/src/index.ts b/src/index.ts index 6c917d7..95befe7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,21 +1,25 @@ -// Bootstrapping Section // import "./modules/globals"; import {Client, Permissions} from "discord.js"; -import {launch} from "./core"; -import setup from "./modules/setup"; -import {Config, getPrefix} from "./structures"; +import path from "path"; // This is here in order to make it much less of a headache to access the client from other files. // This of course won't actually do anything until the setup process is complete and it logs in. export const client = new Client(); +import {launch} from "./core"; +import setup from "./modules/setup"; +import {Config, getPrefix} from "./structures"; +import {toTitleCase} from "./lib"; + // Send the login request to Discord's API and then load modules while waiting for it. setup.init().then(() => { client.login(Config.token).catch(setup.again); }); // Setup the command handler. -launch(client, { +launch(client, path.join(__dirname, "commands"), { + getPrefix, + categoryTransformer: toTitleCase, permissionLevels: [ { // NONE // @@ -57,8 +61,7 @@ launch(client, { name: "Bot Owner", check: (user) => Config.owner === user.id } - ], - getPrefix: getPrefix + ] }); // Initialize Modules // From 54ce28d8d448f323973fe64d6216da323f60aae1 Mon Sep 17 00:00:00 2001 From: WatDuhHekBro <44940783+WatDuhHekBro@users.noreply.github.com> Date: Sat, 10 Apr 2021 06:41:48 -0500 Subject: [PATCH 157/178] Added more library functions to command handler --- src/commands/fun/eco.ts | 12 ++- src/commands/fun/whois.ts | 14 ++- src/commands/utility/info.ts | 8 +- src/commands/utility/time.ts | 12 +-- src/core/command.ts | 101 ++++++++++---------- src/core/index.ts | 12 +-- src/core/interface.ts | 9 +- src/core/libd.ts | 179 +++++++++++++++++++++++++++-------- 8 files changed, 217 insertions(+), 130 deletions(-) diff --git a/src/commands/fun/eco.ts b/src/commands/fun/eco.ts index c87241a..b265ebb 100644 --- a/src/commands/fun/eco.ts +++ b/src/commands/fun/eco.ts @@ -1,9 +1,10 @@ -import {Command, NamedCommand, callMemberByUsername} from "../../core"; +import {Command, NamedCommand, getMemberByName} from "../../core"; import {isAuthorized, getMoneyEmbed} from "./modules/eco-utils"; import {DailyCommand, PayCommand, GuildCommand, LeaderboardCommand} from "./modules/eco-core"; import {BuyCommand, ShopCommand} from "./modules/eco-shop"; import {MondayCommand, AwardCommand} from "./modules/eco-extras"; import {BetCommand} from "./modules/eco-bet"; +import {GuildMember} from "discord.js"; export default new NamedCommand({ description: "Economy command for Monika.", @@ -35,10 +36,11 @@ export default new NamedCommand({ any: new Command({ description: "See how much money someone else has by using their username.", async run({guild, channel, args, message}) { - if (isAuthorized(guild, channel)) - callMemberByUsername(message, args.join(" "), (member) => { - channel.send(getMoneyEmbed(member.user)); - }); + if (isAuthorized(guild, channel)) { + const member = await getMemberByName(guild!, args.join(" ")); + if (member instanceof GuildMember) channel.send(getMoneyEmbed(member.user)); + else channel.send(member); + } } }) }); diff --git a/src/commands/fun/whois.ts b/src/commands/fun/whois.ts index d7285f6..0c16b38 100644 --- a/src/commands/fun/whois.ts +++ b/src/commands/fun/whois.ts @@ -1,5 +1,5 @@ -import {User} from "discord.js"; -import {Command, NamedCommand, getMemberByUsername, CHANNEL_TYPE} from "../../core"; +import {User, GuildMember} from "discord.js"; +import {Command, NamedCommand, getMemberByName, CHANNEL_TYPE} from "../../core"; // Quotes must be used here or the numbers will change const registry: {[id: string]: string} = { @@ -69,12 +69,10 @@ export default new NamedCommand({ channelType: CHANNEL_TYPE.GUILD, async run({message, channel, guild, author, client, args}) { const query = args.join(" ") as string; - const member = await getMemberByUsername(guild!, query); + const member = await getMemberByName(guild!, query); - if (member && member.id in registry) { - const id = member.id; - - if (id in registry) { + if (member instanceof GuildMember) { + if (member.id in registry) { channel.send(`\`${member.nickname ?? member.user.username}\` - ${registry[member.id]}`); } else { channel.send( @@ -82,7 +80,7 @@ export default new NamedCommand({ ); } } else { - channel.send(`Couldn't find a user by the name of \`${query}\`!`); + channel.send(member); } } }) diff --git a/src/commands/utility/info.ts b/src/commands/utility/info.ts index 6b7ca82..a47e549 100644 --- a/src/commands/utility/info.ts +++ b/src/commands/utility/info.ts @@ -1,7 +1,7 @@ import {MessageEmbed, version as djsversion, Guild, User, GuildMember} from "discord.js"; import ms from "ms"; import os from "os"; -import {Command, NamedCommand, getMemberByUsername, CHANNEL_TYPE} from "../../core"; +import {Command, NamedCommand, getMemberByName, CHANNEL_TYPE} from "../../core"; import {formatBytes, trimArray} from "../../lib"; import {verificationLevels, filterLevels, regions} from "../../defs/info"; import moment, {utc} from "moment"; @@ -35,9 +35,9 @@ export default new NamedCommand({ channelType: CHANNEL_TYPE.GUILD, async run({message, channel, guild, author, client, args}) { const name = args.join(" "); - const member = await getMemberByUsername(guild!, name); + const member = await getMemberByName(guild!, name); - if (member) { + if (member instanceof GuildMember) { channel.send( member.user.displayAvatarURL({ dynamic: true, @@ -45,7 +45,7 @@ export default new NamedCommand({ }) ); } else { - channel.send(`No user found by the name \`${name}\`!`); + channel.send(member); } } }) diff --git a/src/commands/utility/time.ts b/src/commands/utility/time.ts index 23250ba..b48a4a2 100644 --- a/src/commands/utility/time.ts +++ b/src/commands/utility/time.ts @@ -1,6 +1,6 @@ -import {Command, NamedCommand, ask, askYesOrNo, askMultipleChoice, prompt, callMemberByUsername} from "../../core"; +import {Command, NamedCommand, ask, askYesOrNo, askMultipleChoice, prompt, getMemberByName} from "../../core"; import {Storage} from "../../structures"; -import {User} from "discord.js"; +import {User, GuildMember} from "discord.js"; import moment from "moment"; const DATE_FORMAT = "D MMMM YYYY"; @@ -383,10 +383,10 @@ export default new NamedCommand({ }), any: new Command({ description: "See what time it is for someone else (by their username).", - async run({channel, args, message}) { - callMemberByUsername(message, args.join(" "), (member) => { - channel.send(getTimeEmbed(member.user)); - }); + async run({channel, args, guild}) { + const member = await getMemberByName(guild!, args.join(" ")); + if (member instanceof GuildMember) channel.send(getTimeEmbed(member.user)); + else channel.send(member); } }) }); diff --git a/src/core/command.ts b/src/core/command.ts index a89449b..0e7e5e4 100644 --- a/src/core/command.ts +++ b/src/core/command.ts @@ -8,9 +8,10 @@ import { Guild, User, GuildMember, - GuildChannel + GuildChannel, + Channel } from "discord.js"; -import {SingleMessageOptions} from "./libd"; +import {getChannelByID, getMessageByID, getUserByID, SingleMessageOptions} from "./libd"; import {hasPermission, getPermissionLevel, getPermissionName} from "./permissions"; import {getPrefix} from "./interface"; import {parseVars, requireAllCasesHandledFor} from "../lib"; @@ -338,17 +339,20 @@ export class Command { return this.subcommands.get(param)!.execute(args, menu, metadata); } else if (this.channel && patterns.channel.test(param)) { const id = patterns.channel.exec(param)![1]; - const channel = menu.client.channels.cache.get(id); + const channel = await getChannelByID(id); - // Users can only enter in this format for text channels, so this restricts it to that. - if (channel instanceof TextChannel) { - metadata.symbolicArgs.push(""); - menu.args.push(channel); - return this.channel.execute(args, menu, metadata); + if (channel instanceof Channel) { + if (channel instanceof TextChannel || channel instanceof DMChannel) { + metadata.symbolicArgs.push(""); + menu.args.push(channel); + return this.channel.execute(args, menu, metadata); + } else { + return { + content: `\`${id}\` is not a valid text channel!` + }; + } } else { - return { - content: `\`${id}\` is not a valid text channel!` - }; + return channel; } } else if (this.role && patterns.role.test(param)) { const id = patterns.role.exec(param)![1]; @@ -397,34 +401,25 @@ export class Command { messageID = result[2]; } - const channel = menu.client.channels.cache.get(channelID); + const message = await getMessageByID(channelID, messageID); - if (channel instanceof TextChannel || channel instanceof DMChannel) { - try { - metadata.symbolicArgs.push(""); - menu.args.push(await channel.messages.fetch(messageID)); - return this.message.execute(args, menu, metadata); - } catch { - return { - content: `\`${messageID}\` isn't a valid message of channel ${channel}!` - }; - } + if (message instanceof Message) { + metadata.symbolicArgs.push(""); + menu.args.push(message); + return this.message.execute(args, menu, metadata); } else { - return { - content: `\`${channelID}\` is not a valid text channel!` - }; + return message; } } else if (this.user && patterns.user.test(param)) { const id = patterns.user.exec(param)![1]; + const user = await getUserByID(id); - try { + if (user instanceof User) { metadata.symbolicArgs.push(""); - menu.args.push(await menu.client.users.fetch(id)); + menu.args.push(user); return this.user.execute(args, menu, metadata); - } catch { - return { - content: `No user found by the ID \`${id}\`!` - }; + } else { + return user; } } else if (this.id && this.idType && patterns.id.test(param)) { metadata.symbolicArgs.push(""); @@ -434,16 +429,20 @@ export class Command { // Because this part is pretty much a whole bunch of copy pastes. switch (this.idType) { case "channel": - const channel = menu.client.channels.cache.get(id); + const channel = await getChannelByID(id); - // Users can only enter in this format for text channels, so this restricts it to that. - if (channel instanceof TextChannel) { - menu.args.push(channel); - return this.id.execute(args, menu, metadata); + if (channel instanceof Channel) { + if (channel instanceof TextChannel || channel instanceof DMChannel) { + metadata.symbolicArgs.push(""); + menu.args.push(channel); + return this.id.execute(args, menu, metadata); + } else { + return { + content: `\`${id}\` is not a valid text channel!` + }; + } } else { - return { - content: `\`${id}\` isn't a valid text channel!` - }; + return channel; } case "role": if (!menu.guild) { @@ -474,22 +473,22 @@ export class Command { }; } case "message": - try { - menu.args.push(await menu.channel.messages.fetch(id)); + const message = await getMessageByID(menu.channel, id); + + if (message instanceof Message) { + menu.args.push(message); return this.id.execute(args, menu, metadata); - } catch { - return { - content: `\`${id}\` isn't a valid message of channel ${menu.channel}!` - }; + } else { + return message; } case "user": - try { - menu.args.push(await menu.client.users.fetch(id)); + const user = await getUserByID(id); + + if (user instanceof User) { + menu.args.push(user); return this.id.execute(args, menu, metadata); - } catch { - return { - content: `No user found by the ID \`${id}\`!` - }; + } else { + return user; } default: requireAllCasesHandledFor(this.idType); diff --git a/src/core/index.ts b/src/core/index.ts index 5d75c14..16d1116 100644 --- a/src/core/index.ts +++ b/src/core/index.ts @@ -2,16 +2,6 @@ export {Command, NamedCommand, CHANNEL_TYPE} from "./command"; export {addInterceptRule} from "./handler"; export {launch} from "./interface"; -export { - SingleMessageOptions, - botHasPermission, - paginate, - prompt, - ask, - askYesOrNo, - askMultipleChoice, - getMemberByUsername, - callMemberByUsername -} from "./libd"; +export * from "./libd"; export {getCommandList, getCommandInfo} from "./loader"; export {hasPermission, getPermissionLevel, getPermissionName} from "./permissions"; diff --git a/src/core/interface.ts b/src/core/interface.ts index 1e1df4b..f6ead8f 100644 --- a/src/core/interface.ts +++ b/src/core/interface.ts @@ -25,11 +25,13 @@ interface LaunchSettings { // Additionally, each method would return the object so multiple methods could be chained, such as OnionCore.setPermissions(...).setPrefixResolver(...).launch(client). // I decided to not do this because creating a class then having a bunch of boilerplate around it just wouldn't really be worth it. // commandsDirectory requires an absolute path to work, so use __dirname. -export async function launch(client: Client, commandsDirectory: string, settings?: LaunchSettings) { +export async function launch(newClient: Client, commandsDirectory: string, settings?: LaunchSettings) { // Core Launch Parameters // + client.destroy(); // Release any resources/connections being used by the placeholder client. + client = newClient; loadableCommands = loadCommands(commandsDirectory); - attachMessageHandlerToClient(client); - attachEventListenersToClient(client); + attachMessageHandlerToClient(newClient); + attachEventListenersToClient(newClient); // Additional Configuration // if (settings?.permissionLevels) { @@ -42,6 +44,7 @@ export async function launch(client: Client, commandsDirectory: string, settings // Placeholder until properly loaded by the user. export let loadableCommands = (async () => new Collection())(); +export let client = new Client(); export let permissionLevels: PermissionLevel[] = [ { name: "User", diff --git a/src/core/libd.ts b/src/core/libd.ts index 7f52c75..45bf919 100644 --- a/src/core/libd.ts +++ b/src/core/libd.ts @@ -7,9 +7,13 @@ import { TextChannel, DMChannel, NewsChannel, - MessageOptions + MessageOptions, + Channel, + GuildChannel, + User } from "discord.js"; import {unreactEventListeners, replyEventListeners} from "./eventListeners"; +import {client} from "./interface"; export type SingleMessageOptions = MessageOptions & {split?: false}; @@ -20,16 +24,19 @@ export function botHasPermission(guild: Guild | null, permission: number): boole return !!guild?.me?.hasPermission(permission); } +// The SoonTM Section // // Maybe promisify this section to reduce the potential for creating callback hell? Especially if multiple questions in a row are being asked. - -// Pagination function that allows for customization via a callback. -// Define your own pages outside the function because this only manages the actual turning of pages. +// It's probably a good idea to modularize the base reaction handler so there's less copy pasted code. +// Maybe also make a reaction handler that listens for when reactions are added and removed. +// The reaction handler would also run an async function to react in order (parallel to the reaction handler). const FIVE_BACKWARDS_EMOJI = "⏪"; const BACKWARDS_EMOJI = "⬅️"; const FORWARDS_EMOJI = "➡️"; const FIVE_FORWARDS_EMOJI = "⏩"; +// Pagination function that allows for customization via a callback. +// Define your own pages outside the function because this only manages the actual turning of pages. /** * Takes a message and some additional parameters and makes a reaction page with it. All the pagination logic is taken care of but nothing more, the page index is returned and you have to send a callback to do something with it. */ @@ -251,44 +258,132 @@ export async function askMultipleChoice( if (!isDeleted) message.delete(); } -/** - * Gets a user by their username. Gets the first one then rolls with it. - */ -export async function getMemberByUsername(guild: Guild, username: string) { - return ( - await guild.members.fetch({ - query: username, - limit: 1 - }) - ).first(); -} - -/** - * Convenience function to handle cases where someone isn't found by a username automatically. - */ -export async function callMemberByUsername( - message: Message, - username: string, - onSuccess: (member: GuildMember) => void -) { - const guild = message.guild; - const send = message.channel.send; - - if (guild) { - const member = await getMemberByUsername(guild, username); - - if (member) onSuccess(member); - else send(`Couldn't find a user by the name of \`${username}\`!`); - } else send("You must execute this command in a server!"); -} - -// TO DO Section // - -// getGuildByID() - checks for guild.available (boolean) -// getGuildByName() -// findMemberByNickname() - gets a member by their nickname or their username -// findUserByUsername() - // For "get x by y" methods: // Caching: All guilds, channels, and roles are fully cached, while the caches for messages, users, and members aren't complete. // It's more reliable to get users/members by fetching their IDs. fetch() will searching through the cache anyway. +// For guilds, do an extra check to make sure there isn't an outage (guild.available). + +export function getGuildByID(id: string): Guild | SingleMessageOptions { + const guild = client.guilds.cache.get(id); + + if (guild) { + if (guild.available) return guild; + else return {content: `The guild \`${guild.name}\` (ID: \`${id}\`) is unavailable due to an outage.`}; + } else { + return { + content: `No guild found by the ID of \`${id}\`!` + }; + } +} + +export function getGuildByName(name: string): Guild | SingleMessageOptions { + const query = name.toLowerCase(); + const guild = client.guilds.cache.find((guild) => guild.name.toLowerCase().includes(query)); + + if (guild) { + if (guild.available) return guild; + else return {content: `The guild \`${guild.name}\` (ID: \`${guild.id}\`) is unavailable due to an outage.`}; + } else { + return { + content: `No guild found by the name of \`${name}\`!` + }; + } +} + +export async function getChannelByID(id: string): Promise { + try { + return await client.channels.fetch(id); + } catch { + return {content: `No channel found by the ID of \`${id}\`!`}; + } +} + +// Only go through the cached channels (non-DM channels). Plus, searching DM channels by name wouldn't really make sense, nor do they have names to search anyway. +export function getChannelByName(name: string): GuildChannel | SingleMessageOptions { + const query = name.toLowerCase(); + const channel = client.channels.cache.find( + (channel) => channel instanceof GuildChannel && channel.name.toLowerCase().includes(query) + ) as GuildChannel | undefined; + if (channel) return channel; + else return {content: `No channel found by the name of \`${name}\`!`}; +} + +export async function getMessageByID( + channel: TextChannel | DMChannel | NewsChannel | string, + id: string +): Promise { + if (typeof channel === "string") { + const targetChannel = await getChannelByID(channel); + if (targetChannel instanceof TextChannel || targetChannel instanceof DMChannel) channel = targetChannel; + else if (targetChannel instanceof Channel) return {content: `\`${id}\` isn't a valid text-based channel!`}; + else return targetChannel; + } + + try { + return await channel.messages.fetch(id); + } catch { + return {content: `\`${id}\` isn't a valid message of the channel ${channel}!`}; + } +} + +export async function getUserByID(id: string): Promise { + try { + return await client.users.fetch(id); + } catch { + return {content: `No user found by the ID of \`${id}\`!`}; + } +} + +// Also check tags (if provided) to narrow down users. +export function getUserByName(name: string): User | SingleMessageOptions { + let query = name.toLowerCase(); + const tagMatch = /^(.+?)#(\d{4})$/.exec(name); + let tag: string | null = null; + + if (tagMatch) { + query = tagMatch[1].toLowerCase(); + tag = tagMatch[2]; + } + + const user = client.users.cache.find((user) => { + const hasUsernameMatch = user.username.toLowerCase().includes(query); + if (tag) return hasUsernameMatch && user.discriminator === tag; + else return hasUsernameMatch; + }); + + if (user) return user; + else return {content: `No user found by the name of \`${name}\`!`}; +} + +export async function getMemberByID(guild: Guild, id: string): Promise { + try { + return await guild.members.fetch(id); + } catch { + return {content: `No member found by the ID of \`${id}\`!`}; + } +} + +// First checks if a member can be found by that nickname, then check if a member can be found by that username. +export async function getMemberByName(guild: Guild, name: string): Promise { + const member = ( + await guild.members.fetch({ + query: name, + limit: 1 + }) + ).first(); + + // Search by username if no member is found, then resolve the user into a member if possible. + if (member) { + return member; + } else { + const user = getUserByName(name); + + if (user instanceof User) { + const member = guild.members.resolve(user); + if (member) return member; + else return {content: `The user \`${user.tag}\` isn't in this guild!`}; + } else { + return {content: `No member found by the name of \`${name}\`!`}; + } + } +} From e8def0aec3faf4375d0e1ade4b7d55acf722b656 Mon Sep 17 00:00:00 2001 From: WatDuhHekBro <44940783+WatDuhHekBro@users.noreply.github.com> Date: Sat, 10 Apr 2021 07:51:32 -0500 Subject: [PATCH 158/178] Added guild subcommand type and various changes --- CHANGELOG.md | 1 + docs/DesignDecisions.md | 4 ++ src/commands/fun/modules/eco-shop.ts | 2 +- src/commands/system/help.ts | 2 +- src/commands/utility/info.ts | 37 ++++++-------- src/commands/utility/lsemotes.ts | 3 +- src/commands/utility/scanemotes.ts | 8 +-- src/core/command.ts | 26 ++++++++-- src/core/handler.ts | 12 +++-- src/core/libd.ts | 20 ++++++-- src/core/loader.ts | 2 +- src/defs/info.ts | 8 +-- src/modules/messageEmbed.ts | 75 ++++++++++++---------------- src/modules/streamNotifications.ts | 1 + src/structures.ts | 1 + 15 files changed, 113 insertions(+), 89 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cefffe1..3f52e17 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ - `urban`: Bug fixes - Changed `help` to display a paginated embed - Various changes to core + - Added `guild` subcommand type (only accessible when `id` is set to `guild`) # 3.2.0 - Internal refactor, more subcommand types, and more command type guards (2021-04-09) - The custom logger changed: `$.log` no longer exists, it's just `console.log`. Now you don't have to do `import $ from "../core/lib"` at the top of every file that uses the custom logger. diff --git a/docs/DesignDecisions.md b/docs/DesignDecisions.md index 4a2bbd5..584396c 100644 --- a/docs/DesignDecisions.md +++ b/docs/DesignDecisions.md @@ -71,6 +71,10 @@ Boolean subcommand types won't be implemented: For common use cases, there wouldn't be a need to go accept numbers of different bases. The only time it would be applicable is if there was some sort of base converter command, and even then, it'd be better to just implement custom logic. +## User Mention + Search by Username Type + +While it's a pretty common pattern, it's probably a bit too specific for the `Command` class itself. Instead, this pattern will be comprised of two subcommands: A `user` type and an `any` type. + # The Command Handler ## The Scope of the Command Handler diff --git a/src/commands/fun/modules/eco-shop.ts b/src/commands/fun/modules/eco-shop.ts index 47d0178..ca161f1 100644 --- a/src/commands/fun/modules/eco-shop.ts +++ b/src/commands/fun/modules/eco-shop.ts @@ -34,7 +34,7 @@ export const ShopCommand = new NamedCommand({ const shopPages = split(ShopItems, 5); const pageAmount = shopPages.length; - paginate(channel, author.id, pageAmount, (page, hasMultiplePages) => { + paginate(channel.send, author.id, pageAmount, (page, hasMultiplePages) => { return getShopEmbed( shopPages[page], hasMultiplePages ? `Shop (Page ${page + 1} of ${pageAmount})` : "Shop" diff --git a/src/commands/system/help.ts b/src/commands/system/help.ts index 180819d..c6ec382 100644 --- a/src/commands/system/help.ts +++ b/src/commands/system/help.ts @@ -20,7 +20,7 @@ export default new NamedCommand({ const commands = await getCommandList(); const categoryArray = commands.keyArray(); - paginate(channel, author.id, categoryArray.length, (page, hasMultiplePages) => { + paginate(channel.send, author.id, categoryArray.length, (page, hasMultiplePages) => { const category = categoryArray[page]; const commandList = commands.get(category)!; let output = `Legend: \`\`, \`[list/of/stuff]\`, \`(optional)\`, \`()\`, \`([optional/list/...])\`\n`; diff --git a/src/commands/utility/info.ts b/src/commands/utility/info.ts index a47e549..f3d5bb3 100644 --- a/src/commands/utility/info.ts +++ b/src/commands/utility/info.ts @@ -1,7 +1,7 @@ import {MessageEmbed, version as djsversion, Guild, User, GuildMember} from "discord.js"; import ms from "ms"; import os from "os"; -import {Command, NamedCommand, getMemberByName, CHANNEL_TYPE} from "../../core"; +import {Command, NamedCommand, getMemberByName, CHANNEL_TYPE, getGuildByName} from "../../core"; import {formatBytes, trimArray} from "../../lib"; import {verificationLevels, filterLevels, regions} from "../../defs/info"; import moment, {utc} from "moment"; @@ -98,30 +98,23 @@ export default new NamedCommand({ async run({message, channel, guild, author, member, client, args}) { channel.send(await getGuildInfo(guild!, guild)); }, - any: new Command({ - description: "Display info about a guild by finding its name or ID.", + id: "guild", + guild: new Command({ + description: "Display info about a guild by its ID.", async run({message, channel, guild, author, member, client, args}) { - // If a guild ID is provided (avoid the "number" subcommand because of inaccuracies), search for that guild - if (args.length === 1 && /^\d{17,}$/.test(args[0])) { - const id = args[0]; - const targetGuild = client.guilds.cache.get(id); + const targetGuild = args[0] as Guild; + channel.send(await getGuildInfo(targetGuild, guild)); + } + }), + any: new Command({ + description: "Display info about a guild by finding its name.", + async run({message, channel, guild, author, member, client, args}) { + const targetGuild = getGuildByName(args.join(" ")); - if (targetGuild) { - channel.send(await getGuildInfo(targetGuild, guild)); - } else { - channel.send(`None of the servers I'm in matches the guild ID \`${id}\`!`); - } + if (targetGuild instanceof Guild) { + channel.send(await getGuildInfo(targetGuild, guild)); } else { - const query: string = args.join(" ").toLowerCase(); - const targetGuild = client.guilds.cache.find((guild) => - guild.name.toLowerCase().includes(query) - ); - - if (targetGuild) { - channel.send(await getGuildInfo(targetGuild, guild)); - } else { - channel.send(`None of the servers I'm in matches the query \`${query}\`!`); - } + channel.send(targetGuild); } } }) diff --git a/src/commands/utility/lsemotes.ts b/src/commands/utility/lsemotes.ts index fef7813..0d858bc 100644 --- a/src/commands/utility/lsemotes.ts +++ b/src/commands/utility/lsemotes.ts @@ -35,7 +35,6 @@ export default new NamedCommand({ let emoteCollection = client.emojis.cache.array(); // Creates a sandbox to stop a regular expression if it takes too much time to search. // To avoid passing in a giant data structure, I'll just pass in the structure {[id: string]: [name: string]}. - //let emotes: {[id: string]: string} = {}; let emotes = new Map(); for (const emote of emoteCollection) { @@ -91,7 +90,7 @@ async function displayEmoteList(emotes: GuildEmoji[], channel: TextChannel | DMC // Gather the first page (if it even exists, which it might not if there no valid emotes appear) if (pages > 0) { - paginate(channel, author.id, pages, (page, hasMultiplePages) => { + paginate(channel.send, author.id, pages, (page, hasMultiplePages) => { embed.setTitle(hasMultiplePages ? `**Emotes** (Page ${page + 1} of ${pages})` : "**Emotes**"); let desc = ""; diff --git a/src/commands/utility/scanemotes.ts b/src/commands/utility/scanemotes.ts index 3662c57..a432147 100644 --- a/src/commands/utility/scanemotes.ts +++ b/src/commands/utility/scanemotes.ts @@ -3,7 +3,7 @@ import {pluralise} from "../../lib"; import moment from "moment"; import {Collection, TextChannel} from "discord.js"; -const lastUsedTimestamps: {[id: string]: number} = {}; +const lastUsedTimestamps = new Collection(); export default new NamedCommand({ description: @@ -13,7 +13,7 @@ export default new NamedCommand({ // Test if the command is on cooldown. This isn't the strictest cooldown possible, because in the event that the bot crashes, the cooldown will be reset. But for all intends and purposes, it's a good enough cooldown. It's a per-server cooldown. const startTime = Date.now(); const cooldown = 86400000; // 24 hours - const lastUsedTimestamp = lastUsedTimestamps[guild!.id] ?? 0; + const lastUsedTimestamp = lastUsedTimestamps.get(guild!.id) ?? 0; const difference = startTime - lastUsedTimestamp; const howLong = moment(startTime).to(lastUsedTimestamp + cooldown); @@ -22,7 +22,7 @@ export default new NamedCommand({ return channel.send( `This command requires a day to cooldown. You'll be able to activate this command ${howLong}.` ); - else lastUsedTimestamps[guild!.id] = startTime; + else lastUsedTimestamps.set(guild!.id, startTime); const stats: { [id: string]: { @@ -188,7 +188,7 @@ export default new NamedCommand({ description: "Forces the cooldown timer to reset.", permission: PERMISSIONS.BOT_SUPPORT, async run({message, channel, guild, author, member, client, args}) { - lastUsedTimestamps[guild!.id] = 0; + lastUsedTimestamps.set(guild!.id, 0); channel.send("Reset the cooldown on `scanemotes`."); } }) diff --git a/src/core/command.ts b/src/core/command.ts index 0e7e5e4..d38d78a 100644 --- a/src/core/command.ts +++ b/src/core/command.ts @@ -11,7 +11,7 @@ import { GuildChannel, Channel } from "discord.js"; -import {getChannelByID, getMessageByID, getUserByID, SingleMessageOptions} from "./libd"; +import {getChannelByID, getGuildByID, getMessageByID, getUserByID, SingleMessageOptions, SendFunction} from "./libd"; import {hasPermission, getPermissionLevel, getPermissionName} from "./permissions"; import {getPrefix} from "./interface"; import {parseVars, requireAllCasesHandledFor} from "../lib"; @@ -44,7 +44,7 @@ const patterns = { }; // Maybe add a guild redirect... somehow? -type ID = "channel" | "role" | "emote" | "message" | "user"; +type ID = "channel" | "role" | "emote" | "message" | "user" | "guild"; // Callbacks don't work with discriminated unions: // - https://github.com/microsoft/TypeScript/issues/41759 @@ -68,6 +68,7 @@ interface CommandMenu { // According to the documentation, a message can be part of a guild while also not having a // member object for the author. This will happen if the author of a message left the guild. readonly member: GuildMember | null; + readonly send: SendFunction; } interface CommandOptionsBase { @@ -95,6 +96,7 @@ interface CommandOptionsNonEndpoint { readonly emote?: Command; readonly message?: Command; readonly user?: Command; + readonly guild?: Command; // Only available if an ID is set to reroute to it. readonly id?: ID; readonly number?: Command; readonly any?: Command; @@ -156,6 +158,7 @@ export class Command { private emote: Command | null; private message: Command | null; private user: Command | null; + private guild: Command | null; private id: Command | null; private idType: ID | null; private number: Command | null; @@ -175,6 +178,7 @@ export class Command { this.emote = null; this.message = null; this.user = null; + this.guild = null; this.id = null; this.idType = null; this.number = null; @@ -186,6 +190,7 @@ export class Command { if (options?.emote) this.emote = options.emote; if (options?.message) this.message = options.message; if (options?.user) this.user = options.user; + if (options?.guild) this.guild = options.guild; if (options?.number) this.number = options.number; if (options?.any) this.any = options.any; if (options?.id) this.idType = options.id; @@ -207,6 +212,9 @@ export class Command { case "user": this.id = this.user; break; + case "guild": + this.id = this.guild; + break; default: requireAllCasesHandledFor(options.id); } @@ -246,6 +254,9 @@ export class Command { // // Calls the resulting subcommand's execute method in order to make more modular code, basically pushing the chain of execution to the subcommand. // For example, a numeric subcommand would accept args of [4] then execute on it. + // + // Because each Command instance is isolated from others, it becomes practically impossible to predict the total amount of subcommands when isolating the code to handle each individual layer of recursion. + // Therefore, if a Command is declared as a rest type, any typed args that come at the end must be handled manually. public async execute( args: string[], menu: CommandMenu, @@ -300,7 +311,7 @@ export class Command { if (typeof this.run === "string") { // Although I *could* add an option in the launcher to attach arbitrary variables to this var string... // I'll just leave it like this, because instead of using var strings for user stuff, you could just make "run" a template string. - await menu.channel.send( + await menu.send( parseVars( this.run, { @@ -490,6 +501,15 @@ export class Command { } else { return user; } + case "guild": + const guild = getGuildByID(id); + + if (guild instanceof Guild) { + menu.args.push(guild); + return this.id.execute(args, menu, metadata); + } else { + return guild; + } default: requireAllCasesHandledFor(this.idType); } diff --git a/src/core/handler.ts b/src/core/handler.ts index 560ca77..52fdc80 100644 --- a/src/core/handler.ts +++ b/src/core/handler.ts @@ -37,6 +37,7 @@ export function attachMessageHandlerToClient(client: Client) { const commands = await loadableCommands; const {author, channel, content, guild, member} = message; + const send = channel.send.bind(channel); const text = content; const menu = { author, @@ -45,7 +46,8 @@ export function attachMessageHandlerToClient(client: Client) { guild, member, message, - args: [] + args: [], + send }; // Execute a dedicated block for messages in DM channels. @@ -70,10 +72,10 @@ export function attachMessageHandlerToClient(client: Client) { // If something went wrong, let the user know (like if they don't have permission to use a command). if (result) { - channel.send(result); + send(result); } } else { - channel.send( + send( `I couldn't find the command or alias that starts with \`${header}\`. To see the list of commands, type \`help\`` ); } @@ -84,7 +86,7 @@ export function attachMessageHandlerToClient(client: Client) { // First, test if the message is just a ping to the bot. if (new RegExp(`^<@!?${client.user!.id}>$`).test(text)) { - channel.send(`${author}, my prefix on this server is \`${prefix}\`.`); + send(`${author}, my prefix on this server is \`${prefix}\`.`); } // Then check if it's a normal command. else if (text.startsWith(prefix)) { @@ -107,7 +109,7 @@ export function attachMessageHandlerToClient(client: Client) { // If something went wrong, let the user know (like if they don't have permission to use a command). if (result) { - channel.send(result); + send(result); } } } diff --git a/src/core/libd.ts b/src/core/libd.ts index 45bf919..c9d5bcf 100644 --- a/src/core/libd.ts +++ b/src/core/libd.ts @@ -10,13 +10,27 @@ import { MessageOptions, Channel, GuildChannel, - User + User, + APIMessageContentResolvable, + MessageAdditions, + SplitOptions, + APIMessage, + StringResolvable } from "discord.js"; import {unreactEventListeners, replyEventListeners} from "./eventListeners"; import {client} from "./interface"; export type SingleMessageOptions = MessageOptions & {split?: false}; +export type SendFunction = (( + content: APIMessageContentResolvable | (MessageOptions & {split?: false}) | MessageAdditions +) => Promise) & + ((options: MessageOptions & {split: true | SplitOptions}) => Promise) & + ((options: MessageOptions | APIMessage) => Promise) & + ((content: StringResolvable, options: (MessageOptions & {split?: false}) | MessageAdditions) => Promise) & + ((content: StringResolvable, options: MessageOptions & {split: true | SplitOptions}) => Promise) & + ((content: StringResolvable, options: MessageOptions) => Promise); + /** * Tests if a bot has a certain permission in a specified guild. */ @@ -41,14 +55,14 @@ const FIVE_FORWARDS_EMOJI = "⏩"; * Takes a message and some additional parameters and makes a reaction page with it. All the pagination logic is taken care of but nothing more, the page index is returned and you have to send a callback to do something with it. */ export async function paginate( - channel: TextChannel | DMChannel | NewsChannel, + send: SendFunction, senderID: string, total: number, callback: (page: number, hasMultiplePages: boolean) => SingleMessageOptions, duration = 60000 ) { const hasMultiplePages = total > 1; - const message = await channel.send(callback(0, hasMultiplePages)); + const message = await send(callback(0, hasMultiplePages)); if (hasMultiplePages) { let page = 0; diff --git a/src/core/loader.ts b/src/core/loader.ts index da22a4a..cabb0fd 100644 --- a/src/core/loader.ts +++ b/src/core/loader.ts @@ -71,7 +71,7 @@ export async function loadCommands(commandsDir: string): Promise { // Only execute if the message is from a user and isn't a command. @@ -10,49 +10,38 @@ client.on("message", async (message) => { if (!messageLink) return; const [guildID, channelID, messageID] = messageLink; - try { - const channel = client.guilds.cache.get(guildID)?.channels.cache.get(channelID) as TextChannel; - const link_message = await channel.messages.fetch(messageID); + const linkMessage = await getMessageByID(channelID, messageID); - let rtmsg: string | APIMessage = ""; - if (link_message.cleanContent) { - rtmsg = new APIMessage(message.channel as TextChannel, { - content: link_message.cleanContent, - disableMentions: "all", - files: link_message.attachments.array() - }); - } - - const embeds = [...link_message.embeds.filter((v) => v.type == "rich"), ...link_message.attachments.values()]; - - /// @ts-ignore - if (!link_message.cleanContent && embeds.empty) { - const Embed = new MessageEmbed().setDescription("🚫 The message is empty."); - return message.channel.send(Embed); - } - - const infoEmbed = new MessageEmbed() - .setAuthor( - link_message.author.username, - link_message.author.displayAvatarURL({format: "png", dynamic: true, size: 4096}) - ) - .setTimestamp(link_message.createdTimestamp) - .setDescription( - `${link_message.cleanContent}\n\nSent in **${link_message.guild?.name}** | <#${link_message.channel.id}> ([link](https://discord.com/channels/${guildID}/${channelID}/${messageID}))` - ); - if (link_message.attachments.size !== 0) { - const image = link_message.attachments.first(); - /// @ts-ignore - infoEmbed.setImage(image.url); - } - - await message.channel.send(infoEmbed); - } catch (error) { - if (error instanceof DiscordAPIError) { - message.channel.send("I don't have access to this channel, or something else went wrong."); - } - return console.error(error); + // If it's an invalid link (or the bot doesn't have access to it). + if (!(linkMessage instanceof Message)) { + return message.channel.send("I don't have access to that channel!"); } + + const embeds = [ + ...linkMessage.embeds.filter((embed) => embed.type === "rich"), + ...linkMessage.attachments.values() + ]; + + if (!linkMessage.cleanContent && embeds.length === 0) { + return message.channel.send(new MessageEmbed().setDescription("🚫 The message is empty.")); + } + + const infoEmbed = new MessageEmbed() + .setAuthor( + linkMessage.author.username, + linkMessage.author.displayAvatarURL({format: "png", dynamic: true, size: 4096}) + ) + .setTimestamp(linkMessage.createdTimestamp) + .setDescription( + `${linkMessage.cleanContent}\n\nSent in **${linkMessage.guild?.name}** | <#${linkMessage.channel.id}> ([link](https://discord.com/channels/${guildID}/${channelID}/${messageID}))` + ); + + if (linkMessage.attachments.size !== 0) { + const image = linkMessage.attachments.first(); + infoEmbed.setImage(image!.url); + } + + return await message.channel.send(infoEmbed); }); export function extractFirstMessageLink(message: string): [string, string, string] | null { diff --git a/src/modules/streamNotifications.ts b/src/modules/streamNotifications.ts index f6e2843..b6e9fef 100644 --- a/src/modules/streamNotifications.ts +++ b/src/modules/streamNotifications.ts @@ -47,6 +47,7 @@ client.on("voiceStateUpdate", async (before, after) => { const voiceChannel = after.channel!; const textChannel = client.channels.cache.get(streamingChannel); + // Although checking the bot's permission to send might seem like a good idea, having the error be thrown will cause it to show up in the last channel rather than just show up in the console. if (textChannel instanceof TextChannel) { if (isStartStreamEvent) { streamList.set(member.id, { diff --git a/src/structures.ts b/src/structures.ts index f9065b9..8991e3f 100644 --- a/src/structures.ts +++ b/src/structures.ts @@ -5,6 +5,7 @@ import {watch} from "fs"; import {Guild as DiscordGuild, Snowflake} from "discord.js"; // Maybe use getters and setters to auto-save on set? +// And maybe use Collections/Maps instead of objects? class ConfigStructure extends GenericStructure { public token: string; From e1e6910b1de3815e0ad12ba3be20df69e31d3ac7 Mon Sep 17 00:00:00 2001 From: WatDuhHekBro <44940783+WatDuhHekBro@users.noreply.github.com> Date: Sat, 10 Apr 2021 08:34:55 -0500 Subject: [PATCH 159/178] Reduced channel.send() to send() --- CHANGELOG.md | 1 + src/commands/fun/8ball.ts | 4 +- src/commands/fun/cookie.ts | 10 +-- src/commands/fun/eco.ts | 14 +-- src/commands/fun/figlet.ts | 6 +- src/commands/fun/insult.ts | 4 +- src/commands/fun/love.ts | 4 +- src/commands/fun/modules/eco-bet.ts | 56 ++++++------ src/commands/fun/modules/eco-core.ts | 49 +++++------ src/commands/fun/modules/eco-extras.ts | 25 +++--- src/commands/fun/modules/eco-shop.ts | 10 +-- src/commands/fun/neko.ts | 12 ++- src/commands/fun/ok.ts | 4 +- src/commands/fun/owoify.ts | 4 +- src/commands/fun/party.ts | 4 +- src/commands/fun/poll.ts | 4 +- src/commands/fun/ravi.ts | 10 +-- src/commands/fun/thonk.ts | 4 +- src/commands/fun/urban.ts | 6 +- src/commands/fun/vaporwave.ts | 6 +- src/commands/fun/weather.ts | 8 +- src/commands/fun/whois.ts | 22 +++-- src/commands/system/admin.ts | 116 ++++++++++++------------- src/commands/system/help.ts | 10 +-- src/commands/template.ts | 2 +- src/commands/utility/calc.ts | 8 +- src/commands/utility/desc.ts | 10 +-- src/commands/utility/emote.ts | 4 +- src/commands/utility/info.ts | 40 ++++----- src/commands/utility/invite.ts | 4 +- src/commands/utility/lsemotes.ts | 24 ++--- src/commands/utility/react.ts | 16 ++-- src/commands/utility/say.ts | 4 +- src/commands/utility/scanemotes.ts | 14 ++- src/commands/utility/shorten.ts | 4 +- src/commands/utility/streaminfo.ts | 6 +- src/commands/utility/time.ts | 34 ++++---- src/commands/utility/todo.ts | 18 ++-- src/commands/utility/translate.ts | 8 +- 39 files changed, 286 insertions(+), 303 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f52e17..2295ec1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ - Changed `help` to display a paginated embed - Various changes to core - Added `guild` subcommand type (only accessible when `id` is set to `guild`) + - Further reduced `channel.send()` to `send()` because it's used in *every, single, command* # 3.2.0 - Internal refactor, more subcommand types, and more command type guards (2021-04-09) - The custom logger changed: `$.log` no longer exists, it's just `console.log`. Now you don't have to do `import $ from "../core/lib"` at the top of every file that uses the custom logger. diff --git a/src/commands/fun/8ball.ts b/src/commands/fun/8ball.ts index 2c45770..afc1f23 100644 --- a/src/commands/fun/8ball.ts +++ b/src/commands/fun/8ball.ts @@ -31,9 +31,9 @@ export default new NamedCommand({ run: "Please provide a question.", any: new Command({ description: "Question to ask the 8-ball.", - async run({message, channel, guild, author, member, client, args}) { + async run({send, message, channel, guild, author, member, client, args}) { const sender = message.author; - channel.send(`${random(responses)} <@${sender.id}>`); + send(`${random(responses)} <@${sender.id}>`); } }) }); diff --git a/src/commands/fun/cookie.ts b/src/commands/fun/cookie.ts index 69a624a..2275c08 100644 --- a/src/commands/fun/cookie.ts +++ b/src/commands/fun/cookie.ts @@ -31,21 +31,21 @@ export default new NamedCommand({ run: ":cookie: Here's a cookie!", subcommands: { all: new NamedCommand({ - async run({message, channel, guild, author, member, client, args}) { - channel.send(`${author} gave everybody a cookie!`); + async run({send, message, channel, guild, author, member, client, args}) { + send(`${author} gave everybody a cookie!`); } }) }, id: "user", user: new Command({ description: "User to give cookie to.", - async run({message, channel, guild, author, member, client, args}) { + async run({send, message, channel, guild, author, member, client, args}) { const sender = author; const mention: User = args[0]; - if (mention.id == sender.id) return channel.send("You can't give yourself cookies!"); + if (mention.id == sender.id) return send("You can't give yourself cookies!"); - return channel.send( + return send( `:cookie: <@${sender.id}> ${parseVars(random(cookies), { target: mention.toString() })}` diff --git a/src/commands/fun/eco.ts b/src/commands/fun/eco.ts index b265ebb..1572151 100644 --- a/src/commands/fun/eco.ts +++ b/src/commands/fun/eco.ts @@ -8,8 +8,8 @@ import {GuildMember} from "discord.js"; export default new NamedCommand({ description: "Economy command for Monika.", - async run({guild, channel, author}) { - if (isAuthorized(guild, channel)) channel.send(getMoneyEmbed(author)); + async run({send, guild, channel, author}) { + if (isAuthorized(guild, channel)) send(getMoneyEmbed(author)); }, subcommands: { daily: DailyCommand, @@ -29,17 +29,17 @@ export default new NamedCommand({ id: "user", user: new Command({ description: "See how much money someone else has by using their user ID or pinging them.", - async run({guild, channel, args}) { - if (isAuthorized(guild, channel)) channel.send(getMoneyEmbed(args[0])); + async run({send, guild, channel, args}) { + if (isAuthorized(guild, channel)) send(getMoneyEmbed(args[0])); } }), any: new Command({ description: "See how much money someone else has by using their username.", - async run({guild, channel, args, message}) { + async run({send, guild, channel, args, message}) { if (isAuthorized(guild, channel)) { const member = await getMemberByName(guild!, args.join(" ")); - if (member instanceof GuildMember) channel.send(getMoneyEmbed(member.user)); - else channel.send(member); + if (member instanceof GuildMember) send(getMoneyEmbed(member.user)); + else send(member); } } }) diff --git a/src/commands/fun/figlet.ts b/src/commands/fun/figlet.ts index 439ce00..0ae2538 100644 --- a/src/commands/fun/figlet.ts +++ b/src/commands/fun/figlet.ts @@ -3,10 +3,10 @@ import figlet from "figlet"; export default new NamedCommand({ description: "Generates a figlet of your input.", - async run({message, channel, guild, author, member, client, args}) { + async run({send, message, channel, guild, author, member, client, args}) { const input = args.join(" "); - if (!args[0]) return channel.send("You have to provide input for me to create a figlet!"); - return channel.send( + if (!args[0]) return send("You have to provide input for me to create a figlet!"); + return send( "```" + figlet.textSync(`${input}`, { horizontalLayout: "full" diff --git a/src/commands/fun/insult.ts b/src/commands/fun/insult.ts index 63847fd..3cd982d 100644 --- a/src/commands/fun/insult.ts +++ b/src/commands/fun/insult.ts @@ -2,10 +2,10 @@ import {Command, NamedCommand} from "../../core"; export default new NamedCommand({ description: "Insult TravBot! >:D", - async run({message, channel, guild, author, member, client, args}) { + async run({send, message, channel, guild, author, member, client, args}) { channel.startTyping(); setTimeout(() => { - channel.send( + send( `${author} What the fuck did you just fucking say about me, you little bitch? I'll have you know I graduated top of my class in the Navy Seals, and I've been involved in numerous secret raids on Al-Quaeda, and I have over 300 confirmed kills. I am trained in gorilla warfare and I'm the top sniper in the entire US armed forces. You are nothing to me but just another target. I will wipe you the fuck out with precision the likes of which has never been seen before on this Earth, mark my fucking words. You think you can get away with saying that shit to me over the Internet? Think again, fucker. As we speak I am contacting my secret network of spies across the USA and your IP is being traced right now so you better prepare for the storm, maggot. The storm that wipes out the pathetic little thing you call your life. You're fucking dead, kid. I can be anywhere, anytime, and I can kill you in over seven hundred ways, and that's just with my bare hands. Not only am I extensively trained in unarmed combat, but I have access to the entire arsenal of the United States Marine Corps and I will use it to its full extent to wipe your miserable ass off the face of the continent, you little shit. If only you could have known what unholy retribution your little "clever" comment was about to bring down upon you, maybe you would have held your fucking tongue. But you couldn't, you didn't, and now you're paying the price, you goddamn idiot. I will shit fury all over you and you will drown in it. You're fucking dead, kiddo.` ); channel.stopTyping(); diff --git a/src/commands/fun/love.ts b/src/commands/fun/love.ts index bf42d0d..e7d2557 100644 --- a/src/commands/fun/love.ts +++ b/src/commands/fun/love.ts @@ -3,8 +3,8 @@ import {Command, NamedCommand, CHANNEL_TYPE} from "../../core"; export default new NamedCommand({ description: "Chooses someone to love.", channelType: CHANNEL_TYPE.GUILD, - async run({message, channel, guild, author, client, args}) { + async run({send, message, channel, guild, author, client, args}) { const member = guild!.members.cache.random(); - channel.send(`I love ${member.nickname ?? member.user.username}!`); + send(`I love ${member.nickname ?? member.user.username}!`); } }); diff --git a/src/commands/fun/modules/eco-bet.ts b/src/commands/fun/modules/eco-bet.ts index f3ea452..9095124 100644 --- a/src/commands/fun/modules/eco-bet.ts +++ b/src/commands/fun/modules/eco-bet.ts @@ -11,21 +11,21 @@ export const BetCommand = new NamedCommand({ user: new Command({ description: "User to bet with.", // handles missing amount argument - async run({args, author, channel, guild}) { + async run({send, args, author, channel, guild}) { if (isAuthorized(guild, channel)) { const target = args[0]; // handle invalid target - if (target.id == author.id) return channel.send("You can't bet Mons with yourself!"); - else if (target.bot && process.argv[2] !== "dev") return channel.send("You can't bet Mons with a bot!"); + if (target.id == author.id) return send("You can't bet Mons with yourself!"); + else if (target.bot && process.argv[2] !== "dev") return send("You can't bet Mons with a bot!"); - return channel.send("How much are you betting?"); + return send("How much are you betting?"); } else return; }, number: new Command({ description: "Amount of Mons to bet.", // handles missing duration argument - async run({args, author, channel, guild}) { + async run({send, args, author, channel, guild}) { if (isAuthorized(guild, channel)) { const sender = Storage.getUser(author.id); const target = args[0] as User; @@ -33,23 +33,22 @@ export const BetCommand = new NamedCommand({ const amount = Math.floor(args[1]); // handle invalid target - if (target.id == author.id) return channel.send("You can't bet Mons with yourself!"); - else if (target.bot && process.argv[2] !== "dev") - return channel.send("You can't bet Mons with a bot!"); + if (target.id == author.id) return send("You can't bet Mons with yourself!"); + else if (target.bot && process.argv[2] !== "dev") return send("You can't bet Mons with a bot!"); // handle invalid amount - if (amount <= 0) return channel.send("You must bet at least one Mon!"); + if (amount <= 0) return send("You must bet at least one Mon!"); else if (sender.money < amount) - return channel.send("You don't have enough Mons for that.", getMoneyEmbed(author)); + return send("You don't have enough Mons for that.", getMoneyEmbed(author)); else if (receiver.money < amount) - return channel.send("They don't have enough Mons for that.", getMoneyEmbed(target)); + return send("They don't have enough Mons for that.", getMoneyEmbed(target)); - return channel.send("How long until the bet ends?"); + return send("How long until the bet ends?"); } else return; }, any: new Command({ description: "Duration of the bet.", - async run({client, args, author, message, channel, guild}) { + async run({send, client, args, author, message, channel, guild}) { if (isAuthorized(guild, channel)) { // [Pertinence to make configurable on the fly.] // Lower and upper bounds for bet @@ -62,27 +61,26 @@ export const BetCommand = new NamedCommand({ const duration = parseDuration(args[2].trim()); // handle invalid target - if (target.id == author.id) return channel.send("You can't bet Mons with yourself!"); - else if (target.bot && process.argv[2] !== "dev") - return channel.send("You can't bet Mons with a bot!"); + if (target.id == author.id) return send("You can't bet Mons with yourself!"); + else if (target.bot && process.argv[2] !== "dev") return send("You can't bet Mons with a bot!"); // handle invalid amount - if (amount <= 0) return channel.send("You must bet at least one Mon!"); + if (amount <= 0) return send("You must bet at least one Mon!"); else if (sender.money < amount) - return channel.send("You don't have enough Mons for that.", getMoneyEmbed(author)); + return send("You don't have enough Mons for that.", getMoneyEmbed(author)); else if (receiver.money < amount) - return channel.send("They don't have enough Mons for that.", getMoneyEmbed(target)); + return send("They don't have enough Mons for that.", getMoneyEmbed(target)); // handle invalid duration - if (duration <= 0) return channel.send("Invalid bet duration"); + if (duration <= 0) return send("Invalid bet duration"); else if (duration <= parseDuration(durationBounds.min)) - return channel.send(`Bet duration is too short, maximum duration is ${durationBounds.min}`); + return send(`Bet duration is too short, maximum duration is ${durationBounds.min}`); else if (duration >= parseDuration(durationBounds.max)) - return channel.send(`Bet duration is too long, maximum duration is ${durationBounds.max}`); + return send(`Bet duration is too long, maximum duration is ${durationBounds.max}`); // Ask target whether or not they want to take the bet. const takeBet = await askYesOrNo( - await channel.send( + await send( `<@${target.id}>, do you want to take this bet of ${pluralise(amount, "Mon", "s")}` ), target.id @@ -99,7 +97,7 @@ export const BetCommand = new NamedCommand({ Storage.save(); // Notify both users. - await channel.send( + await send( `<@${target.id}> has taken <@${author.id}>'s bet, the bet amount of ${pluralise( amount, "Mon", @@ -114,7 +112,7 @@ export const BetCommand = new NamedCommand({ const receiver = Storage.getUser(target.id); // [TODO: when D.JSv13 comes out, inline reply to clean up.] // When bet is over, give a vote to ask people their thoughts. - const voteMsg = await channel.send( + const voteMsg = await send( `VOTE: do you think that <@${ target.id }> has won the bet?\nhttps://discord.com/channels/${guild!.id}/${channel.id}/${ @@ -142,18 +140,18 @@ export const BetCommand = new NamedCommand({ if (ok > no) { receiver.money += amount * 2; - channel.send( + send( `By the people's votes, <@${target.id}> has won the bet that <@${author.id}> had sent them.` ); } else if (ok < no) { sender.money += amount * 2; - channel.send( + send( `By the people's votes, <@${target.id}> has lost the bet that <@${author.id}> had sent them.` ); } else { sender.money += amount; receiver.money += amount; - channel.send( + send( `By the people's votes, <@${target.id}> couldn't be determined to have won or lost the bet that <@${author.id}> had sent them.` ); } @@ -162,7 +160,7 @@ export const BetCommand = new NamedCommand({ Storage.save(); }); }, duration); - } else return await channel.send(`<@${target.id}> has rejected your bet, <@${author.id}>`); + } else return await send(`<@${target.id}> has rejected your bet, <@${author.id}>`); } else return; } }) diff --git a/src/commands/fun/modules/eco-core.ts b/src/commands/fun/modules/eco-core.ts index 6ed9628..4bb5808 100644 --- a/src/commands/fun/modules/eco-core.ts +++ b/src/commands/fun/modules/eco-core.ts @@ -6,7 +6,7 @@ import {isAuthorized, getMoneyEmbed, getSendEmbed, ECO_EMBED_COLOR} from "./eco- export const DailyCommand = new NamedCommand({ description: "Pick up your daily Mons. The cooldown is per user and every 22 hours to allow for some leeway.", aliases: ["get"], - async run({author, channel, guild}) { + async run({send, author, channel, guild}) { if (isAuthorized(guild, channel)) { const user = Storage.getUser(author.id); const now = Date.now(); @@ -15,7 +15,7 @@ export const DailyCommand = new NamedCommand({ user.money++; user.lastReceived = now; Storage.save(); - channel.send({ + send({ embed: { title: "Daily Reward", description: "You received 1 Mon!", @@ -23,7 +23,7 @@ export const DailyCommand = new NamedCommand({ } }); } else - channel.send({ + send({ embed: { title: "Daily Reward", description: `It's too soon to pick up your daily Mons. You have about ${( @@ -39,7 +39,7 @@ export const DailyCommand = new NamedCommand({ export const GuildCommand = new NamedCommand({ description: "Get info on the guild's economy as a whole.", - async run({guild, channel}) { + async run({send, guild, channel}) { if (isAuthorized(guild, channel)) { const users = Storage.users; let totalAmount = 0; @@ -49,7 +49,7 @@ export const GuildCommand = new NamedCommand({ totalAmount += user.money; } - channel.send({ + send({ embed: { title: `The Bank of ${guild!.name}`, color: ECO_EMBED_COLOR, @@ -77,7 +77,7 @@ export const GuildCommand = new NamedCommand({ export const LeaderboardCommand = new NamedCommand({ description: "See the richest players.", aliases: ["top"], - async run({guild, channel, client}) { + async run({send, guild, channel, client}) { if (isAuthorized(guild, channel)) { const users = Storage.users; const ids = Object.keys(users); @@ -94,7 +94,7 @@ export const LeaderboardCommand = new NamedCommand({ }); } - channel.send({ + send({ embed: { title: "Top 10 Richest Players", color: ECO_EMBED_COLOR, @@ -116,24 +116,23 @@ export const PayCommand = new NamedCommand({ user: new Command({ run: "You need to enter an amount you're sending!", number: new Command({ - async run({args, author, channel, guild}): Promise { + async run({send, args, author, channel, guild}): Promise { if (isAuthorized(guild, channel)) { const amount = Math.floor(args[1]); const sender = Storage.getUser(author.id); const target = args[0]; const receiver = Storage.getUser(target.id); - if (amount <= 0) return channel.send("You must send at least one Mon!"); + if (amount <= 0) return send("You must send at least one Mon!"); else if (sender.money < amount) - return channel.send("You don't have enough Mons for that.", getMoneyEmbed(author)); - else if (target.id === author.id) return channel.send("You can't send Mons to yourself!"); - else if (target.bot && process.argv[2] !== "dev") - return channel.send("You can't send Mons to a bot!"); + return send("You don't have enough Mons for that.", getMoneyEmbed(author)); + else if (target.id === author.id) return send("You can't send Mons to yourself!"); + else if (target.bot && process.argv[2] !== "dev") return send("You can't send Mons to a bot!"); sender.money -= amount; receiver.money += amount; Storage.save(); - return channel.send(getSendEmbed(author, target, amount)); + return send(getSendEmbed(author, target, amount)); } } }) @@ -142,21 +141,20 @@ export const PayCommand = new NamedCommand({ run: "You must use the format `eco pay `!" }), any: new Command({ - async run({args, author, channel, guild}) { + async run({send, args, author, channel, guild}) { if (isAuthorized(guild, channel)) { const last = args.pop(); - if (!/\d+/g.test(last) && args.length === 0) - return channel.send("You need to enter an amount you're sending!"); + if (!/\d+/g.test(last) && args.length === 0) return send("You need to enter an amount you're sending!"); const amount = Math.floor(last); const sender = Storage.getUser(author.id); - if (amount <= 0) return channel.send("You must send at least one Mon!"); + if (amount <= 0) return send("You must send at least one Mon!"); else if (sender.money < amount) - return channel.send("You don't have enough Mons to do that!", getMoneyEmbed(author)); + return send("You don't have enough Mons to do that!", getMoneyEmbed(author)); else if (!guild) - return channel.send("You have to use this in a server if you want to send Mons with a username!"); + return send("You have to use this in a server if you want to send Mons with a username!"); const username = args.join(" "); const member = ( @@ -167,17 +165,16 @@ export const PayCommand = new NamedCommand({ ).first(); if (!member) - return channel.send( + return send( `Couldn't find a user by the name of \`${username}\`! If you want to send Mons to someone in a different server, you have to use their user ID!` ); - else if (member.user.id === author.id) return channel.send("You can't send Mons to yourself!"); - else if (member.user.bot && process.argv[2] !== "dev") - return channel.send("You can't send Mons to a bot!"); + else if (member.user.id === author.id) return send("You can't send Mons to yourself!"); + else if (member.user.bot && process.argv[2] !== "dev") return send("You can't send Mons to a bot!"); const target = member.user; return prompt( - await channel.send( + await send( `Are you sure you want to send ${pluralise( amount, "Mon", @@ -202,7 +199,7 @@ export const PayCommand = new NamedCommand({ sender.money -= amount; receiver.money += amount; Storage.save(); - channel.send(getSendEmbed(author, target, amount)); + send(getSendEmbed(author, target, amount)); } ); } diff --git a/src/commands/fun/modules/eco-extras.ts b/src/commands/fun/modules/eco-extras.ts index 2d69f29..a1876d8 100644 --- a/src/commands/fun/modules/eco-extras.ts +++ b/src/commands/fun/modules/eco-extras.ts @@ -8,7 +8,7 @@ const WEEKDAY = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday export const MondayCommand = new NamedCommand({ description: "Use this on a UTC Monday to get an extra Mon. Does not affect your 22 hour timer for `eco daily`.", - async run({guild, channel, author}) { + async run({send, guild, channel, author}) { if (isAuthorized(guild, channel)) { const user = Storage.getUser(author.id); const now = new Date(); @@ -21,13 +21,13 @@ export const MondayCommand = new NamedCommand({ user.money++; user.lastMonday = now.getTime(); Storage.save(); - channel.send("It is **Mon**day, my dudes.", getMoneyEmbed(author)); - } else channel.send("You've already claimed your **Mon**day reward for this week."); + send("It is **Mon**day, my dudes.", getMoneyEmbed(author)); + } else send("You've already claimed your **Mon**day reward for this week."); } else { const weekdayName = WEEKDAY[weekday]; const hourText = now.getUTCHours().toString().padStart(2, "0"); const minuteText = now.getUTCMinutes().toString().padStart(2, "0"); - channel.send( + send( `Come back when it's **Mon**day. Right now, it's ${weekdayName}, ${hourText}:${minuteText} (UTC).` ); } @@ -41,19 +41,19 @@ export const AwardCommand = new NamedCommand({ aliases: ["give"], run: "You need to specify a user!", user: new Command({ - async run({message, channel, guild, author, member, client, args}) { + async run({send, message, channel, guild, author, member, client, args}) { if (author.id === "394808963356688394" || IS_DEV_MODE) { const target = args[0] as User; const user = Storage.getUser(target.id); user.money++; Storage.save(); - channel.send(`1 Mon given to ${target.username}.`, getMoneyEmbed(target)); + send(`1 Mon given to ${target.username}.`, getMoneyEmbed(target)); } else { - channel.send("This command is restricted to the bean."); + send("This command is restricted to the bean."); } }, number: new Command({ - async run({message, channel, guild, author, member, client, args}) { + async run({send, message, channel, guild, author, member, client, args}) { if (author.id === "394808963356688394" || IS_DEV_MODE) { const target = args[0] as User; const amount = Math.floor(args[1]); @@ -62,15 +62,12 @@ export const AwardCommand = new NamedCommand({ const user = Storage.getUser(target.id); user.money += amount; Storage.save(); - channel.send( - `${pluralise(amount, "Mon", "s")} given to ${target.username}.`, - getMoneyEmbed(target) - ); + send(`${pluralise(amount, "Mon", "s")} given to ${target.username}.`, getMoneyEmbed(target)); } else { - channel.send("You need to enter a number greater than 0."); + send("You need to enter a number greater than 0."); } } else { - channel.send("This command is restricted to the bean."); + send("This command is restricted to the bean."); } } }) diff --git a/src/commands/fun/modules/eco-shop.ts b/src/commands/fun/modules/eco-shop.ts index ca161f1..b76c47d 100644 --- a/src/commands/fun/modules/eco-shop.ts +++ b/src/commands/fun/modules/eco-shop.ts @@ -7,7 +7,7 @@ import {EmbedField} from "discord.js"; export const ShopCommand = new NamedCommand({ description: "Displays the list of items you can buy in the shop.", - async run({guild, channel, author}) { + async run({send, guild, channel, author}) { if (isAuthorized(guild, channel)) { function getShopEmbed(selection: ShopItem[], title: string) { const fields: EmbedField[] = []; @@ -34,7 +34,7 @@ export const ShopCommand = new NamedCommand({ const shopPages = split(ShopItems, 5); const pageAmount = shopPages.length; - paginate(channel.send, author.id, pageAmount, (page, hasMultiplePages) => { + paginate(send, author.id, pageAmount, (page, hasMultiplePages) => { return getShopEmbed( shopPages[page], hasMultiplePages ? `Shop (Page ${page + 1} of ${pageAmount})` : "Shop" @@ -47,7 +47,7 @@ export const ShopCommand = new NamedCommand({ export const BuyCommand = new NamedCommand({ description: "Buys an item from the shop.", usage: "", - async run({guild, channel, args, message, author}) { + async run({send, guild, channel, args, message, author}) { if (isAuthorized(guild, channel)) { let found = false; @@ -65,7 +65,7 @@ export const BuyCommand = new NamedCommand({ const cost = item.cost * amount; if (cost > user.money) { - channel.send("Not enough Mons!"); + send("Not enough Mons!"); } else { user.money -= cost; Storage.save(); @@ -77,7 +77,7 @@ export const BuyCommand = new NamedCommand({ } } - if (!found) channel.send(`There's no item in the shop that goes by \`${requested}\`!`); + if (!found) send(`There's no item in the shop that goes by \`${requested}\`!`); } } }); diff --git a/src/commands/fun/neko.ts b/src/commands/fun/neko.ts index 18cfe76..1722232 100644 --- a/src/commands/fun/neko.ts +++ b/src/commands/fun/neko.ts @@ -36,19 +36,17 @@ const endpoints: {sfw: {[key: string]: string}} = { export default new NamedCommand({ description: "Provides you with a random image with the selected argument.", - async run({message, channel, guild, author, member, client, args}) { - channel.send( - `Please provide an image type. Available arguments:\n\`[${Object.keys(endpoints.sfw).join(", ")}]\`.` - ); + async run({send, message, channel, guild, author, member, client, args}) { + send(`Please provide an image type. Available arguments:\n\`[${Object.keys(endpoints.sfw).join(", ")}]\`.`); }, any: new Command({ description: "Image type to send.", - async run({message, channel, guild, author, member, client, args}) { + async run({send, message, channel, guild, author, member, client, args}) { const arg = args[0]; - if (!(arg in endpoints.sfw)) return channel.send("Couldn't find that endpoint!"); + if (!(arg in endpoints.sfw)) return send("Couldn't find that endpoint!"); let url = new URL(`https://nekos.life/api/v2${endpoints.sfw[arg]}`); const content = await getContent(url.toString()); - return channel.send(content.url); + return send(content.url); } }) }); diff --git a/src/commands/fun/ok.ts b/src/commands/fun/ok.ts index b2998ab..66bfa6b 100644 --- a/src/commands/fun/ok.ts +++ b/src/commands/fun/ok.ts @@ -61,7 +61,7 @@ const responses = [ export default new NamedCommand({ description: "Sends random ok message.", - async run({message, channel, guild, author, member, client, args}) { - channel.send(`ok ${random(responses)}`); + async run({send, message, channel, guild, author, member, client, args}) { + send(`ok ${random(responses)}`); } }); diff --git a/src/commands/fun/owoify.ts b/src/commands/fun/owoify.ts index 2b4cdd7..e9e59d9 100644 --- a/src/commands/fun/owoify.ts +++ b/src/commands/fun/owoify.ts @@ -4,9 +4,9 @@ import {URL} from "url"; export default new NamedCommand({ description: "OwO-ifies the input.", - async run({message, channel, guild, author, member, client, args}) { + async run({send, message, channel, guild, author, member, client, args}) { let url = new URL(`https://nekos.life/api/v2/owoify?text=${args.join(" ")}`); const content = (await getContent(url.toString())) as any; // Apparently, the object in question is {owo: string}. - channel.send(content.owo); + send(content.owo); } }); diff --git a/src/commands/fun/party.ts b/src/commands/fun/party.ts index 90e62c5..db4c0e3 100644 --- a/src/commands/fun/party.ts +++ b/src/commands/fun/party.ts @@ -2,8 +2,8 @@ import {Command, NamedCommand} from "../../core"; export default new NamedCommand({ description: "Initiates a celebratory stream from the bot.", - async run({message, channel, guild, author, member, client, args}) { - channel.send("This calls for a celebration!"); + async run({send, message, channel, guild, author, member, client, args}) { + send("This calls for a celebration!"); client.user!.setActivity({ type: "STREAMING", url: "https://www.youtube.com/watch?v=dQw4w9WgXcQ", diff --git a/src/commands/fun/poll.ts b/src/commands/fun/poll.ts index 38efb68..3d60e1e 100644 --- a/src/commands/fun/poll.ts +++ b/src/commands/fun/poll.ts @@ -7,7 +7,7 @@ export default new NamedCommand({ run: "Please provide a question.", any: new Command({ description: "Question for the poll.", - async run({message, channel, guild, author, member, client, args}) { + async run({send, message, channel, guild, author, member, client, args}) { const embed = new MessageEmbed() .setAuthor( `Poll created by ${message.author.username}`, @@ -16,7 +16,7 @@ export default new NamedCommand({ .setColor(0xffffff) .setFooter("React to vote.") .setDescription(args.join(" ")); - const msg = await channel.send(embed); + const msg = await send(embed); await msg.react("✅"); await msg.react("⛔"); message.delete({ diff --git a/src/commands/fun/ravi.ts b/src/commands/fun/ravi.ts index 7b408f6..e0862e0 100644 --- a/src/commands/fun/ravi.ts +++ b/src/commands/fun/ravi.ts @@ -4,8 +4,8 @@ import {Random} from "../../lib"; export default new NamedCommand({ description: "Ravioli ravioli...", usage: "[number from 1 to 9]", - async run({message, channel, guild, author, member, client, args}) { - channel.send({ + async run({send, message, channel, guild, author, member, client, args}) { + send({ embed: { title: "Ravioli ravioli...", image: { @@ -18,11 +18,11 @@ export default new NamedCommand({ }); }, number: new Command({ - async run({message, channel, guild, author, member, client, args}) { + async run({send, message, channel, guild, author, member, client, args}) { const arg: number = args[0]; if (arg >= 1 && arg <= 9) { - channel.send({ + send({ embed: { title: "Ravioli ravioli...", image: { @@ -31,7 +31,7 @@ export default new NamedCommand({ } }); } else { - channel.send("Please provide a number between 1 and 9."); + send("Please provide a number between 1 and 9."); } } }) diff --git a/src/commands/fun/thonk.ts b/src/commands/fun/thonk.ts index 35e4d4a..cdc3afe 100644 --- a/src/commands/fun/thonk.ts +++ b/src/commands/fun/thonk.ts @@ -34,9 +34,9 @@ let phrase = "I have no currently set phrase!"; export default new NamedCommand({ description: "Transforms your text into vietnamese.", usage: "thonk ([text])", - async run({message, channel, guild, author, member, client, args}) { + async run({send, message, channel, guild, author, member, client, args}) { if (args.length > 0) phrase = args.join(" "); - const msg = await channel.send(transform(phrase)); + const msg = await send(transform(phrase)); msg.createReactionCollector( (reaction, user) => { if (user.id === author.id && reaction.emoji.name === "❌") msg.delete(); diff --git a/src/commands/fun/urban.ts b/src/commands/fun/urban.ts index 212a2a5..44d5a64 100644 --- a/src/commands/fun/urban.ts +++ b/src/commands/fun/urban.ts @@ -6,7 +6,7 @@ export default new NamedCommand({ description: "Gives you a definition of the inputted word.", run: "Please input a word.", any: new Command({ - async run({message, channel, guild, author, member, client, args}) { + async run({send, message, channel, guild, author, member, client, args}) { // [Bug Fix]: Use encodeURIComponent() when emojis are used: "TypeError [ERR_UNESCAPED_CHARACTERS]: Request path contains unescaped characters" urban(encodeURIComponent(args.join(" "))) .then((res) => { @@ -21,10 +21,10 @@ export default new NamedCommand({ if (res.tags && res.tags.length > 0 && res.tags.join(" ").length < 1024) embed.addField("Tags", res.tags.join(", "), true); - channel.send(embed); + send(embed); }) .catch(() => { - channel.send("Sorry, that word was not found."); + send("Sorry, that word was not found."); }); } }) diff --git a/src/commands/fun/vaporwave.ts b/src/commands/fun/vaporwave.ts index ad6b945..e2c1727 100644 --- a/src/commands/fun/vaporwave.ts +++ b/src/commands/fun/vaporwave.ts @@ -25,10 +25,10 @@ export default new NamedCommand({ description: "Transforms your text into vaporwave.", run: "You need to enter some text!", any: new Command({ - async run({message, channel, guild, author, member, client, args}) { + async run({send, message, channel, guild, author, member, client, args}) { const text = getVaporwaveText(args.join(" ")); - if (text !== "") channel.send(text); - else channel.send("Make sure to enter at least one valid character."); + if (text !== "") send(text); + else send("Make sure to enter at least one valid character."); } }) }); diff --git a/src/commands/fun/weather.ts b/src/commands/fun/weather.ts index 07c44f5..6760286 100644 --- a/src/commands/fun/weather.ts +++ b/src/commands/fun/weather.ts @@ -6,15 +6,15 @@ export default new NamedCommand({ description: "Shows weather info of specified location.", run: "You need to provide a city.", any: new Command({ - async run({message, channel, guild, author, member, client, args}) { + async run({send, message, channel, guild, author, member, client, args}) { find( { search: args.join(" "), degreeType: "C" }, function (error, result) { - if (error) return channel.send(error.toString()); - if (result.length === 0) return channel.send("No city found by that name."); + if (error) return send(error.toString()); + if (result.length === 0) return send("No city found by that name."); var current = result[0].current; var location = result[0].location; const embed = new MessageEmbed() @@ -28,7 +28,7 @@ export default new NamedCommand({ .addField("Feels like", `${current.feelslike} Degrees`, true) .addField("Winds", current.winddisplay, true) .addField("Humidity", `${current.humidity}%`, true); - return channel.send({ + return send({ embed }); } diff --git a/src/commands/fun/whois.ts b/src/commands/fun/whois.ts index 0c16b38..aea91c7 100644 --- a/src/commands/fun/whois.ts +++ b/src/commands/fun/whois.ts @@ -43,44 +43,42 @@ const registry: {[id: string]: string} = { export default new NamedCommand({ description: "Tells you who you or the specified user is.", aliases: ["whoami"], - async run({message, channel, guild, author, member, client, args}) { + async run({send, message, channel, guild, author, member, client, args}) { const id = author.id; if (id in registry) { - channel.send(registry[id]); + send(registry[id]); } else { - channel.send("You haven't been added to the registry yet!"); + send("You haven't been added to the registry yet!"); } }, id: "user", user: new Command({ - async run({message, channel, guild, author, member, client, args}) { + async run({send, message, channel, guild, author, member, client, args}) { const user: User = args[0]; const id = user.id; if (id in registry) { - channel.send(`\`${user.username}\` - ${registry[id]}`); + send(`\`${user.username}\` - ${registry[id]}`); } else { - channel.send(`\`${user.tag}\` hasn't been added to the registry yet!`); + send(`\`${user.tag}\` hasn't been added to the registry yet!`); } } }), any: new Command({ channelType: CHANNEL_TYPE.GUILD, - async run({message, channel, guild, author, client, args}) { + async run({send, message, channel, guild, author, client, args}) { const query = args.join(" ") as string; const member = await getMemberByName(guild!, query); if (member instanceof GuildMember) { if (member.id in registry) { - channel.send(`\`${member.nickname ?? member.user.username}\` - ${registry[member.id]}`); + send(`\`${member.nickname ?? member.user.username}\` - ${registry[member.id]}`); } else { - channel.send( - `\`${member.nickname ?? member.user.username}\` hasn't been added to the registry yet!` - ); + send(`\`${member.nickname ?? member.user.username}\` hasn't been added to the registry yet!`); } } else { - channel.send(member); + send(member); } } }) diff --git a/src/commands/system/admin.ts b/src/commands/system/admin.ts index f487e07..6b2593a 100644 --- a/src/commands/system/admin.ts +++ b/src/commands/system/admin.ts @@ -21,9 +21,9 @@ const statuses = ["online", "idle", "dnd", "invisible"]; export default new NamedCommand({ description: "An all-in-one command to do admin stuff. You need to be either an admin of the server or one of the bot's mechanics to use this command.", - async run({message, channel, guild, author, member, client, args}) { + async run({send, message, channel, guild, author, member, client, args}) { const permLevel = getPermissionLevel(author, member); - return channel.send(`${author}, your permission level is \`${getPermissionName(permLevel)}\` (${permLevel}).`); + return send(`${author}, your permission level is \`${getPermissionName(permLevel)}\` (${permLevel}).`); }, subcommands: { set: new NamedCommand({ @@ -35,26 +35,26 @@ export default new NamedCommand({ prefix: new NamedCommand({ description: "Set a custom prefix for your guild. Removes your custom prefix if none is provided.", usage: "() (<@bot>)", - async run({message, channel, guild, author, member, client, args}) { + async run({send, message, channel, guild, author, member, client, args}) { Storage.getGuild(guild!.id).prefix = null; Storage.save(); - channel.send( + send( `The custom prefix for this guild has been removed. My prefix is now back to \`${Config.prefix}\`.` ); }, any: new Command({ - async run({message, channel, guild, author, member, client, args}) { + async run({send, message, channel, guild, author, member, client, args}) { Storage.getGuild(guild!.id).prefix = args[0]; Storage.save(); - channel.send(`The custom prefix for this guild is now \`${args[0]}\`.`); + send(`The custom prefix for this guild is now \`${args[0]}\`.`); }, user: new Command({ description: "Specifies the bot in case of conflicting prefixes.", - async run({message, channel, guild, author, member, client, args}) { + async run({send, message, channel, guild, author, member, client, args}) { if ((args[1] as User).id === client.user!.id) { Storage.getGuild(guild!.id).prefix = args[0]; Storage.save(); - channel.send(`The custom prefix for this guild is now \`${args[0]}\`.`); + send(`The custom prefix for this guild is now \`${args[0]}\`.`); } } }) @@ -69,25 +69,25 @@ export default new NamedCommand({ description: "Sets how welcome messages are displayed for your server. Removes welcome messages if unspecified.", usage: "`none`/`text`/`graphical`", - async run({message, channel, guild, author, member, client, args}) { + async run({send, message, channel, guild, author, member, client, args}) { Storage.getGuild(guild!.id).welcomeType = "none"; Storage.save(); - channel.send("Set this server's welcome type to `none`."); + send("Set this server's welcome type to `none`."); }, // I should probably make this a bit more dynamic... Oh well. subcommands: { text: new NamedCommand({ - async run({message, channel, guild, author, member, client, args}) { + async run({send, message, channel, guild, author, member, client, args}) { Storage.getGuild(guild!.id).welcomeType = "text"; Storage.save(); - channel.send("Set this server's welcome type to `text`."); + send("Set this server's welcome type to `text`."); } }), graphical: new NamedCommand({ - async run({message, channel, guild, author, member, client, args}) { + async run({send, message, channel, guild, author, member, client, args}) { Storage.getGuild(guild!.id).welcomeType = "graphical"; Storage.save(); - channel.send("Set this server's welcome type to `graphical`."); + send("Set this server's welcome type to `graphical`."); } }) } @@ -95,18 +95,18 @@ export default new NamedCommand({ channel: new NamedCommand({ description: "Sets the welcome channel for your server. Type `#` to reference the channel.", usage: "()", - async run({message, channel, guild, author, member, client, args}) { + async run({send, message, channel, guild, author, member, client, args}) { Storage.getGuild(guild!.id).welcomeChannel = channel.id; Storage.save(); - channel.send(`Successfully set ${channel} as the welcome channel for this server.`); + send(`Successfully set ${channel} as the welcome channel for this server.`); }, id: "channel", channel: new Command({ - async run({message, channel, guild, author, member, client, args}) { + async run({send, message, channel, guild, author, member, client, args}) { const result = args[0] as TextChannel; Storage.getGuild(guild!.id).welcomeChannel = result.id; Storage.save(); - channel.send(`Successfully set this server's welcome channel to ${result}.`); + send(`Successfully set this server's welcome channel to ${result}.`); } }) }), @@ -114,17 +114,17 @@ export default new NamedCommand({ description: "Sets a custom welcome message for your server. Use `%user%` as the placeholder for the user.", usage: "()", - async run({message, channel, guild, author, member, client, args}) { + async run({send, message, channel, guild, author, member, client, args}) { Storage.getGuild(guild!.id).welcomeMessage = null; Storage.save(); - channel.send("Reset your server's welcome message to the default."); + send("Reset your server's welcome message to the default."); }, any: new Command({ - async run({message, channel, guild, author, member, client, args}) { + async run({send, message, channel, guild, author, member, client, args}) { const newMessage = args.join(" "); Storage.getGuild(guild!.id).welcomeMessage = newMessage; Storage.save(); - channel.send(`Set your server's welcome message to \`${newMessage}\`.`); + send(`Set your server's welcome message to \`${newMessage}\`.`); } }) }) @@ -133,26 +133,26 @@ export default new NamedCommand({ stream: new NamedCommand({ description: "Set a channel to send stream notifications. Type `#` to reference the channel.", usage: "()", - async run({message, channel, guild, author, member, client, args}) { + async run({send, message, channel, guild, author, member, client, args}) { const targetGuild = Storage.getGuild(guild!.id); if (targetGuild.streamingChannel) { targetGuild.streamingChannel = null; - channel.send("Removed your server's stream notifications channel."); + send("Removed your server's stream notifications channel."); } else { targetGuild.streamingChannel = channel.id; - channel.send(`Set your server's stream notifications channel to ${channel}.`); + send(`Set your server's stream notifications channel to ${channel}.`); } Storage.save(); }, id: "channel", channel: new Command({ - async run({message, channel, guild, author, member, client, args}) { + async run({send, message, channel, guild, author, member, client, args}) { const result = args[0] as TextChannel; Storage.getGuild(guild!.id).streamingChannel = result.id; Storage.save(); - channel.send(`Successfully set this server's stream notifications channel to ${result}.`); + send(`Successfully set this server's stream notifications channel to ${result}.`); } }) }) @@ -161,17 +161,17 @@ export default new NamedCommand({ diag: new NamedCommand({ description: 'Requests a debug log with the "info" verbosity level.', permission: PERMISSIONS.BOT_SUPPORT, - async run({message, channel, guild, author, member, client, args}) { - channel.send(getLogBuffer("info")); + async run({send, message, channel, guild, author, member, client, args}) { + send(getLogBuffer("info")); }, any: new Command({ description: `Select a verbosity to listen to. Available levels: \`[${Object.keys(logs).join(", ")}]\``, - async run({message, channel, guild, author, member, client, args}) { + async run({send, message, channel, guild, author, member, client, args}) { const type = args[0]; - if (type in logs) channel.send(getLogBuffer(type)); + if (type in logs) send(getLogBuffer(type)); else - channel.send( + send( `Couldn't find a verbosity level named \`${type}\`! The available types are \`[${Object.keys( logs ).join(", ")}]\`.` @@ -182,17 +182,17 @@ export default new NamedCommand({ status: new NamedCommand({ description: "Changes the bot's status.", permission: PERMISSIONS.BOT_SUPPORT, - async run({message, channel, guild, author, member, client, args}) { - channel.send("Setting status to `online`..."); + async run({send, message, channel, guild, author, member, client, args}) { + send("Setting status to `online`..."); }, any: new Command({ description: `Select a status to set to. Available statuses: \`[${statuses.join(", ")}]\`.`, - async run({message, channel, guild, author, member, client, args}) { + async run({send, message, channel, guild, author, member, client, args}) { if (!statuses.includes(args[0])) { - return channel.send("That status doesn't exist!"); + return send("That status doesn't exist!"); } else { client.user?.setStatus(args[0]); - return channel.send(`Setting status to \`${args[0]}\`...`); + return send(`Setting status to \`${args[0]}\`...`); } } }) @@ -201,7 +201,7 @@ export default new NamedCommand({ description: "Purges the bot's own messages.", permission: PERMISSIONS.BOT_SUPPORT, channelType: CHANNEL_TYPE.GUILD, - async run({message, channel, guild, author, member, client, args}) { + async run({send, message, channel, guild, author, member, client, args}) { // It's probably better to go through the bot's own messages instead of calling bulkDelete which requires MANAGE_MESSAGES. if (botHasPermission(guild, Permissions.FLAGS.MANAGE_MESSAGES)) { message.delete(); @@ -210,16 +210,14 @@ export default new NamedCommand({ }); const travMessages = msgs.filter((m) => m.author.id === client.user?.id); - await channel.send(`Found ${travMessages.size} messages to delete.`).then((m) => + await send(`Found ${travMessages.size} messages to delete.`).then((m) => m.delete({ timeout: 5000 }) ); await (channel as TextChannel).bulkDelete(travMessages); } else { - channel.send( - "This command must be executed in a guild where I have the `MANAGE_MESSAGES` permission." - ); + send("This command must be executed in a guild where I have the `MANAGE_MESSAGES` permission."); } } }), @@ -230,7 +228,7 @@ export default new NamedCommand({ run: "A number was not provided.", number: new Command({ description: "Amount of messages to delete.", - async run({message, channel, guild, author, member, client, args}) { + async run({send, message, channel, guild, author, member, client, args}) { message.delete(); const fetched = await channel.messages.fetch({ limit: args[0] @@ -244,15 +242,15 @@ export default new NamedCommand({ usage: "", permission: PERMISSIONS.BOT_OWNER, // You have to bring everything into scope to use them. AFAIK, there isn't a more maintainable way to do this, but at least TS will let you know if anything gets removed. - async run({message, channel, guild, author, member, client, args}) { + async run({send, message, channel, guild, author, member, client, args}) { try { const code = args.join(" "); let evaled = eval(code); if (typeof evaled !== "string") evaled = require("util").inspect(evaled); - channel.send(clean(evaled), {code: "js", split: true}); + send(clean(evaled), {code: "js", split: true}); } catch (err) { - channel.send(clean(err), {code: "js", split: true}); + send(clean(err), {code: "js", split: true}); } } }), @@ -260,43 +258,43 @@ export default new NamedCommand({ description: "Change the bot's nickname.", permission: PERMISSIONS.BOT_SUPPORT, channelType: CHANNEL_TYPE.GUILD, - async run({message, channel, guild, author, member, client, args}) { + async run({send, message, channel, guild, author, member, client, args}) { const nickName = args.join(" "); await guild!.me?.setNickname(nickName); if (botHasPermission(guild, Permissions.FLAGS.MANAGE_MESSAGES)) message.delete({timeout: 5000}); - channel.send(`Nickname set to \`${nickName}\``).then((m) => m.delete({timeout: 5000})); + send(`Nickname set to \`${nickName}\``).then((m) => m.delete({timeout: 5000})); } }), guilds: new NamedCommand({ description: "Shows a list of all guilds the bot is a member of.", permission: PERMISSIONS.BOT_SUPPORT, - async run({message, channel, guild, author, member, client, args}) { + async run({send, message, channel, guild, author, member, client, args}) { const guildList = client.guilds.cache.array().map((e) => e.name); - channel.send(guildList, {split: true}); + send(guildList, {split: true}); } }), activity: new NamedCommand({ description: "Set the activity of the bot.", permission: PERMISSIONS.BOT_SUPPORT, usage: " ", - async run({message, channel, guild, author, member, client, args}) { + async run({send, message, channel, guild, author, member, client, args}) { client.user?.setActivity(".help", { type: "LISTENING" }); - channel.send("Activity set to default."); + send("Activity set to default."); }, any: new Command({ description: `Select an activity type to set. Available levels: \`[${activities.join(", ")}]\``, - async run({message, channel, guild, author, member, client, args}) { + async run({send, message, channel, guild, author, member, client, args}) { const type = args[0]; if (activities.includes(type)) { client.user?.setActivity(args.slice(1).join(" "), { type: args[0].toUpperCase() }); - channel.send(`Set activity to \`${args[0].toUpperCase()}\` \`${args.slice(1).join(" ")}\`.`); + send(`Set activity to \`${args[0].toUpperCase()}\` \`${args.slice(1).join(" ")}\`.`); } else - channel.send( + send( `Couldn't find an activity type named \`${type}\`! The available types are \`[${activities.join( ", " )}]\`.` @@ -308,17 +306,17 @@ export default new NamedCommand({ description: "Sets up the current channel to receive system logs.", permission: PERMISSIONS.BOT_ADMIN, channelType: CHANNEL_TYPE.GUILD, - async run({message, channel, guild, author, member, client, args}) { + async run({send, message, channel, guild, author, member, client, args}) { Config.systemLogsChannel = channel.id; Config.save(); - channel.send(`Successfully set ${channel} as the system logs channel.`); + send(`Successfully set ${channel} as the system logs channel.`); }, channel: new Command({ - async run({message, channel, guild, author, member, client, args}) { + async run({send, message, channel, guild, author, member, client, args}) { const targetChannel = args[0] as TextChannel; Config.systemLogsChannel = targetChannel.id; Config.save(); - channel.send(`Successfully set ${targetChannel} as the system logs channel.`); + send(`Successfully set ${targetChannel} as the system logs channel.`); } }) }) diff --git a/src/commands/system/help.ts b/src/commands/system/help.ts index c6ec382..9cf6b8c 100644 --- a/src/commands/system/help.ts +++ b/src/commands/system/help.ts @@ -16,11 +16,11 @@ export default new NamedCommand({ description: "Lists all commands. If a command is specified, their arguments are listed as well.", usage: "([command, [subcommand/type], ...])", aliases: ["h"], - async run({message, channel, guild, author, member, client, args}) { + async run({send, message, channel, guild, author, member, client, args}) { const commands = await getCommandList(); const categoryArray = commands.keyArray(); - paginate(channel.send, author.id, categoryArray.length, (page, hasMultiplePages) => { + paginate(send, author.id, categoryArray.length, (page, hasMultiplePages) => { const category = categoryArray[page]; const commandList = commands.get(category)!; let output = `Legend: \`\`, \`[list/of/stuff]\`, \`(optional)\`, \`()\`, \`([optional/list/...])\`\n`; @@ -32,9 +32,9 @@ export default new NamedCommand({ }); }, any: new Command({ - async run({message, channel, guild, author, member, client, args}) { + async run({send, message, channel, guild, author, member, client, args}) { const [result, category] = await getCommandInfo(args); - if (typeof result === "string") return channel.send(result); + if (typeof result === "string") return send(result); let append = ""; const command = result.command; const header = result.args.length > 0 ? `${result.header} ${result.args.join(" ")}` : result.header; @@ -66,7 +66,7 @@ export default new NamedCommand({ aliases = formattedAliases.join(", ") || "None"; } - return channel.send( + return send( new MessageEmbed() .setTitle(header) .setDescription(command.description) diff --git a/src/commands/template.ts b/src/commands/template.ts index 70d0e65..bc7770e 100644 --- a/src/commands/template.ts +++ b/src/commands/template.ts @@ -1,7 +1,7 @@ import {Command, NamedCommand} from "../core"; export default new NamedCommand({ - async run({message, channel, guild, author, member, client, args}) { + async run({send, message, channel, guild, author, member, client, args}) { // code } }); diff --git a/src/commands/utility/calc.ts b/src/commands/utility/calc.ts index dad3e88..e31940d 100644 --- a/src/commands/utility/calc.ts +++ b/src/commands/utility/calc.ts @@ -4,19 +4,19 @@ import {MessageEmbed} from "discord.js"; export default new NamedCommand({ description: "Calculates a specified math expression.", - async run({message, channel, guild, author, member, client, args}) { - if (!args[0]) return channel.send("Please provide a calculation."); + async run({send, message, channel, guild, author, member, client, args}) { + if (!args[0]) return send("Please provide a calculation."); let resp; try { resp = math.evaluate(args.join(" ")); } catch (e) { - return channel.send("Please provide a *valid* calculation."); + return send("Please provide a *valid* calculation."); } const embed = new MessageEmbed() .setColor(0xffffff) .setTitle("Math Calculation") .addField("Input", `\`\`\`js\n${args.join("")}\`\`\``) .addField("Output", `\`\`\`js\n${resp}\`\`\``); - return channel.send(embed); + return send(embed); } }); diff --git a/src/commands/utility/desc.ts b/src/commands/utility/desc.ts index b3c3d57..773b997 100644 --- a/src/commands/utility/desc.ts +++ b/src/commands/utility/desc.ts @@ -3,17 +3,17 @@ import {Command, NamedCommand} from "../../core"; export default new NamedCommand({ description: "Renames current voice channel.", usage: "", - async run({message, channel, guild, author, member, client, args}) { + async run({send, message, channel, guild, author, member, client, args}) { const voiceChannel = message.member?.voice.channel; - if (!voiceChannel) return channel.send("You are not in a voice channel."); + if (!voiceChannel) return send("You are not in a voice channel."); if (!voiceChannel.guild.me?.hasPermission("MANAGE_CHANNELS")) - return channel.send("I am lacking the required permissions to perform this action."); - if (args.length === 0) return channel.send("Please provide a new voice channel name."); + return send("I am lacking the required permissions to perform this action."); + if (args.length === 0) return send("Please provide a new voice channel name."); const prevName = voiceChannel.name; const newName = args.join(" "); await voiceChannel.setName(newName); - return await channel.send(`Changed channel name from "${prevName}" to "${newName}".`); + return await send(`Changed channel name from "${prevName}" to "${newName}".`); } }); diff --git a/src/commands/utility/emote.ts b/src/commands/utility/emote.ts index 1b8aa07..4f5e933 100644 --- a/src/commands/utility/emote.ts +++ b/src/commands/utility/emote.ts @@ -8,9 +8,9 @@ export default new NamedCommand({ any: new Command({ description: "The emote(s) to send.", usage: "", - async run({guild, channel, message, args}) { + async run({send, guild, channel, message, args}) { const output = processEmoteQueryFormatted(args); - if (output.length > 0) channel.send(output); + if (output.length > 0) send(output); } }) }); diff --git a/src/commands/utility/info.ts b/src/commands/utility/info.ts index f3d5bb3..fff17c5 100644 --- a/src/commands/utility/info.ts +++ b/src/commands/utility/info.ts @@ -8,21 +8,21 @@ import moment, {utc} from "moment"; export default new NamedCommand({ description: "Command to provide all sorts of info about the current server, a user, etc.", - async run({message, channel, guild, author, member, client, args}) { - channel.send(await getUserInfo(author, member)); + async run({send, message, channel, guild, author, member, client, args}) { + send(await getUserInfo(author, member)); }, subcommands: { avatar: new NamedCommand({ description: "Shows your own, or another user's avatar.", usage: "()", - async run({message, channel, guild, author, member, client, args}) { - channel.send(author.displayAvatarURL({dynamic: true, size: 2048})); + async run({send, message, channel, guild, author, member, client, args}) { + send(author.displayAvatarURL({dynamic: true, size: 2048})); }, id: "user", user: new Command({ description: "Shows your own, or another user's avatar.", - async run({message, channel, guild, author, member, client, args}) { - channel.send( + async run({send, message, channel, guild, author, member, client, args}) { + send( args[0].displayAvatarURL({ dynamic: true, size: 2048 @@ -33,26 +33,26 @@ export default new NamedCommand({ any: new Command({ description: "Shows another user's avatar by searching their name", channelType: CHANNEL_TYPE.GUILD, - async run({message, channel, guild, author, client, args}) { + async run({send, message, channel, guild, author, client, args}) { const name = args.join(" "); const member = await getMemberByName(guild!, name); if (member instanceof GuildMember) { - channel.send( + send( member.user.displayAvatarURL({ dynamic: true, size: 2048 }) ); } else { - channel.send(member); + send(member); } } }) }), bot: new NamedCommand({ description: "Displays info about the bot.", - async run({message, channel, guild, author, member, client, args}) { + async run({send, message, channel, guild, author, member, client, args}) { const core = os.cpus()[0]; const embed = new MessageEmbed() .setColor(guild?.me?.displayHexColor || "BLUE") @@ -88,33 +88,33 @@ export default new NamedCommand({ size: 2048 }); if (avatarURL) embed.setThumbnail(avatarURL); - channel.send(embed); + send(embed); } }), guild: new NamedCommand({ description: "Displays info about the current guild or another guild.", usage: "(/)", channelType: CHANNEL_TYPE.GUILD, - async run({message, channel, guild, author, member, client, args}) { - channel.send(await getGuildInfo(guild!, guild)); + async run({send, message, channel, guild, author, member, client, args}) { + send(await getGuildInfo(guild!, guild)); }, id: "guild", guild: new Command({ description: "Display info about a guild by its ID.", - async run({message, channel, guild, author, member, client, args}) { + async run({send, message, channel, guild, author, member, client, args}) { const targetGuild = args[0] as Guild; - channel.send(await getGuildInfo(targetGuild, guild)); + send(await getGuildInfo(targetGuild, guild)); } }), any: new Command({ description: "Display info about a guild by finding its name.", - async run({message, channel, guild, author, member, client, args}) { + async run({send, message, channel, guild, author, member, client, args}) { const targetGuild = getGuildByName(args.join(" ")); if (targetGuild instanceof Guild) { - channel.send(await getGuildInfo(targetGuild, guild)); + send(await getGuildInfo(targetGuild, guild)); } else { - channel.send(targetGuild); + send(targetGuild); } } }) @@ -123,11 +123,11 @@ export default new NamedCommand({ id: "user", user: new Command({ description: "Displays info about mentioned user.", - async run({message, channel, guild, author, client, args}) { + async run({send, message, channel, guild, author, client, args}) { const user = args[0] as User; // Transforms the User object into a GuildMember object of the current guild. const member = guild?.members.resolve(args[0]); - channel.send(await getUserInfo(user, member)); + send(await getUserInfo(user, member)); } }) }); diff --git a/src/commands/utility/invite.ts b/src/commands/utility/invite.ts index 5d5657c..9dc0fa3 100644 --- a/src/commands/utility/invite.ts +++ b/src/commands/utility/invite.ts @@ -2,8 +2,8 @@ import {Command, NamedCommand} from "../../core"; export default new NamedCommand({ description: "Gives you the invite link.", - async run({message, channel, guild, author, member, client, args}) { - channel.send( + async run({send, message, channel, guild, author, member, client, args}) { + send( `https://discordapp.com/api/oauth2/authorize?client_id=${client.user!.id}&permissions=${ args[0] || 8 }&scope=bot` diff --git a/src/commands/utility/lsemotes.ts b/src/commands/utility/lsemotes.ts index 0d858bc..6633d63 100644 --- a/src/commands/utility/lsemotes.ts +++ b/src/commands/utility/lsemotes.ts @@ -1,5 +1,5 @@ -import {GuildEmoji, MessageEmbed, TextChannel, DMChannel, NewsChannel, User} from "discord.js"; -import {Command, NamedCommand, paginate} from "../../core"; +import {GuildEmoji, MessageEmbed, User} from "discord.js"; +import {Command, NamedCommand, paginate, SendFunction} from "../../core"; import {split} from "../../lib"; import vm from "vm"; @@ -8,20 +8,20 @@ const REGEX_TIMEOUT_MS = 1000; export default new NamedCommand({ description: "Lists all emotes the bot has in it's registry,", usage: " (-flags)", - async run({message, channel, guild, author, member, client, args}) { - displayEmoteList(client.emojis.cache.array(), channel, author); + async run({send, message, channel, guild, author, member, client, args}) { + displayEmoteList(client.emojis.cache.array(), send, author); }, any: new Command({ description: "Filters emotes by via a regular expression. Flags can be added by adding a dash at the end. For example, to do a case-insensitive search, do %prefix%lsemotes somepattern -i", - async run({message, channel, guild, author, member, client, args}) { + async run({send, message, channel, guild, author, member, client, args}) { // If a guild ID is provided, filter all emotes by that guild (but only if there aren't any arguments afterward) if (args.length === 1 && /^\d{17,}$/.test(args[0])) { const guildID: string = args[0]; displayEmoteList( client.emojis.cache.filter((emote) => emote.guild.id === guildID).array(), - channel, + send, author ); } else { @@ -57,10 +57,10 @@ export default new NamedCommand({ script.runInContext(context, {timeout: REGEX_TIMEOUT_MS}); emotes = sandbox.emotes; emoteCollection = emoteCollection.filter((emote) => emotes.has(emote.id)); // Only allow emotes that haven't been deleted. - displayEmoteList(emoteCollection, channel, author); + displayEmoteList(emoteCollection, send, author); } catch (error) { if (error.code === "ERR_SCRIPT_EXECUTION_TIMEOUT") { - channel.send( + send( `The regular expression you entered exceeded the time limit of ${REGEX_TIMEOUT_MS} milliseconds.` ); } else { @@ -68,14 +68,14 @@ export default new NamedCommand({ } } } else { - channel.send("Failed to initialize sandbox."); + send("Failed to initialize sandbox."); } } } }) }); -async function displayEmoteList(emotes: GuildEmoji[], channel: TextChannel | DMChannel | NewsChannel, author: User) { +async function displayEmoteList(emotes: GuildEmoji[], send: SendFunction, author: User) { emotes.sort((a, b) => { const first = a.name.toLowerCase(); const second = b.name.toLowerCase(); @@ -90,7 +90,7 @@ async function displayEmoteList(emotes: GuildEmoji[], channel: TextChannel | DMC // Gather the first page (if it even exists, which it might not if there no valid emotes appear) if (pages > 0) { - paginate(channel.send, author.id, pages, (page, hasMultiplePages) => { + paginate(send, author.id, pages, (page, hasMultiplePages) => { embed.setTitle(hasMultiplePages ? `**Emotes** (Page ${page + 1} of ${pages})` : "**Emotes**"); let desc = ""; @@ -102,6 +102,6 @@ async function displayEmoteList(emotes: GuildEmoji[], channel: TextChannel | DMC return embed; }); } else { - channel.send("No valid emotes found by that query."); + send("No valid emotes found by that query."); } } diff --git a/src/commands/utility/react.ts b/src/commands/utility/react.ts index 42e8227..c4150c2 100644 --- a/src/commands/utility/react.ts +++ b/src/commands/utility/react.ts @@ -6,7 +6,7 @@ export default new NamedCommand({ description: "Reacts to the a previous message in your place. You have to react with the same emote before the bot removes that reaction.", usage: 'react ()', - async run({message, channel, guild, author, member, client, args}) { + async run({send, message, channel, guild, author, member, client, args}) { let target: Message | undefined; let distance = 1; @@ -32,18 +32,18 @@ export default new NamedCommand({ try { guild = await client.guilds.fetch(guildID); } catch { - return channel.send(`\`${guildID}\` is an invalid guild ID!`); + return send(`\`${guildID}\` is an invalid guild ID!`); } } if (tmpChannel.id !== channelID) tmpChannel = guild.channels.cache.get(channelID); - if (!tmpChannel) return channel.send(`\`${channelID}\` is an invalid channel ID!`); + if (!tmpChannel) return send(`\`${channelID}\` is an invalid channel ID!`); if (message.id !== messageID) { try { target = await (tmpChannel as TextChannel).messages.fetch(messageID); } catch { - return channel.send(`\`${messageID}\` is an invalid message ID!`); + return send(`\`${messageID}\` is an invalid message ID!`); } } @@ -57,13 +57,13 @@ export default new NamedCommand({ let tmpChannel: Channel | undefined = channel; if (tmpChannel.id !== channelID) tmpChannel = guild?.channels.cache.get(channelID); - if (!tmpChannel) return channel.send(`\`${channelID}\` is an invalid channel ID!`); + if (!tmpChannel) return send(`\`${channelID}\` is an invalid channel ID!`); if (message.id !== messageID) { try { target = await (tmpChannel as TextChannel).messages.fetch(messageID); } catch { - return channel.send(`\`${messageID}\` is an invalid message ID!`); + return send(`\`${messageID}\` is an invalid message ID!`); } } @@ -74,7 +74,7 @@ export default new NamedCommand({ try { target = await channel.messages.fetch(last); } catch { - return channel.send(`No valid message found by the ID \`${last}\`!`); + return send(`No valid message found by the ID \`${last}\`!`); } args.pop(); @@ -84,7 +84,7 @@ export default new NamedCommand({ distance = parseInt(last); if (distance >= 0 && distance <= 99) args.pop(); - else return channel.send("Your distance must be between 0 and 99!"); + else return send("Your distance must be between 0 and 99!"); } } diff --git a/src/commands/utility/say.ts b/src/commands/utility/say.ts index 6c70dfb..02067a1 100644 --- a/src/commands/utility/say.ts +++ b/src/commands/utility/say.ts @@ -6,8 +6,8 @@ export default new NamedCommand({ run: "Please provide a message for me to say!", any: new Command({ description: "Message to repeat.", - async run({message, channel, guild, author, member, client, args}) { - channel.send(`*${author} says:*\n${args.join(" ")}`); + async run({send, message, channel, guild, author, member, client, args}) { + send(`*${author} says:*\n${args.join(" ")}`); } }) }); diff --git a/src/commands/utility/scanemotes.ts b/src/commands/utility/scanemotes.ts index a432147..08fb74f 100644 --- a/src/commands/utility/scanemotes.ts +++ b/src/commands/utility/scanemotes.ts @@ -9,7 +9,7 @@ export default new NamedCommand({ description: "Scans all text channels in the current guild and returns the number of times each emoji specific to the guild has been used. Has a cooldown of 24 hours per guild.", channelType: CHANNEL_TYPE.GUILD, - async run({message, channel, guild, author, member, client, args}) { + async run({send, message, channel, guild, author, member, client, args}) { // Test if the command is on cooldown. This isn't the strictest cooldown possible, because in the event that the bot crashes, the cooldown will be reset. But for all intends and purposes, it's a good enough cooldown. It's a per-server cooldown. const startTime = Date.now(); const cooldown = 86400000; // 24 hours @@ -19,9 +19,7 @@ export default new NamedCommand({ // If it's been less than an hour since the command was last used, prevent it from executing. if (difference < cooldown) - return channel.send( - `This command requires a day to cooldown. You'll be able to activate this command ${howLong}.` - ); + return send(`This command requires a day to cooldown. You'll be able to activate this command ${howLong}.`); else lastUsedTimestamps.set(guild!.id, startTime); const stats: { @@ -41,7 +39,7 @@ export default new NamedCommand({ let channelsSearched = 0; let currentChannelName = ""; const totalChannels = allTextChannelsInCurrentGuild.size; - const statusMessage = await channel.send("Gathering emotes..."); + const statusMessage = await send("Gathering emotes..."); let warnings = 0; channel.startTyping(); @@ -181,15 +179,15 @@ export default new NamedCommand({ ); } - return await channel.send(lines, {split: true}); + return await send(lines, {split: true}); }, subcommands: { forcereset: new NamedCommand({ description: "Forces the cooldown timer to reset.", permission: PERMISSIONS.BOT_SUPPORT, - async run({message, channel, guild, author, member, client, args}) { + async run({send, message, channel, guild, author, member, client, args}) { lastUsedTimestamps.set(guild!.id, 0); - channel.send("Reset the cooldown on `scanemotes`."); + send("Reset the cooldown on `scanemotes`."); } }) } diff --git a/src/commands/utility/shorten.ts b/src/commands/utility/shorten.ts index a14c460..9d74544 100644 --- a/src/commands/utility/shorten.ts +++ b/src/commands/utility/shorten.ts @@ -5,14 +5,14 @@ export default new NamedCommand({ description: "Shortens a given URL.", run: "Please provide a URL.", any: new Command({ - async run({message, channel, guild, author, member, client, args}) { + async run({send, message, channel, guild, author, member, client, args}) { https.get("https://is.gd/create.php?format=simple&url=" + encodeURIComponent(args[0]), function (res) { var body = ""; res.on("data", function (chunk) { body += chunk; }); res.on("end", function () { - channel.send(`<${body}>`); + send(`<${body}>`); }); }); } diff --git a/src/commands/utility/streaminfo.ts b/src/commands/utility/streaminfo.ts index c1a0c9d..c1cff8a 100644 --- a/src/commands/utility/streaminfo.ts +++ b/src/commands/utility/streaminfo.ts @@ -3,7 +3,7 @@ import {streamList} from "../../modules/streamNotifications"; export default new NamedCommand({ description: "Sets the description of your stream. You can embed links by writing `[some name](some link)`", - async run({message, channel, guild, author, member, client, args}) { + async run({send, message, channel, guild, author, member, client, args}) { const userID = author.id; if (streamList.has(userID)) { @@ -11,7 +11,7 @@ export default new NamedCommand({ const description = args.join(" ") || "No description set."; stream.description = description; stream.update(); - channel.send(`Successfully set the stream description to:`, { + send(`Successfully set the stream description to:`, { embed: { description, color: member!.displayColor @@ -19,7 +19,7 @@ export default new NamedCommand({ }); } else { // Alternatively, I could make descriptions last outside of just one stream. - channel.send("You can only use this command when streaming."); + send("You can only use this command when streaming."); } } }); diff --git a/src/commands/utility/time.ts b/src/commands/utility/time.ts index b48a4a2..656df97 100644 --- a/src/commands/utility/time.ts +++ b/src/commands/utility/time.ts @@ -169,21 +169,21 @@ function getTimeEmbed(user: User) { export default new NamedCommand({ description: "Show others what time it is for you.", aliases: ["tz"], - async run({channel, author}) { - channel.send(getTimeEmbed(author)); + async run({send, channel, author}) { + send(getTimeEmbed(author)); }, subcommands: { // Welcome to callback hell. We hope you enjoy your stay here! setup: new NamedCommand({ description: "Registers your timezone information for the bot.", - async run({author, channel}) { + async run({send, author, channel}) { const profile = Storage.getUser(author.id); profile.timezone = null; profile.daylightSavingsRegion = null; let hour: number; ask( - await channel.send( + await send( "What hour (0 to 23) is it for you right now?\n*(Note: Make sure to use Discord's inline reply feature or this won't work!)*" ), author.id, @@ -257,7 +257,7 @@ export default new NamedCommand({ // I calculate the list beforehand and check for duplicates to reduce unnecessary asking. if (duplicates.includes(hour)) { const isSameDay = await askYesOrNo( - await channel.send( + await send( `Is the current day of the month the ${moment().utc().format("Do")} for you?` ), author.id @@ -289,13 +289,13 @@ export default new NamedCommand({ // I should note that error handling should be added sometime because await throws an exception on Promise.reject. const hasDST = await askYesOrNo( - await channel.send("Does your timezone change based on daylight savings?"), + await send("Does your timezone change based on daylight savings?"), author.id ); const finalize = () => { Storage.save(); - channel.send( + send( "You've finished setting up your timezone! Just check to see if this looks right, and if it doesn't, run this setup again.", getTimeEmbed(author) ); @@ -313,7 +313,7 @@ export default new NamedCommand({ finalize(); }; - askMultipleChoice(await channel.send(DST_NOTE_SETUP), author.id, [ + askMultipleChoice(await send(DST_NOTE_SETUP), author.id, [ () => finalizeDST("na"), () => finalizeDST("eu"), () => finalizeDST("sh") @@ -328,9 +328,9 @@ export default new NamedCommand({ }), delete: new NamedCommand({ description: "Delete your timezone information.", - async run({channel, author}) { + async run({send, channel, author}) { prompt( - await channel.send( + await send( "Are you sure you want to delete your timezone information?\n*(This message will automatically be deleted after 10 seconds.)*" ), author.id, @@ -345,10 +345,10 @@ export default new NamedCommand({ }), utc: new NamedCommand({ description: "Displays UTC time.", - async run({channel}) { + async run({send, channel}) { const time = moment().utc(); - channel.send({ + send({ embed: { color: TIME_EMBED_COLOR, fields: [ @@ -377,16 +377,16 @@ export default new NamedCommand({ id: "user", user: new Command({ description: "See what time it is for someone else.", - async run({channel, args}) { - channel.send(getTimeEmbed(args[0])); + async run({send, channel, args}) { + send(getTimeEmbed(args[0])); } }), any: new Command({ description: "See what time it is for someone else (by their username).", - async run({channel, args, guild}) { + async run({send, channel, args, guild}) { const member = await getMemberByName(guild!, args.join(" ")); - if (member instanceof GuildMember) channel.send(getTimeEmbed(member.user)); - else channel.send(member); + if (member instanceof GuildMember) send(getTimeEmbed(member.user)); + else send(member); } }) }); diff --git a/src/commands/utility/todo.ts b/src/commands/utility/todo.ts index 594610b..57961c0 100644 --- a/src/commands/utility/todo.ts +++ b/src/commands/utility/todo.ts @@ -5,7 +5,7 @@ import {MessageEmbed} from "discord.js"; export default new NamedCommand({ description: "Keep and edit your personal todo list.", - async run({message, channel, guild, author, member, client, args}) { + async run({send, message, channel, guild, author, member, client, args}) { const user = Storage.getUser(author.id); const embed = new MessageEmbed().setTitle(`Todo list for ${author.tag}`).setColor("BLUE"); @@ -17,21 +17,21 @@ export default new NamedCommand({ ); } - channel.send(embed); + send(embed); }, subcommands: { add: new NamedCommand({ - async run({message, channel, guild, author, member, client, args}) { + async run({send, message, channel, guild, author, member, client, args}) { const user = Storage.getUser(author.id); const note = args.join(" "); user.todoList[Date.now().toString()] = note; console.debug(user.todoList); Storage.save(); - channel.send(`Successfully added \`${note}\` to your todo list.`); + send(`Successfully added \`${note}\` to your todo list.`); } }), remove: new NamedCommand({ - async run({message, channel, guild, author, member, client, args}) { + async run({send, message, channel, guild, author, member, client, args}) { const user = Storage.getUser(author.id); const note = args.join(" "); let isFound = false; @@ -43,19 +43,19 @@ export default new NamedCommand({ delete user.todoList[timestamp]; Storage.save(); isFound = true; - channel.send(`Removed \`${note}\` from your todo list.`); + send(`Removed \`${note}\` from your todo list.`); } } - if (!isFound) channel.send("That item couldn't be found."); + if (!isFound) send("That item couldn't be found."); } }), clear: new NamedCommand({ - async run({message, channel, guild, author, member, client, args}) { + async run({send, message, channel, guild, author, member, client, args}) { const user = Storage.getUser(author.id); user.todoList = {}; Storage.save(); - channel.send("Cleared todo list."); + send("Cleared todo list."); } }) } diff --git a/src/commands/utility/translate.ts b/src/commands/utility/translate.ts index e0e9250..c026cfb 100644 --- a/src/commands/utility/translate.ts +++ b/src/commands/utility/translate.ts @@ -4,14 +4,14 @@ import translate from "translate-google"; export default new NamedCommand({ description: "Translates your input.", usage: " ", - async run({message, channel, guild, author, member, client, args}) { + async run({send, message, channel, guild, author, member, client, args}) { const lang = args[0]; const input = args.slice(1).join(" "); translate(input, { to: lang }) .then((res) => { - channel.send({ + send({ embed: { title: "Translation", fields: [ @@ -29,9 +29,7 @@ export default new NamedCommand({ }) .catch((error) => { console.error(error); - channel.send( - `${error}\nPlease use the following list: https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes` - ); + send(`${error}\nPlease use the following list: https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes`); }); } }); From 26e0bb58248ceb780964ae9c044491a162bcbf91 Mon Sep 17 00:00:00 2001 From: WatDuhHekBro <44940783+WatDuhHekBro@users.noreply.github.com> Date: Sat, 10 Apr 2021 11:30:27 -0500 Subject: [PATCH 160/178] Added rest subcommand type --- CHANGELOG.md | 3 +- src/commands/system/help.ts | 6 +- src/commands/utility/time.ts | 2 +- src/core/command.ts | 337 +++++++++++++++++++++++------------ src/core/index.ts | 2 +- src/core/loader.ts | 17 +- 6 files changed, 245 insertions(+), 122 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2295ec1..c366d51 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,8 +9,9 @@ - `urban`: Bug fixes - Changed `help` to display a paginated embed - Various changes to core - - Added `guild` subcommand type (only accessible when `id` is set to `guild`) + - Added `guild` subcommand type (only accessible when `id: "guild"`) - Further reduced `channel.send()` to `send()` because it's used in *every, single, command* + - Added `rest` subcommand type (only available when `endpoint: true`), declaratively states that the following command will do `args.join(" ")`, preventing any other subcommands from being added # 3.2.0 - Internal refactor, more subcommand types, and more command type guards (2021-04-09) - The custom logger changed: `$.log` no longer exists, it's just `console.log`. Now you don't have to do `import $ from "../core/lib"` at the top of every file that uses the custom logger. diff --git a/src/commands/system/help.ts b/src/commands/system/help.ts index 9cf6b8c..8e32c35 100644 --- a/src/commands/system/help.ts +++ b/src/commands/system/help.ts @@ -33,8 +33,9 @@ export default new NamedCommand({ }, any: new Command({ async run({send, message, channel, guild, author, member, client, args}) { - const [result, category] = await getCommandInfo(args); - if (typeof result === "string") return send(result); + const resultingBlob = await getCommandInfo(args); + if (typeof resultingBlob === "string") return send(resultingBlob); + const [result, category] = resultingBlob; let append = ""; const command = result.command; const header = result.args.length > 0 ? `${result.header} ${result.args.join(" ")}` : result.header; @@ -52,6 +53,7 @@ export default new NamedCommand({ list.push(`❯ \`${header} ${type}${customUsage}\` - ${subcommand.description}`); } + if (result.hasRestCommand) list.push(`❯ \`${header} <...>\``); append = list.length > 0 ? list.join("\n") : "None"; } else { append = `\`${header} ${command.usage}\``; diff --git a/src/commands/utility/time.ts b/src/commands/utility/time.ts index 656df97..1196ea5 100644 --- a/src/commands/utility/time.ts +++ b/src/commands/utility/time.ts @@ -280,7 +280,7 @@ export default new NamedCommand({ } } else { // If it's a unique hour, just search through the tuple list and find the matching entry. - for (const [hourPoint, dayOffset, timezoneOffset] of timezoneTupleList) { + for (const [hourPoint, _dayOffset, timezoneOffset] of timezoneTupleList) { if (hour === hourPoint) { profile.timezone = timezoneOffset; } diff --git a/src/core/command.ts b/src/core/command.ts index d38d78a..703ca9d 100644 --- a/src/core/command.ts +++ b/src/core/command.ts @@ -78,11 +78,12 @@ interface CommandOptionsBase { readonly permission?: number; readonly nsfw?: boolean; readonly channelType?: CHANNEL_TYPE; - readonly run?: (($: CommandMenu) => Promise) | string; } interface CommandOptionsEndpoint { readonly endpoint: true; + readonly rest?: RestCommand; + readonly run?: (($: CommandMenu) => Promise) | string; } // Prevents subcommands from being added by compile-time. @@ -100,10 +101,15 @@ interface CommandOptionsNonEndpoint { readonly id?: ID; readonly number?: Command; readonly any?: Command; + readonly rest?: undefined; // Redeclare it here as undefined to prevent its use otherwise. + readonly run?: (($: CommandMenu) => Promise) | string; } type CommandOptions = CommandOptionsBase & (CommandOptionsEndpoint | CommandOptionsNonEndpoint); -type NamedCommandOptions = CommandOptions & {aliases?: string[]}; +type NamedCommandOptions = CommandOptions & {aliases?: string[]; nameOverride?: string}; +type RestCommandOptions = CommandOptionsBase & { + run?: (($: CommandMenu & {readonly combined: string}) => Promise) | string; +}; interface ExecuteCommandMetadata { readonly header: string; @@ -116,9 +122,10 @@ interface ExecuteCommandMetadata { export interface CommandInfo { readonly type: "info"; - readonly command: Command; + readonly command: BaseCommand; readonly subcommandInfo: Collection; readonly keyedSubcommandInfo: Collection; + readonly hasRestCommand: boolean; readonly permission: number; readonly nsfw: boolean; readonly channelType: CHANNEL_TYPE; @@ -141,14 +148,26 @@ interface CommandInfoMetadata { readonly header: string; } -// Each Command instance represents a block that links other Command instances under it. -export class Command { +// An isolated command of just the metadata. +abstract class BaseCommand { public readonly description: string; - public readonly endpoint: boolean; public readonly usage: string; public readonly permission: number; // -1 (default) indicates to inherit, 0 is the lowest rank, 1 is second lowest rank, and so on. public readonly nsfw: boolean | null; // null (default) indicates to inherit public readonly channelType: CHANNEL_TYPE | null; // null (default) indicates to inherit + + constructor(options?: CommandOptionsBase) { + this.description = options?.description || "No description."; + this.usage = options?.usage ?? ""; + this.permission = options?.permission ?? -1; + this.nsfw = options?.nsfw ?? null; + this.channelType = options?.channelType ?? null; + } +} + +// Each Command instance represents a block that links other Command instances under it. +export class Command extends BaseCommand { + public readonly endpoint: boolean; // The execute and subcommand properties are restricted to the class because subcommand recursion could easily break when manually handled. // The class will handle checking for null fields. private run: (($: CommandMenu) => Promise) | string; @@ -163,14 +182,11 @@ export class Command { private idType: ID | null; private number: Command | null; private any: Command | null; + private rest: RestCommand | null; constructor(options?: CommandOptions) { - this.description = options?.description || "No description."; + super(options); this.endpoint = !!options?.endpoint; - this.usage = options?.usage ?? ""; - this.permission = options?.permission ?? -1; - this.nsfw = options?.nsfw ?? null; - this.channelType = options?.channelType ?? null; this.run = options?.run || "No action was set on this command!"; this.subcommands = new Collection(); // Populate this collection after setting subcommands. this.channel = null; @@ -183,44 +199,45 @@ export class Command { this.idType = null; this.number = null; this.any = null; + this.rest = null; if (options && !options.endpoint) { - if (options?.channel) this.channel = options.channel; - if (options?.role) this.role = options.role; - if (options?.emote) this.emote = options.emote; - if (options?.message) this.message = options.message; - if (options?.user) this.user = options.user; - if (options?.guild) this.guild = options.guild; - if (options?.number) this.number = options.number; - if (options?.any) this.any = options.any; - if (options?.id) this.idType = options.id; + if (options.channel) this.channel = options.channel; + if (options.role) this.role = options.role; + if (options.emote) this.emote = options.emote; + if (options.message) this.message = options.message; + if (options.user) this.user = options.user; + if (options.guild) this.guild = options.guild; + if (options.number) this.number = options.number; + if (options.any) this.any = options.any; + if (options.id) this.idType = options.id; - if (options?.id) { - switch (options.id) { - case "channel": - this.id = this.channel; - break; - case "role": - this.id = this.role; - break; - case "emote": - this.id = this.emote; - break; - case "message": - this.id = this.message; - break; - case "user": - this.id = this.user; - break; - case "guild": - this.id = this.guild; - break; - default: - requireAllCasesHandledFor(options.id); - } + switch (options.id) { + case "channel": + this.id = this.channel; + break; + case "role": + this.id = this.role; + break; + case "emote": + this.id = this.emote; + break; + case "message": + this.id = this.message; + break; + case "user": + this.id = this.user; + break; + case "guild": + this.id = this.guild; + break; + case undefined: + break; + default: + requireAllCasesHandledFor(options.id); } - if (options?.subcommands) { + if (options.subcommands) { const baseSubcommands = Object.keys(options.subcommands); // Loop once to set the base subcommands. @@ -246,6 +263,8 @@ export class Command { } } } + } else if (options && options.endpoint) { + if (options.rest) this.rest = options.rest; } } @@ -273,72 +292,49 @@ export class Command { // If there are no arguments left, execute the current command. Otherwise, continue on. if (param === undefined) { - // See if there is anything that'll prevent the user from executing the command. + const error = canExecute(menu, metadata); + if (error) return error; - // 1. Does this command specify a required channel type? If so, does the channel type match? - if ( - metadata.channelType === CHANNEL_TYPE.GUILD && - (!(menu.channel instanceof GuildChannel) || menu.guild === null || menu.member === null) - ) { - return {content: "This command must be executed in a server."}; - } else if ( - metadata.channelType === CHANNEL_TYPE.DM && - (menu.channel.type !== "dm" || menu.guild !== null || menu.member !== null) - ) { - return {content: "This command must be executed as a direct message."}; - } - - // 2. Is this an NSFW command where the channel prevents such use? (DM channels bypass this requirement.) - if (metadata.nsfw && menu.channel.type !== "dm" && !menu.channel.nsfw) { - return {content: "This command must be executed in either an NSFW channel or as a direct message."}; - } - - // 3. Does the user have permission to execute the command? - if (!hasPermission(menu.author, menu.member, metadata.permission)) { - const userPermLevel = getPermissionLevel(menu.author, menu.member); - - return { - content: `You don't have access to this command! Your permission level is \`${getPermissionName( - userPermLevel - )}\` (${userPermLevel}), but this command requires a permission level of \`${getPermissionName( - metadata.permission - )}\` (${metadata.permission}).` - }; - } - - // Then capture any potential errors. - try { - if (typeof this.run === "string") { - // Although I *could* add an option in the launcher to attach arbitrary variables to this var string... - // I'll just leave it like this, because instead of using var strings for user stuff, you could just make "run" a template string. - await menu.send( - parseVars( - this.run, - { - author: menu.author.toString(), - prefix: getPrefix(menu.guild), - command: `${metadata.header} ${metadata.symbolicArgs.join(", ")}` - }, - "???" - ) - ); - } else { + if (typeof this.run === "string") { + // Although I *could* add an option in the launcher to attach arbitrary variables to this var string... + // I'll just leave it like this, because instead of using var strings for user stuff, you could just make "run" a template string. + await menu.send( + parseVars( + this.run, + { + author: menu.author.toString(), + prefix: getPrefix(menu.guild), + command: `${metadata.header} ${metadata.symbolicArgs.join(", ")}` + }, + "???" + ) + ); + } else { + // Then capture any potential errors. + try { await this.run(menu); + } catch (error) { + const errorMessage = error.stack ?? error; + console.error(`Command Error: ${metadata.header} (${metadata.args.join(", ")})\n${errorMessage}`); + + return { + content: `There was an error while trying to execute that command!\`\`\`${errorMessage}\`\`\`` + }; } - - return null; - } catch (error) { - const errorMessage = error.stack ?? error; - console.error(`Command Error: ${metadata.header} (${metadata.args.join(", ")})\n${errorMessage}`); - - return { - content: `There was an error while trying to execute that command!\`\`\`${errorMessage}\`\`\`` - }; } + + return null; } - // If the current command is an endpoint but there are still some arguments left, don't continue. - if (this.endpoint) return {content: "Too many arguments!"}; + // If the current command is an endpoint but there are still some arguments left, don't continue unless there's a RestCommand. + if (this.endpoint) { + if (this.rest) { + args.unshift(param); + return this.rest.execute(args.join(" "), menu, metadata); + } else { + return {content: "Too many arguments!"}; + } + } // Resolve the value of the current command's argument (adding it to the resolved args), // then pass the thread of execution to whichever subcommand is valid (if any). @@ -532,7 +528,7 @@ export class Command { } // What this does is resolve the resulting subcommand as well as the inherited properties and the available subcommands. - public async resolveInfo(args: string[], header: string): Promise { + public resolveInfo(args: string[], header: string): CommandInfo | CommandInfoError { return this.resolveInfoInternal(args, { permission: 0, nsfw: false, @@ -544,10 +540,7 @@ export class Command { }); } - private async resolveInfoInternal( - args: string[], - metadata: CommandInfoMetadata - ): Promise { + private resolveInfoInternal(args: string[], metadata: CommandInfoMetadata): CommandInfo | CommandInfoError { // Update inherited properties if the current command specifies a property. // In case there are no initial arguments, these should go first so that it can register. if (this.permission !== -1) metadata.permission = this.permission; @@ -586,6 +579,7 @@ export class Command { command: this, keyedSubcommandInfo, subcommandInfo, + hasRestCommand: !!this.rest, ...metadata }; } @@ -652,6 +646,13 @@ export class Command { } else { return invalidSubcommandGenerator(); } + } else if (param === "<...>") { + if (this.rest) { + metadata.args.push("<...>"); + return this.rest.resolveInfoFinale(metadata); + } else { + return invalidSubcommandGenerator(); + } } else if (this.subcommands?.has(param)) { metadata.args.push(param); return this.subcommands.get(param)!.resolveInfoInternal(args, metadata); @@ -668,7 +669,8 @@ export class NamedCommand extends Command { constructor(options?: NamedCommandOptions) { super(options); this.aliases = options?.aliases || []; - this.originalCommandName = null; + // The name override exists in case a user wants to bypass filename restrictions. + this.originalCommandName = options?.nameOverride ?? null; } public get name(): string { @@ -681,4 +683,119 @@ export class NamedCommand extends Command { throw new Error(`originalCommandName cannot be set twice! Attempted to set the value to "${value}".`); else this.originalCommandName = value; } + + public isNameSet(): boolean { + return this.originalCommandName !== null; + } +} + +// RestCommand is a declarative version of the common "any: args.join(' ')" pattern, basically the Command version of a rest parameter. +// This way, you avoid having extra subcommands when using this pattern. +// I'm probably not going to add a transformer function (a callback to automatically handle stuff like searching for usernames). +// I don't think the effort to figure this part out via generics or something is worth it. +export class RestCommand extends BaseCommand { + private run: (($: CommandMenu & {readonly combined: string}) => Promise) | string; + + constructor(options?: RestCommandOptions) { + super(options); + this.run = options?.run || "No action was set on this command!"; + } + + public async execute( + combined: string, + menu: CommandMenu, + metadata: ExecuteCommandMetadata + ): Promise { + // Update inherited properties if the current command specifies a property. + // In case there are no initial arguments, these should go first so that it can register. + if (this.permission !== -1) metadata.permission = this.permission; + if (this.nsfw !== null) metadata.nsfw = this.nsfw; + if (this.channelType !== null) metadata.channelType = this.channelType; + + const error = canExecute(menu, metadata); + if (error) return error; + + if (typeof this.run === "string") { + // Although I *could* add an option in the launcher to attach arbitrary variables to this var string... + // I'll just leave it like this, because instead of using var strings for user stuff, you could just make "run" a template string. + await menu.send( + parseVars( + this.run, + { + author: menu.author.toString(), + prefix: getPrefix(menu.guild), + command: `${metadata.header} ${metadata.symbolicArgs.join(", ")}` + }, + "???" + ) + ); + } else { + // Then capture any potential errors. + try { + await this.run({...menu, combined}); + } catch (error) { + const errorMessage = error.stack ?? error; + console.error(`Command Error: ${metadata.header} (${metadata.args.join(", ")})\n${errorMessage}`); + + return { + content: `There was an error while trying to execute that command!\`\`\`${errorMessage}\`\`\`` + }; + } + } + + return null; + } + + public resolveInfoFinale(metadata: CommandInfoMetadata): CommandInfo { + if (this.permission !== -1) metadata.permission = this.permission; + if (this.nsfw !== null) metadata.nsfw = this.nsfw; + if (this.channelType !== null) metadata.channelType = this.channelType; + if (this.usage !== "") metadata.usage = this.usage; + + return { + type: "info", + command: this, + keyedSubcommandInfo: new Collection(), + subcommandInfo: new Collection(), + hasRestCommand: false, + ...metadata + }; + } +} + +// See if there is anything that'll prevent the user from executing the command. +// Returns null if successful, otherwise returns a message with the error. +function canExecute(menu: CommandMenu, metadata: ExecuteCommandMetadata): SingleMessageOptions | null { + // 1. Does this command specify a required channel type? If so, does the channel type match? + if ( + metadata.channelType === CHANNEL_TYPE.GUILD && + (!(menu.channel instanceof GuildChannel) || menu.guild === null || menu.member === null) + ) { + return {content: "This command must be executed in a server."}; + } else if ( + metadata.channelType === CHANNEL_TYPE.DM && + (menu.channel.type !== "dm" || menu.guild !== null || menu.member !== null) + ) { + return {content: "This command must be executed as a direct message."}; + } + + // 2. Is this an NSFW command where the channel prevents such use? (DM channels bypass this requirement.) + if (metadata.nsfw && menu.channel.type !== "dm" && !menu.channel.nsfw) { + return {content: "This command must be executed in either an NSFW channel or as a direct message."}; + } + + // 3. Does the user have permission to execute the command? + if (!hasPermission(menu.author, menu.member, metadata.permission)) { + const userPermLevel = getPermissionLevel(menu.author, menu.member); + + return { + content: `You don't have access to this command! Your permission level is \`${getPermissionName( + userPermLevel + )}\` (${userPermLevel}), but this command requires a permission level of \`${getPermissionName( + metadata.permission + )}\` (${metadata.permission}).` + }; + } + + return null; } diff --git a/src/core/index.ts b/src/core/index.ts index 16d1116..f80074a 100644 --- a/src/core/index.ts +++ b/src/core/index.ts @@ -1,5 +1,5 @@ // Onion Lasers Command Handler // -export {Command, NamedCommand, CHANNEL_TYPE} from "./command"; +export {Command, NamedCommand, RestCommand, CHANNEL_TYPE} from "./command"; export {addInterceptRule} from "./handler"; export {launch} from "./interface"; export * from "./libd"; diff --git a/src/core/loader.ts b/src/core/loader.ts index cabb0fd..9939519 100644 --- a/src/core/loader.ts +++ b/src/core/loader.ts @@ -43,14 +43,16 @@ export async function loadCommands(commandsDir: string): Promise Date: Sat, 10 Apr 2021 12:07:55 -0500 Subject: [PATCH 161/178] Reduced clunkiness of rest type and applied changes to commands --- src/commands/fun/eco.ts | 8 ++-- src/commands/fun/figlet.ts | 24 ++++++------ src/commands/fun/modules/eco-core.ts | 21 +++-------- src/commands/fun/modules/eco-shop.ts | 52 +++++++++++++------------- src/commands/fun/owoify.ts | 15 +++++--- src/commands/fun/poll.ts | 8 ++-- src/commands/fun/urban.ts | 8 ++-- src/commands/fun/vaporwave.ts | 8 ++-- src/commands/fun/weather.ts | 8 ++-- src/commands/fun/whois.ts | 9 ++--- src/commands/system/admin.ts | 56 ++++++++++++++++------------ src/commands/system/help.ts | 1 - src/commands/utility/calc.ts | 32 ++++++++-------- src/commands/utility/desc.ts | 26 +++++++------ src/commands/utility/info.ts | 15 ++++---- src/commands/utility/say.ts | 8 ++-- src/commands/utility/time.ts | 17 +++++++-- src/commands/utility/todo.ts | 50 +++++++++++++------------ src/core/command.ts | 55 ++++++++++++--------------- 19 files changed, 217 insertions(+), 204 deletions(-) diff --git a/src/commands/fun/eco.ts b/src/commands/fun/eco.ts index 1572151..96c1b43 100644 --- a/src/commands/fun/eco.ts +++ b/src/commands/fun/eco.ts @@ -1,4 +1,4 @@ -import {Command, NamedCommand, getMemberByName} from "../../core"; +import {Command, NamedCommand, getMemberByName, RestCommand} from "../../core"; import {isAuthorized, getMoneyEmbed} from "./modules/eco-utils"; import {DailyCommand, PayCommand, GuildCommand, LeaderboardCommand} from "./modules/eco-core"; import {BuyCommand, ShopCommand} from "./modules/eco-shop"; @@ -33,11 +33,11 @@ export default new NamedCommand({ if (isAuthorized(guild, channel)) send(getMoneyEmbed(args[0])); } }), - any: new Command({ + any: new RestCommand({ description: "See how much money someone else has by using their username.", - async run({send, guild, channel, args, message}) { + async run({send, guild, channel, args, message, combined}) { if (isAuthorized(guild, channel)) { - const member = await getMemberByName(guild!, args.join(" ")); + const member = await getMemberByName(guild!, combined); if (member instanceof GuildMember) send(getMoneyEmbed(member.user)); else send(member); } diff --git a/src/commands/fun/figlet.ts b/src/commands/fun/figlet.ts index 0ae2538..a25ed2a 100644 --- a/src/commands/fun/figlet.ts +++ b/src/commands/fun/figlet.ts @@ -1,17 +1,19 @@ -import {Command, NamedCommand} from "../../core"; +import {Command, NamedCommand, RestCommand} from "../../core"; import figlet from "figlet"; export default new NamedCommand({ description: "Generates a figlet of your input.", - async run({send, message, channel, guild, author, member, client, args}) { - const input = args.join(" "); - if (!args[0]) return send("You have to provide input for me to create a figlet!"); - return send( - "```" + - figlet.textSync(`${input}`, { + run: "You have to provide input for me to create a figlet!", + any: new RestCommand({ + async run({send, message, channel, guild, author, member, client, args, combined}) { + return send( + figlet.textSync(combined, { horizontalLayout: "full" - }) + - "```" - ); - } + }), + { + code: true + } + ); + } + }) }); diff --git a/src/commands/fun/modules/eco-core.ts b/src/commands/fun/modules/eco-core.ts index 4bb5808..390c268 100644 --- a/src/commands/fun/modules/eco-core.ts +++ b/src/commands/fun/modules/eco-core.ts @@ -1,4 +1,5 @@ -import {Command, NamedCommand, prompt} from "../../../core"; +import {GuildMember} from "discord.js"; +import {Command, getMemberByName, NamedCommand, prompt, RestCommand} from "../../../core"; import {pluralise} from "../../../lib"; import {Storage} from "../../../structures"; import {isAuthorized, getMoneyEmbed, getSendEmbed, ECO_EMBED_COLOR} from "./eco-utils"; @@ -140,8 +141,8 @@ export const PayCommand = new NamedCommand({ number: new Command({ run: "You must use the format `eco pay `!" }), - any: new Command({ - async run({send, args, author, channel, guild}) { + any: new RestCommand({ + async run({send, args, author, channel, guild, combined}) { if (isAuthorized(guild, channel)) { const last = args.pop(); @@ -156,18 +157,8 @@ export const PayCommand = new NamedCommand({ else if (!guild) return send("You have to use this in a server if you want to send Mons with a username!"); - const username = args.join(" "); - const member = ( - await guild.members.fetch({ - query: username, - limit: 1 - }) - ).first(); - - if (!member) - return send( - `Couldn't find a user by the name of \`${username}\`! If you want to send Mons to someone in a different server, you have to use their user ID!` - ); + const member = await getMemberByName(guild, combined); + if (!(member instanceof GuildMember)) return send(member); else if (member.user.id === author.id) return send("You can't send Mons to yourself!"); else if (member.user.bot && process.argv[2] !== "dev") return send("You can't send Mons to a bot!"); diff --git a/src/commands/fun/modules/eco-shop.ts b/src/commands/fun/modules/eco-shop.ts index b76c47d..acd0e4a 100644 --- a/src/commands/fun/modules/eco-shop.ts +++ b/src/commands/fun/modules/eco-shop.ts @@ -1,4 +1,4 @@ -import {Command, NamedCommand, paginate} from "../../../core"; +import {Command, NamedCommand, paginate, RestCommand} from "../../../core"; import {pluralise, split} from "../../../lib"; import {Storage, getPrefix} from "../../../structures"; import {isAuthorized, ECO_EMBED_COLOR} from "./eco-utils"; @@ -47,37 +47,37 @@ export const ShopCommand = new NamedCommand({ export const BuyCommand = new NamedCommand({ description: "Buys an item from the shop.", usage: "", - async run({send, guild, channel, args, message, author}) { - if (isAuthorized(guild, channel)) { - let found = false; + run: "You need to specify an item to buy.", + any: new RestCommand({ + async run({send, guild, channel, args, message, author, combined}) { + if (isAuthorized(guild, channel)) { + let found = false; + let amount = 1; // The amount the user is buying. - let amount = 1; // The amount the user is buying. + // For now, no shop items support being bought multiple times. Uncomment these 2 lines when it's supported/needed. + //if (/\d+/g.test(args[args.length - 1])) + //amount = parseInt(args.pop()); - // For now, no shop items support being bought multiple times. Uncomment these 2 lines when it's supported/needed. - //if (/\d+/g.test(args[args.length - 1])) - //amount = parseInt(args.pop()); + for (let item of ShopItems) { + if (item.usage === combined) { + const user = Storage.getUser(author.id); + const cost = item.cost * amount; - let requested = args.join(" "); // The item the user is buying. + if (cost > user.money) { + send("Not enough Mons!"); + } else { + user.money -= cost; + Storage.save(); + item.run(message, cost, amount); + } - for (let item of ShopItems) { - if (item.usage === requested) { - const user = Storage.getUser(author.id); - const cost = item.cost * amount; - - if (cost > user.money) { - send("Not enough Mons!"); - } else { - user.money -= cost; - Storage.save(); - item.run(message, cost, amount); + found = true; + break; } - - found = true; - break; } - } - if (!found) send(`There's no item in the shop that goes by \`${requested}\`!`); + if (!found) send(`There's no item in the shop that goes by \`${combined}\`!`); + } } - } + }) }); diff --git a/src/commands/fun/owoify.ts b/src/commands/fun/owoify.ts index e9e59d9..cbc4ccf 100644 --- a/src/commands/fun/owoify.ts +++ b/src/commands/fun/owoify.ts @@ -1,12 +1,15 @@ -import {Command, NamedCommand} from "../../core"; +import {Command, NamedCommand, RestCommand} from "../../core"; import {getContent} from "../../lib"; import {URL} from "url"; export default new NamedCommand({ description: "OwO-ifies the input.", - async run({send, message, channel, guild, author, member, client, args}) { - let url = new URL(`https://nekos.life/api/v2/owoify?text=${args.join(" ")}`); - const content = (await getContent(url.toString())) as any; // Apparently, the object in question is {owo: string}. - send(content.owo); - } + run: "You need to specify some text to owoify.", + any: new RestCommand({ + async run({send, message, channel, guild, author, member, client, args, combined}) { + let url = new URL(`https://nekos.life/api/v2/owoify?text=${combined}`); + const content = (await getContent(url.toString())) as any; // Apparently, the object in question is {owo: string}. + send(content.owo); + } + }) }); diff --git a/src/commands/fun/poll.ts b/src/commands/fun/poll.ts index 3d60e1e..5795db1 100644 --- a/src/commands/fun/poll.ts +++ b/src/commands/fun/poll.ts @@ -1,13 +1,13 @@ import {MessageEmbed} from "discord.js"; -import {Command, NamedCommand} from "../../core"; +import {Command, NamedCommand, RestCommand} from "../../core"; export default new NamedCommand({ description: "Create a poll.", usage: "", run: "Please provide a question.", - any: new Command({ + any: new RestCommand({ description: "Question for the poll.", - async run({send, message, channel, guild, author, member, client, args}) { + async run({send, message, channel, guild, author, member, client, args, combined}) { const embed = new MessageEmbed() .setAuthor( `Poll created by ${message.author.username}`, @@ -15,7 +15,7 @@ export default new NamedCommand({ ) .setColor(0xffffff) .setFooter("React to vote.") - .setDescription(args.join(" ")); + .setDescription(combined); const msg = await send(embed); await msg.react("✅"); await msg.react("⛔"); diff --git a/src/commands/fun/urban.ts b/src/commands/fun/urban.ts index 44d5a64..928ca57 100644 --- a/src/commands/fun/urban.ts +++ b/src/commands/fun/urban.ts @@ -1,14 +1,14 @@ -import {Command, NamedCommand} from "../../core"; +import {Command, NamedCommand, RestCommand} from "../../core"; import {MessageEmbed} from "discord.js"; import urban from "relevant-urban"; export default new NamedCommand({ description: "Gives you a definition of the inputted word.", run: "Please input a word.", - any: new Command({ - async run({send, message, channel, guild, author, member, client, args}) { + any: new RestCommand({ + async run({send, message, channel, guild, author, member, client, args, combined}) { // [Bug Fix]: Use encodeURIComponent() when emojis are used: "TypeError [ERR_UNESCAPED_CHARACTERS]: Request path contains unescaped characters" - urban(encodeURIComponent(args.join(" "))) + urban(encodeURIComponent(combined)) .then((res) => { const embed = new MessageEmbed() .setColor(0x1d2439) diff --git a/src/commands/fun/vaporwave.ts b/src/commands/fun/vaporwave.ts index e2c1727..71ae065 100644 --- a/src/commands/fun/vaporwave.ts +++ b/src/commands/fun/vaporwave.ts @@ -1,4 +1,4 @@ -import {Command, NamedCommand} from "../../core"; +import {Command, NamedCommand, RestCommand} from "../../core"; const vaporwave = (() => { const map = new Map(); @@ -24,9 +24,9 @@ function getVaporwaveText(text: string): string { export default new NamedCommand({ description: "Transforms your text into vaporwave.", run: "You need to enter some text!", - any: new Command({ - async run({send, message, channel, guild, author, member, client, args}) { - const text = getVaporwaveText(args.join(" ")); + any: new RestCommand({ + async run({send, message, channel, guild, author, member, client, args, combined}) { + const text = getVaporwaveText(combined); if (text !== "") send(text); else send("Make sure to enter at least one valid character."); } diff --git a/src/commands/fun/weather.ts b/src/commands/fun/weather.ts index 6760286..44c82b7 100644 --- a/src/commands/fun/weather.ts +++ b/src/commands/fun/weather.ts @@ -1,15 +1,15 @@ -import {Command, NamedCommand} from "../../core"; +import {Command, NamedCommand, RestCommand} from "../../core"; import {MessageEmbed} from "discord.js"; import {find} from "weather-js"; export default new NamedCommand({ description: "Shows weather info of specified location.", run: "You need to provide a city.", - any: new Command({ - async run({send, message, channel, guild, author, member, client, args}) { + any: new RestCommand({ + async run({send, message, channel, guild, author, member, client, args, combined}) { find( { - search: args.join(" "), + search: combined, degreeType: "C" }, function (error, result) { diff --git a/src/commands/fun/whois.ts b/src/commands/fun/whois.ts index aea91c7..4ad9c97 100644 --- a/src/commands/fun/whois.ts +++ b/src/commands/fun/whois.ts @@ -1,5 +1,5 @@ import {User, GuildMember} from "discord.js"; -import {Command, NamedCommand, getMemberByName, CHANNEL_TYPE} from "../../core"; +import {Command, NamedCommand, getMemberByName, CHANNEL_TYPE, RestCommand} from "../../core"; // Quotes must be used here or the numbers will change const registry: {[id: string]: string} = { @@ -65,11 +65,10 @@ export default new NamedCommand({ } } }), - any: new Command({ + any: new RestCommand({ channelType: CHANNEL_TYPE.GUILD, - async run({send, message, channel, guild, author, client, args}) { - const query = args.join(" ") as string; - const member = await getMemberByName(guild!, query); + async run({send, message, channel, guild, author, client, args, combined}) { + const member = await getMemberByName(guild!, combined); if (member instanceof GuildMember) { if (member.id in registry) { diff --git a/src/commands/system/admin.ts b/src/commands/system/admin.ts index 6b2593a..b49e890 100644 --- a/src/commands/system/admin.ts +++ b/src/commands/system/admin.ts @@ -1,4 +1,12 @@ -import {Command, NamedCommand, botHasPermission, getPermissionLevel, getPermissionName, CHANNEL_TYPE} from "../../core"; +import { + Command, + NamedCommand, + botHasPermission, + getPermissionLevel, + getPermissionName, + CHANNEL_TYPE, + RestCommand +} from "../../core"; import {clean} from "../../lib"; import {Config, Storage} from "../../structures"; import {Permissions, TextChannel, User} from "discord.js"; @@ -119,12 +127,11 @@ export default new NamedCommand({ Storage.save(); send("Reset your server's welcome message to the default."); }, - any: new Command({ - async run({send, message, channel, guild, author, member, client, args}) { - const newMessage = args.join(" "); - Storage.getGuild(guild!.id).welcomeMessage = newMessage; + any: new RestCommand({ + async run({send, message, channel, guild, author, member, client, args, combined}) { + Storage.getGuild(guild!.id).welcomeMessage = combined; Storage.save(); - send(`Set your server's welcome message to \`${newMessage}\`.`); + send(`Set your server's welcome message to \`${combined}\`.`); } }) }) @@ -241,29 +248,32 @@ export default new NamedCommand({ description: "Evaluate code.", usage: "", permission: PERMISSIONS.BOT_OWNER, - // You have to bring everything into scope to use them. AFAIK, there isn't a more maintainable way to do this, but at least TS will let you know if anything gets removed. - async run({send, message, channel, guild, author, member, client, args}) { - try { - const code = args.join(" "); - let evaled = eval(code); - - if (typeof evaled !== "string") evaled = require("util").inspect(evaled); - send(clean(evaled), {code: "js", split: true}); - } catch (err) { - send(clean(err), {code: "js", split: true}); + run: "You have to enter some code to execute first.", + any: new RestCommand({ + // You have to bring everything into scope to use them. AFAIK, there isn't a more maintainable way to do this, but at least TS will let you know if anything gets removed. + async run({send, message, channel, guild, author, member, client, args, combined}) { + try { + let evaled = eval(combined); + if (typeof evaled !== "string") evaled = require("util").inspect(evaled); + send(clean(evaled), {code: "js", split: true}); + } catch (err) { + send(clean(err), {code: "js", split: true}); + } } - } + }) }), nick: new NamedCommand({ description: "Change the bot's nickname.", permission: PERMISSIONS.BOT_SUPPORT, channelType: CHANNEL_TYPE.GUILD, - async run({send, message, channel, guild, author, member, client, args}) { - const nickName = args.join(" "); - await guild!.me?.setNickname(nickName); - if (botHasPermission(guild, Permissions.FLAGS.MANAGE_MESSAGES)) message.delete({timeout: 5000}); - send(`Nickname set to \`${nickName}\``).then((m) => m.delete({timeout: 5000})); - } + run: "You have to specify a nickname to set for the bot", + any: new RestCommand({ + async run({send, message, channel, guild, author, member, client, args, combined}) { + await guild!.me?.setNickname(combined); + if (botHasPermission(guild, Permissions.FLAGS.MANAGE_MESSAGES)) message.delete({timeout: 5000}); + send(`Nickname set to \`${combined}\``).then((m) => m.delete({timeout: 5000})); + } + }) }), guilds: new NamedCommand({ description: "Shows a list of all guilds the bot is a member of.", diff --git a/src/commands/system/help.ts b/src/commands/system/help.ts index 8e32c35..7691a5a 100644 --- a/src/commands/system/help.ts +++ b/src/commands/system/help.ts @@ -53,7 +53,6 @@ export default new NamedCommand({ list.push(`❯ \`${header} ${type}${customUsage}\` - ${subcommand.description}`); } - if (result.hasRestCommand) list.push(`❯ \`${header} <...>\``); append = list.length > 0 ? list.join("\n") : "None"; } else { append = `\`${header} ${command.usage}\``; diff --git a/src/commands/utility/calc.ts b/src/commands/utility/calc.ts index e31940d..ac0727d 100644 --- a/src/commands/utility/calc.ts +++ b/src/commands/utility/calc.ts @@ -1,22 +1,24 @@ -import {Command, NamedCommand} from "../../core"; +import {Command, NamedCommand, RestCommand} from "../../core"; import * as math from "mathjs"; import {MessageEmbed} from "discord.js"; export default new NamedCommand({ description: "Calculates a specified math expression.", - async run({send, message, channel, guild, author, member, client, args}) { - if (!args[0]) return send("Please provide a calculation."); - let resp; - try { - resp = math.evaluate(args.join(" ")); - } catch (e) { - return send("Please provide a *valid* calculation."); + run: "Please provide a calculation.", + any: new RestCommand({ + async run({send, message, channel, guild, author, member, client, args, combined}) { + let resp; + try { + resp = math.evaluate(combined); + } catch (e) { + return send("Please provide a *valid* calculation."); + } + const embed = new MessageEmbed() + .setColor(0xffffff) + .setTitle("Math Calculation") + .addField("Input", `\`\`\`js\n${combined}\`\`\``) + .addField("Output", `\`\`\`js\n${resp}\`\`\``); + return send(embed); } - const embed = new MessageEmbed() - .setColor(0xffffff) - .setTitle("Math Calculation") - .addField("Input", `\`\`\`js\n${args.join("")}\`\`\``) - .addField("Output", `\`\`\`js\n${resp}\`\`\``); - return send(embed); - } + }) }); diff --git a/src/commands/utility/desc.ts b/src/commands/utility/desc.ts index 773b997..9930df3 100644 --- a/src/commands/utility/desc.ts +++ b/src/commands/utility/desc.ts @@ -1,19 +1,21 @@ -import {Command, NamedCommand} from "../../core"; +import {Command, NamedCommand, RestCommand} from "../../core"; export default new NamedCommand({ description: "Renames current voice channel.", usage: "", - async run({send, message, channel, guild, author, member, client, args}) { - const voiceChannel = message.member?.voice.channel; + run: "Please provide a new voice channel name.", + any: new RestCommand({ + async run({send, message, channel, guild, author, member, client, args, combined}) { + const voiceChannel = message.member?.voice.channel; - if (!voiceChannel) return send("You are not in a voice channel."); - if (!voiceChannel.guild.me?.hasPermission("MANAGE_CHANNELS")) - return send("I am lacking the required permissions to perform this action."); - if (args.length === 0) return send("Please provide a new voice channel name."); + if (!voiceChannel) return send("You are not in a voice channel."); + if (!voiceChannel.guild.me?.hasPermission("MANAGE_CHANNELS")) + return send("I am lacking the required permissions to perform this action."); - const prevName = voiceChannel.name; - const newName = args.join(" "); - await voiceChannel.setName(newName); - return await send(`Changed channel name from "${prevName}" to "${newName}".`); - } + const prevName = voiceChannel.name; + const newName = combined; + await voiceChannel.setName(newName); + return await send(`Changed channel name from "${prevName}" to "${newName}".`); + } + }) }); diff --git a/src/commands/utility/info.ts b/src/commands/utility/info.ts index fff17c5..5e51363 100644 --- a/src/commands/utility/info.ts +++ b/src/commands/utility/info.ts @@ -1,7 +1,7 @@ import {MessageEmbed, version as djsversion, Guild, User, GuildMember} from "discord.js"; import ms from "ms"; import os from "os"; -import {Command, NamedCommand, getMemberByName, CHANNEL_TYPE, getGuildByName} from "../../core"; +import {Command, NamedCommand, getMemberByName, CHANNEL_TYPE, getGuildByName, RestCommand} from "../../core"; import {formatBytes, trimArray} from "../../lib"; import {verificationLevels, filterLevels, regions} from "../../defs/info"; import moment, {utc} from "moment"; @@ -30,12 +30,11 @@ export default new NamedCommand({ ); } }), - any: new Command({ + any: new RestCommand({ description: "Shows another user's avatar by searching their name", channelType: CHANNEL_TYPE.GUILD, - async run({send, message, channel, guild, author, client, args}) { - const name = args.join(" "); - const member = await getMemberByName(guild!, name); + async run({send, message, channel, guild, author, client, args, combined}) { + const member = await getMemberByName(guild!, combined); if (member instanceof GuildMember) { send( @@ -106,10 +105,10 @@ export default new NamedCommand({ send(await getGuildInfo(targetGuild, guild)); } }), - any: new Command({ + any: new RestCommand({ description: "Display info about a guild by finding its name.", - async run({send, message, channel, guild, author, member, client, args}) { - const targetGuild = getGuildByName(args.join(" ")); + async run({send, message, channel, guild, author, member, client, args, combined}) { + const targetGuild = getGuildByName(combined); if (targetGuild instanceof Guild) { send(await getGuildInfo(targetGuild, guild)); diff --git a/src/commands/utility/say.ts b/src/commands/utility/say.ts index 02067a1..0ffe639 100644 --- a/src/commands/utility/say.ts +++ b/src/commands/utility/say.ts @@ -1,13 +1,13 @@ -import {Command, NamedCommand} from "../../core"; +import {Command, NamedCommand, RestCommand} from "../../core"; export default new NamedCommand({ description: "Repeats your message.", usage: "", run: "Please provide a message for me to say!", - any: new Command({ + any: new RestCommand({ description: "Message to repeat.", - async run({send, message, channel, guild, author, member, client, args}) { - send(`*${author} says:*\n${args.join(" ")}`); + async run({send, message, channel, guild, author, member, client, args, combined}) { + send(`*${author} says:*\n${combined}`); } }) }); diff --git a/src/commands/utility/time.ts b/src/commands/utility/time.ts index 1196ea5..7598f5a 100644 --- a/src/commands/utility/time.ts +++ b/src/commands/utility/time.ts @@ -1,4 +1,13 @@ -import {Command, NamedCommand, ask, askYesOrNo, askMultipleChoice, prompt, getMemberByName} from "../../core"; +import { + Command, + NamedCommand, + ask, + askYesOrNo, + askMultipleChoice, + prompt, + getMemberByName, + RestCommand +} from "../../core"; import {Storage} from "../../structures"; import {User, GuildMember} from "discord.js"; import moment from "moment"; @@ -381,10 +390,10 @@ export default new NamedCommand({ send(getTimeEmbed(args[0])); } }), - any: new Command({ + any: new RestCommand({ description: "See what time it is for someone else (by their username).", - async run({send, channel, args, guild}) { - const member = await getMemberByName(guild!, args.join(" ")); + async run({send, channel, args, guild, combined}) { + const member = await getMemberByName(guild!, combined); if (member instanceof GuildMember) send(getTimeEmbed(member.user)); else send(member); } diff --git a/src/commands/utility/todo.ts b/src/commands/utility/todo.ts index 57961c0..9057166 100644 --- a/src/commands/utility/todo.ts +++ b/src/commands/utility/todo.ts @@ -1,4 +1,4 @@ -import {Command, NamedCommand} from "../../core"; +import {Command, NamedCommand, RestCommand} from "../../core"; import moment from "moment"; import {Storage} from "../../structures"; import {MessageEmbed} from "discord.js"; @@ -21,34 +21,38 @@ export default new NamedCommand({ }, subcommands: { add: new NamedCommand({ - async run({send, message, channel, guild, author, member, client, args}) { - const user = Storage.getUser(author.id); - const note = args.join(" "); - user.todoList[Date.now().toString()] = note; - console.debug(user.todoList); - Storage.save(); - send(`Successfully added \`${note}\` to your todo list.`); - } + run: "You need to specify a note to add.", + any: new RestCommand({ + async run({send, message, channel, guild, author, member, client, args, combined}) { + const user = Storage.getUser(author.id); + user.todoList[Date.now().toString()] = combined; + console.debug(user.todoList); + Storage.save(); + send(`Successfully added \`${combined}\` to your todo list.`); + } + }) }), remove: new NamedCommand({ - async run({send, message, channel, guild, author, member, client, args}) { - const user = Storage.getUser(author.id); - const note = args.join(" "); - let isFound = false; + run: "You need to specify a note to remove.", + any: new RestCommand({ + async run({send, message, channel, guild, author, member, client, args, combined}) { + const user = Storage.getUser(author.id); + let isFound = false; - for (const timestamp in user.todoList) { - const selectedNote = user.todoList[timestamp]; + for (const timestamp in user.todoList) { + const selectedNote = user.todoList[timestamp]; - if (selectedNote === note) { - delete user.todoList[timestamp]; - Storage.save(); - isFound = true; - send(`Removed \`${note}\` from your todo list.`); + if (selectedNote === combined) { + delete user.todoList[timestamp]; + Storage.save(); + isFound = true; + send(`Removed \`${combined}\` from your todo list.`); + } } - } - if (!isFound) send("That item couldn't be found."); - } + if (!isFound) send("That item couldn't be found."); + } + }) }), clear: new NamedCommand({ async run({send, message, channel, guild, author, member, client, args}) { diff --git a/src/core/command.ts b/src/core/command.ts index 703ca9d..c818986 100644 --- a/src/core/command.ts +++ b/src/core/command.ts @@ -82,7 +82,6 @@ interface CommandOptionsBase { interface CommandOptionsEndpoint { readonly endpoint: true; - readonly rest?: RestCommand; readonly run?: (($: CommandMenu) => Promise) | string; } @@ -91,6 +90,7 @@ interface CommandOptionsEndpoint { // Role pings, maybe not, but it's not a big deal. interface CommandOptionsNonEndpoint { readonly endpoint?: false; + readonly run?: (($: CommandMenu) => Promise) | string; readonly subcommands?: {[key: string]: NamedCommand}; readonly channel?: Command; readonly role?: Command; @@ -100,9 +100,7 @@ interface CommandOptionsNonEndpoint { readonly guild?: Command; // Only available if an ID is set to reroute to it. readonly id?: ID; readonly number?: Command; - readonly any?: Command; - readonly rest?: undefined; // Redeclare it here as undefined to prevent its use otherwise. - readonly run?: (($: CommandMenu) => Promise) | string; + readonly any?: Command | RestCommand; } type CommandOptions = CommandOptionsBase & (CommandOptionsEndpoint | CommandOptionsNonEndpoint); @@ -123,9 +121,8 @@ interface ExecuteCommandMetadata { export interface CommandInfo { readonly type: "info"; readonly command: BaseCommand; - readonly subcommandInfo: Collection; - readonly keyedSubcommandInfo: Collection; - readonly hasRestCommand: boolean; + readonly subcommandInfo: Collection; + readonly keyedSubcommandInfo: Collection; readonly permission: number; readonly nsfw: boolean; readonly channelType: CHANNEL_TYPE; @@ -181,8 +178,7 @@ export class Command extends BaseCommand { private id: Command | null; private idType: ID | null; private number: Command | null; - private any: Command | null; - private rest: RestCommand | null; + private any: Command | RestCommand | null; constructor(options?: CommandOptions) { super(options); @@ -199,7 +195,6 @@ export class Command extends BaseCommand { this.idType = null; this.number = null; this.any = null; - this.rest = null; if (options && !options.endpoint) { if (options.channel) this.channel = options.channel; @@ -263,8 +258,6 @@ export class Command extends BaseCommand { } } } - } else if (options && options.endpoint) { - if (options.rest) this.rest = options.rest; } } @@ -327,14 +320,7 @@ export class Command extends BaseCommand { } // If the current command is an endpoint but there are still some arguments left, don't continue unless there's a RestCommand. - if (this.endpoint) { - if (this.rest) { - args.unshift(param); - return this.rest.execute(args.join(" "), menu, metadata); - } else { - return {content: "Too many arguments!"}; - } - } + if (this.endpoint) return {content: "Too many arguments!"}; // Resolve the value of the current command's argument (adding it to the resolved args), // then pass the thread of execution to whichever subcommand is valid (if any). @@ -513,10 +499,14 @@ export class Command extends BaseCommand { metadata.symbolicArgs.push(""); menu.args.push(Number(param)); return this.number.execute(args, menu, metadata); - } else if (this.any) { + } else if (this.any instanceof Command) { metadata.symbolicArgs.push(""); menu.args.push(param); return this.any.execute(args, menu, metadata); + } else if (this.any instanceof RestCommand) { + metadata.symbolicArgs.push("<...>"); + args.unshift(param); + return this.any.execute(args.join(" "), menu, metadata); } else { // Continue adding on the rest of the arguments if there's no valid subcommand. menu.args.push(param); @@ -553,8 +543,8 @@ export class Command extends BaseCommand { // If there are no arguments left, return the data or an error message. if (param === undefined) { - const keyedSubcommandInfo = new Collection(); - const subcommandInfo = new Collection(); + const keyedSubcommandInfo = new Collection(); + const subcommandInfo = new Collection(); // Get all the subcommands of the current command but without aliases. for (const [tag, command] of this.subcommands.entries()) { @@ -572,14 +562,18 @@ export class Command extends BaseCommand { if (this.user) subcommandInfo.set("", this.user); if (this.id) subcommandInfo.set(`>`, this.id); if (this.number) subcommandInfo.set("", this.number); - if (this.any) subcommandInfo.set("", this.any); + + // The special case for a possible rest command. + if (this.any) { + if (this.any instanceof Command) subcommandInfo.set("", this.any); + else subcommandInfo.set("<...>", this.any); + } return { type: "info", command: this, keyedSubcommandInfo, subcommandInfo, - hasRestCommand: !!this.rest, ...metadata }; } @@ -640,16 +634,16 @@ export class Command extends BaseCommand { return invalidSubcommandGenerator(); } } else if (param === "") { - if (this.any) { + if (this.any instanceof Command) { metadata.args.push(""); return this.any.resolveInfoInternal(args, metadata); } else { return invalidSubcommandGenerator(); } } else if (param === "<...>") { - if (this.rest) { + if (this.any instanceof RestCommand) { metadata.args.push("<...>"); - return this.rest.resolveInfoFinale(metadata); + return this.any.resolveInfoFinale(metadata); } else { return invalidSubcommandGenerator(); } @@ -755,9 +749,8 @@ export class RestCommand extends BaseCommand { return { type: "info", command: this, - keyedSubcommandInfo: new Collection(), - subcommandInfo: new Collection(), - hasRestCommand: false, + keyedSubcommandInfo: new Collection(), + subcommandInfo: new Collection(), ...metadata }; } From 3798c27df9f595952e5c42533b155cc600bc3089 Mon Sep 17 00:00:00 2001 From: WatDuhHekBro <44940783+WatDuhHekBro@users.noreply.github.com> Date: Sat, 10 Apr 2021 14:08:36 -0500 Subject: [PATCH 162/178] Removed lenient command handling --- CHANGELOG.md | 3 +- src/commands/fun/8ball.ts | 1 - src/commands/fun/modules/eco-bet.ts | 2 +- src/commands/fun/modules/eco-core.ts | 2 +- src/commands/fun/thonk.ts | 17 ++- src/commands/system/admin.ts | 2 +- src/commands/template.ts | 2 +- src/commands/utility/emote.ts | 4 +- src/commands/utility/lsemotes.ts | 4 +- src/commands/utility/react.ts | 177 ++++++++++++++------------- src/commands/utility/streaminfo.ts | 29 ++++- src/commands/utility/translate.ts | 64 +++++----- src/core/command.ts | 113 ++++++++--------- src/core/handler.ts | 9 +- 14 files changed, 228 insertions(+), 201 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c366d51..34a876a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,8 @@ - Various changes to core - Added `guild` subcommand type (only accessible when `id: "guild"`) - Further reduced `channel.send()` to `send()` because it's used in *every, single, command* - - Added `rest` subcommand type (only available when `endpoint: true`), declaratively states that the following command will do `args.join(" ")`, preventing any other subcommands from being added + - Added a `RestCommand` type, declaratively states that the following command will do `args.join(" ")`, preventing any other subcommands from being added + - Is no longer lenient to arguments when no proper subcommand fits (now it doesn't silently fail anymore), you now have to explicitly declare a `RestCommand` to get an arbitrary number of arguments # 3.2.0 - Internal refactor, more subcommand types, and more command type guards (2021-04-09) - The custom logger changed: `$.log` no longer exists, it's just `console.log`. Now you don't have to do `import $ from "../core/lib"` at the top of every file that uses the custom logger. diff --git a/src/commands/fun/8ball.ts b/src/commands/fun/8ball.ts index afc1f23..898eafe 100644 --- a/src/commands/fun/8ball.ts +++ b/src/commands/fun/8ball.ts @@ -26,7 +26,6 @@ const responses = [ export default new NamedCommand({ description: "Answers your question in an 8-ball manner.", - endpoint: false, usage: "", run: "Please provide a question.", any: new Command({ diff --git a/src/commands/fun/modules/eco-bet.ts b/src/commands/fun/modules/eco-bet.ts index 9095124..ecda43c 100644 --- a/src/commands/fun/modules/eco-bet.ts +++ b/src/commands/fun/modules/eco-bet.ts @@ -62,7 +62,7 @@ export const BetCommand = new NamedCommand({ // handle invalid target if (target.id == author.id) return send("You can't bet Mons with yourself!"); - else if (target.bot && process.argv[2] !== "dev") return send("You can't bet Mons with a bot!"); + else if (target.bot && !IS_DEV_MODE) return send("You can't bet Mons with a bot!"); // handle invalid amount if (amount <= 0) return send("You must bet at least one Mon!"); diff --git a/src/commands/fun/modules/eco-core.ts b/src/commands/fun/modules/eco-core.ts index 390c268..ec3d8b6 100644 --- a/src/commands/fun/modules/eco-core.ts +++ b/src/commands/fun/modules/eco-core.ts @@ -128,7 +128,7 @@ export const PayCommand = new NamedCommand({ else if (sender.money < amount) return send("You don't have enough Mons for that.", getMoneyEmbed(author)); else if (target.id === author.id) return send("You can't send Mons to yourself!"); - else if (target.bot && process.argv[2] !== "dev") return send("You can't send Mons to a bot!"); + else if (target.bot && !IS_DEV_MODE) return send("You can't send Mons to a bot!"); sender.money -= amount; receiver.money += amount; diff --git a/src/commands/fun/thonk.ts b/src/commands/fun/thonk.ts index cdc3afe..de09d20 100644 --- a/src/commands/fun/thonk.ts +++ b/src/commands/fun/thonk.ts @@ -1,4 +1,4 @@ -import {Command, NamedCommand} from "../../core"; +import {Command, NamedCommand, RestCommand} from "../../core"; const letters: {[letter: string]: string[]} = { a: "aáàảãạâấầẩẫậăắằẳẵặ".split(""), @@ -35,7 +35,6 @@ export default new NamedCommand({ description: "Transforms your text into vietnamese.", usage: "thonk ([text])", async run({send, message, channel, guild, author, member, client, args}) { - if (args.length > 0) phrase = args.join(" "); const msg = await send(transform(phrase)); msg.createReactionCollector( (reaction, user) => { @@ -44,5 +43,17 @@ export default new NamedCommand({ }, {time: 60000} ); - } + }, + any: new RestCommand({ + async run({send, message, channel, guild, author, member, client, args, combined}) { + const msg = await send(transform(combined)); + msg.createReactionCollector( + (reaction, user) => { + if (user.id === author.id && reaction.emoji.name === "❌") msg.delete(); + return false; + }, + {time: 60000} + ); + } + }) }); diff --git a/src/commands/system/admin.ts b/src/commands/system/admin.ts index b49e890..a57d37c 100644 --- a/src/commands/system/admin.ts +++ b/src/commands/system/admin.ts @@ -293,7 +293,7 @@ export default new NamedCommand({ }); send("Activity set to default."); }, - any: new Command({ + any: new RestCommand({ description: `Select an activity type to set. Available levels: \`[${activities.join(", ")}]\``, async run({send, message, channel, guild, author, member, client, args}) { const type = args[0]; diff --git a/src/commands/template.ts b/src/commands/template.ts index bc7770e..9ea84bf 100644 --- a/src/commands/template.ts +++ b/src/commands/template.ts @@ -1,4 +1,4 @@ -import {Command, NamedCommand} from "../core"; +import {Command, NamedCommand, RestCommand} from "../core"; export default new NamedCommand({ async run({send, message, channel, guild, author, member, client, args}) { diff --git a/src/commands/utility/emote.ts b/src/commands/utility/emote.ts index 4f5e933..4d79446 100644 --- a/src/commands/utility/emote.ts +++ b/src/commands/utility/emote.ts @@ -1,11 +1,11 @@ -import {Command, NamedCommand} from "../../core"; +import {Command, NamedCommand, RestCommand} from "../../core"; import {processEmoteQueryFormatted} from "./modules/emote-utils"; export default new NamedCommand({ description: "Send the specified emote list. Enter + to move an emote list to the next line, - to add a space, and _ to add a zero-width space.", run: "Please provide a list of emotes.", - any: new Command({ + any: new RestCommand({ description: "The emote(s) to send.", usage: "", async run({send, guild, channel, message, args}) { diff --git a/src/commands/utility/lsemotes.ts b/src/commands/utility/lsemotes.ts index 6633d63..37840b9 100644 --- a/src/commands/utility/lsemotes.ts +++ b/src/commands/utility/lsemotes.ts @@ -1,5 +1,5 @@ import {GuildEmoji, MessageEmbed, User} from "discord.js"; -import {Command, NamedCommand, paginate, SendFunction} from "../../core"; +import {Command, NamedCommand, RestCommand, paginate, SendFunction} from "../../core"; import {split} from "../../lib"; import vm from "vm"; @@ -11,7 +11,7 @@ export default new NamedCommand({ async run({send, message, channel, guild, author, member, client, args}) { displayEmoteList(client.emojis.cache.array(), send, author); }, - any: new Command({ + any: new RestCommand({ description: "Filters emotes by via a regular expression. Flags can be added by adding a dash at the end. For example, to do a case-insensitive search, do %prefix%lsemotes somepattern -i", async run({send, message, channel, guild, author, member, client, args}) { diff --git a/src/commands/utility/react.ts b/src/commands/utility/react.ts index c4150c2..ef1aebb 100644 --- a/src/commands/utility/react.ts +++ b/src/commands/utility/react.ts @@ -1,4 +1,4 @@ -import {Command, NamedCommand} from "../../core"; +import {Command, NamedCommand, RestCommand} from "../../core"; import {Message, Channel, TextChannel} from "discord.js"; import {processEmoteQueryArray} from "./modules/emote-utils"; @@ -6,109 +6,112 @@ export default new NamedCommand({ description: "Reacts to the a previous message in your place. You have to react with the same emote before the bot removes that reaction.", usage: 'react ()', - async run({send, message, channel, guild, author, member, client, args}) { - let target: Message | undefined; - let distance = 1; + run: "You need to enter some emotes first.", + any: new RestCommand({ + async run({send, message, channel, guild, author, member, client, args}) { + let target: Message | undefined; + let distance = 1; - if (message.reference) { - // If the command message is a reply to another message, use that as the react target. - target = await channel.messages.fetch(message.reference.messageID!); - } - // handles reacts by message id/distance - else if (args.length >= 2) { - const last = args[args.length - 1]; // Because this is optional, do not .pop() unless you're sure it's a message link indicator. - const URLPattern = /^(?:https:\/\/discord.com\/channels\/(\d{17,})\/(\d{17,})\/(\d{17,}))$/; - const copyIDPattern = /^(?:(\d{17,})-(\d{17,}))$/; + if (message.reference) { + // If the command message is a reply to another message, use that as the react target. + target = await channel.messages.fetch(message.reference.messageID!); + } + // handles reacts by message id/distance + else if (args.length >= 2) { + const last = args[args.length - 1]; // Because this is optional, do not .pop() unless you're sure it's a message link indicator. + const URLPattern = /^(?:https:\/\/discord.com\/channels\/(\d{17,})\/(\d{17,})\/(\d{17,}))$/; + const copyIDPattern = /^(?:(\d{17,})-(\d{17,}))$/; - // https://discord.com/channels/// ("Copy Message Link" Button) - if (URLPattern.test(last)) { - const match = URLPattern.exec(last)!; - const guildID = match[1]; - const channelID = match[2]; - const messageID = match[3]; - let tmpChannel: Channel | undefined = channel; + // https://discord.com/channels/// ("Copy Message Link" Button) + if (URLPattern.test(last)) { + const match = URLPattern.exec(last)!; + const guildID = match[1]; + const channelID = match[2]; + const messageID = match[3]; + let tmpChannel: Channel | undefined = channel; - if (guild?.id !== guildID) { - try { - guild = await client.guilds.fetch(guildID); - } catch { - return send(`\`${guildID}\` is an invalid guild ID!`); + if (guild?.id !== guildID) { + try { + guild = await client.guilds.fetch(guildID); + } catch { + return send(`\`${guildID}\` is an invalid guild ID!`); + } } - } - if (tmpChannel.id !== channelID) tmpChannel = guild.channels.cache.get(channelID); - if (!tmpChannel) return send(`\`${channelID}\` is an invalid channel ID!`); + if (tmpChannel.id !== channelID) tmpChannel = guild.channels.cache.get(channelID); + if (!tmpChannel) return send(`\`${channelID}\` is an invalid channel ID!`); - if (message.id !== messageID) { - try { - target = await (tmpChannel as TextChannel).messages.fetch(messageID); - } catch { - return send(`\`${messageID}\` is an invalid message ID!`); + if (message.id !== messageID) { + try { + target = await (tmpChannel as TextChannel).messages.fetch(messageID); + } catch { + return send(`\`${messageID}\` is an invalid message ID!`); + } } + + args.pop(); } + // - ("Copy ID" Button) + else if (copyIDPattern.test(last)) { + const match = copyIDPattern.exec(last)!; + const channelID = match[1]; + const messageID = match[2]; + let tmpChannel: Channel | undefined = channel; - args.pop(); - } - // - ("Copy ID" Button) - else if (copyIDPattern.test(last)) { - const match = copyIDPattern.exec(last)!; - const channelID = match[1]; - const messageID = match[2]; - let tmpChannel: Channel | undefined = channel; + if (tmpChannel.id !== channelID) tmpChannel = guild?.channels.cache.get(channelID); + if (!tmpChannel) return send(`\`${channelID}\` is an invalid channel ID!`); - if (tmpChannel.id !== channelID) tmpChannel = guild?.channels.cache.get(channelID); - if (!tmpChannel) return send(`\`${channelID}\` is an invalid channel ID!`); - - if (message.id !== messageID) { - try { - target = await (tmpChannel as TextChannel).messages.fetch(messageID); - } catch { - return send(`\`${messageID}\` is an invalid message ID!`); + if (message.id !== messageID) { + try { + target = await (tmpChannel as TextChannel).messages.fetch(messageID); + } catch { + return send(`\`${messageID}\` is an invalid message ID!`); + } } + + args.pop(); } + // + else if (/^\d{17,}$/.test(last)) { + try { + target = await channel.messages.fetch(last); + } catch { + return send(`No valid message found by the ID \`${last}\`!`); + } - args.pop(); - } - // - else if (/^\d{17,}$/.test(last)) { - try { - target = await channel.messages.fetch(last); - } catch { - return send(`No valid message found by the ID \`${last}\`!`); + args.pop(); } + // The entire string has to be a number for this to match. Prevents leaCheeseAmerican1 from triggering this. + else if (/^\d+$/.test(last)) { + distance = parseInt(last); - args.pop(); + if (distance >= 0 && distance <= 99) args.pop(); + else return send("Your distance must be between 0 and 99!"); + } } - // The entire string has to be a number for this to match. Prevents leaCheeseAmerican1 from triggering this. - else if (/^\d+$/.test(last)) { - distance = parseInt(last); - if (distance >= 0 && distance <= 99) args.pop(); - else return send("Your distance must be between 0 and 99!"); + if (!target) { + // Messages are ordered from latest to earliest. + // You also have to add 1 as well because fetchMessages includes your own message. + target = ( + await message.channel.messages.fetch({ + limit: distance + 1 + }) + ).last(); } + + for (const emote of processEmoteQueryArray(args)) { + // Even though the bot will always grab *some* emote, the user can choose not to keep that emote there if it isn't what they want + const reaction = await target!.react(emote); + + // This part is called with a promise because you don't want to wait 5 seconds between each reaction. + setTimeout(() => { + // This reason for this null assertion is that by the time you use this command, the client is going to be loaded. + reaction.users.remove(client.user!); + }, 5000); + } + + return; } - - if (!target) { - // Messages are ordered from latest to earliest. - // You also have to add 1 as well because fetchMessages includes your own message. - target = ( - await message.channel.messages.fetch({ - limit: distance + 1 - }) - ).last(); - } - - for (const emote of processEmoteQueryArray(args)) { - // Even though the bot will always grab *some* emote, the user can choose not to keep that emote there if it isn't what they want - const reaction = await target!.react(emote); - - // This part is called with a promise because you don't want to wait 5 seconds between each reaction. - setTimeout(() => { - // This reason for this null assertion is that by the time you use this command, the client is going to be loaded. - reaction.users.remove(client.user!); - }, 5000); - } - - return; - } + }) }); diff --git a/src/commands/utility/streaminfo.ts b/src/commands/utility/streaminfo.ts index c1cff8a..c122b8b 100644 --- a/src/commands/utility/streaminfo.ts +++ b/src/commands/utility/streaminfo.ts @@ -1,4 +1,4 @@ -import {Command, NamedCommand} from "../../core"; +import {Command, NamedCommand, RestCommand} from "../../core"; import {streamList} from "../../modules/streamNotifications"; export default new NamedCommand({ @@ -8,12 +8,11 @@ export default new NamedCommand({ if (streamList.has(userID)) { const stream = streamList.get(userID)!; - const description = args.join(" ") || "No description set."; - stream.description = description; + stream.description = "No description set."; stream.update(); send(`Successfully set the stream description to:`, { embed: { - description, + description: "No description set.", color: member!.displayColor } }); @@ -21,5 +20,25 @@ export default new NamedCommand({ // Alternatively, I could make descriptions last outside of just one stream. send("You can only use this command when streaming."); } - } + }, + any: new RestCommand({ + async run({send, message, channel, guild, author, member, client, args, combined}) { + const userID = author.id; + + if (streamList.has(userID)) { + const stream = streamList.get(userID)!; + stream.description = combined; + stream.update(); + send(`Successfully set the stream description to:`, { + embed: { + description: stream.description, + color: member!.displayColor + } + }); + } else { + // Alternatively, I could make descriptions last outside of just one stream. + send("You can only use this command when streaming."); + } + } + }) }); diff --git a/src/commands/utility/translate.ts b/src/commands/utility/translate.ts index c026cfb..d4a1aab 100644 --- a/src/commands/utility/translate.ts +++ b/src/commands/utility/translate.ts @@ -1,35 +1,43 @@ -import {Command, NamedCommand} from "../../core"; +import {Command, NamedCommand, RestCommand} from "../../core"; import translate from "translate-google"; export default new NamedCommand({ description: "Translates your input.", usage: " ", - async run({send, message, channel, guild, author, member, client, args}) { - const lang = args[0]; - const input = args.slice(1).join(" "); - translate(input, { - to: lang - }) - .then((res) => { - send({ - embed: { - title: "Translation", - fields: [ - { - name: "Input", - value: `\`\`\`${input}\`\`\`` - }, - { - name: "Output", - value: `\`\`\`${res}\`\`\`` + run: "You need to specify a language to translate to.", + any: new Command({ + run: "You need to enter some text to translate.", + any: new RestCommand({ + async run({send, message, channel, guild, author, member, client, args}) { + const lang = args[0]; + const input = args.slice(1).join(" "); + translate(input, { + to: lang + }) + .then((res) => { + send({ + embed: { + title: "Translation", + fields: [ + { + name: "Input", + value: `\`\`\`${input}\`\`\`` + }, + { + name: "Output", + value: `\`\`\`${res}\`\`\`` + } + ] } - ] - } - }); - }) - .catch((error) => { - console.error(error); - send(`${error}\nPlease use the following list: https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes`); - }); - } + }); + }) + .catch((error) => { + console.error(error); + send( + `${error}\nPlease use the following list: https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes` + ); + }); + } + }) + }) }); diff --git a/src/core/command.ts b/src/core/command.ts index c818986..cded03c 100644 --- a/src/core/command.ts +++ b/src/core/command.ts @@ -73,23 +73,15 @@ interface CommandMenu { interface CommandOptionsBase { readonly description?: string; - readonly endpoint?: boolean; readonly usage?: string; readonly permission?: number; readonly nsfw?: boolean; readonly channelType?: CHANNEL_TYPE; } -interface CommandOptionsEndpoint { - readonly endpoint: true; - readonly run?: (($: CommandMenu) => Promise) | string; -} - -// Prevents subcommands from being added by compile-time. // Also, contrary to what you might think, channel pings do still work in DM channels. // Role pings, maybe not, but it's not a big deal. -interface CommandOptionsNonEndpoint { - readonly endpoint?: false; +interface CommandOptions extends CommandOptionsBase { readonly run?: (($: CommandMenu) => Promise) | string; readonly subcommands?: {[key: string]: NamedCommand}; readonly channel?: Command; @@ -103,11 +95,14 @@ interface CommandOptionsNonEndpoint { readonly any?: Command | RestCommand; } -type CommandOptions = CommandOptionsBase & (CommandOptionsEndpoint | CommandOptionsNonEndpoint); -type NamedCommandOptions = CommandOptions & {aliases?: string[]; nameOverride?: string}; -type RestCommandOptions = CommandOptionsBase & { - run?: (($: CommandMenu & {readonly combined: string}) => Promise) | string; -}; +interface NamedCommandOptions extends CommandOptions { + readonly aliases?: string[]; + readonly nameOverride?: string; +} + +interface RestCommandOptions extends CommandOptionsBase { + readonly run?: (($: CommandMenu & {readonly combined: string}) => Promise) | string; +} interface ExecuteCommandMetadata { readonly header: string; @@ -164,7 +159,6 @@ abstract class BaseCommand { // Each Command instance represents a block that links other Command instances under it. export class Command extends BaseCommand { - public readonly endpoint: boolean; // The execute and subcommand properties are restricted to the class because subcommand recursion could easily break when manually handled. // The class will handle checking for null fields. private run: (($: CommandMenu) => Promise) | string; @@ -182,31 +176,20 @@ export class Command extends BaseCommand { constructor(options?: CommandOptions) { super(options); - this.endpoint = !!options?.endpoint; this.run = options?.run || "No action was set on this command!"; this.subcommands = new Collection(); // Populate this collection after setting subcommands. - this.channel = null; - this.role = null; - this.emote = null; - this.message = null; - this.user = null; - this.guild = null; + this.channel = options?.channel || null; + this.role = options?.role || null; + this.emote = options?.emote || null; + this.message = options?.message || null; + this.user = options?.user || null; + this.guild = options?.guild || null; this.id = null; - this.idType = null; - this.number = null; - this.any = null; - - if (options && !options.endpoint) { - if (options.channel) this.channel = options.channel; - if (options.role) this.role = options.role; - if (options.emote) this.emote = options.emote; - if (options.message) this.message = options.message; - if (options.user) this.user = options.user; - if (options.guild) this.guild = options.guild; - if (options.number) this.number = options.number; - if (options.any) this.any = options.any; - if (options.id) this.idType = options.id; + this.idType = options?.id || null; + this.number = options?.number || null; + this.any = options?.any || null; + if (options) switch (options.id) { case "channel": this.id = this.channel; @@ -232,30 +215,29 @@ export class Command extends BaseCommand { requireAllCasesHandledFor(options.id); } - if (options.subcommands) { - const baseSubcommands = Object.keys(options.subcommands); + if (options?.subcommands) { + const baseSubcommands = Object.keys(options.subcommands); - // Loop once to set the base subcommands. - for (const name in options.subcommands) this.subcommands.set(name, options.subcommands[name]); + // Loop once to set the base subcommands. + for (const name in options.subcommands) this.subcommands.set(name, options.subcommands[name]); - // Then loop again to make aliases point to the base subcommands and warn if something's not right. - // This shouldn't be a problem because I'm hoping that JS stores these as references that point to the same object. - for (const name in options.subcommands) { - const subcmd = options.subcommands[name]; - subcmd.name = name; - const aliases = subcmd.aliases; + // Then loop again to make aliases point to the base subcommands and warn if something's not right. + // This shouldn't be a problem because I'm hoping that JS stores these as references that point to the same object. + for (const name in options.subcommands) { + const subcmd = options.subcommands[name]; + subcmd.name = name; + const aliases = subcmd.aliases; - for (const alias of aliases) { - if (baseSubcommands.includes(alias)) - console.warn( - `"${alias}" in subcommand "${name}" was attempted to be declared as an alias but it already exists in the base commands! (Look at the next "Loading Command" line to see which command is affected.)` - ); - else if (this.subcommands.has(alias)) - console.warn( - `Duplicate alias "${alias}" at subcommand "${name}"! (Look at the next "Loading Command" line to see which command is affected.)` - ); - else this.subcommands.set(alias, subcmd); - } + for (const alias of aliases) { + if (baseSubcommands.includes(alias)) + console.warn( + `"${alias}" in subcommand "${name}" was attempted to be declared as an alias but it already exists in the base commands! (Look at the next "Loading Command" line to see which command is affected.)` + ); + else if (this.subcommands.has(alias)) + console.warn( + `Duplicate alias "${alias}" at subcommand "${name}"! (Look at the next "Loading Command" line to see which command is affected.)` + ); + else this.subcommands.set(alias, subcmd); } } } @@ -319,9 +301,6 @@ export class Command extends BaseCommand { return null; } - // If the current command is an endpoint but there are still some arguments left, don't continue unless there's a RestCommand. - if (this.endpoint) return {content: "Too many arguments!"}; - // Resolve the value of the current command's argument (adding it to the resolved args), // then pass the thread of execution to whichever subcommand is valid (if any). const isMessageLink = patterns.messageLink.test(param); @@ -506,11 +485,15 @@ export class Command extends BaseCommand { } else if (this.any instanceof RestCommand) { metadata.symbolicArgs.push("<...>"); args.unshift(param); + menu.args.push(...args); return this.any.execute(args.join(" "), menu, metadata); } else { - // Continue adding on the rest of the arguments if there's no valid subcommand. - menu.args.push(param); - return this.execute(args, menu, metadata); + metadata.symbolicArgs.push(`"${param}"`); + return { + content: `No valid command sequence matching \`${metadata.header} ${metadata.symbolicArgs.join( + " " + )}\` found.` + }; } // Note: Do NOT add a return statement here. In case one of the other sections is missing @@ -726,7 +709,9 @@ export class RestCommand extends BaseCommand { } else { // Then capture any potential errors. try { - await this.run({...menu, combined}); + // Args will still be kept intact. A common pattern is popping some parameters off the end then doing some branching. + // That way, you can still declaratively mark an argument list as continuing while also handling the individual args. + await this.run({...menu, args: menu.args, combined}); } catch (error) { const errorMessage = error.stack ?? error; console.error(`Command Error: ${metadata.header} (${metadata.args.join(", ")})\n${errorMessage}`); diff --git a/src/core/handler.ts b/src/core/handler.ts index 52fdc80..c93f217 100644 --- a/src/core/handler.ts +++ b/src/core/handler.ts @@ -21,8 +21,7 @@ const lastCommandInfo: { const defaultMetadata = { permission: 0, nsfw: false, - channelType: 0, // CHANNEL_TYPE.ANY, apparently isn't initialized at this point yet - symbolicArgs: [] + channelType: 0 // CHANNEL_TYPE.ANY, apparently isn't initialized at this point yet }; // Note: client.user is only undefined before the bot logs in, so by this point, client.user cannot be undefined. @@ -67,7 +66,8 @@ export function attachMessageHandlerToClient(client: Client) { const result = await command.execute(args, menu, { header, args: [...args], - ...defaultMetadata + ...defaultMetadata, + symbolicArgs: [] }); // If something went wrong, let the user know (like if they don't have permission to use a command). @@ -104,7 +104,8 @@ export function attachMessageHandlerToClient(client: Client) { const result = await command.execute(args, menu, { header, args: [...args], - ...defaultMetadata + ...defaultMetadata, + symbolicArgs: [] }); // If something went wrong, let the user know (like if they don't have permission to use a command). From c980a182f869b0a960bd1104184042f3b11a25bf Mon Sep 17 00:00:00 2001 From: WatDuhHekBro <44940783+WatDuhHekBro@users.noreply.github.com> Date: Sun, 11 Apr 2021 03:02:56 -0500 Subject: [PATCH 163/178] Updated library functions --- docs/Documentation.md | 27 +-- src/commands/fun/eco.ts | 3 +- src/commands/fun/modules/eco-bet.ts | 147 ++++++------ src/commands/fun/modules/eco-core.ts | 55 ++--- src/commands/fun/modules/eco-shop.ts | 17 +- src/commands/fun/modules/eco-utils.ts | 4 +- src/commands/fun/whois.ts | 4 +- src/commands/system/help.ts | 25 +- src/commands/utility/info.ts | 4 +- src/commands/utility/lsemotes.ts | 23 +- src/commands/utility/time.ts | 326 +++++++++++++------------- src/core/command.ts | 94 +++----- src/core/libd.ts | 288 ++++++++++------------- src/modules/messageEmbed.ts | 4 +- 14 files changed, 479 insertions(+), 542 deletions(-) diff --git a/docs/Documentation.md b/docs/Documentation.md index 1907bc9..28c2c32 100644 --- a/docs/Documentation.md +++ b/docs/Documentation.md @@ -75,27 +75,24 @@ Because versions are assigned to batches of changes rather than single changes ( ```ts const pages = ["one", "two", "three"]; -paginate(channel, author.id, pages.length, (page) => { - return { - content: pages[page] - }; -}); +paginate(send, page => { + return {content: pages[page]}; +}, pages.length, author.id); ``` -`prompt()` +`confirm()` ```ts -const msg = await channel.send('Are you sure you want to delete this?'); - -prompt(msg, author.id, () => { - //... -}); +const result = await confirm(await send("Are you sure you want to delete this?"), author.id); // boolean | null ``` -`callMemberByUsername()` +`askMultipleChoice()` ```ts -callMemberByUsername(message, args.join(" "), (member) => { - channel.send(`Your nickname is ${member.nickname}.`); -}); +const result = await askMultipleChoice(await send("Which of the following numbers is your favorite?"), author.id, 4, 10000); // number (0 to 3) | null +``` + +`askForReply()` +```ts +const reply = await askForReply(await send("What is your favorite thing to do?"), author.id, 10000); // Message | null ``` ## [src/lib](../src/lib.ts) - General utility functions diff --git a/src/commands/fun/eco.ts b/src/commands/fun/eco.ts index 96c1b43..241411d 100644 --- a/src/commands/fun/eco.ts +++ b/src/commands/fun/eco.ts @@ -4,7 +4,6 @@ import {DailyCommand, PayCommand, GuildCommand, LeaderboardCommand} from "./modu import {BuyCommand, ShopCommand} from "./modules/eco-shop"; import {MondayCommand, AwardCommand} from "./modules/eco-extras"; import {BetCommand} from "./modules/eco-bet"; -import {GuildMember} from "discord.js"; export default new NamedCommand({ description: "Economy command for Monika.", @@ -38,7 +37,7 @@ export default new NamedCommand({ async run({send, guild, channel, args, message, combined}) { if (isAuthorized(guild, channel)) { const member = await getMemberByName(guild!, combined); - if (member instanceof GuildMember) send(getMoneyEmbed(member.user)); + if (typeof member !== "string") send(getMoneyEmbed(member.user)); else send(member); } } diff --git a/src/commands/fun/modules/eco-bet.ts b/src/commands/fun/modules/eco-bet.ts index ecda43c..7b38485 100644 --- a/src/commands/fun/modules/eco-bet.ts +++ b/src/commands/fun/modules/eco-bet.ts @@ -1,7 +1,7 @@ -import {Command, NamedCommand, askYesOrNo} from "../../../core"; +import {Command, NamedCommand, confirm} from "../../../core"; import {pluralise} from "../../../lib"; import {Storage} from "../../../structures"; -import {isAuthorized, getMoneyEmbed, getSendEmbed, ECO_EMBED_COLOR} from "./eco-utils"; +import {isAuthorized, getMoneyEmbed} from "./eco-utils"; import {User} from "discord.js"; export const BetCommand = new NamedCommand({ @@ -79,88 +79,89 @@ export const BetCommand = new NamedCommand({ return send(`Bet duration is too long, maximum duration is ${durationBounds.max}`); // Ask target whether or not they want to take the bet. - const takeBet = await askYesOrNo( + const takeBet = await confirm( await send( `<@${target.id}>, do you want to take this bet of ${pluralise(amount, "Mon", "s")}` ), target.id ); - if (takeBet) { - // [MEDIUM PRIORITY: bet persistence to prevent losses in case of shutdown.] - // Remove amount money from both parts at the start to avoid duplication of money. - sender.money -= amount; - receiver.money -= amount; - // Very hacky solution for persistence but better than no solution, backup returns runs during the bot's setup code. - sender.ecoBetInsurance += amount; - receiver.ecoBetInsurance += amount; - Storage.save(); + if (!takeBet) return send(`<@${target.id}> has rejected your bet, <@${author.id}>`); - // Notify both users. - await send( - `<@${target.id}> has taken <@${author.id}>'s bet, the bet amount of ${pluralise( - amount, - "Mon", - "s" - )} has been deducted from each of them.` + // [MEDIUM PRIORITY: bet persistence to prevent losses in case of shutdown.] + // Remove amount money from both parts at the start to avoid duplication of money. + sender.money -= amount; + receiver.money -= amount; + // Very hacky solution for persistence but better than no solution, backup returns runs during the bot's setup code. + sender.ecoBetInsurance += amount; + receiver.ecoBetInsurance += amount; + Storage.save(); + + // Notify both users. + send( + `<@${target.id}> has taken <@${author.id}>'s bet, the bet amount of ${pluralise( + amount, + "Mon", + "s" + )} has been deducted from each of them.` + ); + + // Wait for the duration of the bet. + return client.setTimeout(async () => { + // In debug mode, saving the storage will break the references, so you have to redeclare sender and receiver for it to actually save. + const sender = Storage.getUser(author.id); + const receiver = Storage.getUser(target.id); + // [TODO: when D.JSv13 comes out, inline reply to clean up.] + // When bet is over, give a vote to ask people their thoughts. + const voteMsg = await send( + `VOTE: do you think that <@${ + target.id + }> has won the bet?\nhttps://discord.com/channels/${guild!.id}/${channel.id}/${ + message.id + }` ); + await voteMsg.react("✅"); + await voteMsg.react("❌"); - // Wait for the duration of the bet. - return client.setTimeout(async () => { - // In debug mode, saving the storage will break the references, so you have to redeclare sender and receiver for it to actually save. - const sender = Storage.getUser(author.id); - const receiver = Storage.getUser(target.id); - // [TODO: when D.JSv13 comes out, inline reply to clean up.] - // When bet is over, give a vote to ask people their thoughts. - const voteMsg = await send( - `VOTE: do you think that <@${ - target.id - }> has won the bet?\nhttps://discord.com/channels/${guild!.id}/${channel.id}/${ - message.id - }` - ); - await voteMsg.react("✅"); - await voteMsg.react("❌"); + // Filter reactions to only collect the pertinent ones. + voteMsg + .awaitReactions( + (reaction, user) => { + return ["✅", "❌"].includes(reaction.emoji.name); + }, + // [Pertinence to make configurable on the fly.] + {time: parseDuration("2m")} + ) + .then((reactions) => { + // Count votes + const okReaction = reactions.get("✅"); + const noReaction = reactions.get("❌"); + const ok = okReaction ? (okReaction.count ?? 1) - 1 : 0; + const no = noReaction ? (noReaction.count ?? 1) - 1 : 0; - // Filter reactions to only collect the pertinent ones. - voteMsg - .awaitReactions( - (reaction, user) => { - return ["✅", "❌"].includes(reaction.emoji.name); - }, - // [Pertinence to make configurable on the fly.] - {time: parseDuration("2m")} - ) - .then((reactions) => { - // Count votes - const okReaction = reactions.get("✅"); - const noReaction = reactions.get("❌"); - const ok = okReaction ? (okReaction.count ?? 1) - 1 : 0; - const no = noReaction ? (noReaction.count ?? 1) - 1 : 0; + if (ok > no) { + receiver.money += amount * 2; + send( + `By the people's votes, <@${target.id}> has won the bet that <@${author.id}> had sent them.` + ); + } else if (ok < no) { + sender.money += amount * 2; + send( + `By the people's votes, <@${target.id}> has lost the bet that <@${author.id}> had sent them.` + ); + } else { + sender.money += amount; + receiver.money += amount; + send( + `By the people's votes, <@${target.id}> couldn't be determined to have won or lost the bet that <@${author.id}> had sent them.` + ); + } - if (ok > no) { - receiver.money += amount * 2; - send( - `By the people's votes, <@${target.id}> has won the bet that <@${author.id}> had sent them.` - ); - } else if (ok < no) { - sender.money += amount * 2; - send( - `By the people's votes, <@${target.id}> has lost the bet that <@${author.id}> had sent them.` - ); - } else { - sender.money += amount; - receiver.money += amount; - send( - `By the people's votes, <@${target.id}> couldn't be determined to have won or lost the bet that <@${author.id}> had sent them.` - ); - } - sender.ecoBetInsurance -= amount; - receiver.ecoBetInsurance -= amount; - Storage.save(); - }); - }, duration); - } else return await send(`<@${target.id}> has rejected your bet, <@${author.id}>`); + sender.ecoBetInsurance -= amount; + receiver.ecoBetInsurance -= amount; + Storage.save(); + }); + }, duration); } else return; } }) diff --git a/src/commands/fun/modules/eco-core.ts b/src/commands/fun/modules/eco-core.ts index ec3d8b6..4e0a520 100644 --- a/src/commands/fun/modules/eco-core.ts +++ b/src/commands/fun/modules/eco-core.ts @@ -1,5 +1,4 @@ -import {GuildMember} from "discord.js"; -import {Command, getMemberByName, NamedCommand, prompt, RestCommand} from "../../../core"; +import {Command, getMemberByName, NamedCommand, confirm, RestCommand} from "../../../core"; import {pluralise} from "../../../lib"; import {Storage} from "../../../structures"; import {isAuthorized, getMoneyEmbed, getSendEmbed, ECO_EMBED_COLOR} from "./eco-utils"; @@ -90,7 +89,7 @@ export const LeaderboardCommand = new NamedCommand({ const user = await client.users.fetch(id); fields.push({ - name: `#${i + 1}. ${user.username}#${user.discriminator}`, + name: `#${i + 1}. ${user.tag}`, value: pluralise(users[id].money, "Mon", "s") }); } @@ -158,42 +157,38 @@ export const PayCommand = new NamedCommand({ return send("You have to use this in a server if you want to send Mons with a username!"); const member = await getMemberByName(guild, combined); - if (!(member instanceof GuildMember)) return send(member); + if (typeof member === "string") return send(member); else if (member.user.id === author.id) return send("You can't send Mons to yourself!"); else if (member.user.bot && process.argv[2] !== "dev") return send("You can't send Mons to a bot!"); const target = member.user; - return prompt( - await send( - `Are you sure you want to send ${pluralise( - amount, - "Mon", - "s" - )} to this person?\n*(This message will automatically be deleted after 10 seconds.)*`, - { - embed: { - color: ECO_EMBED_COLOR, - author: { - name: `${target.username}#${target.discriminator}`, - icon_url: target.displayAvatarURL({ - format: "png", - dynamic: true - }) - } + const result = await confirm( + await send(`Are you sure you want to send ${pluralise(amount, "Mon", "s")} to this person?`, { + embed: { + color: ECO_EMBED_COLOR, + author: { + name: target.tag, + icon_url: target.displayAvatarURL({ + format: "png", + dynamic: true + }) } } - ), - author.id, - () => { - const receiver = Storage.getUser(target.id); - sender.money -= amount; - receiver.money += amount; - Storage.save(); - send(getSendEmbed(author, target, amount)); - } + }), + author.id ); + + if (result) { + const receiver = Storage.getUser(target.id); + sender.money -= amount; + receiver.money += amount; + Storage.save(); + send(getSendEmbed(author, target, amount)); + } } + + return; } }) }); diff --git a/src/commands/fun/modules/eco-shop.ts b/src/commands/fun/modules/eco-shop.ts index acd0e4a..91ac71d 100644 --- a/src/commands/fun/modules/eco-shop.ts +++ b/src/commands/fun/modules/eco-shop.ts @@ -34,12 +34,17 @@ export const ShopCommand = new NamedCommand({ const shopPages = split(ShopItems, 5); const pageAmount = shopPages.length; - paginate(send, author.id, pageAmount, (page, hasMultiplePages) => { - return getShopEmbed( - shopPages[page], - hasMultiplePages ? `Shop (Page ${page + 1} of ${pageAmount})` : "Shop" - ); - }); + paginate( + send, + (page, hasMultiplePages) => { + return getShopEmbed( + shopPages[page], + hasMultiplePages ? `Shop (Page ${page + 1} of ${pageAmount})` : "Shop" + ); + }, + pageAmount, + author.id + ); } } }); diff --git a/src/commands/fun/modules/eco-utils.ts b/src/commands/fun/modules/eco-utils.ts index 327abdb..d014374 100644 --- a/src/commands/fun/modules/eco-utils.ts +++ b/src/commands/fun/modules/eco-utils.ts @@ -42,11 +42,11 @@ export function getSendEmbed(sender: User, receiver: User, amount: number): obje description: `${sender.toString()} has sent ${pluralise(amount, "Mon", "s")} to ${receiver.toString()}!`, fields: [ { - name: `Sender: ${sender.username}#${sender.discriminator}`, + name: `Sender: ${sender.tag}`, value: pluralise(Storage.getUser(sender.id).money, "Mon", "s") }, { - name: `Receiver: ${receiver.username}#${receiver.discriminator}`, + name: `Receiver: ${receiver.tag}`, value: pluralise(Storage.getUser(receiver.id).money, "Mon", "s") } ], diff --git a/src/commands/fun/whois.ts b/src/commands/fun/whois.ts index 4ad9c97..507c14f 100644 --- a/src/commands/fun/whois.ts +++ b/src/commands/fun/whois.ts @@ -1,4 +1,4 @@ -import {User, GuildMember} from "discord.js"; +import {User} from "discord.js"; import {Command, NamedCommand, getMemberByName, CHANNEL_TYPE, RestCommand} from "../../core"; // Quotes must be used here or the numbers will change @@ -70,7 +70,7 @@ export default new NamedCommand({ async run({send, message, channel, guild, author, client, args, combined}) { const member = await getMemberByName(guild!, combined); - if (member instanceof GuildMember) { + if (typeof member !== "string") { if (member.id in registry) { send(`\`${member.nickname ?? member.user.username}\` - ${registry[member.id]}`); } else { diff --git a/src/commands/system/help.ts b/src/commands/system/help.ts index 7691a5a..7e5139c 100644 --- a/src/commands/system/help.ts +++ b/src/commands/system/help.ts @@ -20,16 +20,21 @@ export default new NamedCommand({ const commands = await getCommandList(); const categoryArray = commands.keyArray(); - paginate(send, author.id, categoryArray.length, (page, hasMultiplePages) => { - const category = categoryArray[page]; - const commandList = commands.get(category)!; - let output = `Legend: \`\`, \`[list/of/stuff]\`, \`(optional)\`, \`()\`, \`([optional/list/...])\`\n`; - for (const command of commandList) output += `\n❯ \`${command.name}\`: ${command.description}`; - return new MessageEmbed() - .setTitle(hasMultiplePages ? `${category} (Page ${page + 1} of ${categoryArray.length})` : category) - .setDescription(output) - .setColor(EMBED_COLOR); - }); + paginate( + send, + (page, hasMultiplePages) => { + const category = categoryArray[page]; + const commandList = commands.get(category)!; + let output = `Legend: \`\`, \`[list/of/stuff]\`, \`(optional)\`, \`()\`, \`([optional/list/...])\`\n`; + for (const command of commandList) output += `\n❯ \`${command.name}\`: ${command.description}`; + return new MessageEmbed() + .setTitle(hasMultiplePages ? `${category} (Page ${page + 1} of ${categoryArray.length})` : category) + .setDescription(output) + .setColor(EMBED_COLOR); + }, + categoryArray.length, + author.id + ); }, any: new Command({ async run({send, message, channel, guild, author, member, client, args}) { diff --git a/src/commands/utility/info.ts b/src/commands/utility/info.ts index 5e51363..c9368e6 100644 --- a/src/commands/utility/info.ts +++ b/src/commands/utility/info.ts @@ -36,7 +36,7 @@ export default new NamedCommand({ async run({send, message, channel, guild, author, client, args, combined}) { const member = await getMemberByName(guild!, combined); - if (member instanceof GuildMember) { + if (typeof member !== "string") { send( member.user.displayAvatarURL({ dynamic: true, @@ -110,7 +110,7 @@ export default new NamedCommand({ async run({send, message, channel, guild, author, member, client, args, combined}) { const targetGuild = getGuildByName(combined); - if (targetGuild instanceof Guild) { + if (typeof targetGuild !== "string") { send(await getGuildInfo(targetGuild, guild)); } else { send(targetGuild); diff --git a/src/commands/utility/lsemotes.ts b/src/commands/utility/lsemotes.ts index 37840b9..7e1e2d9 100644 --- a/src/commands/utility/lsemotes.ts +++ b/src/commands/utility/lsemotes.ts @@ -90,17 +90,22 @@ async function displayEmoteList(emotes: GuildEmoji[], send: SendFunction, author // Gather the first page (if it even exists, which it might not if there no valid emotes appear) if (pages > 0) { - paginate(send, author.id, pages, (page, hasMultiplePages) => { - embed.setTitle(hasMultiplePages ? `**Emotes** (Page ${page + 1} of ${pages})` : "**Emotes**"); + paginate( + send, + (page, hasMultiplePages) => { + embed.setTitle(hasMultiplePages ? `**Emotes** (Page ${page + 1} of ${pages})` : "**Emotes**"); - let desc = ""; - for (const emote of sections[page]) { - desc += `${emote} ${emote.name} (**${emote.guild.name}**)\n`; - } - embed.setDescription(desc); + let desc = ""; + for (const emote of sections[page]) { + desc += `${emote} ${emote.name} (**${emote.guild.name}**)\n`; + } + embed.setDescription(desc); - return embed; - }); + return embed; + }, + pages, + author.id + ); } else { send("No valid emotes found by that query."); } diff --git a/src/commands/utility/time.ts b/src/commands/utility/time.ts index 7598f5a..9852b86 100644 --- a/src/commands/utility/time.ts +++ b/src/commands/utility/time.ts @@ -1,15 +1,6 @@ -import { - Command, - NamedCommand, - ask, - askYesOrNo, - askMultipleChoice, - prompt, - getMemberByName, - RestCommand -} from "../../core"; +import {Command, NamedCommand, askForReply, confirm, askMultipleChoice, getMemberByName, RestCommand} from "../../core"; import {Storage} from "../../structures"; -import {User, GuildMember} from "discord.js"; +import {User} from "discord.js"; import moment from "moment"; const DATE_FORMAT = "D MMMM YYYY"; @@ -178,183 +169,184 @@ function getTimeEmbed(user: User) { export default new NamedCommand({ description: "Show others what time it is for you.", aliases: ["tz"], - async run({send, channel, author}) { + async run({send, author}) { send(getTimeEmbed(author)); }, subcommands: { // Welcome to callback hell. We hope you enjoy your stay here! setup: new NamedCommand({ description: "Registers your timezone information for the bot.", - async run({send, author, channel}) { + async run({send, author}) { const profile = Storage.getUser(author.id); profile.timezone = null; profile.daylightSavingsRegion = null; - let hour: number; - ask( + // Parse and validate reply + const reply = await askForReply( await send( "What hour (0 to 23) is it for you right now?\n*(Note: Make sure to use Discord's inline reply feature or this won't work!)*" ), author.id, - (reply) => { - hour = parseInt(reply); - - if (isNaN(hour)) { - return false; - } - - return hour >= 0 && hour <= 23; - }, - async () => { - // You need to also take into account whether or not it's the same day in UTC or not. - // The problem this setup avoids is messing up timezones by 24 hours. - // For example, the old logic was just (hour - hourUTC). When I setup my timezone (UTC-6) at 18:00, it was UTC 00:00. - // That means that that formula was doing (18 - 0) getting me UTC+18 instead of UTC-6 because that naive formula didn't take into account a difference in days. - - // (day * 24 + hour) - (day * 24 + hour) - // Since the timezones will be restricted to -12 to +14, you'll be given three options. - // The end of the month should be calculated automatically, you should have enough information at that point. - - // But after mapping out the edge cases, I figured out that you can safely gather accurate information just based on whether the UTC day matches the user's day. - // 21:xx (-12, -d) -- 09:xx (+0, 0d) -- 23:xx (+14, 0d) - // 22:xx (-12, -d) -- 10:xx (+0, 0d) -- 00:xx (+14, +d) - // 23:xx (-12, -d) -- 11:xx (+0, 0d) -- 01:xx (+14, +d) - // 00:xx (-12, 0d) -- 12:xx (+0, 0d) -- 02:xx (+14, +d) - - // For example, 23:xx (-12) - 01:xx (+14) shows that even the edge cases of UTC-12 and UTC+14 cannot overlap, so the dataset can be reduced to a yes or no option. - // - 23:xx same day = +0, 23:xx diff day = -1 - // - 00:xx same day = +0, 00:xx diff day = +1 - // - 01:xx same day = +0, 01:xx diff day = +1 - - // First, create a tuple list matching each possible hour-dayOffset-timezoneOffset combination. In the above example, it'd go something like this: - // [[23, -1, -12], [0, 0, -11], ..., [23, 0, 12], [0, 1, 13], [1, 1, 14]] - // Then just find the matching one by filtering through dayOffset (equals zero or not), then the hour from user input. - // Remember that you don't know where the difference in day might be at this point, so you can't do (hour - hourUTC) safely. - // In terms of the equation, you're missing a variable in (--> day <-- * 24 + hour) - (day * 24 + hour). That's what the loop is for. - - // Now you might be seeing a problem with setting this up at the end/beginning of a month, but that actually isn't a problem. - // Let's say that it's 00:xx of the first UTC day of a month. hourSumUTC = 24 - // UTC-12 --> hourSumLowerBound (hourSumUTC - 12) = 12 - // UTC+14 --> hourSumUpperBound (hourSumUTC + 14) = 38 - // Remember, the nice thing about making these bounds relative to the UTC hour sum is that there can't be any conflicts even at the edges of months. - // And remember that we aren't using this question: (day * 24 + hour) - (day * 24 + hour). We're drawing from a list which does not store its data in absolute terms. - // That means there's no 31st, 1st, 2nd, it's -1, 0, +1. I just need to make sure to calculate an offset to subtract from the hour sums. - - const date = new Date(); // e.g. 2021-05-01 @ 05:00 - const day = date.getUTCDate(); // e.g. 1 - const hourSumUTC = day * 24 + date.getUTCHours(); // e.g. 29 - const timezoneTupleList: [number, number, number][] = []; - const uniques: number[] = []; // only for temporary use - const duplicates = []; - - // Setup the tuple list in a separate block. - for (let timezoneOffset = -12; timezoneOffset <= 14; timezoneOffset++) { - const hourSum = hourSumUTC + timezoneOffset; // e.g. 23, UTC-6 (17 to 43) - const hour = hourSum % 24; // e.g. 23 - // This works because you get the # of days w/o hours minus UTC days without hours. - // Since it's all relative to UTC, it'll end up being -1, 0, or 1. - const dayOffset = Math.floor(hourSum / 24) - day; // e.g. -1 - timezoneTupleList.push([hour, dayOffset, timezoneOffset]); - - if (uniques.includes(hour)) { - duplicates.push(hour); - } else { - uniques.push(hour); - } - } - - // I calculate the list beforehand and check for duplicates to reduce unnecessary asking. - if (duplicates.includes(hour)) { - const isSameDay = await askYesOrNo( - await send( - `Is the current day of the month the ${moment().utc().format("Do")} for you?` - ), - author.id - ); - - // Filter through isSameDay (aka !hasDayOffset) then hour from user-generated input. - // isSameDay is checked first to reduce the amount of conditionals per loop. - if (isSameDay) { - for (const [hourPoint, dayOffset, timezoneOffset] of timezoneTupleList) { - if (dayOffset === 0 && hour === hourPoint) { - profile.timezone = timezoneOffset; - } - } - } else { - for (const [hourPoint, dayOffset, timezoneOffset] of timezoneTupleList) { - if (dayOffset !== 0 && hour === hourPoint) { - profile.timezone = timezoneOffset; - } - } - } - } else { - // If it's a unique hour, just search through the tuple list and find the matching entry. - for (const [hourPoint, _dayOffset, timezoneOffset] of timezoneTupleList) { - if (hour === hourPoint) { - profile.timezone = timezoneOffset; - } - } - } - - // I should note that error handling should be added sometime because await throws an exception on Promise.reject. - const hasDST = await askYesOrNo( - await send("Does your timezone change based on daylight savings?"), - author.id - ); - - const finalize = () => { - Storage.save(); - send( - "You've finished setting up your timezone! Just check to see if this looks right, and if it doesn't, run this setup again.", - getTimeEmbed(author) - ); - }; - - if (hasDST) { - const finalizeDST = (region: DST) => { - profile.daylightSavingsRegion = region; - - // If daylight savings is active, subtract the timezone offset by one to store the standard time. - if (hasDaylightSavings(region)) { - profile.timezone!--; - } - - finalize(); - }; - - askMultipleChoice(await send(DST_NOTE_SETUP), author.id, [ - () => finalizeDST("na"), - () => finalizeDST("eu"), - () => finalizeDST("sh") - ]); - } else { - finalize(); - } - }, - () => "you need to enter in a valid integer between 0 to 23" + 30000 ); + if (reply === null) return send("Message timed out."); + const hour = parseInt(reply.content); + const isValidHour = !isNaN(hour) && hour >= 0 && hour <= 23; + if (!isValidHour) return reply.reply("you need to enter in a valid integer between 0 to 23"); + + // You need to also take into account whether or not it's the same day in UTC or not. + // The problem this setup avoids is messing up timezones by 24 hours. + // For example, the old logic was just (hour - hourUTC). When I setup my timezone (UTC-6) at 18:00, it was UTC 00:00. + // That means that that formula was doing (18 - 0) getting me UTC+18 instead of UTC-6 because that naive formula didn't take into account a difference in days. + + // (day * 24 + hour) - (day * 24 + hour) + // Since the timezones will be restricted to -12 to +14, you'll be given three options. + // The end of the month should be calculated automatically, you should have enough information at that point. + + // But after mapping out the edge cases, I figured out that you can safely gather accurate information just based on whether the UTC day matches the user's day. + // 21:xx (-12, -d) -- 09:xx (+0, 0d) -- 23:xx (+14, 0d) + // 22:xx (-12, -d) -- 10:xx (+0, 0d) -- 00:xx (+14, +d) + // 23:xx (-12, -d) -- 11:xx (+0, 0d) -- 01:xx (+14, +d) + // 00:xx (-12, 0d) -- 12:xx (+0, 0d) -- 02:xx (+14, +d) + + // For example, 23:xx (-12) - 01:xx (+14) shows that even the edge cases of UTC-12 and UTC+14 cannot overlap, so the dataset can be reduced to a yes or no option. + // - 23:xx same day = +0, 23:xx diff day = -1 + // - 00:xx same day = +0, 00:xx diff day = +1 + // - 01:xx same day = +0, 01:xx diff day = +1 + + // First, create a tuple list matching each possible hour-dayOffset-timezoneOffset combination. In the above example, it'd go something like this: + // [[23, -1, -12], [0, 0, -11], ..., [23, 0, 12], [0, 1, 13], [1, 1, 14]] + // Then just find the matching one by filtering through dayOffset (equals zero or not), then the hour from user input. + // Remember that you don't know where the difference in day might be at this point, so you can't do (hour - hourUTC) safely. + // In terms of the equation, you're missing a variable in (--> day <-- * 24 + hour) - (day * 24 + hour). That's what the loop is for. + + // Now you might be seeing a problem with setting this up at the end/beginning of a month, but that actually isn't a problem. + // Let's say that it's 00:xx of the first UTC day of a month. hourSumUTC = 24 + // UTC-12 --> hourSumLowerBound (hourSumUTC - 12) = 12 + // UTC+14 --> hourSumUpperBound (hourSumUTC + 14) = 38 + // Remember, the nice thing about making these bounds relative to the UTC hour sum is that there can't be any conflicts even at the edges of months. + // And remember that we aren't using this question: (day * 24 + hour) - (day * 24 + hour). We're drawing from a list which does not store its data in absolute terms. + // That means there's no 31st, 1st, 2nd, it's -1, 0, +1. I just need to make sure to calculate an offset to subtract from the hour sums. + + const date = new Date(); // e.g. 2021-05-01 @ 05:00 + const day = date.getUTCDate(); // e.g. 1 + const hourSumUTC = day * 24 + date.getUTCHours(); // e.g. 29 + const timezoneTupleList: [number, number, number][] = []; + const uniques: number[] = []; // only for temporary use + const duplicates = []; + + // Setup the tuple list in a separate block. + for (let timezoneOffset = -12; timezoneOffset <= 14; timezoneOffset++) { + const hourSum = hourSumUTC + timezoneOffset; // e.g. 23, UTC-6 (17 to 43) + const hour = hourSum % 24; // e.g. 23 + // This works because you get the # of days w/o hours minus UTC days without hours. + // Since it's all relative to UTC, it'll end up being -1, 0, or 1. + const dayOffset = Math.floor(hourSum / 24) - day; // e.g. -1 + timezoneTupleList.push([hour, dayOffset, timezoneOffset]); + + if (uniques.includes(hour)) { + duplicates.push(hour); + } else { + uniques.push(hour); + } + } + + // I calculate the list beforehand and check for duplicates to reduce unnecessary asking. + if (duplicates.includes(hour)) { + const isSameDay = await confirm( + await send(`Is the current day of the month the ${moment().utc().format("Do")} for you?`), + author.id + ); + + // Filter through isSameDay (aka !hasDayOffset) then hour from user-generated input. + // isSameDay is checked first to reduce the amount of conditionals per loop. + if (isSameDay) { + for (const [hourPoint, dayOffset, timezoneOffset] of timezoneTupleList) { + if (dayOffset === 0 && hour === hourPoint) { + profile.timezone = timezoneOffset; + } + } + } else { + for (const [hourPoint, dayOffset, timezoneOffset] of timezoneTupleList) { + if (dayOffset !== 0 && hour === hourPoint) { + profile.timezone = timezoneOffset; + } + } + } + } else { + // If it's a unique hour, just search through the tuple list and find the matching entry. + for (const [hourPoint, _dayOffset, timezoneOffset] of timezoneTupleList) { + if (hour === hourPoint) { + profile.timezone = timezoneOffset; + } + } + } + + // I should note that error handling should be added sometime because await throws an exception on Promise.reject. + const hasDST = await confirm( + await send("Does your timezone change based on daylight savings?"), + author.id + ); + + const finalize = () => { + Storage.save(); + send( + "You've finished setting up your timezone! Just check to see if this looks right, and if it doesn't, run this setup again.", + getTimeEmbed(author) + ); + }; + + if (hasDST) { + const finalizeDST = (region: DST) => { + profile.daylightSavingsRegion = region; + + // If daylight savings is active, subtract the timezone offset by one to store the standard time. + if (hasDaylightSavings(region)) { + profile.timezone!--; + } + + finalize(); + }; + + const index = await askMultipleChoice(await send(DST_NOTE_SETUP), author.id, 3); + + switch (index) { + case 0: + finalizeDST("na"); + break; + case 1: + finalizeDST("eu"); + break; + case 2: + finalizeDST("sh"); + break; + } + } else { + finalize(); + } + + return; } }), delete: new NamedCommand({ description: "Delete your timezone information.", - async run({send, channel, author}) { - prompt( - await send( - "Are you sure you want to delete your timezone information?\n*(This message will automatically be deleted after 10 seconds.)*" - ), - author.id, - () => { - const profile = Storage.getUser(author.id); - profile.timezone = null; - profile.daylightSavingsRegion = null; - Storage.save(); - } + async run({send, author}) { + const result = await confirm( + await send("Are you sure you want to delete your timezone information?"), + author.id ); + + if (result) { + const profile = Storage.getUser(author.id); + profile.timezone = null; + profile.daylightSavingsRegion = null; + Storage.save(); + } } }), utc: new NamedCommand({ description: "Displays UTC time.", - async run({send, channel}) { + async run({send}) { const time = moment().utc(); send({ @@ -386,15 +378,15 @@ export default new NamedCommand({ id: "user", user: new Command({ description: "See what time it is for someone else.", - async run({send, channel, args}) { + async run({send, args}) { send(getTimeEmbed(args[0])); } }), any: new RestCommand({ description: "See what time it is for someone else (by their username).", - async run({send, channel, args, guild, combined}) { + async run({send, guild, combined}) { const member = await getMemberByName(guild!, combined); - if (member instanceof GuildMember) send(getTimeEmbed(member.user)); + if (typeof member !== "string") send(getTimeEmbed(member.user)); else send(member); } }) diff --git a/src/core/command.ts b/src/core/command.ts index cded03c..7e024da 100644 --- a/src/core/command.ts +++ b/src/core/command.ts @@ -11,7 +11,7 @@ import { GuildChannel, Channel } from "discord.js"; -import {getChannelByID, getGuildByID, getMessageByID, getUserByID, SingleMessageOptions, SendFunction} from "./libd"; +import {getChannelByID, getGuildByID, getMessageByID, getUserByID, SendFunction} from "./libd"; import {hasPermission, getPermissionLevel, getPermissionName} from "./permissions"; import {getPrefix} from "./interface"; import {parseVars, requireAllCasesHandledFor} from "../lib"; @@ -244,18 +244,14 @@ export class Command extends BaseCommand { } // Go through the arguments provided and find the right subcommand, then execute with the given arguments. - // Will return null if it successfully executes, SingleMessageOptions if there's an error (to let the user know what it is). + // Will return null if it successfully executes, string if there's an error (to let the user know what it is). // // Calls the resulting subcommand's execute method in order to make more modular code, basically pushing the chain of execution to the subcommand. // For example, a numeric subcommand would accept args of [4] then execute on it. // // Because each Command instance is isolated from others, it becomes practically impossible to predict the total amount of subcommands when isolating the code to handle each individual layer of recursion. // Therefore, if a Command is declared as a rest type, any typed args that come at the end must be handled manually. - public async execute( - args: string[], - menu: CommandMenu, - metadata: ExecuteCommandMetadata - ): Promise { + public async execute(args: string[], menu: CommandMenu, metadata: ExecuteCommandMetadata): Promise { // Update inherited properties if the current command specifies a property. // In case there are no initial arguments, these should go first so that it can register. if (this.permission !== -1) metadata.permission = this.permission; @@ -292,9 +288,7 @@ export class Command extends BaseCommand { const errorMessage = error.stack ?? error; console.error(`Command Error: ${metadata.header} (${metadata.args.join(", ")})\n${errorMessage}`); - return { - content: `There was an error while trying to execute that command!\`\`\`${errorMessage}\`\`\`` - }; + return `There was an error while trying to execute that command!\`\`\`${errorMessage}\`\`\``; } } @@ -313,15 +307,13 @@ export class Command extends BaseCommand { const id = patterns.channel.exec(param)![1]; const channel = await getChannelByID(id); - if (channel instanceof Channel) { + if (typeof channel !== "string") { if (channel instanceof TextChannel || channel instanceof DMChannel) { metadata.symbolicArgs.push(""); menu.args.push(channel); return this.channel.execute(args, menu, metadata); } else { - return { - content: `\`${id}\` is not a valid text channel!` - }; + return `\`${id}\` is not a valid text channel!`; } } else { return channel; @@ -330,9 +322,7 @@ export class Command extends BaseCommand { const id = patterns.role.exec(param)![1]; if (!menu.guild) { - return { - content: "You can't use role parameters in DM channels!" - }; + return "You can't use role parameters in DM channels!"; } const role = menu.guild.roles.cache.get(id); @@ -342,9 +332,7 @@ export class Command extends BaseCommand { menu.args.push(role); return this.role.execute(args, menu, metadata); } else { - return { - content: `\`${id}\` is not a valid role in this server!` - }; + return `\`${id}\` is not a valid role in this server!`; } } else if (this.emote && patterns.emote.test(param)) { const id = patterns.emote.exec(param)![1]; @@ -355,9 +343,7 @@ export class Command extends BaseCommand { menu.args.push(emote); return this.emote.execute(args, menu, metadata); } else { - return { - content: `\`${id}\` isn't a valid emote!` - }; + return `\`${id}\` isn't a valid emote!`; } } else if (this.message && (isMessageLink || isMessagePair)) { let channelID = ""; @@ -375,7 +361,7 @@ export class Command extends BaseCommand { const message = await getMessageByID(channelID, messageID); - if (message instanceof Message) { + if (typeof message !== "string") { metadata.symbolicArgs.push(""); menu.args.push(message); return this.message.execute(args, menu, metadata); @@ -386,7 +372,7 @@ export class Command extends BaseCommand { const id = patterns.user.exec(param)![1]; const user = await getUserByID(id); - if (user instanceof User) { + if (typeof user !== "string") { metadata.symbolicArgs.push(""); menu.args.push(user); return this.user.execute(args, menu, metadata); @@ -403,24 +389,20 @@ export class Command extends BaseCommand { case "channel": const channel = await getChannelByID(id); - if (channel instanceof Channel) { + if (typeof channel !== "string") { if (channel instanceof TextChannel || channel instanceof DMChannel) { metadata.symbolicArgs.push(""); menu.args.push(channel); return this.id.execute(args, menu, metadata); } else { - return { - content: `\`${id}\` is not a valid text channel!` - }; + return `\`${id}\` is not a valid text channel!`; } } else { return channel; } case "role": if (!menu.guild) { - return { - content: "You can't use role parameters in DM channels!" - }; + return "You can't use role parameters in DM channels!"; } const role = menu.guild.roles.cache.get(id); @@ -429,9 +411,7 @@ export class Command extends BaseCommand { menu.args.push(role); return this.id.execute(args, menu, metadata); } else { - return { - content: `\`${id}\` isn't a valid role in this server!` - }; + return `\`${id}\` isn't a valid role in this server!`; } case "emote": const emote = menu.client.emojis.cache.get(id); @@ -440,14 +420,12 @@ export class Command extends BaseCommand { menu.args.push(emote); return this.id.execute(args, menu, metadata); } else { - return { - content: `\`${id}\` isn't a valid emote!` - }; + return `\`${id}\` isn't a valid emote!`; } case "message": const message = await getMessageByID(menu.channel, id); - if (message instanceof Message) { + if (typeof message !== "string") { menu.args.push(message); return this.id.execute(args, menu, metadata); } else { @@ -456,7 +434,7 @@ export class Command extends BaseCommand { case "user": const user = await getUserByID(id); - if (user instanceof User) { + if (typeof user !== "string") { menu.args.push(user); return this.id.execute(args, menu, metadata); } else { @@ -465,7 +443,7 @@ export class Command extends BaseCommand { case "guild": const guild = getGuildByID(id); - if (guild instanceof Guild) { + if (typeof guild !== "string") { menu.args.push(guild); return this.id.execute(args, menu, metadata); } else { @@ -489,11 +467,9 @@ export class Command extends BaseCommand { return this.any.execute(args.join(" "), menu, metadata); } else { metadata.symbolicArgs.push(`"${param}"`); - return { - content: `No valid command sequence matching \`${metadata.header} ${metadata.symbolicArgs.join( - " " - )}\` found.` - }; + return `No valid command sequence matching \`${metadata.header} ${metadata.symbolicArgs.join( + " " + )}\` found.`; } // Note: Do NOT add a return statement here. In case one of the other sections is missing @@ -682,7 +658,7 @@ export class RestCommand extends BaseCommand { combined: string, menu: CommandMenu, metadata: ExecuteCommandMetadata - ): Promise { + ): Promise { // Update inherited properties if the current command specifies a property. // In case there are no initial arguments, these should go first so that it can register. if (this.permission !== -1) metadata.permission = this.permission; @@ -716,9 +692,7 @@ export class RestCommand extends BaseCommand { const errorMessage = error.stack ?? error; console.error(`Command Error: ${metadata.header} (${metadata.args.join(", ")})\n${errorMessage}`); - return { - content: `There was an error while trying to execute that command!\`\`\`${errorMessage}\`\`\`` - }; + return `There was an error while trying to execute that command!\`\`\`${errorMessage}\`\`\``; } } @@ -743,36 +717,34 @@ export class RestCommand extends BaseCommand { // See if there is anything that'll prevent the user from executing the command. // Returns null if successful, otherwise returns a message with the error. -function canExecute(menu: CommandMenu, metadata: ExecuteCommandMetadata): SingleMessageOptions | null { +function canExecute(menu: CommandMenu, metadata: ExecuteCommandMetadata): string | null { // 1. Does this command specify a required channel type? If so, does the channel type match? if ( metadata.channelType === CHANNEL_TYPE.GUILD && (!(menu.channel instanceof GuildChannel) || menu.guild === null || menu.member === null) ) { - return {content: "This command must be executed in a server."}; + return "This command must be executed in a server."; } else if ( metadata.channelType === CHANNEL_TYPE.DM && (menu.channel.type !== "dm" || menu.guild !== null || menu.member !== null) ) { - return {content: "This command must be executed as a direct message."}; + return "This command must be executed as a direct message."; } // 2. Is this an NSFW command where the channel prevents such use? (DM channels bypass this requirement.) if (metadata.nsfw && menu.channel.type !== "dm" && !menu.channel.nsfw) { - return {content: "This command must be executed in either an NSFW channel or as a direct message."}; + return "This command must be executed in either an NSFW channel or as a direct message."; } // 3. Does the user have permission to execute the command? if (!hasPermission(menu.author, menu.member, metadata.permission)) { const userPermLevel = getPermissionLevel(menu.author, menu.member); - return { - content: `You don't have access to this command! Your permission level is \`${getPermissionName( - userPermLevel - )}\` (${userPermLevel}), but this command requires a permission level of \`${getPermissionName( - metadata.permission - )}\` (${metadata.permission}).` - }; + return `You don't have access to this command! Your permission level is \`${getPermissionName( + userPermLevel + )}\` (${userPermLevel}), but this command requires a permission level of \`${getPermissionName( + metadata.permission + )}\` (${metadata.permission}).`; } return null; diff --git a/src/core/libd.ts b/src/core/libd.ts index c9d5bcf..58338a5 100644 --- a/src/core/libd.ts +++ b/src/core/libd.ts @@ -15,7 +15,9 @@ import { MessageAdditions, SplitOptions, APIMessage, - StringResolvable + StringResolvable, + EmojiIdentifierResolvable, + MessageReaction } from "discord.js"; import {unreactEventListeners, replyEventListeners} from "./eventListeners"; import {client} from "./interface"; @@ -31,19 +33,6 @@ export type SendFunction = (( ((content: StringResolvable, options: MessageOptions & {split: true | SplitOptions}) => Promise) & ((content: StringResolvable, options: MessageOptions) => Promise); -/** - * Tests if a bot has a certain permission in a specified guild. - */ -export function botHasPermission(guild: Guild | null, permission: number): boolean { - return !!guild?.me?.hasPermission(permission); -} - -// The SoonTM Section // -// Maybe promisify this section to reduce the potential for creating callback hell? Especially if multiple questions in a row are being asked. -// It's probably a good idea to modularize the base reaction handler so there's less copy pasted code. -// Maybe also make a reaction handler that listens for when reactions are added and removed. -// The reaction handler would also run an async function to react in order (parallel to the reaction handler). - const FIVE_BACKWARDS_EMOJI = "⏪"; const BACKWARDS_EMOJI = "⬅️"; const FORWARDS_EMOJI = "➡️"; @@ -56,34 +45,35 @@ const FIVE_FORWARDS_EMOJI = "⏩"; */ export async function paginate( send: SendFunction, - senderID: string, - total: number, - callback: (page: number, hasMultiplePages: boolean) => SingleMessageOptions, + onTurnPage: (page: number, hasMultiplePages: boolean) => SingleMessageOptions, + totalPages: number, + listenTo: string | null = null, duration = 60000 -) { - const hasMultiplePages = total > 1; - const message = await send(callback(0, hasMultiplePages)); +): Promise { + const hasMultiplePages = totalPages > 1; + const message = await send(onTurnPage(0, hasMultiplePages)); if (hasMultiplePages) { let page = 0; const turn = (amount: number) => { page += amount; - if (page >= total) { - page %= total; + if (page >= totalPages) { + page %= totalPages; } else if (page < 0) { // Assuming 3 total pages, it's a bit tricker, but if we just take the modulo of the absolute value (|page| % total), we get (1 2 0 ...), and we just need the pattern (2 1 0 ...). It needs to reverse order except for when it's 0. I want to find a better solution, but for the time being... total - (|page| % total) unless (|page| % total) = 0, then return 0. - const flattened = Math.abs(page) % total; - if (flattened !== 0) page = total - flattened; + const flattened = Math.abs(page) % totalPages; + if (flattened !== 0) page = totalPages - flattened; } - message.edit(callback(page, true)); + message.edit(onTurnPage(page, true)); }; const handle = (emote: string, reacterID: string) => { - if (senderID === reacterID) { + if (reacterID === listenTo || listenTo === null) { + collector.resetTimer(); // The timer refresh MUST be present in both react and unreact. switch (emote) { case FIVE_BACKWARDS_EMOJI: - if (total > 5) turn(-5); + if (totalPages > 5) turn(-5); break; case BACKWARDS_EMOJI: turn(-1); @@ -92,28 +82,28 @@ export async function paginate( turn(1); break; case FIVE_FORWARDS_EMOJI: - if (total > 5) turn(5); + if (totalPages > 5) turn(5); break; } } }; // Listen for reactions and call the handler. - let backwardsReactionFive = total > 5 ? await message.react(FIVE_BACKWARDS_EMOJI) : null; + let backwardsReactionFive = totalPages > 5 ? await message.react(FIVE_BACKWARDS_EMOJI) : null; let backwardsReaction = await message.react(BACKWARDS_EMOJI); let forwardsReaction = await message.react(FORWARDS_EMOJI); - let forwardsReactionFive = total > 5 ? await message.react(FIVE_FORWARDS_EMOJI) : null; + let forwardsReactionFive = totalPages > 5 ? await message.react(FIVE_FORWARDS_EMOJI) : null; unreactEventListeners.set(message.id, handle); const collector = message.createReactionCollector( (reaction, user) => { - if (user.id === senderID) { + // This check is actually redundant because of handle(). + if (user.id === listenTo || listenTo === null) { // The reason this is inside the call is because it's possible to switch a user's permissions halfway and suddenly throw an error. // This will dynamically adjust for that, switching modes depending on whether it currently has the "Manage Messages" permission. const canDeleteEmotes = botHasPermission(message.guild, Permissions.FLAGS.MANAGE_MESSAGES); handle(reaction.emoji.name, user.id); if (canDeleteEmotes) reaction.users.remove(user); - collector.resetTimer(); } return false; @@ -134,100 +124,73 @@ export async function paginate( } } -// Waits for the sender to either confirm an action or let it pass (and delete the message). -// This should probably be renamed to "confirm" now that I think of it, "prompt" is better used elsewhere. -// Append "\n*(This message will automatically be deleted after 10 seconds.)*" in the future? -/** - * Prompts the user about a decision before following through. - */ -export async function prompt(message: Message, senderID: string, onConfirm: () => void, duration = 10000) { - let isDeleted = false; +//export function generateMulti +// paginate after generateonetimeprompt - message.react("✅"); - await message.awaitReactions( - (reaction, user) => { - if (user.id === senderID) { - if (reaction.emoji.name === "✅") { - onConfirm(); - isDeleted = true; - message.delete(); - } - } - - // CollectorFilter requires a boolean to be returned. - // My guess is that the return value of awaitReactions can be altered by making a boolean filter. - // However, because that's not my concern with this command, I don't have to worry about it. - // May as well just set it to false because I'm not concerned with collecting any reactions. - return false; - }, - {time: duration} - ); - - if (!isDeleted) message.delete(); -} - -// Asks the user for some input using the inline reply feature. The message here is a message you send beforehand. -// If the reply is rejected, reply with an error message (when stable support comes from discord.js). -// Append "\n*(Note: Make sure to use Discord's inline reply feature or this won't work!)*" in the future? And also the "you can now reply to this message" edit. -export function ask( +// Returns null if timed out, otherwise, returns the value. +export function generateOneTimePrompt( message: Message, - senderID: string, - condition: (reply: string) => boolean, - onSuccess: () => void, - onReject: () => string, - timeout = 60000 -) { - const referenceID = `${message.channel.id}-${message.id}`; + stack: {[emote: string]: T}, + listenTo: string | null = null, + duration = 60000 +): Promise { + return new Promise(async (resolve) => { + // First, start reacting to the message in order. + reactInOrder(message, Object.keys(stack)); - replyEventListeners.set(referenceID, (reply) => { - if (reply.author.id === senderID) { - if (condition(reply.content)) { - onSuccess(); - replyEventListeners.delete(referenceID); - } else { - reply.reply(onReject()); - } - } - }); - - setTimeout(() => { - replyEventListeners.set(referenceID, (reply) => { - reply.reply("that action timed out, try using the command again"); - replyEventListeners.delete(referenceID); - }); - }, timeout); -} - -export function askYesOrNo(message: Message, senderID: string, timeout = 30000): Promise { - return new Promise(async (resolve, reject) => { - let isDeleted = false; - - await message.react("✅"); - message.react("❌"); + // Then setup the reaction listener in parallel. await message.awaitReactions( - (reaction, user) => { - if (user.id === senderID) { - const isCheckReacted = reaction.emoji.name === "✅"; + (reaction: MessageReaction, user: User) => { + if (user.id === listenTo || listenTo === null) { + const emote = reaction.emoji.name; - if (isCheckReacted || reaction.emoji.name === "❌") { - resolve(isCheckReacted); - isDeleted = true; + if (emote in stack) { + resolve(stack[emote]); message.delete(); } } + // CollectorFilter requires a boolean to be returned. + // My guess is that the return value of awaitReactions can be altered by making a boolean filter. + // However, because that's not my concern with this command, I don't have to worry about it. + // May as well just set it to false because I'm not concerned with collecting any reactions. return false; }, - {time: timeout} + {time: duration} ); - if (!isDeleted) { + if (!message.deleted) { message.delete(); - reject("Prompt timed out."); + resolve(null); } }); } +// Start a parallel chain of ordered reactions, allowing a collector to end early. +// Check if the collector ended early by seeing if the message is already deleted. +// Though apparently, message.deleted doesn't seem to update fast enough, so just put a try catch block on message.react(). +async function reactInOrder(message: Message, emotes: EmojiIdentifierResolvable[]): Promise { + for (const emote of emotes) { + try { + await message.react(emote); + } catch { + return; + } + } +} + +export function confirm(message: Message, senderID: string, timeout = 30000): Promise { + return generateOneTimePrompt( + message, + { + "✅": true, + "❌": false + }, + senderID, + timeout + ); +} + // This MUST be split into an array. These emojis are made up of several characters each, adding up to 29 in length. const multiNumbers = ["1️⃣", "2️⃣", "3️⃣", "4️⃣", "5️⃣", "6️⃣", "7️⃣", "8️⃣", "9️⃣", "🔟"]; @@ -236,40 +199,47 @@ const multiNumbers = ["1️⃣", "2️⃣", "3️⃣", "4️⃣", "5️⃣", "6 export async function askMultipleChoice( message: Message, senderID: string, - callbackStack: (() => void)[], + choices: number, timeout = 90000 -) { - if (callbackStack.length > multiNumbers.length) { - message.channel.send( - `\`ERROR: The amount of callbacks in "askMultipleChoice" must not exceed the total amount of allowed options (${multiNumbers.length})!\`` +): Promise { + if (choices > multiNumbers.length) + throw new Error( + `askMultipleChoice only accepts up to ${multiNumbers.length} options, ${choices} was provided.` ); - return; - } + const numbers: {[emote: string]: number} = {}; + for (let i = 0; i < choices; i++) numbers[multiNumbers[i]] = i; + return generateOneTimePrompt(message, numbers, senderID, timeout); +} - let isDeleted = false; +// Asks the user for some input using the inline reply feature. The message here is a message you send beforehand. +// If the reply is rejected, reply with an error message (when stable support comes from discord.js). +export function askForReply(message: Message, listenTo: string, timeout?: number): Promise { + return new Promise((resolve) => { + const referenceID = `${message.channel.id}-${message.id}`; - for (let i = 0; i < callbackStack.length; i++) { - await message.react(multiNumbers[i]); - } - - await message.awaitReactions( - (reaction, user) => { - if (user.id === senderID) { - const index = multiNumbers.indexOf(reaction.emoji.name); - - if (index !== -1) { - callbackStack[index](); - isDeleted = true; - message.delete(); - } + replyEventListeners.set(referenceID, (reply) => { + if (reply.author.id === listenTo) { + message.delete(); + replyEventListeners.delete(referenceID); + resolve(reply); } + }); - return false; - }, - {time: timeout} - ); + if (timeout) { + client.setTimeout(() => { + if (!message.deleted) message.delete(); + replyEventListeners.delete(referenceID); + resolve(null); + }, timeout); + } + }); +} - if (!isDeleted) message.delete(); +/** + * Tests if a bot has a certain permission in a specified guild. + */ +export function botHasPermission(guild: Guild | null, permission: number): boolean { + return !!guild?.me?.hasPermission(permission); } // For "get x by y" methods: @@ -277,79 +247,75 @@ export async function askMultipleChoice( // It's more reliable to get users/members by fetching their IDs. fetch() will searching through the cache anyway. // For guilds, do an extra check to make sure there isn't an outage (guild.available). -export function getGuildByID(id: string): Guild | SingleMessageOptions { +export function getGuildByID(id: string): Guild | string { const guild = client.guilds.cache.get(id); if (guild) { if (guild.available) return guild; - else return {content: `The guild \`${guild.name}\` (ID: \`${id}\`) is unavailable due to an outage.`}; + else return `The guild \`${guild.name}\` (ID: \`${id}\`) is unavailable due to an outage.`; } else { - return { - content: `No guild found by the ID of \`${id}\`!` - }; + return `No guild found by the ID of \`${id}\`!`; } } -export function getGuildByName(name: string): Guild | SingleMessageOptions { +export function getGuildByName(name: string): Guild | string { const query = name.toLowerCase(); const guild = client.guilds.cache.find((guild) => guild.name.toLowerCase().includes(query)); if (guild) { if (guild.available) return guild; - else return {content: `The guild \`${guild.name}\` (ID: \`${guild.id}\`) is unavailable due to an outage.`}; + else return `The guild \`${guild.name}\` (ID: \`${guild.id}\`) is unavailable due to an outage.`; } else { - return { - content: `No guild found by the name of \`${name}\`!` - }; + return `No guild found by the name of \`${name}\`!`; } } -export async function getChannelByID(id: string): Promise { +export async function getChannelByID(id: string): Promise { try { return await client.channels.fetch(id); } catch { - return {content: `No channel found by the ID of \`${id}\`!`}; + return `No channel found by the ID of \`${id}\`!`; } } // Only go through the cached channels (non-DM channels). Plus, searching DM channels by name wouldn't really make sense, nor do they have names to search anyway. -export function getChannelByName(name: string): GuildChannel | SingleMessageOptions { +export function getChannelByName(name: string): GuildChannel | string { const query = name.toLowerCase(); const channel = client.channels.cache.find( (channel) => channel instanceof GuildChannel && channel.name.toLowerCase().includes(query) ) as GuildChannel | undefined; if (channel) return channel; - else return {content: `No channel found by the name of \`${name}\`!`}; + else return `No channel found by the name of \`${name}\`!`; } export async function getMessageByID( channel: TextChannel | DMChannel | NewsChannel | string, id: string -): Promise { +): Promise { if (typeof channel === "string") { const targetChannel = await getChannelByID(channel); if (targetChannel instanceof TextChannel || targetChannel instanceof DMChannel) channel = targetChannel; - else if (targetChannel instanceof Channel) return {content: `\`${id}\` isn't a valid text-based channel!`}; + else if (targetChannel instanceof Channel) return `\`${id}\` isn't a valid text-based channel!`; else return targetChannel; } try { return await channel.messages.fetch(id); } catch { - return {content: `\`${id}\` isn't a valid message of the channel ${channel}!`}; + return `\`${id}\` isn't a valid message of the channel ${channel}!`; } } -export async function getUserByID(id: string): Promise { +export async function getUserByID(id: string): Promise { try { return await client.users.fetch(id); } catch { - return {content: `No user found by the ID of \`${id}\`!`}; + return `No user found by the ID of \`${id}\`!`; } } // Also check tags (if provided) to narrow down users. -export function getUserByName(name: string): User | SingleMessageOptions { +export function getUserByName(name: string): User | string { let query = name.toLowerCase(); const tagMatch = /^(.+?)#(\d{4})$/.exec(name); let tag: string | null = null; @@ -366,19 +332,19 @@ export function getUserByName(name: string): User | SingleMessageOptions { }); if (user) return user; - else return {content: `No user found by the name of \`${name}\`!`}; + else return `No user found by the name of \`${name}\`!`; } -export async function getMemberByID(guild: Guild, id: string): Promise { +export async function getMemberByID(guild: Guild, id: string): Promise { try { return await guild.members.fetch(id); } catch { - return {content: `No member found by the ID of \`${id}\`!`}; + return `No member found by the ID of \`${id}\`!`; } } // First checks if a member can be found by that nickname, then check if a member can be found by that username. -export async function getMemberByName(guild: Guild, name: string): Promise { +export async function getMemberByName(guild: Guild, name: string): Promise { const member = ( await guild.members.fetch({ query: name, @@ -395,9 +361,9 @@ export async function getMemberByName(guild: Guild, name: string): Promise { const linkMessage = await getMessageByID(channelID, messageID); // If it's an invalid link (or the bot doesn't have access to it). - if (!(linkMessage instanceof Message)) { + if (typeof linkMessage === "string") { return message.channel.send("I don't have access to that channel!"); } From 51d19d5787ea79e34d01da7c0224ceb28f65af2b Mon Sep 17 00:00:00 2001 From: Keanu Date: Sun, 11 Apr 2021 10:58:06 +0200 Subject: [PATCH 164/178] Added Discord.JS Docs command. --- src/commands/utility/docs.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 src/commands/utility/docs.ts diff --git a/src/commands/utility/docs.ts b/src/commands/utility/docs.ts new file mode 100644 index 0000000..8c1d74b --- /dev/null +++ b/src/commands/utility/docs.ts @@ -0,0 +1,17 @@ +import {NamedCommand, RestCommand} from "../../core"; +import {URL} from "url"; +import {getContent} from "../../lib"; + +export default new NamedCommand({ + description: "Provides you with info from the Discord.JS docs.", + run: "You need to specify a term to query the docs with.", + any: new RestCommand({ + description: "What to query the docs with.", + async run({send, args}) { + var queryString = args[0]; + let url = new URL(`https://djsdocs.sorta.moe/v2/embed?src=master&q=${queryString}`); + const content = await getContent(url.toString()); + return send({embed: content}); + } + }) +}); From 0a265dcd5c2f0784f421cbddba0f60ea612caf1e Mon Sep 17 00:00:00 2001 From: Keanu Date: Sun, 11 Apr 2021 11:11:21 +0200 Subject: [PATCH 165/178] Removed unused run args and Command imports. --- src/commands/fun/8ball.ts | 2 +- src/commands/fun/cookie.ts | 4 ++-- src/commands/fun/eco.ts | 2 +- src/commands/fun/figlet.ts | 4 ++-- src/commands/fun/insult.ts | 4 ++-- src/commands/fun/love.ts | 4 ++-- src/commands/fun/neko.ts | 4 ++-- src/commands/fun/ok.ts | 4 ++-- src/commands/fun/owoify.ts | 4 ++-- src/commands/fun/party.ts | 4 ++-- src/commands/fun/poll.ts | 4 ++-- src/commands/fun/ravi.ts | 4 ++-- src/commands/fun/thonk.ts | 6 +++--- src/commands/fun/urban.ts | 4 ++-- src/commands/fun/vaporwave.ts | 4 ++-- src/commands/fun/weather.ts | 4 ++-- src/commands/fun/whois.ts | 6 +++--- src/commands/utility/calc.ts | 4 ++-- src/commands/utility/code.ts | 2 +- src/commands/utility/desc.ts | 4 ++-- src/commands/utility/emote.ts | 4 ++-- src/commands/utility/info.ts | 18 +++++++++--------- src/commands/utility/invite.ts | 4 ++-- src/commands/utility/lsemotes.ts | 6 +++--- src/commands/utility/react.ts | 4 ++-- src/commands/utility/say.ts | 4 ++-- src/commands/utility/scanemotes.ts | 6 +++--- src/commands/utility/shorten.ts | 2 +- src/commands/utility/streaminfo.ts | 6 +++--- src/commands/utility/todo.ts | 10 +++++----- src/commands/utility/translate.ts | 2 +- 31 files changed, 72 insertions(+), 72 deletions(-) diff --git a/src/commands/fun/8ball.ts b/src/commands/fun/8ball.ts index 898eafe..f79a2c0 100644 --- a/src/commands/fun/8ball.ts +++ b/src/commands/fun/8ball.ts @@ -30,7 +30,7 @@ export default new NamedCommand({ run: "Please provide a question.", any: new Command({ description: "Question to ask the 8-ball.", - async run({send, message, channel, guild, author, member, client, args}) { + async run({send, message}) { const sender = message.author; send(`${random(responses)} <@${sender.id}>`); } diff --git a/src/commands/fun/cookie.ts b/src/commands/fun/cookie.ts index 2275c08..71d9c20 100644 --- a/src/commands/fun/cookie.ts +++ b/src/commands/fun/cookie.ts @@ -31,7 +31,7 @@ export default new NamedCommand({ run: ":cookie: Here's a cookie!", subcommands: { all: new NamedCommand({ - async run({send, message, channel, guild, author, member, client, args}) { + async run({send, author}) { send(`${author} gave everybody a cookie!`); } }) @@ -39,7 +39,7 @@ export default new NamedCommand({ id: "user", user: new Command({ description: "User to give cookie to.", - async run({send, message, channel, guild, author, member, client, args}) { + async run({send, author, args}) { const sender = author; const mention: User = args[0]; diff --git a/src/commands/fun/eco.ts b/src/commands/fun/eco.ts index 241411d..4644101 100644 --- a/src/commands/fun/eco.ts +++ b/src/commands/fun/eco.ts @@ -34,7 +34,7 @@ export default new NamedCommand({ }), any: new RestCommand({ description: "See how much money someone else has by using their username.", - async run({send, guild, channel, args, message, combined}) { + async run({send, guild, channel, combined}) { if (isAuthorized(guild, channel)) { const member = await getMemberByName(guild!, combined); if (typeof member !== "string") send(getMoneyEmbed(member.user)); diff --git a/src/commands/fun/figlet.ts b/src/commands/fun/figlet.ts index a25ed2a..b2f9bb0 100644 --- a/src/commands/fun/figlet.ts +++ b/src/commands/fun/figlet.ts @@ -1,11 +1,11 @@ -import {Command, NamedCommand, RestCommand} from "../../core"; +import {NamedCommand, RestCommand} from "../../core"; import figlet from "figlet"; export default new NamedCommand({ description: "Generates a figlet of your input.", run: "You have to provide input for me to create a figlet!", any: new RestCommand({ - async run({send, message, channel, guild, author, member, client, args, combined}) { + async run({send, combined}) { return send( figlet.textSync(combined, { horizontalLayout: "full" diff --git a/src/commands/fun/insult.ts b/src/commands/fun/insult.ts index 3cd982d..251444a 100644 --- a/src/commands/fun/insult.ts +++ b/src/commands/fun/insult.ts @@ -1,8 +1,8 @@ -import {Command, NamedCommand} from "../../core"; +import {NamedCommand} from "../../core"; export default new NamedCommand({ description: "Insult TravBot! >:D", - async run({send, message, channel, guild, author, member, client, args}) { + async run({send, channel, author}) { channel.startTyping(); setTimeout(() => { send( diff --git a/src/commands/fun/love.ts b/src/commands/fun/love.ts index e7d2557..25a7c8a 100644 --- a/src/commands/fun/love.ts +++ b/src/commands/fun/love.ts @@ -1,9 +1,9 @@ -import {Command, NamedCommand, CHANNEL_TYPE} from "../../core"; +import {NamedCommand, CHANNEL_TYPE} from "../../core"; export default new NamedCommand({ description: "Chooses someone to love.", channelType: CHANNEL_TYPE.GUILD, - async run({send, message, channel, guild, author, client, args}) { + async run({send, guild}) { const member = guild!.members.cache.random(); send(`I love ${member.nickname ?? member.user.username}!`); } diff --git a/src/commands/fun/neko.ts b/src/commands/fun/neko.ts index 1722232..f430406 100644 --- a/src/commands/fun/neko.ts +++ b/src/commands/fun/neko.ts @@ -36,12 +36,12 @@ const endpoints: {sfw: {[key: string]: string}} = { export default new NamedCommand({ description: "Provides you with a random image with the selected argument.", - async run({send, message, channel, guild, author, member, client, args}) { + async run({send}) { send(`Please provide an image type. Available arguments:\n\`[${Object.keys(endpoints.sfw).join(", ")}]\`.`); }, any: new Command({ description: "Image type to send.", - async run({send, message, channel, guild, author, member, client, args}) { + async run({send, args}) { const arg = args[0]; if (!(arg in endpoints.sfw)) return send("Couldn't find that endpoint!"); let url = new URL(`https://nekos.life/api/v2${endpoints.sfw[arg]}`); diff --git a/src/commands/fun/ok.ts b/src/commands/fun/ok.ts index 66bfa6b..13296a3 100644 --- a/src/commands/fun/ok.ts +++ b/src/commands/fun/ok.ts @@ -1,4 +1,4 @@ -import {Command, NamedCommand} from "../../core"; +import {NamedCommand} from "../../core"; import {random} from "../../lib"; const responses = [ @@ -61,7 +61,7 @@ const responses = [ export default new NamedCommand({ description: "Sends random ok message.", - async run({send, message, channel, guild, author, member, client, args}) { + async run({send}) { send(`ok ${random(responses)}`); } }); diff --git a/src/commands/fun/owoify.ts b/src/commands/fun/owoify.ts index cbc4ccf..3cc2dd5 100644 --- a/src/commands/fun/owoify.ts +++ b/src/commands/fun/owoify.ts @@ -1,4 +1,4 @@ -import {Command, NamedCommand, RestCommand} from "../../core"; +import {NamedCommand, RestCommand} from "../../core"; import {getContent} from "../../lib"; import {URL} from "url"; @@ -6,7 +6,7 @@ export default new NamedCommand({ description: "OwO-ifies the input.", run: "You need to specify some text to owoify.", any: new RestCommand({ - async run({send, message, channel, guild, author, member, client, args, combined}) { + async run({send, combined}) { let url = new URL(`https://nekos.life/api/v2/owoify?text=${combined}`); const content = (await getContent(url.toString())) as any; // Apparently, the object in question is {owo: string}. send(content.owo); diff --git a/src/commands/fun/party.ts b/src/commands/fun/party.ts index db4c0e3..ce58b13 100644 --- a/src/commands/fun/party.ts +++ b/src/commands/fun/party.ts @@ -1,8 +1,8 @@ -import {Command, NamedCommand} from "../../core"; +import {NamedCommand} from "../../core"; export default new NamedCommand({ description: "Initiates a celebratory stream from the bot.", - async run({send, message, channel, guild, author, member, client, args}) { + async run({send, client}) { send("This calls for a celebration!"); client.user!.setActivity({ type: "STREAMING", diff --git a/src/commands/fun/poll.ts b/src/commands/fun/poll.ts index 5795db1..3909968 100644 --- a/src/commands/fun/poll.ts +++ b/src/commands/fun/poll.ts @@ -1,5 +1,5 @@ import {MessageEmbed} from "discord.js"; -import {Command, NamedCommand, RestCommand} from "../../core"; +import {NamedCommand, RestCommand} from "../../core"; export default new NamedCommand({ description: "Create a poll.", @@ -7,7 +7,7 @@ export default new NamedCommand({ run: "Please provide a question.", any: new RestCommand({ description: "Question for the poll.", - async run({send, message, channel, guild, author, member, client, args, combined}) { + async run({send, message, combined}) { const embed = new MessageEmbed() .setAuthor( `Poll created by ${message.author.username}`, diff --git a/src/commands/fun/ravi.ts b/src/commands/fun/ravi.ts index e0862e0..ff64376 100644 --- a/src/commands/fun/ravi.ts +++ b/src/commands/fun/ravi.ts @@ -4,7 +4,7 @@ import {Random} from "../../lib"; export default new NamedCommand({ description: "Ravioli ravioli...", usage: "[number from 1 to 9]", - async run({send, message, channel, guild, author, member, client, args}) { + async run({send}) { send({ embed: { title: "Ravioli ravioli...", @@ -18,7 +18,7 @@ export default new NamedCommand({ }); }, number: new Command({ - async run({send, message, channel, guild, author, member, client, args}) { + async run({send, args}) { const arg: number = args[0]; if (arg >= 1 && arg <= 9) { diff --git a/src/commands/fun/thonk.ts b/src/commands/fun/thonk.ts index de09d20..821216f 100644 --- a/src/commands/fun/thonk.ts +++ b/src/commands/fun/thonk.ts @@ -1,4 +1,4 @@ -import {Command, NamedCommand, RestCommand} from "../../core"; +import {NamedCommand, RestCommand} from "../../core"; const letters: {[letter: string]: string[]} = { a: "aáàảãạâấầẩẫậăắằẳẵặ".split(""), @@ -34,7 +34,7 @@ let phrase = "I have no currently set phrase!"; export default new NamedCommand({ description: "Transforms your text into vietnamese.", usage: "thonk ([text])", - async run({send, message, channel, guild, author, member, client, args}) { + async run({send, author}) { const msg = await send(transform(phrase)); msg.createReactionCollector( (reaction, user) => { @@ -45,7 +45,7 @@ export default new NamedCommand({ ); }, any: new RestCommand({ - async run({send, message, channel, guild, author, member, client, args, combined}) { + async run({send, author, combined}) { const msg = await send(transform(combined)); msg.createReactionCollector( (reaction, user) => { diff --git a/src/commands/fun/urban.ts b/src/commands/fun/urban.ts index 928ca57..9964008 100644 --- a/src/commands/fun/urban.ts +++ b/src/commands/fun/urban.ts @@ -1,4 +1,4 @@ -import {Command, NamedCommand, RestCommand} from "../../core"; +import {NamedCommand, RestCommand} from "../../core"; import {MessageEmbed} from "discord.js"; import urban from "relevant-urban"; @@ -6,7 +6,7 @@ export default new NamedCommand({ description: "Gives you a definition of the inputted word.", run: "Please input a word.", any: new RestCommand({ - async run({send, message, channel, guild, author, member, client, args, combined}) { + async run({send, combined}) { // [Bug Fix]: Use encodeURIComponent() when emojis are used: "TypeError [ERR_UNESCAPED_CHARACTERS]: Request path contains unescaped characters" urban(encodeURIComponent(combined)) .then((res) => { diff --git a/src/commands/fun/vaporwave.ts b/src/commands/fun/vaporwave.ts index 71ae065..6486c25 100644 --- a/src/commands/fun/vaporwave.ts +++ b/src/commands/fun/vaporwave.ts @@ -1,4 +1,4 @@ -import {Command, NamedCommand, RestCommand} from "../../core"; +import {NamedCommand, RestCommand} from "../../core"; const vaporwave = (() => { const map = new Map(); @@ -25,7 +25,7 @@ export default new NamedCommand({ description: "Transforms your text into vaporwave.", run: "You need to enter some text!", any: new RestCommand({ - async run({send, message, channel, guild, author, member, client, args, combined}) { + async run({send, combined}) { const text = getVaporwaveText(combined); if (text !== "") send(text); else send("Make sure to enter at least one valid character."); diff --git a/src/commands/fun/weather.ts b/src/commands/fun/weather.ts index 44c82b7..b30daa1 100644 --- a/src/commands/fun/weather.ts +++ b/src/commands/fun/weather.ts @@ -1,4 +1,4 @@ -import {Command, NamedCommand, RestCommand} from "../../core"; +import {NamedCommand, RestCommand} from "../../core"; import {MessageEmbed} from "discord.js"; import {find} from "weather-js"; @@ -6,7 +6,7 @@ export default new NamedCommand({ description: "Shows weather info of specified location.", run: "You need to provide a city.", any: new RestCommand({ - async run({send, message, channel, guild, author, member, client, args, combined}) { + async run({send, combined}) { find( { search: combined, diff --git a/src/commands/fun/whois.ts b/src/commands/fun/whois.ts index 507c14f..92d29d3 100644 --- a/src/commands/fun/whois.ts +++ b/src/commands/fun/whois.ts @@ -43,7 +43,7 @@ const registry: {[id: string]: string} = { export default new NamedCommand({ description: "Tells you who you or the specified user is.", aliases: ["whoami"], - async run({send, message, channel, guild, author, member, client, args}) { + async run({send, author}) { const id = author.id; if (id in registry) { @@ -54,7 +54,7 @@ export default new NamedCommand({ }, id: "user", user: new Command({ - async run({send, message, channel, guild, author, member, client, args}) { + async run({send, args}) { const user: User = args[0]; const id = user.id; @@ -67,7 +67,7 @@ export default new NamedCommand({ }), any: new RestCommand({ channelType: CHANNEL_TYPE.GUILD, - async run({send, message, channel, guild, author, client, args, combined}) { + async run({send, guild, combined}) { const member = await getMemberByName(guild!, combined); if (typeof member !== "string") { diff --git a/src/commands/utility/calc.ts b/src/commands/utility/calc.ts index ac0727d..649ba1f 100644 --- a/src/commands/utility/calc.ts +++ b/src/commands/utility/calc.ts @@ -1,4 +1,4 @@ -import {Command, NamedCommand, RestCommand} from "../../core"; +import {NamedCommand, RestCommand} from "../../core"; import * as math from "mathjs"; import {MessageEmbed} from "discord.js"; @@ -6,7 +6,7 @@ export default new NamedCommand({ description: "Calculates a specified math expression.", run: "Please provide a calculation.", any: new RestCommand({ - async run({send, message, channel, guild, author, member, client, args, combined}) { + async run({send, combined}) { let resp; try { resp = math.evaluate(combined); diff --git a/src/commands/utility/code.ts b/src/commands/utility/code.ts index 39e41b7..5c73897 100644 --- a/src/commands/utility/code.ts +++ b/src/commands/utility/code.ts @@ -1,4 +1,4 @@ -import {Command, NamedCommand} from "../../core"; +import {NamedCommand} from "../../core"; export default new NamedCommand({ description: "Gives you the Github link.", diff --git a/src/commands/utility/desc.ts b/src/commands/utility/desc.ts index 9930df3..4307469 100644 --- a/src/commands/utility/desc.ts +++ b/src/commands/utility/desc.ts @@ -1,11 +1,11 @@ -import {Command, NamedCommand, RestCommand} from "../../core"; +import {NamedCommand, RestCommand} from "../../core"; export default new NamedCommand({ description: "Renames current voice channel.", usage: "", run: "Please provide a new voice channel name.", any: new RestCommand({ - async run({send, message, channel, guild, author, member, client, args, combined}) { + async run({send, message, combined}) { const voiceChannel = message.member?.voice.channel; if (!voiceChannel) return send("You are not in a voice channel."); diff --git a/src/commands/utility/emote.ts b/src/commands/utility/emote.ts index 4d79446..0cb48a2 100644 --- a/src/commands/utility/emote.ts +++ b/src/commands/utility/emote.ts @@ -1,4 +1,4 @@ -import {Command, NamedCommand, RestCommand} from "../../core"; +import {NamedCommand, RestCommand} from "../../core"; import {processEmoteQueryFormatted} from "./modules/emote-utils"; export default new NamedCommand({ @@ -8,7 +8,7 @@ export default new NamedCommand({ any: new RestCommand({ description: "The emote(s) to send.", usage: "", - async run({send, guild, channel, message, args}) { + async run({send, args}) { const output = processEmoteQueryFormatted(args); if (output.length > 0) send(output); } diff --git a/src/commands/utility/info.ts b/src/commands/utility/info.ts index c9368e6..b490bcd 100644 --- a/src/commands/utility/info.ts +++ b/src/commands/utility/info.ts @@ -8,20 +8,20 @@ import moment, {utc} from "moment"; export default new NamedCommand({ description: "Command to provide all sorts of info about the current server, a user, etc.", - async run({send, message, channel, guild, author, member, client, args}) { + async run({send, author, member}) { send(await getUserInfo(author, member)); }, subcommands: { avatar: new NamedCommand({ description: "Shows your own, or another user's avatar.", usage: "()", - async run({send, message, channel, guild, author, member, client, args}) { + async run({send, author}) { send(author.displayAvatarURL({dynamic: true, size: 2048})); }, id: "user", user: new Command({ description: "Shows your own, or another user's avatar.", - async run({send, message, channel, guild, author, member, client, args}) { + async run({send, args}) { send( args[0].displayAvatarURL({ dynamic: true, @@ -33,7 +33,7 @@ export default new NamedCommand({ any: new RestCommand({ description: "Shows another user's avatar by searching their name", channelType: CHANNEL_TYPE.GUILD, - async run({send, message, channel, guild, author, client, args, combined}) { + async run({send, guild, combined}) { const member = await getMemberByName(guild!, combined); if (typeof member !== "string") { @@ -51,7 +51,7 @@ export default new NamedCommand({ }), bot: new NamedCommand({ description: "Displays info about the bot.", - async run({send, message, channel, guild, author, member, client, args}) { + async run({send, guild, client}) { const core = os.cpus()[0]; const embed = new MessageEmbed() .setColor(guild?.me?.displayHexColor || "BLUE") @@ -94,20 +94,20 @@ export default new NamedCommand({ description: "Displays info about the current guild or another guild.", usage: "(/)", channelType: CHANNEL_TYPE.GUILD, - async run({send, message, channel, guild, author, member, client, args}) { + async run({send, guild}) { send(await getGuildInfo(guild!, guild)); }, id: "guild", guild: new Command({ description: "Display info about a guild by its ID.", - async run({send, message, channel, guild, author, member, client, args}) { + async run({send, guild, args}) { const targetGuild = args[0] as Guild; send(await getGuildInfo(targetGuild, guild)); } }), any: new RestCommand({ description: "Display info about a guild by finding its name.", - async run({send, message, channel, guild, author, member, client, args, combined}) { + async run({send, guild, combined}) { const targetGuild = getGuildByName(combined); if (typeof targetGuild !== "string") { @@ -122,7 +122,7 @@ export default new NamedCommand({ id: "user", user: new Command({ description: "Displays info about mentioned user.", - async run({send, message, channel, guild, author, client, args}) { + async run({send, guild, args}) { const user = args[0] as User; // Transforms the User object into a GuildMember object of the current guild. const member = guild?.members.resolve(args[0]); diff --git a/src/commands/utility/invite.ts b/src/commands/utility/invite.ts index 9dc0fa3..58b38d8 100644 --- a/src/commands/utility/invite.ts +++ b/src/commands/utility/invite.ts @@ -1,8 +1,8 @@ -import {Command, NamedCommand} from "../../core"; +import {NamedCommand} from "../../core"; export default new NamedCommand({ description: "Gives you the invite link.", - async run({send, message, channel, guild, author, member, client, args}) { + async run({send, client, args}) { send( `https://discordapp.com/api/oauth2/authorize?client_id=${client.user!.id}&permissions=${ args[0] || 8 diff --git a/src/commands/utility/lsemotes.ts b/src/commands/utility/lsemotes.ts index 7e1e2d9..955328a 100644 --- a/src/commands/utility/lsemotes.ts +++ b/src/commands/utility/lsemotes.ts @@ -1,5 +1,5 @@ import {GuildEmoji, MessageEmbed, User} from "discord.js"; -import {Command, NamedCommand, RestCommand, paginate, SendFunction} from "../../core"; +import {NamedCommand, RestCommand, paginate, SendFunction} from "../../core"; import {split} from "../../lib"; import vm from "vm"; @@ -8,13 +8,13 @@ const REGEX_TIMEOUT_MS = 1000; export default new NamedCommand({ description: "Lists all emotes the bot has in it's registry,", usage: " (-flags)", - async run({send, message, channel, guild, author, member, client, args}) { + async run({send, author, client}) { displayEmoteList(client.emojis.cache.array(), send, author); }, any: new RestCommand({ description: "Filters emotes by via a regular expression. Flags can be added by adding a dash at the end. For example, to do a case-insensitive search, do %prefix%lsemotes somepattern -i", - async run({send, message, channel, guild, author, member, client, args}) { + async run({send, author, client, args}) { // If a guild ID is provided, filter all emotes by that guild (but only if there aren't any arguments afterward) if (args.length === 1 && /^\d{17,}$/.test(args[0])) { const guildID: string = args[0]; diff --git a/src/commands/utility/react.ts b/src/commands/utility/react.ts index ef1aebb..39301c9 100644 --- a/src/commands/utility/react.ts +++ b/src/commands/utility/react.ts @@ -1,4 +1,4 @@ -import {Command, NamedCommand, RestCommand} from "../../core"; +import {NamedCommand, RestCommand} from "../../core"; import {Message, Channel, TextChannel} from "discord.js"; import {processEmoteQueryArray} from "./modules/emote-utils"; @@ -8,7 +8,7 @@ export default new NamedCommand({ usage: 'react ()', run: "You need to enter some emotes first.", any: new RestCommand({ - async run({send, message, channel, guild, author, member, client, args}) { + async run({send, message, channel, guild, client, args}) { let target: Message | undefined; let distance = 1; diff --git a/src/commands/utility/say.ts b/src/commands/utility/say.ts index 0ffe639..5d8805a 100644 --- a/src/commands/utility/say.ts +++ b/src/commands/utility/say.ts @@ -1,4 +1,4 @@ -import {Command, NamedCommand, RestCommand} from "../../core"; +import {NamedCommand, RestCommand} from "../../core"; export default new NamedCommand({ description: "Repeats your message.", @@ -6,7 +6,7 @@ export default new NamedCommand({ run: "Please provide a message for me to say!", any: new RestCommand({ description: "Message to repeat.", - async run({send, message, channel, guild, author, member, client, args, combined}) { + async run({send, author, combined}) { send(`*${author} says:*\n${combined}`); } }) diff --git a/src/commands/utility/scanemotes.ts b/src/commands/utility/scanemotes.ts index 08fb74f..e9fbcd0 100644 --- a/src/commands/utility/scanemotes.ts +++ b/src/commands/utility/scanemotes.ts @@ -1,4 +1,4 @@ -import {Command, NamedCommand, CHANNEL_TYPE} from "../../core"; +import {NamedCommand, CHANNEL_TYPE} from "../../core"; import {pluralise} from "../../lib"; import moment from "moment"; import {Collection, TextChannel} from "discord.js"; @@ -9,7 +9,7 @@ export default new NamedCommand({ description: "Scans all text channels in the current guild and returns the number of times each emoji specific to the guild has been used. Has a cooldown of 24 hours per guild.", channelType: CHANNEL_TYPE.GUILD, - async run({send, message, channel, guild, author, member, client, args}) { + async run({send, message, channel, guild}) { // Test if the command is on cooldown. This isn't the strictest cooldown possible, because in the event that the bot crashes, the cooldown will be reset. But for all intends and purposes, it's a good enough cooldown. It's a per-server cooldown. const startTime = Date.now(); const cooldown = 86400000; // 24 hours @@ -185,7 +185,7 @@ export default new NamedCommand({ forcereset: new NamedCommand({ description: "Forces the cooldown timer to reset.", permission: PERMISSIONS.BOT_SUPPORT, - async run({send, message, channel, guild, author, member, client, args}) { + async run({send, guild}) { lastUsedTimestamps.set(guild!.id, 0); send("Reset the cooldown on `scanemotes`."); } diff --git a/src/commands/utility/shorten.ts b/src/commands/utility/shorten.ts index 9d74544..c8a4618 100644 --- a/src/commands/utility/shorten.ts +++ b/src/commands/utility/shorten.ts @@ -5,7 +5,7 @@ export default new NamedCommand({ description: "Shortens a given URL.", run: "Please provide a URL.", any: new Command({ - async run({send, message, channel, guild, author, member, client, args}) { + async run({send, args}) { https.get("https://is.gd/create.php?format=simple&url=" + encodeURIComponent(args[0]), function (res) { var body = ""; res.on("data", function (chunk) { diff --git a/src/commands/utility/streaminfo.ts b/src/commands/utility/streaminfo.ts index c122b8b..b58b1f8 100644 --- a/src/commands/utility/streaminfo.ts +++ b/src/commands/utility/streaminfo.ts @@ -1,9 +1,9 @@ -import {Command, NamedCommand, RestCommand} from "../../core"; +import {NamedCommand, RestCommand} from "../../core"; import {streamList} from "../../modules/streamNotifications"; export default new NamedCommand({ description: "Sets the description of your stream. You can embed links by writing `[some name](some link)`", - async run({send, message, channel, guild, author, member, client, args}) { + async run({send, author, member}) { const userID = author.id; if (streamList.has(userID)) { @@ -22,7 +22,7 @@ export default new NamedCommand({ } }, any: new RestCommand({ - async run({send, message, channel, guild, author, member, client, args, combined}) { + async run({send, author, member, combined}) { const userID = author.id; if (streamList.has(userID)) { diff --git a/src/commands/utility/todo.ts b/src/commands/utility/todo.ts index 9057166..3962126 100644 --- a/src/commands/utility/todo.ts +++ b/src/commands/utility/todo.ts @@ -1,11 +1,11 @@ -import {Command, NamedCommand, RestCommand} from "../../core"; +import {NamedCommand, RestCommand} from "../../core"; import moment from "moment"; import {Storage} from "../../structures"; import {MessageEmbed} from "discord.js"; export default new NamedCommand({ description: "Keep and edit your personal todo list.", - async run({send, message, channel, guild, author, member, client, args}) { + async run({send, author}) { const user = Storage.getUser(author.id); const embed = new MessageEmbed().setTitle(`Todo list for ${author.tag}`).setColor("BLUE"); @@ -23,7 +23,7 @@ export default new NamedCommand({ add: new NamedCommand({ run: "You need to specify a note to add.", any: new RestCommand({ - async run({send, message, channel, guild, author, member, client, args, combined}) { + async run({send, author, combined}) { const user = Storage.getUser(author.id); user.todoList[Date.now().toString()] = combined; console.debug(user.todoList); @@ -35,7 +35,7 @@ export default new NamedCommand({ remove: new NamedCommand({ run: "You need to specify a note to remove.", any: new RestCommand({ - async run({send, message, channel, guild, author, member, client, args, combined}) { + async run({send, author, combined}) { const user = Storage.getUser(author.id); let isFound = false; @@ -55,7 +55,7 @@ export default new NamedCommand({ }) }), clear: new NamedCommand({ - async run({send, message, channel, guild, author, member, client, args}) { + async run({send, author}) { const user = Storage.getUser(author.id); user.todoList = {}; Storage.save(); diff --git a/src/commands/utility/translate.ts b/src/commands/utility/translate.ts index d4a1aab..e3ca9ed 100644 --- a/src/commands/utility/translate.ts +++ b/src/commands/utility/translate.ts @@ -8,7 +8,7 @@ export default new NamedCommand({ any: new Command({ run: "You need to enter some text to translate.", any: new RestCommand({ - async run({send, message, channel, guild, author, member, client, args}) { + async run({send, args}) { const lang = args[0]; const input = args.slice(1).join(" "); translate(input, { From a493536a23edcfc7124302f75917216dd1e3a58d Mon Sep 17 00:00:00 2001 From: WatDuhHekBro <44940783+WatDuhHekBro@users.noreply.github.com> Date: Sun, 11 Apr 2021 05:45:50 -0500 Subject: [PATCH 166/178] Refactored paginate and added poll to library --- docs/Documentation.md | 11 +- src/commands/fun/modules/eco-bet.ts | 79 ++++---- src/commands/fun/modules/eco-shop.ts | 17 +- src/commands/system/help.ts | 25 +-- src/commands/utility/lsemotes.ts | 23 +-- src/commands/utility/todo.ts | 1 - src/core/eventListeners.ts | 26 ++- src/core/libd.ts | 271 +++++++++++++++------------ 8 files changed, 231 insertions(+), 222 deletions(-) diff --git a/docs/Documentation.md b/docs/Documentation.md index 28c2c32..173f686 100644 --- a/docs/Documentation.md +++ b/docs/Documentation.md @@ -75,9 +75,16 @@ Because versions are assigned to batches of changes rather than single changes ( ```ts const pages = ["one", "two", "three"]; -paginate(send, page => { +paginate(send, author.id, pages.length, page => { return {content: pages[page]}; -}, pages.length, author.id); +}); +``` + +`poll()` +```ts +const results = await poll(await send("Do you agree with this decision?"), ["✅", "❌"]); +results["✅"]; // number +results["❌"]; // number ``` `confirm()` diff --git a/src/commands/fun/modules/eco-bet.ts b/src/commands/fun/modules/eco-bet.ts index 7b38485..3bcb103 100644 --- a/src/commands/fun/modules/eco-bet.ts +++ b/src/commands/fun/modules/eco-bet.ts @@ -1,4 +1,4 @@ -import {Command, NamedCommand, confirm} from "../../../core"; +import {Command, NamedCommand, confirm, poll} from "../../../core"; import {pluralise} from "../../../lib"; import {Storage} from "../../../structures"; import {isAuthorized, getMoneyEmbed} from "./eco-utils"; @@ -113,54 +113,41 @@ export const BetCommand = new NamedCommand({ const receiver = Storage.getUser(target.id); // [TODO: when D.JSv13 comes out, inline reply to clean up.] // When bet is over, give a vote to ask people their thoughts. - const voteMsg = await send( - `VOTE: do you think that <@${ - target.id - }> has won the bet?\nhttps://discord.com/channels/${guild!.id}/${channel.id}/${ - message.id - }` - ); - await voteMsg.react("✅"); - await voteMsg.react("❌"); - // Filter reactions to only collect the pertinent ones. - voteMsg - .awaitReactions( - (reaction, user) => { - return ["✅", "❌"].includes(reaction.emoji.name); - }, - // [Pertinence to make configurable on the fly.] - {time: parseDuration("2m")} - ) - .then((reactions) => { - // Count votes - const okReaction = reactions.get("✅"); - const noReaction = reactions.get("❌"); - const ok = okReaction ? (okReaction.count ?? 1) - 1 : 0; - const no = noReaction ? (noReaction.count ?? 1) - 1 : 0; + const results = await poll( + await send( + `VOTE: do you think that <@${ + target.id + }> has won the bet?\nhttps://discord.com/channels/${guild!.id}/${channel.id}/${ + message.id + }` + ), + ["✅", "❌"], + // [Pertinence to make configurable on the fly.] + parseDuration("2m") + ); - if (ok > no) { - receiver.money += amount * 2; - send( - `By the people's votes, <@${target.id}> has won the bet that <@${author.id}> had sent them.` - ); - } else if (ok < no) { - sender.money += amount * 2; - send( - `By the people's votes, <@${target.id}> has lost the bet that <@${author.id}> had sent them.` - ); - } else { - sender.money += amount; - receiver.money += amount; - send( - `By the people's votes, <@${target.id}> couldn't be determined to have won or lost the bet that <@${author.id}> had sent them.` - ); - } + // Count votes + const ok = results["✅"]; + const no = results["❌"]; - sender.ecoBetInsurance -= amount; - receiver.ecoBetInsurance -= amount; - Storage.save(); - }); + if (ok > no) { + receiver.money += amount * 2; + send(`By the people's votes, ${target} has won the bet that ${author} had sent them.`); + } else if (ok < no) { + sender.money += amount * 2; + send(`By the people's votes, ${target} has lost the bet that ${author} had sent them.`); + } else { + sender.money += amount; + receiver.money += amount; + send( + `By the people's votes, ${target} couldn't be determined to have won or lost the bet that ${author} had sent them.` + ); + } + + sender.ecoBetInsurance -= amount; + receiver.ecoBetInsurance -= amount; + Storage.save(); }, duration); } else return; } diff --git a/src/commands/fun/modules/eco-shop.ts b/src/commands/fun/modules/eco-shop.ts index 91ac71d..acd0e4a 100644 --- a/src/commands/fun/modules/eco-shop.ts +++ b/src/commands/fun/modules/eco-shop.ts @@ -34,17 +34,12 @@ export const ShopCommand = new NamedCommand({ const shopPages = split(ShopItems, 5); const pageAmount = shopPages.length; - paginate( - send, - (page, hasMultiplePages) => { - return getShopEmbed( - shopPages[page], - hasMultiplePages ? `Shop (Page ${page + 1} of ${pageAmount})` : "Shop" - ); - }, - pageAmount, - author.id - ); + paginate(send, author.id, pageAmount, (page, hasMultiplePages) => { + return getShopEmbed( + shopPages[page], + hasMultiplePages ? `Shop (Page ${page + 1} of ${pageAmount})` : "Shop" + ); + }); } } }); diff --git a/src/commands/system/help.ts b/src/commands/system/help.ts index 7e5139c..7691a5a 100644 --- a/src/commands/system/help.ts +++ b/src/commands/system/help.ts @@ -20,21 +20,16 @@ export default new NamedCommand({ const commands = await getCommandList(); const categoryArray = commands.keyArray(); - paginate( - send, - (page, hasMultiplePages) => { - const category = categoryArray[page]; - const commandList = commands.get(category)!; - let output = `Legend: \`\`, \`[list/of/stuff]\`, \`(optional)\`, \`()\`, \`([optional/list/...])\`\n`; - for (const command of commandList) output += `\n❯ \`${command.name}\`: ${command.description}`; - return new MessageEmbed() - .setTitle(hasMultiplePages ? `${category} (Page ${page + 1} of ${categoryArray.length})` : category) - .setDescription(output) - .setColor(EMBED_COLOR); - }, - categoryArray.length, - author.id - ); + paginate(send, author.id, categoryArray.length, (page, hasMultiplePages) => { + const category = categoryArray[page]; + const commandList = commands.get(category)!; + let output = `Legend: \`\`, \`[list/of/stuff]\`, \`(optional)\`, \`()\`, \`([optional/list/...])\`\n`; + for (const command of commandList) output += `\n❯ \`${command.name}\`: ${command.description}`; + return new MessageEmbed() + .setTitle(hasMultiplePages ? `${category} (Page ${page + 1} of ${categoryArray.length})` : category) + .setDescription(output) + .setColor(EMBED_COLOR); + }); }, any: new Command({ async run({send, message, channel, guild, author, member, client, args}) { diff --git a/src/commands/utility/lsemotes.ts b/src/commands/utility/lsemotes.ts index 7e1e2d9..37840b9 100644 --- a/src/commands/utility/lsemotes.ts +++ b/src/commands/utility/lsemotes.ts @@ -90,22 +90,17 @@ async function displayEmoteList(emotes: GuildEmoji[], send: SendFunction, author // Gather the first page (if it even exists, which it might not if there no valid emotes appear) if (pages > 0) { - paginate( - send, - (page, hasMultiplePages) => { - embed.setTitle(hasMultiplePages ? `**Emotes** (Page ${page + 1} of ${pages})` : "**Emotes**"); + paginate(send, author.id, pages, (page, hasMultiplePages) => { + embed.setTitle(hasMultiplePages ? `**Emotes** (Page ${page + 1} of ${pages})` : "**Emotes**"); - let desc = ""; - for (const emote of sections[page]) { - desc += `${emote} ${emote.name} (**${emote.guild.name}**)\n`; - } - embed.setDescription(desc); + let desc = ""; + for (const emote of sections[page]) { + desc += `${emote} ${emote.name} (**${emote.guild.name}**)\n`; + } + embed.setDescription(desc); - return embed; - }, - pages, - author.id - ); + return embed; + }); } else { send("No valid emotes found by that query."); } diff --git a/src/commands/utility/todo.ts b/src/commands/utility/todo.ts index 9057166..2761db7 100644 --- a/src/commands/utility/todo.ts +++ b/src/commands/utility/todo.ts @@ -26,7 +26,6 @@ export default new NamedCommand({ async run({send, message, channel, guild, author, member, client, args, combined}) { const user = Storage.getUser(author.id); user.todoList[Date.now().toString()] = combined; - console.debug(user.todoList); Storage.save(); send(`Successfully added \`${combined}\` to your todo list.`); } diff --git a/src/core/eventListeners.ts b/src/core/eventListeners.ts index 705647e..bad74cd 100644 --- a/src/core/eventListeners.ts +++ b/src/core/eventListeners.ts @@ -1,22 +1,32 @@ -import {Client, Permissions, Message} from "discord.js"; +import {Client, Permissions, Message, MessageReaction, User, PartialUser} from "discord.js"; import {botHasPermission} from "./libd"; // A list of message ID and callback pairs. You get the emote name and ID of the user reacting. -export const unreactEventListeners: Map void> = new Map(); +// This will handle removing reactions automatically (if the bot has the right permission). +export const reactEventListeners = new Map void>(); +export const emptyReactEventListeners = new Map void>(); // A list of "channel-message" and callback pairs. Also, I imagine that the callback will be much more maintainable when discord.js v13 comes out with a dedicated message.referencedMessage property. -// Also, I'm defining it here instead of the message event because the load order screws up if you export it from there. Yeah... I'm starting to notice just how much technical debt has been built up. The command handler needs to be modularized and refactored sooner rather than later. Define all constants in one area then grab from there. export const replyEventListeners = new Map void>(); export function attachEventListenersToClient(client: Client) { - // Attached to the client, there can be one event listener attached to a message ID which is executed if present. + client.on("messageReactionAdd", (reaction, user) => { + // The reason this is inside the call is because it's possible to switch a user's permissions halfway and suddenly throw an error. + // This will dynamically adjust for that, switching modes depending on whether it currently has the "Manage Messages" permission. + const canDeleteEmotes = botHasPermission(reaction.message.guild, Permissions.FLAGS.MANAGE_MESSAGES); + reactEventListeners.get(reaction.message.id)?.(reaction, user); + if (canDeleteEmotes && !user.partial) reaction.users.remove(user); + }); + client.on("messageReactionRemove", (reaction, user) => { const canDeleteEmotes = botHasPermission(reaction.message.guild, Permissions.FLAGS.MANAGE_MESSAGES); + if (!canDeleteEmotes) reactEventListeners.get(reaction.message.id)?.(reaction, user); + }); - if (!canDeleteEmotes) { - const callback = unreactEventListeners.get(reaction.message.id); - callback && callback(reaction.emoji.name, user.id); - } + client.on("messageReactionRemoveAll", (message) => { + reactEventListeners.delete(message.id); + emptyReactEventListeners.get(message.id)?.(); + emptyReactEventListeners.delete(message.id); }); client.on("message", (message) => { diff --git a/src/core/libd.ts b/src/core/libd.ts index 58338a5..aed4a07 100644 --- a/src/core/libd.ts +++ b/src/core/libd.ts @@ -3,7 +3,6 @@ import { Message, Guild, GuildMember, - Permissions, TextChannel, DMChannel, NewsChannel, @@ -17,9 +16,10 @@ import { APIMessage, StringResolvable, EmojiIdentifierResolvable, - MessageReaction + MessageReaction, + PartialUser } from "discord.js"; -import {unreactEventListeners, replyEventListeners} from "./eventListeners"; +import {reactEventListeners, emptyReactEventListeners, replyEventListeners} from "./eventListeners"; import {client} from "./interface"; export type SingleMessageOptions = MessageOptions & {split?: false}; @@ -33,150 +33,119 @@ export type SendFunction = (( ((content: StringResolvable, options: MessageOptions & {split: true | SplitOptions}) => Promise) & ((content: StringResolvable, options: MessageOptions) => Promise); -const FIVE_BACKWARDS_EMOJI = "⏪"; -const BACKWARDS_EMOJI = "⬅️"; -const FORWARDS_EMOJI = "➡️"; -const FIVE_FORWARDS_EMOJI = "⏩"; +interface PaginateOptions { + multiPageSize?: number; + idleTimeout?: number; +} // Pagination function that allows for customization via a callback. // Define your own pages outside the function because this only manages the actual turning of pages. /** * Takes a message and some additional parameters and makes a reaction page with it. All the pagination logic is taken care of but nothing more, the page index is returned and you have to send a callback to do something with it. + * + * Returns the page number the user left off on in case you want to implement a return to page function. */ -export async function paginate( +export function paginate( send: SendFunction, - onTurnPage: (page: number, hasMultiplePages: boolean) => SingleMessageOptions, + listenTo: string | null, totalPages: number, - listenTo: string | null = null, - duration = 60000 -): Promise { - const hasMultiplePages = totalPages > 1; - const message = await send(onTurnPage(0, hasMultiplePages)); + onTurnPage: (page: number, hasMultiplePages: boolean) => SingleMessageOptions, + options?: PaginateOptions +): Promise { + if (totalPages < 1) throw new Error(`totalPages on paginate() must be 1 or more, ${totalPages} given.`); - if (hasMultiplePages) { - let page = 0; - const turn = (amount: number) => { - page += amount; - - if (page >= totalPages) { - page %= totalPages; - } else if (page < 0) { - // Assuming 3 total pages, it's a bit tricker, but if we just take the modulo of the absolute value (|page| % total), we get (1 2 0 ...), and we just need the pattern (2 1 0 ...). It needs to reverse order except for when it's 0. I want to find a better solution, but for the time being... total - (|page| % total) unless (|page| % total) = 0, then return 0. - const flattened = Math.abs(page) % totalPages; - if (flattened !== 0) page = totalPages - flattened; - } - - message.edit(onTurnPage(page, true)); - }; - const handle = (emote: string, reacterID: string) => { - if (reacterID === listenTo || listenTo === null) { - collector.resetTimer(); // The timer refresh MUST be present in both react and unreact. - switch (emote) { - case FIVE_BACKWARDS_EMOJI: - if (totalPages > 5) turn(-5); - break; - case BACKWARDS_EMOJI: - turn(-1); - break; - case FORWARDS_EMOJI: - turn(1); - break; - case FIVE_FORWARDS_EMOJI: - if (totalPages > 5) turn(5); - break; - } - } - }; - - // Listen for reactions and call the handler. - let backwardsReactionFive = totalPages > 5 ? await message.react(FIVE_BACKWARDS_EMOJI) : null; - let backwardsReaction = await message.react(BACKWARDS_EMOJI); - let forwardsReaction = await message.react(FORWARDS_EMOJI); - let forwardsReactionFive = totalPages > 5 ? await message.react(FIVE_FORWARDS_EMOJI) : null; - unreactEventListeners.set(message.id, handle); - - const collector = message.createReactionCollector( - (reaction, user) => { - // This check is actually redundant because of handle(). - if (user.id === listenTo || listenTo === null) { - // The reason this is inside the call is because it's possible to switch a user's permissions halfway and suddenly throw an error. - // This will dynamically adjust for that, switching modes depending on whether it currently has the "Manage Messages" permission. - const canDeleteEmotes = botHasPermission(message.guild, Permissions.FLAGS.MANAGE_MESSAGES); - handle(reaction.emoji.name, user.id); - if (canDeleteEmotes) reaction.users.remove(user); - } - - return false; - }, - // Apparently, regardless of whether you put "time" or "idle", it won't matter to the collector. - // In order to actually reset the timer, you have to do it manually via collector.resetTimer(). - {time: duration} - ); - - // When time's up, remove the bot's own reactions. - collector.on("end", () => { - unreactEventListeners.delete(message.id); - backwardsReactionFive?.users.remove(message.author); - backwardsReaction.users.remove(message.author); - forwardsReaction.users.remove(message.author); - forwardsReactionFive?.users.remove(message.author); - }); - } -} - -//export function generateMulti -// paginate after generateonetimeprompt - -// Returns null if timed out, otherwise, returns the value. -export function generateOneTimePrompt( - message: Message, - stack: {[emote: string]: T}, - listenTo: string | null = null, - duration = 60000 -): Promise { return new Promise(async (resolve) => { - // First, start reacting to the message in order. - reactInOrder(message, Object.keys(stack)); + const hasMultiplePages = totalPages > 1; + const message = await send(onTurnPage(0, hasMultiplePages)); - // Then setup the reaction listener in parallel. - await message.awaitReactions( - (reaction: MessageReaction, user: User) => { - if (user.id === listenTo || listenTo === null) { - const emote = reaction.emoji.name; + if (hasMultiplePages) { + const multiPageSize = options?.multiPageSize ?? 5; + const idleTimeout = options?.idleTimeout ?? 60000; + let page = 0; - if (emote in stack) { - resolve(stack[emote]); - message.delete(); - } + const turn = (amount: number) => { + page += amount; + + if (page >= totalPages) { + page %= totalPages; + } else if (page < 0) { + // Assuming 3 total pages, it's a bit tricker, but if we just take the modulo of the absolute value (|page| % total), we get (1 2 0 ...), and we just need the pattern (2 1 0 ...). It needs to reverse order except for when it's 0. I want to find a better solution, but for the time being... total - (|page| % total) unless (|page| % total) = 0, then return 0. + const flattened = Math.abs(page) % totalPages; + if (flattened !== 0) page = totalPages - flattened; } - // CollectorFilter requires a boolean to be returned. - // My guess is that the return value of awaitReactions can be altered by making a boolean filter. - // However, because that's not my concern with this command, I don't have to worry about it. - // May as well just set it to false because I'm not concerned with collecting any reactions. - return false; - }, - {time: duration} - ); + message.edit(onTurnPage(page, true)); + }; - if (!message.deleted) { - message.delete(); - resolve(null); + let stack: {[emote: string]: number} = { + "⬅️": -1, + "➡️": 1 + }; + + if (totalPages > multiPageSize) { + stack = { + "⏪": -multiPageSize, + ...stack, + "⏩": multiPageSize + }; + } + + const handle = (reaction: MessageReaction, user: User | PartialUser) => { + if (user.id === listenTo || (listenTo === null && user.id !== client.user!.id)) { + // Turn the page + const emote = reaction.emoji.name; + if (emote in stack) turn(stack[emote]); + + // Reset the timer + client.clearTimeout(timeout); + timeout = client.setTimeout(destroy, idleTimeout); + } + }; + + // When time's up, remove the bot's own reactions. + const destroy = () => { + reactEventListeners.delete(message.id); + for (const emote of message.reactions.cache.values()) emote.users.remove(message.author); + resolve(page); + }; + + // Start the reactions and call the handler. + reactInOrder(message, Object.keys(stack)); + reactEventListeners.set(message.id, handle); + emptyReactEventListeners.set(message.id, destroy); + let timeout = client.setTimeout(destroy, idleTimeout); } }); } -// Start a parallel chain of ordered reactions, allowing a collector to end early. -// Check if the collector ended early by seeing if the message is already deleted. -// Though apparently, message.deleted doesn't seem to update fast enough, so just put a try catch block on message.react(). -async function reactInOrder(message: Message, emotes: EmojiIdentifierResolvable[]): Promise { +export async function poll(message: Message, emotes: string[], duration = 60000): Promise<{[emote: string]: number}> { + if (emotes.length === 0) throw new Error("poll() was called without any emotes."); + + reactInOrder(message, emotes); + const reactions = await message.awaitReactions( + (reaction: MessageReaction) => emotes.includes(reaction.emoji.name), + {time: duration} + ); + const reactionsByCount: {[emote: string]: number} = {}; + for (const emote of emotes) { - try { - await message.react(emote); - } catch { - return; + const reaction = reactions.get(emote); + + if (reaction) { + const hasBot = reaction.users.cache.has(client.user!.id); // Apparently, reaction.me doesn't work properly. + + if (reaction.count !== null) { + const difference = hasBot ? 1 : 0; + reactionsByCount[emote] = reaction.count - difference; + } else { + reactionsByCount[emote] = 0; + } + } else { + reactionsByCount[emote] = 0; } } + + return reactionsByCount; } export function confirm(message: Message, senderID: string, timeout = 30000): Promise { @@ -235,6 +204,58 @@ export function askForReply(message: Message, listenTo: string, timeout?: number }); } +// Returns null if timed out, otherwise, returns the value. +export function generateOneTimePrompt( + message: Message, + stack: {[emote: string]: T}, + listenTo: string | null = null, + duration = 60000 +): Promise { + return new Promise(async (resolve) => { + // First, start reacting to the message in order. + reactInOrder(message, Object.keys(stack)); + + // Then setup the reaction listener in parallel. + await message.awaitReactions( + (reaction: MessageReaction, user: User) => { + if (user.id === listenTo || listenTo === null) { + const emote = reaction.emoji.name; + + if (emote in stack) { + resolve(stack[emote]); + message.delete(); + } + } + + // CollectorFilter requires a boolean to be returned. + // My guess is that the return value of awaitReactions can be altered by making a boolean filter. + // However, because that's not my concern with this command, I don't have to worry about it. + // May as well just set it to false because I'm not concerned with collecting any reactions. + return false; + }, + {time: duration} + ); + + if (!message.deleted) { + message.delete(); + resolve(null); + } + }); +} + +// Start a parallel chain of ordered reactions, allowing a collector to end early. +// Check if the collector ended early by seeing if the message is already deleted. +// Though apparently, message.deleted doesn't seem to update fast enough, so just put a try catch block on message.react(). +async function reactInOrder(message: Message, emotes: EmojiIdentifierResolvable[]): Promise { + for (const emote of emotes) { + try { + await message.react(emote); + } catch { + return; + } + } +} + /** * Tests if a bot has a certain permission in a specified guild. */ From 8142709581d45b3288f0a912435ec32fbba2cfbd Mon Sep 17 00:00:00 2001 From: WatDuhHekBro <44940783+WatDuhHekBro@users.noreply.github.com> Date: Mon, 12 Apr 2021 02:44:23 -0500 Subject: [PATCH 167/178] Fixed help/thonk bugs and removed more unused vars --- src/commands/fun/modules/eco-extras.ts | 4 +- src/commands/fun/modules/eco-shop.ts | 2 +- src/commands/fun/thonk.ts | 5 ++- src/commands/system/admin.ts | 52 +++++++++++++------------- src/commands/system/help.ts | 36 ++++++++++++++---- 5 files changed, 60 insertions(+), 39 deletions(-) diff --git a/src/commands/fun/modules/eco-extras.ts b/src/commands/fun/modules/eco-extras.ts index a1876d8..aeda4d0 100644 --- a/src/commands/fun/modules/eco-extras.ts +++ b/src/commands/fun/modules/eco-extras.ts @@ -41,7 +41,7 @@ export const AwardCommand = new NamedCommand({ aliases: ["give"], run: "You need to specify a user!", user: new Command({ - async run({send, message, channel, guild, author, member, client, args}) { + async run({send, author, args}) { if (author.id === "394808963356688394" || IS_DEV_MODE) { const target = args[0] as User; const user = Storage.getUser(target.id); @@ -53,7 +53,7 @@ export const AwardCommand = new NamedCommand({ } }, number: new Command({ - async run({send, message, channel, guild, author, member, client, args}) { + async run({send, author, args}) { if (author.id === "394808963356688394" || IS_DEV_MODE) { const target = args[0] as User; const amount = Math.floor(args[1]); diff --git a/src/commands/fun/modules/eco-shop.ts b/src/commands/fun/modules/eco-shop.ts index acd0e4a..b4cf1a5 100644 --- a/src/commands/fun/modules/eco-shop.ts +++ b/src/commands/fun/modules/eco-shop.ts @@ -49,7 +49,7 @@ export const BuyCommand = new NamedCommand({ usage: "", run: "You need to specify an item to buy.", any: new RestCommand({ - async run({send, guild, channel, args, message, author, combined}) { + async run({send, guild, channel, message, author, combined}) { if (isAuthorized(guild, channel)) { let found = false; let amount = 1; // The amount the user is buying. diff --git a/src/commands/fun/thonk.ts b/src/commands/fun/thonk.ts index 821216f..c81a9e7 100644 --- a/src/commands/fun/thonk.ts +++ b/src/commands/fun/thonk.ts @@ -33,7 +33,7 @@ let phrase = "I have no currently set phrase!"; export default new NamedCommand({ description: "Transforms your text into vietnamese.", - usage: "thonk ([text])", + usage: "([text])", async run({send, author}) { const msg = await send(transform(phrase)); msg.createReactionCollector( @@ -46,7 +46,8 @@ export default new NamedCommand({ }, any: new RestCommand({ async run({send, author, combined}) { - const msg = await send(transform(combined)); + phrase = combined; + const msg = await send(transform(phrase)); msg.createReactionCollector( (reaction, user) => { if (user.id === author.id && reaction.emoji.name === "❌") msg.delete(); diff --git a/src/commands/system/admin.ts b/src/commands/system/admin.ts index a57d37c..1ec78ef 100644 --- a/src/commands/system/admin.ts +++ b/src/commands/system/admin.ts @@ -29,7 +29,7 @@ const statuses = ["online", "idle", "dnd", "invisible"]; export default new NamedCommand({ description: "An all-in-one command to do admin stuff. You need to be either an admin of the server or one of the bot's mechanics to use this command.", - async run({send, message, channel, guild, author, member, client, args}) { + async run({send, author, member}) { const permLevel = getPermissionLevel(author, member); return send(`${author}, your permission level is \`${getPermissionName(permLevel)}\` (${permLevel}).`); }, @@ -43,7 +43,7 @@ export default new NamedCommand({ prefix: new NamedCommand({ description: "Set a custom prefix for your guild. Removes your custom prefix if none is provided.", usage: "() (<@bot>)", - async run({send, message, channel, guild, author, member, client, args}) { + async run({send, guild}) { Storage.getGuild(guild!.id).prefix = null; Storage.save(); send( @@ -51,14 +51,14 @@ export default new NamedCommand({ ); }, any: new Command({ - async run({send, message, channel, guild, author, member, client, args}) { + async run({send, guild, args}) { Storage.getGuild(guild!.id).prefix = args[0]; Storage.save(); send(`The custom prefix for this guild is now \`${args[0]}\`.`); }, user: new Command({ description: "Specifies the bot in case of conflicting prefixes.", - async run({send, message, channel, guild, author, member, client, args}) { + async run({send, guild, client, args}) { if ((args[1] as User).id === client.user!.id) { Storage.getGuild(guild!.id).prefix = args[0]; Storage.save(); @@ -77,7 +77,7 @@ export default new NamedCommand({ description: "Sets how welcome messages are displayed for your server. Removes welcome messages if unspecified.", usage: "`none`/`text`/`graphical`", - async run({send, message, channel, guild, author, member, client, args}) { + async run({send, guild}) { Storage.getGuild(guild!.id).welcomeType = "none"; Storage.save(); send("Set this server's welcome type to `none`."); @@ -85,14 +85,14 @@ export default new NamedCommand({ // I should probably make this a bit more dynamic... Oh well. subcommands: { text: new NamedCommand({ - async run({send, message, channel, guild, author, member, client, args}) { + async run({send, guild}) { Storage.getGuild(guild!.id).welcomeType = "text"; Storage.save(); send("Set this server's welcome type to `text`."); } }), graphical: new NamedCommand({ - async run({send, message, channel, guild, author, member, client, args}) { + async run({send, guild}) { Storage.getGuild(guild!.id).welcomeType = "graphical"; Storage.save(); send("Set this server's welcome type to `graphical`."); @@ -103,14 +103,14 @@ export default new NamedCommand({ channel: new NamedCommand({ description: "Sets the welcome channel for your server. Type `#` to reference the channel.", usage: "()", - async run({send, message, channel, guild, author, member, client, args}) { + async run({send, channel, guild}) { Storage.getGuild(guild!.id).welcomeChannel = channel.id; Storage.save(); send(`Successfully set ${channel} as the welcome channel for this server.`); }, id: "channel", channel: new Command({ - async run({send, message, channel, guild, author, member, client, args}) { + async run({send, guild, args}) { const result = args[0] as TextChannel; Storage.getGuild(guild!.id).welcomeChannel = result.id; Storage.save(); @@ -122,13 +122,13 @@ export default new NamedCommand({ description: "Sets a custom welcome message for your server. Use `%user%` as the placeholder for the user.", usage: "()", - async run({send, message, channel, guild, author, member, client, args}) { + async run({send, guild}) { Storage.getGuild(guild!.id).welcomeMessage = null; Storage.save(); send("Reset your server's welcome message to the default."); }, any: new RestCommand({ - async run({send, message, channel, guild, author, member, client, args, combined}) { + async run({send, guild, combined}) { Storage.getGuild(guild!.id).welcomeMessage = combined; Storage.save(); send(`Set your server's welcome message to \`${combined}\`.`); @@ -140,7 +140,7 @@ export default new NamedCommand({ stream: new NamedCommand({ description: "Set a channel to send stream notifications. Type `#` to reference the channel.", usage: "()", - async run({send, message, channel, guild, author, member, client, args}) { + async run({send, channel, guild}) { const targetGuild = Storage.getGuild(guild!.id); if (targetGuild.streamingChannel) { @@ -155,7 +155,7 @@ export default new NamedCommand({ }, id: "channel", channel: new Command({ - async run({send, message, channel, guild, author, member, client, args}) { + async run({send, guild, args}) { const result = args[0] as TextChannel; Storage.getGuild(guild!.id).streamingChannel = result.id; Storage.save(); @@ -168,12 +168,12 @@ export default new NamedCommand({ diag: new NamedCommand({ description: 'Requests a debug log with the "info" verbosity level.', permission: PERMISSIONS.BOT_SUPPORT, - async run({send, message, channel, guild, author, member, client, args}) { + async run({send}) { send(getLogBuffer("info")); }, any: new Command({ description: `Select a verbosity to listen to. Available levels: \`[${Object.keys(logs).join(", ")}]\``, - async run({send, message, channel, guild, author, member, client, args}) { + async run({send, args}) { const type = args[0]; if (type in logs) send(getLogBuffer(type)); @@ -189,12 +189,12 @@ export default new NamedCommand({ status: new NamedCommand({ description: "Changes the bot's status.", permission: PERMISSIONS.BOT_SUPPORT, - async run({send, message, channel, guild, author, member, client, args}) { + async run({send}) { send("Setting status to `online`..."); }, any: new Command({ description: `Select a status to set to. Available statuses: \`[${statuses.join(", ")}]\`.`, - async run({send, message, channel, guild, author, member, client, args}) { + async run({send, client, args}) { if (!statuses.includes(args[0])) { return send("That status doesn't exist!"); } else { @@ -208,7 +208,7 @@ export default new NamedCommand({ description: "Purges the bot's own messages.", permission: PERMISSIONS.BOT_SUPPORT, channelType: CHANNEL_TYPE.GUILD, - async run({send, message, channel, guild, author, member, client, args}) { + async run({send, message, channel, guild, client}) { // It's probably better to go through the bot's own messages instead of calling bulkDelete which requires MANAGE_MESSAGES. if (botHasPermission(guild, Permissions.FLAGS.MANAGE_MESSAGES)) { message.delete(); @@ -235,7 +235,7 @@ export default new NamedCommand({ run: "A number was not provided.", number: new Command({ description: "Amount of messages to delete.", - async run({send, message, channel, guild, author, member, client, args}) { + async run({message, channel, args}) { message.delete(); const fetched = await channel.messages.fetch({ limit: args[0] @@ -251,7 +251,7 @@ export default new NamedCommand({ run: "You have to enter some code to execute first.", any: new RestCommand({ // You have to bring everything into scope to use them. AFAIK, there isn't a more maintainable way to do this, but at least TS will let you know if anything gets removed. - async run({send, message, channel, guild, author, member, client, args, combined}) { + async run({send, combined}) { try { let evaled = eval(combined); if (typeof evaled !== "string") evaled = require("util").inspect(evaled); @@ -268,7 +268,7 @@ export default new NamedCommand({ channelType: CHANNEL_TYPE.GUILD, run: "You have to specify a nickname to set for the bot", any: new RestCommand({ - async run({send, message, channel, guild, author, member, client, args, combined}) { + async run({send, message, guild, combined}) { await guild!.me?.setNickname(combined); if (botHasPermission(guild, Permissions.FLAGS.MANAGE_MESSAGES)) message.delete({timeout: 5000}); send(`Nickname set to \`${combined}\``).then((m) => m.delete({timeout: 5000})); @@ -278,7 +278,7 @@ export default new NamedCommand({ guilds: new NamedCommand({ description: "Shows a list of all guilds the bot is a member of.", permission: PERMISSIONS.BOT_SUPPORT, - async run({send, message, channel, guild, author, member, client, args}) { + async run({send, client}) { const guildList = client.guilds.cache.array().map((e) => e.name); send(guildList, {split: true}); } @@ -287,7 +287,7 @@ export default new NamedCommand({ description: "Set the activity of the bot.", permission: PERMISSIONS.BOT_SUPPORT, usage: " ", - async run({send, message, channel, guild, author, member, client, args}) { + async run({send, client}) { client.user?.setActivity(".help", { type: "LISTENING" }); @@ -295,7 +295,7 @@ export default new NamedCommand({ }, any: new RestCommand({ description: `Select an activity type to set. Available levels: \`[${activities.join(", ")}]\``, - async run({send, message, channel, guild, author, member, client, args}) { + async run({send, client, args}) { const type = args[0]; if (activities.includes(type)) { @@ -316,13 +316,13 @@ export default new NamedCommand({ description: "Sets up the current channel to receive system logs.", permission: PERMISSIONS.BOT_ADMIN, channelType: CHANNEL_TYPE.GUILD, - async run({send, message, channel, guild, author, member, client, args}) { + async run({send, channel}) { Config.systemLogsChannel = channel.id; Config.save(); send(`Successfully set ${channel} as the system logs channel.`); }, channel: new Command({ - async run({send, message, channel, guild, author, member, client, args}) { + async run({send, args}) { const targetChannel = args[0] as TextChannel; Config.systemLogsChannel = targetChannel.id; Config.save(); diff --git a/src/commands/system/help.ts b/src/commands/system/help.ts index 7691a5a..fca0280 100644 --- a/src/commands/system/help.ts +++ b/src/commands/system/help.ts @@ -11,28 +11,48 @@ import {requireAllCasesHandledFor} from "../../lib"; import {MessageEmbed} from "discord.js"; const EMBED_COLOR = "#158a28"; +const LEGEND = "Legend: ``, `[list/of/stuff]`, `(optional)`, `()`, `([optional/list/...])`\n"; export default new NamedCommand({ description: "Lists all commands. If a command is specified, their arguments are listed as well.", usage: "([command, [subcommand/type], ...])", aliases: ["h"], - async run({send, message, channel, guild, author, member, client, args}) { + async run({send, author}) { const commands = await getCommandList(); - const categoryArray = commands.keyArray(); + const helpMenuPages: [string, string][] = []; // An array of (category, description) tuples. - paginate(send, author.id, categoryArray.length, (page, hasMultiplePages) => { - const category = categoryArray[page]; + // Prevent the description of one category from overflowing by splitting it into multiple pages if needed. + for (const category of commands.keyArray()) { const commandList = commands.get(category)!; - let output = `Legend: \`\`, \`[list/of/stuff]\`, \`(optional)\`, \`()\`, \`([optional/list/...])\`\n`; - for (const command of commandList) output += `\n❯ \`${command.name}\`: ${command.description}`; + let output = LEGEND; + + for (const command of commandList) { + const field = `\n❯ \`${command.name}\`: ${command.description}`.repeat(2); + const newOutput = output + field; + + // Push then reset the output if it overflows, otherwise, continue as normal. + if (newOutput.length > 2048) { + helpMenuPages.push([category, output]); + output = LEGEND + field; + } else { + output = newOutput; + } + } + + // Then push whatever's remaining. + helpMenuPages.push([category, output]); + } + + paginate(send, author.id, helpMenuPages.length, (page, hasMultiplePages) => { + const [category, output] = helpMenuPages[page]; return new MessageEmbed() - .setTitle(hasMultiplePages ? `${category} (Page ${page + 1} of ${categoryArray.length})` : category) + .setTitle(hasMultiplePages ? `${category} (Page ${page + 1} of ${helpMenuPages.length})` : category) .setDescription(output) .setColor(EMBED_COLOR); }); }, any: new Command({ - async run({send, message, channel, guild, author, member, client, args}) { + async run({send, args}) { const resultingBlob = await getCommandInfo(args); if (typeof resultingBlob === "string") return send(resultingBlob); const [result, category] = resultingBlob; From 6ea052ae6fba97e2876ba1aa75efa303f72f0b51 Mon Sep 17 00:00:00 2001 From: WatDuhHekBro <44940783+WatDuhHekBro@users.noreply.github.com> Date: Mon, 12 Apr 2021 04:07:04 -0500 Subject: [PATCH 168/178] Small fixes and rework of poll --- src/commands/fun/8ball.ts | 9 ++--- src/commands/fun/cookie.ts | 5 +-- src/commands/fun/poll.ts | 67 +++++++++++++++++++++++++--------- src/commands/utility/invite.ts | 21 +++++++---- 4 files changed, 68 insertions(+), 34 deletions(-) diff --git a/src/commands/fun/8ball.ts b/src/commands/fun/8ball.ts index f79a2c0..66d2aa6 100644 --- a/src/commands/fun/8ball.ts +++ b/src/commands/fun/8ball.ts @@ -1,4 +1,4 @@ -import {Command, NamedCommand} from "../../core"; +import {NamedCommand, RestCommand} from "../../core"; import {random} from "../../lib"; const responses = [ @@ -28,11 +28,10 @@ export default new NamedCommand({ description: "Answers your question in an 8-ball manner.", usage: "", run: "Please provide a question.", - any: new Command({ + any: new RestCommand({ description: "Question to ask the 8-ball.", - async run({send, message}) { - const sender = message.author; - send(`${random(responses)} <@${sender.id}>`); + async run({send, author}) { + send(`${random(responses)} ${author}`); } }) }); diff --git a/src/commands/fun/cookie.ts b/src/commands/fun/cookie.ts index 71d9c20..3ca68b9 100644 --- a/src/commands/fun/cookie.ts +++ b/src/commands/fun/cookie.ts @@ -40,13 +40,12 @@ export default new NamedCommand({ user: new Command({ description: "User to give cookie to.", async run({send, author, args}) { - const sender = author; const mention: User = args[0]; - if (mention.id == sender.id) return send("You can't give yourself cookies!"); + if (mention.id == author.id) return send("You can't give yourself cookies!"); return send( - `:cookie: <@${sender.id}> ${parseVars(random(cookies), { + `:cookie: ${author} ${parseVars(random(cookies), { target: mention.toString() })}` ); diff --git a/src/commands/fun/poll.ts b/src/commands/fun/poll.ts index 3909968..5e48a60 100644 --- a/src/commands/fun/poll.ts +++ b/src/commands/fun/poll.ts @@ -1,27 +1,58 @@ -import {MessageEmbed} from "discord.js"; -import {NamedCommand, RestCommand} from "../../core"; +import {MessageEmbed, Message, User} from "discord.js"; +import {NamedCommand, RestCommand, poll, CHANNEL_TYPE, SendFunction, Command} from "../../core"; +import {pluralise} from "../../lib"; export default new NamedCommand({ description: "Create a poll.", - usage: "", + usage: "() ", run: "Please provide a question.", + channelType: CHANNEL_TYPE.GUILD, + number: new Command({ + run: "Please provide a question in addition to the provided duration.", + any: new RestCommand({ + description: "Question for the poll.", + async run({send, message, author, args, combined}) { + execPoll(send, message, author, combined, args[0]); + } + }) + }), any: new RestCommand({ description: "Question for the poll.", - async run({send, message, combined}) { - const embed = new MessageEmbed() - .setAuthor( - `Poll created by ${message.author.username}`, - message.guild?.iconURL({dynamic: true}) ?? undefined - ) - .setColor(0xffffff) - .setFooter("React to vote.") - .setDescription(combined); - const msg = await send(embed); - await msg.react("✅"); - await msg.react("⛔"); - message.delete({ - timeout: 1000 - }); + async run({send, message, author, combined}) { + execPoll(send, message, author, combined); } }) }); + +const AGREE = "✅"; +const DISAGREE = "⛔"; + +async function execPoll(send: SendFunction, message: Message, user: User, question: string, duration = 60000) { + const icon = + user.avatarURL({ + dynamic: true, + size: 2048 + }) || user.defaultAvatarURL; + const msg = await send( + new MessageEmbed() + .setAuthor(`Poll created by ${message.author.username}`, icon) + .setColor(0xffffff) + .setFooter("React to vote.") + .setDescription(question) + ); + const results = await poll(msg, [AGREE, DISAGREE], duration); + send( + new MessageEmbed() + .setAuthor(`The results of ${message.author.username}'s poll:`, icon) + .setTitle(question) + .setDescription( + `${AGREE} - ${pluralise( + results[AGREE], + "", + "people who agree", + "person who agrees" + )}\n${DISAGREE} - ${pluralise(results[DISAGREE], "", "people who disagree", "person who disagrees")}` + ) + ); + msg.delete(); +} diff --git a/src/commands/utility/invite.ts b/src/commands/utility/invite.ts index 58b38d8..d0eb559 100644 --- a/src/commands/utility/invite.ts +++ b/src/commands/utility/invite.ts @@ -1,12 +1,17 @@ -import {NamedCommand} from "../../core"; +import {Command, NamedCommand} from "../../core"; export default new NamedCommand({ description: "Gives you the invite link.", - async run({send, client, args}) { - send( - `https://discordapp.com/api/oauth2/authorize?client_id=${client.user!.id}&permissions=${ - args[0] || 8 - }&scope=bot` - ); - } + async run({send, client}) { + send(`https://discordapp.com/api/oauth2/authorize?client_id=${client.user!.id}&permissions=8&scope=bot`); + }, + number: new Command({ + async run({send, client, args}) { + send( + `https://discordapp.com/api/oauth2/authorize?client_id=${client.user!.id}&permissions=${ + args[0] + }&scope=bot` + ); + } + }) }); From 3cd05cd48cbc8490b182b6e2c6c7ea2f6478159d Mon Sep 17 00:00:00 2001 From: Keanu Date: Mon, 12 Apr 2021 15:43:43 +0200 Subject: [PATCH 169/178] Fixed duplicate help entries. --- src/commands/system/help.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands/system/help.ts b/src/commands/system/help.ts index fca0280..588aa92 100644 --- a/src/commands/system/help.ts +++ b/src/commands/system/help.ts @@ -27,7 +27,7 @@ export default new NamedCommand({ let output = LEGEND; for (const command of commandList) { - const field = `\n❯ \`${command.name}\`: ${command.description}`.repeat(2); + const field = `\n❯ \`${command.name}\`: ${command.description}`; const newOutput = output + field; // Push then reset the output if it overflows, otherwise, continue as normal. From 6361b83c054dad0c747872266fbeaeca1f58c1aa Mon Sep 17 00:00:00 2001 From: WatDuhHekBro <44940783+WatDuhHekBro@users.noreply.github.com> Date: Mon, 12 Apr 2021 09:02:19 -0500 Subject: [PATCH 170/178] Fixed GitHub Actions and addressed CodeQL issues --- .github/workflows/image.yml | 8 +++-- Dockerfile | 67 ++++++++++++++++++++++++++++++++++++- package.json | 3 ++ src/lib.ts | 8 +++-- 4 files changed, 80 insertions(+), 6 deletions(-) diff --git a/.github/workflows/image.yml b/.github/workflows/image.yml index c575066..27419b7 100644 --- a/.github/workflows/image.yml +++ b/.github/workflows/image.yml @@ -3,7 +3,6 @@ on: push: branches: - typescript - - docker jobs: analyze: @@ -16,9 +15,12 @@ jobs: fetch-depth: 2 - name: Setup Node.JS - uses: actions/setup-node@v2-beta + uses: actions/setup-node@v2 with: - node-version: "12" + node-version: "14" + # https://github.com/npm/cli/issues/558#issuecomment-580018468 + # Error: "npm ERR! fsevents not accessible from jest-haste-map" + # (supposed to just be a warning b/c optional dependency, but CI environment causes it to fail) - run: npm ci - name: Build codebase diff --git a/Dockerfile b/Dockerfile index ab33a22..cd595ba 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,69 @@ -FROM node:current-alpine +############### +# Solution #1 # +############### +# https://github.com/geekduck/docker-node-canvas +# Took 20m 55s + +#FROM node:12 +# +#RUN apt-get update \ +# && apt-get install -qq build-essential libcairo2-dev libpango1.0-dev libjpeg-dev libgif-dev librsvg2-dev +# +#RUN mkdir -p /opt/node/js \ +# && cd /opt/node \ +# && npm i canvas +# +#WORKDIR /opt/node/js +# +#ENTRYPOINT ["node"] + +############### +# Solution #2 # +############### +# https://github.com/Automattic/node-canvas/issues/729#issuecomment-352991456 +# Took 22m 50s + +#FROM ubuntu:xenial +# +#RUN apt-get update && apt-get install -y \ +# curl \ +# git +# +#RUN curl -sL https://deb.nodesource.com/setup_8.x | bash - \ +# && curl -sL https://deb.nodesource.com/setup_8.x | bash - \ +# && curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \ +# && echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list +# +#RUN apt-get update && apt-get install -y \ +# nodejs \ +# yarn \ +# libcairo2-dev \ +# libjpeg-dev \ +# libpango1.0-dev \ +# libgif-dev \ +# libpng-dev \ +# build-essential \ +# g++ + +############### +# Solution #3 # +############### +# https://github.com/Automattic/node-canvas/issues/866#issuecomment-330001221 +# Took 7m 29s + +FROM node:10.16.0-alpine +FROM mhart/alpine-node:8.5.0 + +RUN apk add --no-cache \ + build-base \ + g++ \ + cairo-dev \ + jpeg-dev \ + pango-dev \ + bash \ + imagemagick + +# The rest of the commands to execute COPY . . diff --git a/package.json b/package.json index 74c35eb..3da03e0 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,9 @@ "tsc-watch": "^4.2.9", "typescript": "^3.9.7" }, + "optionalDependencies": { + "fsevents": "^2.1.2" + }, "author": "Keanu Timmermans", "license": "MIT", "keywords": [ diff --git a/src/lib.ts b/src/lib.ts index 490dd13..06ae42e 100644 --- a/src/lib.ts +++ b/src/lib.ts @@ -139,12 +139,16 @@ export interface GenericJSON { [key: string]: any; } +// In order to define a file to write to while also not: +// - Using the delete operator (which doesn't work on properties which cannot be undefined) +// - Assigning it first then using Object.defineProperty (which raises a flag on CodeQL) +// A non-null assertion is used on the class property to say that it'll definitely be assigned. export abstract class GenericStructure { - private __meta__ = "generic"; + private __meta__!: string; constructor(tag?: string) { - this.__meta__ = tag || this.__meta__; Object.defineProperty(this, "__meta__", { + value: tag || "generic", enumerable: false }); } From 728f115de95d605e1c1135445d5d20164f65bb90 Mon Sep 17 00:00:00 2001 From: WatDuhHekBro <44940783+WatDuhHekBro@users.noreply.github.com> Date: Mon, 12 Apr 2021 09:08:04 -0500 Subject: [PATCH 171/178] The actual GitHub actions fix (hopefully) --- .github/workflows/image.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/image.yml b/.github/workflows/image.yml index 27419b7..a43d289 100644 --- a/.github/workflows/image.yml +++ b/.github/workflows/image.yml @@ -21,7 +21,7 @@ jobs: # https://github.com/npm/cli/issues/558#issuecomment-580018468 # Error: "npm ERR! fsevents not accessible from jest-haste-map" # (supposed to just be a warning b/c optional dependency, but CI environment causes it to fail) - - run: npm ci + - run: npm i - name: Build codebase run: npm run build From 9bf44c160a52639094b8fd2fb3bcc387f527bd10 Mon Sep 17 00:00:00 2001 From: WatDuhHekBro <44940783+WatDuhHekBro@users.noreply.github.com> Date: Mon, 12 Apr 2021 12:43:13 -0500 Subject: [PATCH 172/178] Improved stream notifications --- src/commands/system/admin.ts | 44 +++++++- src/commands/utility/streaminfo.ts | 168 +++++++++++++++++++++++------ src/modules/streamNotifications.ts | 72 +++++++++++-- src/structures.ts | 43 ++++++++ 4 files changed, 279 insertions(+), 48 deletions(-) diff --git a/src/commands/system/admin.ts b/src/commands/system/admin.ts index 1ec78ef..5510ec8 100644 --- a/src/commands/system/admin.ts +++ b/src/commands/system/admin.ts @@ -9,7 +9,7 @@ import { } from "../../core"; import {clean} from "../../lib"; import {Config, Storage} from "../../structures"; -import {Permissions, TextChannel, User} from "discord.js"; +import {Permissions, TextChannel, User, Role} from "discord.js"; import {logs} from "../../modules/globals"; function getLogBuffer(type: string) { @@ -162,6 +162,46 @@ export default new NamedCommand({ send(`Successfully set this server's stream notifications channel to ${result}.`); } }) + }), + streamrole: new NamedCommand({ + description: "Sets/removes a stream notification role (and the corresponding category name)", + usage: "set/remove <...>", + run: "You need to enter in a role.", + subcommands: { + set: new NamedCommand({ + usage: " ", + id: "role", + role: new Command({ + run: "You need to enter a category name.", + any: new RestCommand({ + async run({send, guild, args, combined}) { + const role = args[0] as Role; + Storage.getGuild(guild!.id).streamingRoles[role.id] = combined; + Storage.save(); + send( + `Successfully set the category \`${combined}\` to notify \`${role.name}\`.` + ); + } + }) + }) + }), + remove: new NamedCommand({ + usage: "", + id: "role", + role: new Command({ + async run({send, guild, args}) { + const role = args[0] as Role; + const guildStorage = Storage.getGuild(guild!.id); + const category = guildStorage.streamingRoles[role.id]; + delete guildStorage.streamingRoles[role.id]; + Storage.save(); + send( + `Successfully removed the category \`${category}\` to notify \`${role.name}\`.` + ); + } + }) + }) + } }) } }), @@ -251,7 +291,7 @@ export default new NamedCommand({ run: "You have to enter some code to execute first.", any: new RestCommand({ // You have to bring everything into scope to use them. AFAIK, there isn't a more maintainable way to do this, but at least TS will let you know if anything gets removed. - async run({send, combined}) { + async run({send, message, channel, guild, author, member, client, args, combined}) { try { let evaled = eval(combined); if (typeof evaled !== "string") evaled = require("util").inspect(evaled); diff --git a/src/commands/utility/streaminfo.ts b/src/commands/utility/streaminfo.ts index b58b1f8..67d6197 100644 --- a/src/commands/utility/streaminfo.ts +++ b/src/commands/utility/streaminfo.ts @@ -1,44 +1,142 @@ import {NamedCommand, RestCommand} from "../../core"; import {streamList} from "../../modules/streamNotifications"; +import {Storage} from "../../structures"; +// Alternatively, I could make descriptions last outside of just one stream. +// But then again, users could just copy paste descriptions. :leaSMUG: +// Stream presets (for permanent parts of the description) might come some time in the future. export default new NamedCommand({ - description: "Sets the description of your stream. You can embed links by writing `[some name](some link)`", - async run({send, author, member}) { - const userID = author.id; + description: "Modifies the current embed for your stream", + run: "You need to specify whether to set the description or the image (`desc` and `img` respectively).", + subcommands: { + description: new NamedCommand({ + aliases: ["desc"], + description: + "Sets the description of your stream. You can embed links by writing `[some name](some link)` or remove it", + usage: "()", + async run({send, author}) { + const userID = author.id; - if (streamList.has(userID)) { - const stream = streamList.get(userID)!; - stream.description = "No description set."; - stream.update(); - send(`Successfully set the stream description to:`, { - embed: { - description: "No description set.", - color: member!.displayColor + if (streamList.has(userID)) { + const stream = streamList.get(userID)!; + stream.description = undefined; + stream.update(); + send("Successfully removed the stream description."); + } else { + send("You can only use this command when streaming."); } - }); - } else { - // Alternatively, I could make descriptions last outside of just one stream. - send("You can only use this command when streaming."); - } - }, - any: new RestCommand({ - async run({send, author, member, combined}) { - const userID = author.id; + }, + any: new RestCommand({ + async run({send, author, member, combined}) { + const userID = author.id; - if (streamList.has(userID)) { - const stream = streamList.get(userID)!; - stream.description = combined; - stream.update(); - send(`Successfully set the stream description to:`, { - embed: { - description: stream.description, - color: member!.displayColor + if (streamList.has(userID)) { + const stream = streamList.get(userID)!; + stream.description = combined; + stream.update(); + send("Successfully set the stream description to:", { + embed: { + description: stream.description, + color: member!.displayColor + } + }); + } else { + send("You can only use this command when streaming."); } - }); - } else { - // Alternatively, I could make descriptions last outside of just one stream. - send("You can only use this command when streaming."); - } - } - }) + } + }) + }), + thumbnail: new NamedCommand({ + aliases: ["img"], + description: "Sets a thumbnail to display alongside the embed or remove it", + usage: "()", + async run({send, author}) { + const userID = author.id; + + if (streamList.has(userID)) { + const stream = streamList.get(userID)!; + stream.thumbnail = undefined; + stream.update(); + send("Successfully removed the stream thumbnail."); + } else { + send("You can only use this command when streaming."); + } + }, + any: new RestCommand({ + async run({send, author, member, combined}) { + const userID = author.id; + + if (streamList.has(userID)) { + const stream = streamList.get(userID)!; + stream.thumbnail = combined; + stream.update(); + send(`Successfully set the stream thumbnail to: ${combined}`, { + embed: { + description: stream.description, + thumbnail: {url: combined}, + color: member!.displayColor + } + }); + } else { + send("You can only use this command when streaming."); + } + } + }) + }), + category: new NamedCommand({ + aliases: ["cat", "group"], + description: + "Sets the stream category any future streams will be in (as well as notification roles if set)", + usage: "()", + async run({send, guild, author}) { + const userID = author.id; + const memberStorage = Storage.getGuild(guild!.id).getMember(userID); + memberStorage.streamCategory = null; + Storage.save(); + send("Successfully removed the category for all your current and future streams."); + + // Then modify the current category if the user is streaming + if (streamList.has(userID)) { + const stream = streamList.get(userID)!; + stream.category = "None"; + stream.update(); + } + }, + any: new RestCommand({ + async run({send, guild, author, combined}) { + const userID = author.id; + const guildStorage = Storage.getGuild(guild!.id); + const memberStorage = guildStorage.getMember(userID); + let found = false; + + // Check if it's a valid category + for (const [roleID, categoryName] of Object.entries(guildStorage.streamingRoles)) { + if (combined === categoryName) { + found = true; + memberStorage.streamCategory = roleID; + Storage.save(); + send( + `Successfully set the category for your current and future streams to: \`${categoryName}\`` + ); + + // Then modify the current category if the user is streaming + if (streamList.has(userID)) { + const stream = streamList.get(userID)!; + stream.category = categoryName; + stream.update(); + } + } + } + + if (!found) { + send( + `No valid category found by \`${combined}\`! The available categories are: \`${Object.values( + guildStorage.streamingRoles + ).join(", ")}\`` + ); + } + } + }) + }) + } }); diff --git a/src/modules/streamNotifications.ts b/src/modules/streamNotifications.ts index b6e9fef..0577b46 100644 --- a/src/modules/streamNotifications.ts +++ b/src/modules/streamNotifications.ts @@ -1,12 +1,15 @@ -import {GuildMember, VoiceChannel, MessageEmbed, TextChannel, Permissions, Message, Collection} from "discord.js"; +import {GuildMember, VoiceChannel, MessageEmbed, TextChannel, Message, Collection} from "discord.js"; import {client} from "../index"; import {Storage} from "../structures"; type Stream = { streamer: GuildMember; channel: VoiceChannel; + category: string; description?: string; + thumbnail?: string; message: Message; + streamStart: number; update: () => void; }; @@ -14,10 +17,17 @@ type Stream = { export const streamList = new Collection(); // Probably find a better, DRY way of doing this. -function getStreamEmbed(streamer: GuildMember, channel: VoiceChannel, description?: string): MessageEmbed { +function getStreamEmbed( + streamer: GuildMember, + channel: VoiceChannel, + streamStart: number, + category: string, + description?: string, + thumbnail?: string +): MessageEmbed { const user = streamer.user; const embed = new MessageEmbed() - .setTitle(`Stream: \`#${channel.name}\``) + .setTitle(channel.name) .setAuthor( streamer.nickname ?? user.username, user.avatarURL({ @@ -25,11 +35,22 @@ function getStreamEmbed(streamer: GuildMember, channel: VoiceChannel, descriptio format: "png" }) ?? user.defaultAvatarURL ) - .setColor(streamer.displayColor); + // I decided to not include certain fields: + // .addField("Activity", "CrossCode", true) - Probably too much presence data involved, increasing memory usage. + // .addField("Viewers", 5, true) - There doesn't seem to currently be a way to track how many viewers there are. Presence data for "WATCHING" doesn't seem to affect it, and listening to raw client events doesn't seem to make it appear either. + .addField("Voice Channel", channel, true) + .addField("Category", category, true) + .setColor(streamer.displayColor) + .setFooter( + "Stream Started", + streamer.guild.iconURL({ + dynamic: true + }) || undefined + ) + .setTimestamp(streamStart); - if (description) { - embed.setDescription(description); - } + if (description) embed.setDescription(description); + if (thumbnail) embed.setThumbnail(thumbnail); return embed; } @@ -40,7 +61,7 @@ client.on("voiceStateUpdate", async (before, after) => { // Note: isStopStreamEvent can be called twice in a row - If Discord crashes/quits while you're streaming, it'll call once with a null channel and a second time with a channel. if (isStartStreamEvent || isStopStreamEvent) { - const {streamingChannel} = Storage.getGuild(after.guild.id); + const {streamingChannel, streamingRoles, members} = Storage.getGuild(after.guild.id); if (streamingChannel) { const member = after.member!; @@ -50,13 +71,42 @@ client.on("voiceStateUpdate", async (before, after) => { // Although checking the bot's permission to send might seem like a good idea, having the error be thrown will cause it to show up in the last channel rather than just show up in the console. if (textChannel instanceof TextChannel) { if (isStartStreamEvent) { + const streamStart = Date.now(); + let streamNotificationPing = ""; + let category = "None"; + + // Check the category if there's one set then ping that role. + if (member.id in members) { + const roleID = members[member.id].streamCategory; + + // Only continue if they set a valid category. + if (roleID && roleID in streamingRoles) { + streamNotificationPing = `<@&${roleID}>`; + category = streamingRoles[roleID]; + } + } + streamList.set(member.id, { streamer: member, channel: voiceChannel, - message: await textChannel.send(getStreamEmbed(member, voiceChannel)), + category, + message: await textChannel.send( + streamNotificationPing, + getStreamEmbed(member, voiceChannel, streamStart, category) + ), update(this: Stream) { - this.message.edit(getStreamEmbed(this.streamer, this.channel, this.description)); - } + this.message.edit( + getStreamEmbed( + this.streamer, + this.channel, + streamStart, + this.category, + this.description, + this.thumbnail + ) + ); + }, + streamStart }); } else if (isStopStreamEvent) { if (streamList.has(member.id)) { diff --git a/src/structures.ts b/src/structures.ts index 8991e3f..6da13a7 100644 --- a/src/structures.ts +++ b/src/structures.ts @@ -57,18 +57,30 @@ class User { } } +class Member { + public streamCategory: string | null; + + constructor(data?: GenericJSON) { + this.streamCategory = select(data?.streamCategory, null, String); + } +} + class Guild { public prefix: string | null; public welcomeType: "none" | "text" | "graphical"; public welcomeChannel: string | null; public welcomeMessage: string | null; public streamingChannel: string | null; + public streamingRoles: {[role: string]: string}; // Role ID: Category Name + public members: {[id: string]: Member}; constructor(data?: GenericJSON) { this.prefix = select(data?.prefix, null, String); this.welcomeChannel = select(data?.welcomeChannel, null, String); this.welcomeMessage = select(data?.welcomeMessage, null, String); this.streamingChannel = select(data?.streamingChannel, null, String); + this.streamingRoles = {}; + this.members = {}; switch (data?.welcomeType) { case "text": @@ -81,6 +93,37 @@ class Guild { this.welcomeType = "none"; break; } + + if (data?.streamingRoles) { + for (const id in data.streamingRoles) { + const category = data.streamingRoles[id]; + + if (/\d{17,}/g.test(id) && typeof category === "string") { + this.streamingRoles[id] = category; + } + } + } + + if (data?.members) { + for (let id in data.members) { + if (/\d{17,}/g.test(id)) { + this.members[id] = new Member(data.members[id]); + } + } + } + } + + /** Gets a member's profile if they exist and generate one if not. */ + public getMember(id: string): Member { + if (!/\d{17,}/g.test(id)) + console.warn(`"${id}" is not a valid user ID! It will be erased when the data loads again.`); + + if (id in this.members) return this.members[id]; + else { + const member = new Member(); + this.members[id] = member; + return member; + } } } From 793822f3d060c781e29e79dd850737ff11d0c1e2 Mon Sep 17 00:00:00 2001 From: WatDuhHekBro <44940783+WatDuhHekBro@users.noreply.github.com> Date: Mon, 12 Apr 2021 12:46:48 -0500 Subject: [PATCH 173/178] Fixed help command --- src/commands/system/help.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/commands/system/help.ts b/src/commands/system/help.ts index 588aa92..a9c4df8 100644 --- a/src/commands/system/help.ts +++ b/src/commands/system/help.ts @@ -1,5 +1,5 @@ import { - Command, + RestCommand, NamedCommand, CHANNEL_TYPE, getPermissionName, @@ -51,7 +51,7 @@ export default new NamedCommand({ .setColor(EMBED_COLOR); }); }, - any: new Command({ + any: new RestCommand({ async run({send, args}) { const resultingBlob = await getCommandInfo(args); if (typeof resultingBlob === "string") return send(resultingBlob); From 2dd776c86d3240d74dbafdf0044054b2a088d92b Mon Sep 17 00:00:00 2001 From: Keanu Date: Mon, 12 Apr 2021 20:42:16 +0200 Subject: [PATCH 174/178] Added functionality to remove docs embed. --- src/commands/utility/docs.ts | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/commands/utility/docs.ts b/src/commands/utility/docs.ts index 8c1d74b..0363355 100644 --- a/src/commands/utility/docs.ts +++ b/src/commands/utility/docs.ts @@ -7,11 +7,24 @@ export default new NamedCommand({ run: "You need to specify a term to query the docs with.", any: new RestCommand({ description: "What to query the docs with.", - async run({send, args}) { + async run({send, author, args}) { var queryString = args[0]; let url = new URL(`https://djsdocs.sorta.moe/v2/embed?src=master&q=${queryString}`); const content = await getContent(url.toString()); - return send({embed: content}); + const msg = await send({embed: content}); + const react = await msg.react("❌"); + + const collector = msg.createReactionCollector( + (reaction, user) => { + if (user.id === author.id && reaction.emoji.name === "❌") msg.delete(); + return false; + }, + {time: 60000} + ); + + collector.on("end", () => { + react.users.remove(msg.author); + }); } }) }); From 6243570eb36cc95c7e09de6ac4f91038408dce26 Mon Sep 17 00:00:00 2001 From: Keanu Date: Mon, 12 Apr 2021 20:46:20 +0200 Subject: [PATCH 175/178] Fixed bug in message collector. --- src/commands/utility/docs.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands/utility/docs.ts b/src/commands/utility/docs.ts index 0363355..c1a8e47 100644 --- a/src/commands/utility/docs.ts +++ b/src/commands/utility/docs.ts @@ -23,7 +23,7 @@ export default new NamedCommand({ ); collector.on("end", () => { - react.users.remove(msg.author); + if (!msg.deleted) react.users.remove(msg.author); }); } }) From b3ce4e513435ab3e6e7a5ce704145b4693791451 Mon Sep 17 00:00:00 2001 From: WatDuhHekBro <44940783+WatDuhHekBro@users.noreply.github.com> Date: Tue, 13 Apr 2021 07:38:52 -0500 Subject: [PATCH 176/178] Moved command handler code to Onion Lasers --- CHANGELOG.md | 6 + docs/DesignDecisions.md | 129 ----- docs/Documentation.md | 77 +-- docs/Overview.md | 171 +----- package-lock.json | 94 +++- package.json | 1 + src/commands/fun/8ball.ts | 2 +- src/commands/fun/cookie.ts | 2 +- src/commands/fun/eco.ts | 2 +- src/commands/fun/figlet.ts | 2 +- src/commands/fun/insult.ts | 2 +- src/commands/fun/love.ts | 2 +- src/commands/fun/modules/eco-bet.ts | 2 +- src/commands/fun/modules/eco-core.ts | 2 +- src/commands/fun/modules/eco-extras.ts | 2 +- src/commands/fun/modules/eco-shop.ts | 2 +- src/commands/fun/neko.ts | 2 +- src/commands/fun/ok.ts | 2 +- src/commands/fun/owoify.ts | 2 +- src/commands/fun/party.ts | 2 +- src/commands/fun/poll.ts | 2 +- src/commands/fun/ravi.ts | 2 +- src/commands/fun/thonk.ts | 2 +- src/commands/fun/urban.ts | 2 +- src/commands/fun/vaporwave.ts | 2 +- src/commands/fun/weather.ts | 2 +- src/commands/fun/whois.ts | 2 +- src/commands/system/admin.ts | 55 +- src/commands/system/help.ts | 2 +- src/commands/template.ts | 7 - src/commands/utility/calc.ts | 2 +- src/commands/utility/code.ts | 2 +- src/commands/utility/desc.ts | 2 +- src/commands/utility/docs.ts | 2 +- src/commands/utility/emote.ts | 2 +- src/commands/utility/info.ts | 2 +- src/commands/utility/invite.ts | 2 +- src/commands/utility/lsemotes.ts | 2 +- src/commands/utility/react.ts | 2 +- src/commands/utility/say.ts | 2 +- src/commands/utility/scanemotes.ts | 2 +- src/commands/utility/shorten.ts | 2 +- src/commands/utility/streaminfo.ts | 2 +- src/commands/utility/time.ts | 10 +- src/commands/utility/todo.ts | 2 +- src/commands/utility/translate.ts | 2 +- src/core/command.ts | 751 ------------------------- src/core/eventListeners.ts | 39 -- src/core/handler.ts | 138 ----- src/core/index.ts | 7 - src/core/interface.ts | 55 -- src/core/libd.ts | 390 ------------- src/core/loader.ts | 148 ----- src/core/permissions.ts | 26 - src/index.ts | 2 +- src/modules/messageEmbed.ts | 2 +- src/modules/setup.ts | 15 +- 57 files changed, 183 insertions(+), 2014 deletions(-) delete mode 100644 docs/DesignDecisions.md delete mode 100644 src/commands/template.ts delete mode 100644 src/core/command.ts delete mode 100644 src/core/eventListeners.ts delete mode 100644 src/core/handler.ts delete mode 100644 src/core/index.ts delete mode 100644 src/core/interface.ts delete mode 100644 src/core/libd.ts delete mode 100644 src/core/loader.ts delete mode 100644 src/core/permissions.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 34a876a..ca44f4e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +# 3.2.2 +- Moved command handler code to [Onion Lasers](https://github.com/WatDuhHekBro/OnionLasers) +- Reworked `poll` +- Extended stream notifications feature +- Fixed various bugs + # 3.2.1 - `vaporwave`: Transforms input into full-width text - `eco post`: A play on `eco get` diff --git a/docs/DesignDecisions.md b/docs/DesignDecisions.md deleted file mode 100644 index 584396c..0000000 --- a/docs/DesignDecisions.md +++ /dev/null @@ -1,129 +0,0 @@ -# Using the Command Class - -## any[] Parameters For Subcommand Run - -Unless there's some sort of TypeScript wizardry to solve this, the `args` parameter in the subcommand type will have to be `any[]` because it's simply too context-dependent to statically figure it out. -- Each subcommand is its own layer which doesn't know about parent commands at compile-time. -- Subcommands can be split into different files for code maintainability. -- Even though the last argument is able to be strongly-typed, if you have multiple parameters, you'd essentially only get static benefits for one of the arguments, and you wouldn't even know the location of that one argument. -- Overall, it's just easier to use your best judgement then use type assertions. - -## Channel Type Type Guards - -As of right now, it's currently not feasible to implement type guards for channel types. [Discriminated unions with a default parameter don't work with callbacks.](https://github.com/microsoft/TypeScript/issues/41759) In order to implement type guards, the `channelType` parameter would have to be required, making each command layer quite tedious. - -So instead, use non-null assertions when setting the `channelType`. For example: - -```ts -import {Command, NamedCommand, CHANNEL_TYPE} from "../core"; -import {TextChannel} from "discord.js"; - -export default new NamedCommand({ - channelType: CHANNEL_TYPE.GUILD, - async run({message, channel, guild, author, member, client, args}) { - console.log(guild!.name); - console.log(member!.nickname); - console.log((channel as TextChannel).name !== "dm"); - } -}); -``` - -```ts -import {Command, NamedCommand, CHANNEL_TYPE} from "../core"; -import {DMChannel} from "discord.js"; - -export default new NamedCommand({ - channelType: CHANNEL_TYPE.DM, - async run({message, channel, guild, author, member, client, args}) { - console.log(guild === null); - console.log(member === null); - console.log((channel as DMChannel).type === "dm"); - } -}); -``` - -The three guarantees are whether or not `guild` will be `null`, whether or not `member` will be `null`, and the type of `channel`. - -*Take note that `member` can still be `null` even in a guild (for example, if you target a message by someone who left), `member` cannot be `null` here because the `message` being sent must be by someone who is in the guild by this point.* - -## Uneven Return Paths - -`Command.run` doesn't use the return values for anything, so it's safe to do `return channel.send(...)` to merge those two statements. However, you'll come across an error: `Not all code paths return a value.` - -There are several ways to resolve this issue: -- Split all `return channel.send(...)` statements to `{channel.send(...); return;}` -- Set an explicit any return type in the function header: `async run(...): Promise {` -- Add an extra `return` statement at the end of each path - -## Type Guards - -The `Command` class is implemented in a certain way to provide type guards which reduce unnecessary properties at compile-time rather than warning the user at runtime. -- The reason `NamedCommand` (which extends `Command`) exists is to provide a type guard for `aliases`. After all, `aliases` doesn't really make sense for generic subcommand types - how would you handle an alias for a type that accepts a number for example? -- The `endpoint` property changes what other properties are available via a discriminated union. If `endpoint` is `true`, no subcommands of any type can be defined. After all, it wouldn't make sense logically. - -## Boolean Types - -Boolean subcommand types won't be implemented: -- Since there are only two values, why not just put it under `subcommands`? -- If boolean types were to be implemented, how many different types of input would have to be considered? `yes`/`no`, `y`/`n`, `true`/`false`, `1`/`0`, etc. - -## Hex and Octal Number Types - -For common use cases, there wouldn't be a need to go accept numbers of different bases. The only time it would be applicable is if there was some sort of base converter command, and even then, it'd be better to just implement custom logic. - -## User Mention + Search by Username Type - -While it's a pretty common pattern, it's probably a bit too specific for the `Command` class itself. Instead, this pattern will be comprised of two subcommands: A `user` type and an `any` type. - -# The Command Handler - -## The Scope of the Command Handler - -What this does: -- Provides the `Command`/`NamedCommand` classes. -- Dynamically loads commands and attaches runtime metadata. -- Provides utility functions specific to Discord to make certain patterns of commands less tedious to implement. - -What this doesn't do: -- Manage the general file system or serialization/deserialization of data. -- Provide general utility functions. -- Provide any Discord-related functionality besides strictly command handling. - -## Client Creation - -Creating the client is beyond the scope of the command handler and will not be abstracted away. Instead, the user will simply attach the command handler to the client to initialize it. -- This makes it so if a user wants to specify their own `ClientOptions` when instantiating the client, it's less troublesome to implement. -- The user can export the client and use it throughout different parts of their code. - -## Bot-Specific Mentions - -Pinging the bot will display the current guild prefix. The bot mention will not serve as an alternate prefix. -- When talking about a bot, the bot might be pinged to show who it is. It could be in the middle (so don't listen for a prefix anywhere) or it could be at the start (so only listen to a standalone ping). -- It likely isn't a common use case to ping the bot. The only time it would really shine is in the event two bots have a prefix conflict, but the command that changes prefixes can simply add a parameter to deal with that case. For example, instead of `@bot set prefix `, you'd use `set prefix @bot`. - -## Direct Messages - -When direct messaging a bot, no prefixes will be used at all because it's assumed that you're executing a command. Because the only people allowed is the user and the bot, NSFW-only commands can also be executed here. - -## Permission Setup - -Because the command handler provides no specific permission set, it's up to the user to come up with functions to add permissions as well as create the enum that assigns permissions. -- The `permission` property of a `Command` instance is `-1` by default, which means to inherit the permission level from the parent command. If you want, you can create your enum like this: `enum Permissions {INHERIT = -1, USER, ADMIN}`, where `Permissions.USER = 0` and `Permissions.ADMIN = 1`. - -# Miscellaneous - -## Static Event Loading - -While dynamic loading fits very well with commands, it was more or less clunky design to try and make events fit the same model: -- There are no restrictions when it comes to command names, and the name of the file will determine the name of the command, which avoids repetition. Events on the other hand involved lots of boilerplate to get static types back. -- Since there can be multiple listeners per event, large event files can be split up into more organized blocks. -- Likewise, small event listeners which span multiple events can be grouped together like `channelCreate` and `channelDelete`, showing the relation in one single file rather than splitting them up just because they're two different events. - -## Testing - -For TravBot, there'll be two types of tests: standard unit tests and manual integration tests. -- Standard unit tests are executed only on isolated functions and are part of the pre-commit hook. -- Somehow, including the bot in an import chain will cause the system to crash (same error message as [this](https://stackoverflow.com/questions/66102858/discord-clientuser-is-not-a-constructor)). That's why the integration tests are manually done. There would be a list of inputs and outputs to check of each command for tests while simultaneously serving as a help menu with examples of all possible inputs/outputs for others to see. -- An idea which will not be implemented is prompting the user for inputs during the tests. This is no better than manual tests, worse actually, because if this had to run before each commit, it'd quickly become a nightmare. -- Maybe take some ideas from something like [this](https://github.com/stuyy/jest-unit-tests-demo) in the future to get tests to properly work. -- Another possibility is to use `client.emit(...)` then mock the `message.channel.send(...)` function which would listen if the input is correct. diff --git a/docs/Documentation.md b/docs/Documentation.md index 173f686..e0081f2 100644 --- a/docs/Documentation.md +++ b/docs/Documentation.md @@ -2,11 +2,8 @@ - [Structure](#structure) - [Version Numbers](#version-numbers) -- [Message Subcommand Type](#message-subcommand-type) -- [Command Menu](#command-menu) -- [Command Metadata](#command-metadata) -- [Command Var String](#command-var-string) - [Utility Functions](#utility-functions) +- [Testing](#testing) # Structure @@ -37,71 +34,8 @@ Because versions are assigned to batches of changes rather than single changes ( *Note: This system doesn't retroactively apply to TravBot-v2, which is why this version naming system won't make sense for v2's changelog.* -# Message Subcommand Type - -- `https://discord.com/channels///` comes from the `Copy Message Link` button. -- `-` comes from holding `Shift` on the desktop application and clicking on the `Copy ID` button. - -# Command Menu - -- `args`: A list of arguments in the command. It's relative to the subcommand, so if you do `$test this 5`, `5` becomes `$.args[0]` if `this` is a subcommand. Args are already converted, so a `number` subcommand would return a number rather than a string. -- `client`: `message.client` -- `message`: `message` -- `channel`: `message.channel` -- `guild`: `message.guild` -- `author`: `message.author` -- `member`: `message.member` - -# Command Metadata - -- `description`: The command description that'll appear in the help menu. -- `endpoint`: Whether or not any arguments are allowed after the command. -- `usage`: Defines a custom usage when showing the command in the help menu. -- `permission`: *(Inherits)* -1 (default) indicates to inherit, 0 is the lowest rank, 1 is second lowest rank, and so on. -- `nsfw`: *(Inherits)* Whether or not the command is restricted to NSFW channels and DM channels. -- `channelType`: *(Inherits)* Whether the command is restricted to guild channels, DM channels, or has no restriction. Uses the `CHANNEL_TYPE` enum provided by the command handler. - -# Command Var String - -- `%author%` - A user mention of the person who called the command. -- `%prefix%` - The prefix of the current guild. -- `%command%` - The command's execution path up to the current subcommand. - # Utility Functions -## [src/core (libd)](../src/core/libd.ts) - Utility functions specific for working with Discord - -`paginate()` -```ts -const pages = ["one", "two", "three"]; - -paginate(send, author.id, pages.length, page => { - return {content: pages[page]}; -}); -``` - -`poll()` -```ts -const results = await poll(await send("Do you agree with this decision?"), ["✅", "❌"]); -results["✅"]; // number -results["❌"]; // number -``` - -`confirm()` -```ts -const result = await confirm(await send("Are you sure you want to delete this?"), author.id); // boolean | null -``` - -`askMultipleChoice()` -```ts -const result = await askMultipleChoice(await send("Which of the following numbers is your favorite?"), author.id, 4, 10000); // number (0 to 3) | null -``` - -`askForReply()` -```ts -const reply = await askForReply(await send("What is your favorite thing to do?"), author.id, 10000); // Message | null -``` - ## [src/lib](../src/lib.ts) - General utility functions - `parseArgs()`: Turns `call test "args with spaces" "even more spaces"` into `["call", "test", "args with spaces", "even more spaces"]`, inspired by the command line. @@ -115,3 +49,12 @@ const reply = await askForReply(await send("What is your favorite thing to do?") - `toTitleCase()`: Capitalizes the first letter of each word. `toTitleCase("this is some text")` = `"This Is Some Text"`. - `random()`: Returns a random element from an array. `random([1,2,3])` could be any one of those elements. - `split()`: Splits an array into different arrays by a specified length. `split([1,2,3,4,5,6,7,8,9,10], 3)` = `[[1,2,3],[4,5,6],[7,8,9],[10]]`. + +# Testing + +For TravBot, there'll be two types of tests: standard unit tests and manual integration tests. +- Standard unit tests are executed only on isolated functions and are part of the pre-commit hook. +- Somehow, including the bot in an import chain will cause the system to crash (same error message as [this](https://stackoverflow.com/questions/66102858/discord-clientuser-is-not-a-constructor)). That's why the integration tests are manually done. There would be a list of inputs and outputs to check of each command for tests while simultaneously serving as a help menu with examples of all possible inputs/outputs for others to see. +- An idea which will not be implemented is prompting the user for inputs during the tests. This is no better than manual tests, worse actually, because if this had to run before each commit, it'd quickly become a nightmare. +- Maybe take some ideas from something like [this](https://github.com/stuyy/jest-unit-tests-demo) in the future to get tests to properly work. +- Another possibility is to use `client.emit(...)` then mock the `message.channel.send(...)` function which would listen if the input is correct. diff --git a/docs/Overview.md b/docs/Overview.md index 31935f1..620f832 100644 --- a/docs/Overview.md +++ b/docs/Overview.md @@ -7,7 +7,7 @@ # Introduction -This is a brief overview that'll describe where and how to add new features to TravBot. For more details on specific functions, head over to the [documentation](Documentation.md). Assume the prefix for all of these examples is `$`. +This is a brief overview that'll describe where and how to add new features to TravBot. For more details on specific functions, head over to the [documentation](Documentation.md). TravBot uses the [Onion Lasers Command Handler](https://github.com/WatDuhHekBro/OnionLasers) to load and setup commands. Also, if you ever want to see the definition of a function or its surrounding types and you're using VSCode, put your cursor at the word you want to go to and press `[F12]`. # Setting up the development environment @@ -20,175 +20,18 @@ This is a brief overview that'll describe where and how to add new features to T # Adding a new command -To add a new command, go to `src/commands` and either copy the [template](../src/commands/template.ts) or rename the auto-generated test file (`../src/commands/test.ts`). For reference, this is the barebones requirement for a command file. - -## The very basics of a command +To add a new command, go to `src/commands` and create a new `.ts` file named as the command name. Then, use and expand upon the following template. ```ts -import {NamedCommand} from "../core"; - -export default new NamedCommand(); -``` - -To make something actually happen when the command is run however, you implement the `run` property. - -```ts -import {Command, NamedCommand} from "../core"; +import {Command, NamedCommand, RestCommand} from "onion-lasers"; export default new NamedCommand({ - async run({message, channel, guild, author, member, client, args}) { - channel.send("test"); - } + async run({send, message, channel, guild, author, member, client, args}) { + // code + } }); ``` -### Quick note on the run property - -You can also enter a string for the `run` property which will send a message with that string specified ([you can also specify some variables in that string](Documentation.md#command-var-string)). The above is functionally equivalent to the below. - -```ts -import {Command, NamedCommand} from "../core"; - -export default new NamedCommand({ - run: "test" -}); -``` - -## Introducing subcommands - -Where this command handler really shines though is from its subcommands feature. You can filter and parse argument lists in a declarative manner. - -```ts -import {Command, NamedCommand} from "../core"; - -export default new NamedCommand({ - user: new Command({ - async run({message, channel, guild, author, member, client, args}) { - const user = args[0]; - } - }) -}); -``` - -Here, . For example, if this file was named `test.ts`, `$test <@237359961842253835>` would get the user by the ID `237359961842253835` into `args[0]` as a [User](https://discord.js.org/#/docs/main/stable/class/User) object. `$test experiment` would run as if you just called `$test` *(given that [endpoint](Documentation.md#command-metadata) isn't set to `true`)*. - -If you want, you can typecast the argument to be more strongly typed, because the type of `args` is `any[]`. *([See why if you're curious.](DesignDecisions.md#any[]-parameters-for-subcommand-run))* - -```ts -import {Command, NamedCommand} from "../core"; -import {User} from "discord.js"; - -export default new NamedCommand({ - user: new Command({ - async run({message, channel, guild, author, member, client, args}) { - const user = args[0] as User; - } - }) -}); -``` - -## Keyed subcommands - -For keyed subcommands, you would instead use a `NamedCommand`. - -```ts -import {Command, NamedCommand} from "../core"; - -export default new NamedCommand({ - run: "one", - subcommands: { - bread: new NamedCommand({ - run: "two" - }) - } -}); -``` - -If the file was named `cat.ts`: -- `$cat` would output `one` -- `$cat bread` would output `two` - -Only `bread` in this case would lead to `two` being the output, which is different from the generic subcommand types in previous examples. - -You get an additional property with `NamedCommand`s: `aliases`. That means you can define aliases not only for top-level commands, but also every layer of subcommands. - -```ts -import {Command, NamedCommand} from "../core"; - -export default new NamedCommand({ - aliases: ["potato"], - subcommands: { - slice: new NamedCommand({ - aliases: ["pear"] - }) - } -}); -``` - -For example, if this file was named `plant.ts`, the following would work: -- `$plant` -- `$potato` -- `$plant slice` -- `$plant pear` -- `$potato slice` -- `$potato pear` - -## Metadata / Command Properties - -You can also specify metadata for commands by adding additional properties. Some of these properties are per-command while others are inherited. - -```ts -import {Command, NamedCommand} from "../core"; - -export default new NamedCommand({ - description: "desc one", - subcommands: { - pineapple: new NamedCommand({ - //... - }) - } -}); -``` - -`description` is an example of a per-command property (which is used in a help command). If the file was named `siege.ts`: -- The description of `$siege` would be `desc one`. -- There wouldn't be a description for `$siege pineapple`. - -This is in contrast to inherited properties. - -```ts -import {Command, NamedCommand, CHANNEL_TYPE} from "../core"; - -export default new NamedCommand({ - channelType: CHANNEL_TYPE.GUILD, - subcommands: { - pineapple: new NamedCommand({ - //... - }) - } -}); -``` - -Here, the property `channelType` would spread to all subcommands unless a subcommand defines it. Using the above example, the `channelType` for both `$siege` and `$siege pineapple` would be `CHANNEL_TYPE.GUILD`. - -*To get a full list of metadata properties, see the [documentation](Documentation.md#command-menu).* - -## Utility Functions - -You'll have to import these manually, however it's in the same import list as `Command` and `NamedCommand`. - -```ts -import {Command, NamedCommand, paginate} from "../core"; - -export default new NamedCommand({ - async run({message, channel, guild, author, member, client, args}) { - paginate(/* enter your code here */); - } -}); -``` - -*To get a full list of utility functions, see the [documentation](Documentation.md#utility-functions).* - # Adding a new non-command feature If the feature you want to add isn't specifically a command, or the change you're making involves adding event listeners, go to `src/modules` and create a file. Code written here won't be automatically loaded, so make sure to open [src/index.ts](../src/index.ts) and add an import statement at the bottom. @@ -201,7 +44,7 @@ This will just run whatever code is in there. ## Listening for events -Rather than have an `events` folder which contains dynamically loaded events, you add an event listener directly via `client.on("...", () => {})`. *([See why if you're curious.](DesignDecisions.md#static-event-loading))* The client can be imported from the index file. +Rather than have an `events` folder which contains dynamically loaded events, you add an event listener directly via `client.on("...", () => {})`. *([See why if you're curious.](https://github.com/WatDuhHekBro/OnionLasers/blob/master/README.md#static-event-loading))* The client can be imported from the index file. ```ts import {client} from ".."; diff --git a/package-lock.json b/package-lock.json index 344651c..ea19ad8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,7 @@ "mathjs": "^9.3.0", "moment": "^2.29.1", "ms": "^2.1.3", + "onion-lasers": "^1.0.0", "relevant-urban": "^2.0.0", "translate-google": "^1.4.3", "weather-js": "^2.0.0" @@ -39,6 +40,9 @@ "ts-jest": "^26.4.4", "tsc-watch": "^4.2.9", "typescript": "^3.9.7" + }, + "optionalDependencies": { + "fsevents": "^2.1.2" } }, "node_modules/@babel/code-frame": { @@ -2275,18 +2279,18 @@ } }, "node_modules/discord.js": { - "version": "12.5.1", - "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-12.5.1.tgz", - "integrity": "sha512-VwZkVaUAIOB9mKdca0I5MefPMTQJTNg0qdgi1huF3iwsFwJ0L5s/Y69AQe+iPmjuV6j9rtKoG0Ta0n9vgEIL6w==", + "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.2", + "prism-media": "^1.2.9", "setimmediate": "^1.0.5", "tweetnacl": "^1.0.3", - "ws": "^7.3.1" + "ws": "^7.4.4" }, "engines": { "node": ">=12.0.0" @@ -2925,6 +2929,19 @@ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", @@ -5388,6 +5405,15 @@ "node": ">=6" } }, + "node_modules/onion-lasers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/onion-lasers/-/onion-lasers-1.0.0.tgz", + "integrity": "sha512-vVpywipeVUBMffvhxTeq8i+kuIO3zV+t2RQcPFo7XiUjfARR6Kq4CGyPhi9EhVXmylzwSDH+HzBzxZcdfajqng==", + "dependencies": { + "discord.js": "^12.5.3", + "glob": "^7.1.6" + } + }, "node_modules/optionator": { "version": "0.8.3", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", @@ -5678,9 +5704,29 @@ } }, "node_modules/prism-media": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/prism-media/-/prism-media-1.2.3.tgz", - "integrity": "sha512-fSrR66n0l6roW9Rx4rSLMyTPTjRTiXy5RVqDOurACQ6si1rKHHKDU5gwBJoCsIV0R3o9gi+K50akl/qyw1C74A==" + "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/process-nextick-args": { "version": "2.0.1", @@ -9922,18 +9968,18 @@ "dev": true }, "discord.js": { - "version": "12.5.1", - "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-12.5.1.tgz", - "integrity": "sha512-VwZkVaUAIOB9mKdca0I5MefPMTQJTNg0qdgi1huF3iwsFwJ0L5s/Y69AQe+iPmjuV6j9rtKoG0Ta0n9vgEIL6w==", + "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.2", + "prism-media": "^1.2.9", "setimmediate": "^1.0.5", "tweetnacl": "^1.0.3", - "ws": "^7.3.1" + "ws": "^7.4.4" } }, "discord.js-lavalink-lib": { @@ -10437,6 +10483,12 @@ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "optional": true + }, "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", @@ -12372,6 +12424,15 @@ "mimic-fn": "^2.1.0" } }, + "onion-lasers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/onion-lasers/-/onion-lasers-1.0.0.tgz", + "integrity": "sha512-vVpywipeVUBMffvhxTeq8i+kuIO3zV+t2RQcPFo7XiUjfARR6Kq4CGyPhi9EhVXmylzwSDH+HzBzxZcdfajqng==", + "requires": { + "discord.js": "^12.5.3", + "glob": "^7.1.6" + } + }, "optionator": { "version": "0.8.3", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", @@ -12583,9 +12644,10 @@ } }, "prism-media": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/prism-media/-/prism-media-1.2.3.tgz", - "integrity": "sha512-fSrR66n0l6roW9Rx4rSLMyTPTjRTiXy5RVqDOurACQ6si1rKHHKDU5gwBJoCsIV0R3o9gi+K50akl/qyw1C74A==" + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/prism-media/-/prism-media-1.2.9.tgz", + "integrity": "sha512-UHCYuqHipbTR1ZsXr5eg4JUmHER8Ss4YEb9Azn+9zzJ7/jlTtD1h0lc4g6tNx3eMlB8Mp6bfll0LPMAV4R6r3Q==", + "requires": {} }, "process-nextick-args": { "version": "2.0.1", diff --git a/package.json b/package.json index 3da03e0..d4bc26f 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "mathjs": "^9.3.0", "moment": "^2.29.1", "ms": "^2.1.3", + "onion-lasers": "^1.0.0", "relevant-urban": "^2.0.0", "translate-google": "^1.4.3", "weather-js": "^2.0.0" diff --git a/src/commands/fun/8ball.ts b/src/commands/fun/8ball.ts index 66d2aa6..fc4358e 100644 --- a/src/commands/fun/8ball.ts +++ b/src/commands/fun/8ball.ts @@ -1,4 +1,4 @@ -import {NamedCommand, RestCommand} from "../../core"; +import {NamedCommand, RestCommand} from "onion-lasers"; import {random} from "../../lib"; const responses = [ diff --git a/src/commands/fun/cookie.ts b/src/commands/fun/cookie.ts index 3ca68b9..4e5c3af 100644 --- a/src/commands/fun/cookie.ts +++ b/src/commands/fun/cookie.ts @@ -1,5 +1,5 @@ import {User} from "discord.js"; -import {Command, NamedCommand} from "../../core"; +import {Command, NamedCommand} from "onion-lasers"; import {random, parseVars} from "../../lib"; const cookies = [ diff --git a/src/commands/fun/eco.ts b/src/commands/fun/eco.ts index 4644101..0ece25e 100644 --- a/src/commands/fun/eco.ts +++ b/src/commands/fun/eco.ts @@ -1,4 +1,4 @@ -import {Command, NamedCommand, getMemberByName, RestCommand} from "../../core"; +import {Command, NamedCommand, getMemberByName, RestCommand} from "onion-lasers"; import {isAuthorized, getMoneyEmbed} from "./modules/eco-utils"; import {DailyCommand, PayCommand, GuildCommand, LeaderboardCommand} from "./modules/eco-core"; import {BuyCommand, ShopCommand} from "./modules/eco-shop"; diff --git a/src/commands/fun/figlet.ts b/src/commands/fun/figlet.ts index b2f9bb0..8764ff4 100644 --- a/src/commands/fun/figlet.ts +++ b/src/commands/fun/figlet.ts @@ -1,4 +1,4 @@ -import {NamedCommand, RestCommand} from "../../core"; +import {NamedCommand, RestCommand} from "onion-lasers"; import figlet from "figlet"; export default new NamedCommand({ diff --git a/src/commands/fun/insult.ts b/src/commands/fun/insult.ts index 251444a..76491b3 100644 --- a/src/commands/fun/insult.ts +++ b/src/commands/fun/insult.ts @@ -1,4 +1,4 @@ -import {NamedCommand} from "../../core"; +import {NamedCommand} from "onion-lasers"; export default new NamedCommand({ description: "Insult TravBot! >:D", diff --git a/src/commands/fun/love.ts b/src/commands/fun/love.ts index 25a7c8a..936b51d 100644 --- a/src/commands/fun/love.ts +++ b/src/commands/fun/love.ts @@ -1,4 +1,4 @@ -import {NamedCommand, CHANNEL_TYPE} from "../../core"; +import {NamedCommand, CHANNEL_TYPE} from "onion-lasers"; export default new NamedCommand({ description: "Chooses someone to love.", diff --git a/src/commands/fun/modules/eco-bet.ts b/src/commands/fun/modules/eco-bet.ts index 3bcb103..0fb6096 100644 --- a/src/commands/fun/modules/eco-bet.ts +++ b/src/commands/fun/modules/eco-bet.ts @@ -1,4 +1,4 @@ -import {Command, NamedCommand, confirm, poll} from "../../../core"; +import {Command, NamedCommand, confirm, poll} from "onion-lasers"; import {pluralise} from "../../../lib"; import {Storage} from "../../../structures"; import {isAuthorized, getMoneyEmbed} from "./eco-utils"; diff --git a/src/commands/fun/modules/eco-core.ts b/src/commands/fun/modules/eco-core.ts index 4e0a520..ce95785 100644 --- a/src/commands/fun/modules/eco-core.ts +++ b/src/commands/fun/modules/eco-core.ts @@ -1,4 +1,4 @@ -import {Command, getMemberByName, NamedCommand, confirm, RestCommand} from "../../../core"; +import {Command, getMemberByName, NamedCommand, confirm, RestCommand} from "onion-lasers"; import {pluralise} from "../../../lib"; import {Storage} from "../../../structures"; import {isAuthorized, getMoneyEmbed, getSendEmbed, ECO_EMBED_COLOR} from "./eco-utils"; diff --git a/src/commands/fun/modules/eco-extras.ts b/src/commands/fun/modules/eco-extras.ts index aeda4d0..d3a3519 100644 --- a/src/commands/fun/modules/eco-extras.ts +++ b/src/commands/fun/modules/eco-extras.ts @@ -1,4 +1,4 @@ -import {Command, NamedCommand} from "../../../core"; +import {Command, NamedCommand} from "onion-lasers"; import {Storage} from "../../../structures"; import {isAuthorized, getMoneyEmbed} from "./eco-utils"; import {User} from "discord.js"; diff --git a/src/commands/fun/modules/eco-shop.ts b/src/commands/fun/modules/eco-shop.ts index b4cf1a5..8092854 100644 --- a/src/commands/fun/modules/eco-shop.ts +++ b/src/commands/fun/modules/eco-shop.ts @@ -1,4 +1,4 @@ -import {Command, NamedCommand, paginate, RestCommand} from "../../../core"; +import {Command, NamedCommand, paginate, RestCommand} from "onion-lasers"; import {pluralise, split} from "../../../lib"; import {Storage, getPrefix} from "../../../structures"; import {isAuthorized, ECO_EMBED_COLOR} from "./eco-utils"; diff --git a/src/commands/fun/neko.ts b/src/commands/fun/neko.ts index f430406..55aa592 100644 --- a/src/commands/fun/neko.ts +++ b/src/commands/fun/neko.ts @@ -1,5 +1,5 @@ import {URL} from "url"; -import {Command, NamedCommand} from "../../core"; +import {Command, NamedCommand} from "onion-lasers"; import {getContent} from "../../lib"; const endpoints: {sfw: {[key: string]: string}} = { diff --git a/src/commands/fun/ok.ts b/src/commands/fun/ok.ts index 13296a3..73c368b 100644 --- a/src/commands/fun/ok.ts +++ b/src/commands/fun/ok.ts @@ -1,4 +1,4 @@ -import {NamedCommand} from "../../core"; +import {NamedCommand} from "onion-lasers"; import {random} from "../../lib"; const responses = [ diff --git a/src/commands/fun/owoify.ts b/src/commands/fun/owoify.ts index 3cc2dd5..5819f09 100644 --- a/src/commands/fun/owoify.ts +++ b/src/commands/fun/owoify.ts @@ -1,4 +1,4 @@ -import {NamedCommand, RestCommand} from "../../core"; +import {NamedCommand, RestCommand} from "onion-lasers"; import {getContent} from "../../lib"; import {URL} from "url"; diff --git a/src/commands/fun/party.ts b/src/commands/fun/party.ts index ce58b13..179e23d 100644 --- a/src/commands/fun/party.ts +++ b/src/commands/fun/party.ts @@ -1,4 +1,4 @@ -import {NamedCommand} from "../../core"; +import {NamedCommand} from "onion-lasers"; export default new NamedCommand({ description: "Initiates a celebratory stream from the bot.", diff --git a/src/commands/fun/poll.ts b/src/commands/fun/poll.ts index 5e48a60..3224d08 100644 --- a/src/commands/fun/poll.ts +++ b/src/commands/fun/poll.ts @@ -1,5 +1,5 @@ import {MessageEmbed, Message, User} from "discord.js"; -import {NamedCommand, RestCommand, poll, CHANNEL_TYPE, SendFunction, Command} from "../../core"; +import {NamedCommand, RestCommand, poll, CHANNEL_TYPE, SendFunction, Command} from "onion-lasers"; import {pluralise} from "../../lib"; export default new NamedCommand({ diff --git a/src/commands/fun/ravi.ts b/src/commands/fun/ravi.ts index ff64376..b1b0b4c 100644 --- a/src/commands/fun/ravi.ts +++ b/src/commands/fun/ravi.ts @@ -1,4 +1,4 @@ -import {Command, NamedCommand} from "../../core"; +import {Command, NamedCommand} from "onion-lasers"; import {Random} from "../../lib"; export default new NamedCommand({ diff --git a/src/commands/fun/thonk.ts b/src/commands/fun/thonk.ts index c81a9e7..76e3d17 100644 --- a/src/commands/fun/thonk.ts +++ b/src/commands/fun/thonk.ts @@ -1,4 +1,4 @@ -import {NamedCommand, RestCommand} from "../../core"; +import {NamedCommand, RestCommand} from "onion-lasers"; const letters: {[letter: string]: string[]} = { a: "aáàảãạâấầẩẫậăắằẳẵặ".split(""), diff --git a/src/commands/fun/urban.ts b/src/commands/fun/urban.ts index 9964008..94aca51 100644 --- a/src/commands/fun/urban.ts +++ b/src/commands/fun/urban.ts @@ -1,4 +1,4 @@ -import {NamedCommand, RestCommand} from "../../core"; +import {NamedCommand, RestCommand} from "onion-lasers"; import {MessageEmbed} from "discord.js"; import urban from "relevant-urban"; diff --git a/src/commands/fun/vaporwave.ts b/src/commands/fun/vaporwave.ts index 6486c25..7997ba2 100644 --- a/src/commands/fun/vaporwave.ts +++ b/src/commands/fun/vaporwave.ts @@ -1,4 +1,4 @@ -import {NamedCommand, RestCommand} from "../../core"; +import {NamedCommand, RestCommand} from "onion-lasers"; const vaporwave = (() => { const map = new Map(); diff --git a/src/commands/fun/weather.ts b/src/commands/fun/weather.ts index b30daa1..9246dc1 100644 --- a/src/commands/fun/weather.ts +++ b/src/commands/fun/weather.ts @@ -1,4 +1,4 @@ -import {NamedCommand, RestCommand} from "../../core"; +import {NamedCommand, RestCommand} from "onion-lasers"; import {MessageEmbed} from "discord.js"; import {find} from "weather-js"; diff --git a/src/commands/fun/whois.ts b/src/commands/fun/whois.ts index 92d29d3..86d7360 100644 --- a/src/commands/fun/whois.ts +++ b/src/commands/fun/whois.ts @@ -1,5 +1,5 @@ import {User} from "discord.js"; -import {Command, NamedCommand, getMemberByName, CHANNEL_TYPE, RestCommand} from "../../core"; +import {Command, NamedCommand, getMemberByName, CHANNEL_TYPE, RestCommand} from "onion-lasers"; // Quotes must be used here or the numbers will change const registry: {[id: string]: string} = { diff --git a/src/commands/system/admin.ts b/src/commands/system/admin.ts index 5510ec8..45ff624 100644 --- a/src/commands/system/admin.ts +++ b/src/commands/system/admin.ts @@ -1,15 +1,7 @@ -import { - Command, - NamedCommand, - botHasPermission, - getPermissionLevel, - getPermissionName, - CHANNEL_TYPE, - RestCommand -} from "../../core"; +import {Command, NamedCommand, getPermissionLevel, getPermissionName, CHANNEL_TYPE, RestCommand} from "onion-lasers"; import {clean} from "../../lib"; import {Config, Storage} from "../../structures"; -import {Permissions, TextChannel, User, Role} from "discord.js"; +import {Permissions, TextChannel, User, Role, Channel} from "discord.js"; import {logs} from "../../modules/globals"; function getLogBuffer(type: string) { @@ -111,10 +103,15 @@ export default new NamedCommand({ id: "channel", channel: new Command({ async run({send, guild, args}) { - const result = args[0] as TextChannel; - Storage.getGuild(guild!.id).welcomeChannel = result.id; - Storage.save(); - send(`Successfully set this server's welcome channel to ${result}.`); + const result = args[0] as Channel; + + if (result instanceof TextChannel) { + Storage.getGuild(guild!.id).welcomeChannel = result.id; + Storage.save(); + send(`Successfully set this server's welcome channel to ${result}.`); + } else { + send(`\`${result.id}\` is not a valid text channel!`); + } } }) }), @@ -156,10 +153,15 @@ export default new NamedCommand({ id: "channel", channel: new Command({ async run({send, guild, args}) { - const result = args[0] as TextChannel; - Storage.getGuild(guild!.id).streamingChannel = result.id; - Storage.save(); - send(`Successfully set this server's stream notifications channel to ${result}.`); + const result = args[0] as Channel; + + if (result instanceof TextChannel) { + Storage.getGuild(guild!.id).streamingChannel = result.id; + Storage.save(); + send(`Successfully set this server's stream notifications channel to ${result}.`); + } else { + send(`\`${result.id}\` is not a valid text channel!`); + } } }) }), @@ -250,7 +252,7 @@ export default new NamedCommand({ channelType: CHANNEL_TYPE.GUILD, async run({send, message, channel, guild, client}) { // It's probably better to go through the bot's own messages instead of calling bulkDelete which requires MANAGE_MESSAGES. - if (botHasPermission(guild, Permissions.FLAGS.MANAGE_MESSAGES)) { + if (guild!.me?.hasPermission(Permissions.FLAGS.MANAGE_MESSAGES)) { message.delete(); const msgs = await channel.messages.fetch({ limit: 100 @@ -310,7 +312,7 @@ export default new NamedCommand({ any: new RestCommand({ async run({send, message, guild, combined}) { await guild!.me?.setNickname(combined); - if (botHasPermission(guild, Permissions.FLAGS.MANAGE_MESSAGES)) message.delete({timeout: 5000}); + if (guild!.me?.hasPermission(Permissions.FLAGS.MANAGE_MESSAGES)) message.delete({timeout: 5000}); send(`Nickname set to \`${combined}\``).then((m) => m.delete({timeout: 5000})); } }) @@ -363,10 +365,15 @@ export default new NamedCommand({ }, channel: new Command({ async run({send, args}) { - const targetChannel = args[0] as TextChannel; - Config.systemLogsChannel = targetChannel.id; - Config.save(); - send(`Successfully set ${targetChannel} as the system logs channel.`); + const targetChannel = args[0] as Channel; + + if (targetChannel instanceof TextChannel) { + Config.systemLogsChannel = targetChannel.id; + Config.save(); + send(`Successfully set ${targetChannel} as the system logs channel.`); + } else { + send(`\`${targetChannel.id}\` is not a valid text channel!`); + } } }) }) diff --git a/src/commands/system/help.ts b/src/commands/system/help.ts index a9c4df8..f1d9b51 100644 --- a/src/commands/system/help.ts +++ b/src/commands/system/help.ts @@ -6,7 +6,7 @@ import { getCommandList, getCommandInfo, paginate -} from "../../core"; +} from "onion-lasers"; import {requireAllCasesHandledFor} from "../../lib"; import {MessageEmbed} from "discord.js"; diff --git a/src/commands/template.ts b/src/commands/template.ts deleted file mode 100644 index 9ea84bf..0000000 --- a/src/commands/template.ts +++ /dev/null @@ -1,7 +0,0 @@ -import {Command, NamedCommand, RestCommand} from "../core"; - -export default new NamedCommand({ - async run({send, message, channel, guild, author, member, client, args}) { - // code - } -}); diff --git a/src/commands/utility/calc.ts b/src/commands/utility/calc.ts index 649ba1f..0e85165 100644 --- a/src/commands/utility/calc.ts +++ b/src/commands/utility/calc.ts @@ -1,4 +1,4 @@ -import {NamedCommand, RestCommand} from "../../core"; +import {NamedCommand, RestCommand} from "onion-lasers"; import * as math from "mathjs"; import {MessageEmbed} from "discord.js"; diff --git a/src/commands/utility/code.ts b/src/commands/utility/code.ts index 5c73897..eb62e70 100644 --- a/src/commands/utility/code.ts +++ b/src/commands/utility/code.ts @@ -1,4 +1,4 @@ -import {NamedCommand} from "../../core"; +import {NamedCommand} from "onion-lasers"; export default new NamedCommand({ description: "Gives you the Github link.", diff --git a/src/commands/utility/desc.ts b/src/commands/utility/desc.ts index 4307469..68c5c17 100644 --- a/src/commands/utility/desc.ts +++ b/src/commands/utility/desc.ts @@ -1,4 +1,4 @@ -import {NamedCommand, RestCommand} from "../../core"; +import {NamedCommand, RestCommand} from "onion-lasers"; export default new NamedCommand({ description: "Renames current voice channel.", diff --git a/src/commands/utility/docs.ts b/src/commands/utility/docs.ts index c1a8e47..5f5b2be 100644 --- a/src/commands/utility/docs.ts +++ b/src/commands/utility/docs.ts @@ -1,4 +1,4 @@ -import {NamedCommand, RestCommand} from "../../core"; +import {NamedCommand, RestCommand} from "onion-lasers"; import {URL} from "url"; import {getContent} from "../../lib"; diff --git a/src/commands/utility/emote.ts b/src/commands/utility/emote.ts index 0cb48a2..c312316 100644 --- a/src/commands/utility/emote.ts +++ b/src/commands/utility/emote.ts @@ -1,4 +1,4 @@ -import {NamedCommand, RestCommand} from "../../core"; +import {NamedCommand, RestCommand} from "onion-lasers"; import {processEmoteQueryFormatted} from "./modules/emote-utils"; export default new NamedCommand({ diff --git a/src/commands/utility/info.ts b/src/commands/utility/info.ts index b490bcd..9dbbc09 100644 --- a/src/commands/utility/info.ts +++ b/src/commands/utility/info.ts @@ -1,7 +1,7 @@ import {MessageEmbed, version as djsversion, Guild, User, GuildMember} from "discord.js"; import ms from "ms"; import os from "os"; -import {Command, NamedCommand, getMemberByName, CHANNEL_TYPE, getGuildByName, RestCommand} from "../../core"; +import {Command, NamedCommand, getMemberByName, CHANNEL_TYPE, getGuildByName, RestCommand} from "onion-lasers"; import {formatBytes, trimArray} from "../../lib"; import {verificationLevels, filterLevels, regions} from "../../defs/info"; import moment, {utc} from "moment"; diff --git a/src/commands/utility/invite.ts b/src/commands/utility/invite.ts index d0eb559..b181583 100644 --- a/src/commands/utility/invite.ts +++ b/src/commands/utility/invite.ts @@ -1,4 +1,4 @@ -import {Command, NamedCommand} from "../../core"; +import {Command, NamedCommand} from "onion-lasers"; export default new NamedCommand({ description: "Gives you the invite link.", diff --git a/src/commands/utility/lsemotes.ts b/src/commands/utility/lsemotes.ts index a0a140f..921a573 100644 --- a/src/commands/utility/lsemotes.ts +++ b/src/commands/utility/lsemotes.ts @@ -1,5 +1,5 @@ import {GuildEmoji, MessageEmbed, User} from "discord.js"; -import {NamedCommand, RestCommand, paginate, SendFunction} from "../../core"; +import {NamedCommand, RestCommand, paginate, SendFunction} from "onion-lasers"; import {split} from "../../lib"; import vm from "vm"; diff --git a/src/commands/utility/react.ts b/src/commands/utility/react.ts index 39301c9..25158e7 100644 --- a/src/commands/utility/react.ts +++ b/src/commands/utility/react.ts @@ -1,4 +1,4 @@ -import {NamedCommand, RestCommand} from "../../core"; +import {NamedCommand, RestCommand} from "onion-lasers"; import {Message, Channel, TextChannel} from "discord.js"; import {processEmoteQueryArray} from "./modules/emote-utils"; diff --git a/src/commands/utility/say.ts b/src/commands/utility/say.ts index 5d8805a..316773d 100644 --- a/src/commands/utility/say.ts +++ b/src/commands/utility/say.ts @@ -1,4 +1,4 @@ -import {NamedCommand, RestCommand} from "../../core"; +import {NamedCommand, RestCommand} from "onion-lasers"; export default new NamedCommand({ description: "Repeats your message.", diff --git a/src/commands/utility/scanemotes.ts b/src/commands/utility/scanemotes.ts index e9fbcd0..7845b19 100644 --- a/src/commands/utility/scanemotes.ts +++ b/src/commands/utility/scanemotes.ts @@ -1,4 +1,4 @@ -import {NamedCommand, CHANNEL_TYPE} from "../../core"; +import {NamedCommand, CHANNEL_TYPE} from "onion-lasers"; import {pluralise} from "../../lib"; import moment from "moment"; import {Collection, TextChannel} from "discord.js"; diff --git a/src/commands/utility/shorten.ts b/src/commands/utility/shorten.ts index c8a4618..e3e5f61 100644 --- a/src/commands/utility/shorten.ts +++ b/src/commands/utility/shorten.ts @@ -1,4 +1,4 @@ -import {Command, NamedCommand} from "../../core"; +import {Command, NamedCommand} from "onion-lasers"; import * as https from "https"; export default new NamedCommand({ diff --git a/src/commands/utility/streaminfo.ts b/src/commands/utility/streaminfo.ts index 67d6197..a4ed019 100644 --- a/src/commands/utility/streaminfo.ts +++ b/src/commands/utility/streaminfo.ts @@ -1,4 +1,4 @@ -import {NamedCommand, RestCommand} from "../../core"; +import {NamedCommand, RestCommand} from "onion-lasers"; import {streamList} from "../../modules/streamNotifications"; import {Storage} from "../../structures"; diff --git a/src/commands/utility/time.ts b/src/commands/utility/time.ts index 9852b86..5d6bf83 100644 --- a/src/commands/utility/time.ts +++ b/src/commands/utility/time.ts @@ -1,4 +1,12 @@ -import {Command, NamedCommand, askForReply, confirm, askMultipleChoice, getMemberByName, RestCommand} from "../../core"; +import { + Command, + NamedCommand, + askForReply, + confirm, + askMultipleChoice, + getMemberByName, + RestCommand +} from "onion-lasers"; import {Storage} from "../../structures"; import {User} from "discord.js"; import moment from "moment"; diff --git a/src/commands/utility/todo.ts b/src/commands/utility/todo.ts index 0317bfc..e88eefc 100644 --- a/src/commands/utility/todo.ts +++ b/src/commands/utility/todo.ts @@ -1,4 +1,4 @@ -import {NamedCommand, RestCommand} from "../../core"; +import {NamedCommand, RestCommand} from "onion-lasers"; import moment from "moment"; import {Storage} from "../../structures"; import {MessageEmbed} from "discord.js"; diff --git a/src/commands/utility/translate.ts b/src/commands/utility/translate.ts index e3ca9ed..85a6486 100644 --- a/src/commands/utility/translate.ts +++ b/src/commands/utility/translate.ts @@ -1,4 +1,4 @@ -import {Command, NamedCommand, RestCommand} from "../../core"; +import {Command, NamedCommand, RestCommand} from "onion-lasers"; import translate from "translate-google"; export default new NamedCommand({ diff --git a/src/core/command.ts b/src/core/command.ts deleted file mode 100644 index 7e024da..0000000 --- a/src/core/command.ts +++ /dev/null @@ -1,751 +0,0 @@ -import { - Collection, - Client, - Message, - TextChannel, - DMChannel, - NewsChannel, - Guild, - User, - GuildMember, - GuildChannel, - Channel -} from "discord.js"; -import {getChannelByID, getGuildByID, getMessageByID, getUserByID, SendFunction} from "./libd"; -import {hasPermission, getPermissionLevel, getPermissionName} from "./permissions"; -import {getPrefix} from "./interface"; -import {parseVars, requireAllCasesHandledFor} from "../lib"; - -/** - * ===[ Command Types ]=== - * SUBCOMMAND - Any specifically-defined keywords / string literals. - * CHANNEL - <#...> - * ROLE - <@&...> - * EMOTE - <::ID> (The previous two values, animated and emote name respectively, do not matter at all for finding the emote.) - * MESSAGE - Available by using the built-in "Copy Message Link" or "Copy ID" buttons. https://discordapp.com/channels/// or - (automatically searches all guilds for the channel ID). - * USER - <@...> and <@!...> - * ID - Any number with 17-19 digits. Only used as a redirect to another subcommand type. - * NUMBER - Any valid number via the Number() function, except for NaN and Infinity (because those can really mess with the program). - * ANY - Generic argument case. - * NONE - No subcommands exist. - */ - -// RegEx patterns used for identifying/extracting each type from a string argument. -// The reason why \d{17,} is used is because the max safe number for JS numbers is 16 characters when stringified (decimal). Beyond that are IDs. -const patterns = { - channel: /^<#(\d{17,})>$/, - role: /^<@&(\d{17,})>$/, - emote: /^$/, - // The message type won't include #. At that point, you may as well just use a search usernames function. Even then, tags would only be taken into account to differentiate different users with identical usernames. - messageLink: /^https?:\/\/(?:ptb\.|canary\.)?discord(?:app)?\.com\/channels\/(?:\d{17,}|@me)\/(\d{17,})\/(\d{17,})$/, - messagePair: /^(\d{17,})-(\d{17,})$/, - user: /^<@!?(\d{17,})>$/, - id: /^(\d{17,})$/ -}; - -// Maybe add a guild redirect... somehow? -type ID = "channel" | "role" | "emote" | "message" | "user" | "guild"; - -// Callbacks don't work with discriminated unions: -// - https://github.com/microsoft/TypeScript/issues/41759 -// - https://github.com/microsoft/TypeScript/issues/35769 -// Therefore, there won't by any type narrowing on channel or guild of CommandMenu until this is fixed. -// Otherwise, you'd have to define channelType for every single subcommand, which would get very tedious. -// Just use type assertions when you specify a channel type. -export enum CHANNEL_TYPE { - ANY, - GUILD, - DM -} - -interface CommandMenu { - readonly args: any[]; - readonly client: Client; - readonly message: Message; - readonly channel: TextChannel | DMChannel | NewsChannel; - readonly guild: Guild | null; - readonly author: User; - // According to the documentation, a message can be part of a guild while also not having a - // member object for the author. This will happen if the author of a message left the guild. - readonly member: GuildMember | null; - readonly send: SendFunction; -} - -interface CommandOptionsBase { - readonly description?: string; - readonly usage?: string; - readonly permission?: number; - readonly nsfw?: boolean; - readonly channelType?: CHANNEL_TYPE; -} - -// Also, contrary to what you might think, channel pings do still work in DM channels. -// Role pings, maybe not, but it's not a big deal. -interface CommandOptions extends CommandOptionsBase { - readonly run?: (($: CommandMenu) => Promise) | string; - readonly subcommands?: {[key: string]: NamedCommand}; - readonly channel?: Command; - readonly role?: Command; - readonly emote?: Command; - readonly message?: Command; - readonly user?: Command; - readonly guild?: Command; // Only available if an ID is set to reroute to it. - readonly id?: ID; - readonly number?: Command; - readonly any?: Command | RestCommand; -} - -interface NamedCommandOptions extends CommandOptions { - readonly aliases?: string[]; - readonly nameOverride?: string; -} - -interface RestCommandOptions extends CommandOptionsBase { - readonly run?: (($: CommandMenu & {readonly combined: string}) => Promise) | string; -} - -interface ExecuteCommandMetadata { - readonly header: string; - readonly args: string[]; - permission: number; - nsfw: boolean; - channelType: CHANNEL_TYPE; - symbolicArgs: string[]; // i.e. instead of <#...> -} - -export interface CommandInfo { - readonly type: "info"; - readonly command: BaseCommand; - readonly subcommandInfo: Collection; - readonly keyedSubcommandInfo: Collection; - readonly permission: number; - readonly nsfw: boolean; - readonly channelType: CHANNEL_TYPE; - readonly args: string[]; - readonly header: string; -} - -interface CommandInfoError { - readonly type: "error"; - readonly message: string; -} - -interface CommandInfoMetadata { - permission: number; - nsfw: boolean; - channelType: CHANNEL_TYPE; - args: string[]; - usage: string; - readonly originalArgs: string[]; - readonly header: string; -} - -// An isolated command of just the metadata. -abstract class BaseCommand { - public readonly description: string; - public readonly usage: string; - public readonly permission: number; // -1 (default) indicates to inherit, 0 is the lowest rank, 1 is second lowest rank, and so on. - public readonly nsfw: boolean | null; // null (default) indicates to inherit - public readonly channelType: CHANNEL_TYPE | null; // null (default) indicates to inherit - - constructor(options?: CommandOptionsBase) { - this.description = options?.description || "No description."; - this.usage = options?.usage ?? ""; - this.permission = options?.permission ?? -1; - this.nsfw = options?.nsfw ?? null; - this.channelType = options?.channelType ?? null; - } -} - -// Each Command instance represents a block that links other Command instances under it. -export class Command extends BaseCommand { - // The execute and subcommand properties are restricted to the class because subcommand recursion could easily break when manually handled. - // The class will handle checking for null fields. - private run: (($: CommandMenu) => Promise) | string; - private readonly subcommands: Collection; // This is the final data structure you'll actually use to work with the commands the aliases point to. - private channel: Command | null; - private role: Command | null; - private emote: Command | null; - private message: Command | null; - private user: Command | null; - private guild: Command | null; - private id: Command | null; - private idType: ID | null; - private number: Command | null; - private any: Command | RestCommand | null; - - constructor(options?: CommandOptions) { - super(options); - this.run = options?.run || "No action was set on this command!"; - this.subcommands = new Collection(); // Populate this collection after setting subcommands. - this.channel = options?.channel || null; - this.role = options?.role || null; - this.emote = options?.emote || null; - this.message = options?.message || null; - this.user = options?.user || null; - this.guild = options?.guild || null; - this.id = null; - this.idType = options?.id || null; - this.number = options?.number || null; - this.any = options?.any || null; - - if (options) - switch (options.id) { - case "channel": - this.id = this.channel; - break; - case "role": - this.id = this.role; - break; - case "emote": - this.id = this.emote; - break; - case "message": - this.id = this.message; - break; - case "user": - this.id = this.user; - break; - case "guild": - this.id = this.guild; - break; - case undefined: - break; - default: - requireAllCasesHandledFor(options.id); - } - - if (options?.subcommands) { - const baseSubcommands = Object.keys(options.subcommands); - - // Loop once to set the base subcommands. - for (const name in options.subcommands) this.subcommands.set(name, options.subcommands[name]); - - // Then loop again to make aliases point to the base subcommands and warn if something's not right. - // This shouldn't be a problem because I'm hoping that JS stores these as references that point to the same object. - for (const name in options.subcommands) { - const subcmd = options.subcommands[name]; - subcmd.name = name; - const aliases = subcmd.aliases; - - for (const alias of aliases) { - if (baseSubcommands.includes(alias)) - console.warn( - `"${alias}" in subcommand "${name}" was attempted to be declared as an alias but it already exists in the base commands! (Look at the next "Loading Command" line to see which command is affected.)` - ); - else if (this.subcommands.has(alias)) - console.warn( - `Duplicate alias "${alias}" at subcommand "${name}"! (Look at the next "Loading Command" line to see which command is affected.)` - ); - else this.subcommands.set(alias, subcmd); - } - } - } - } - - // Go through the arguments provided and find the right subcommand, then execute with the given arguments. - // Will return null if it successfully executes, string if there's an error (to let the user know what it is). - // - // Calls the resulting subcommand's execute method in order to make more modular code, basically pushing the chain of execution to the subcommand. - // For example, a numeric subcommand would accept args of [4] then execute on it. - // - // Because each Command instance is isolated from others, it becomes practically impossible to predict the total amount of subcommands when isolating the code to handle each individual layer of recursion. - // Therefore, if a Command is declared as a rest type, any typed args that come at the end must be handled manually. - public async execute(args: string[], menu: CommandMenu, metadata: ExecuteCommandMetadata): Promise { - // Update inherited properties if the current command specifies a property. - // In case there are no initial arguments, these should go first so that it can register. - if (this.permission !== -1) metadata.permission = this.permission; - if (this.nsfw !== null) metadata.nsfw = this.nsfw; - if (this.channelType !== null) metadata.channelType = this.channelType; - - // Take off the leftmost argument from the list. - const param = args.shift(); - - // If there are no arguments left, execute the current command. Otherwise, continue on. - if (param === undefined) { - const error = canExecute(menu, metadata); - if (error) return error; - - if (typeof this.run === "string") { - // Although I *could* add an option in the launcher to attach arbitrary variables to this var string... - // I'll just leave it like this, because instead of using var strings for user stuff, you could just make "run" a template string. - await menu.send( - parseVars( - this.run, - { - author: menu.author.toString(), - prefix: getPrefix(menu.guild), - command: `${metadata.header} ${metadata.symbolicArgs.join(", ")}` - }, - "???" - ) - ); - } else { - // Then capture any potential errors. - try { - await this.run(menu); - } catch (error) { - const errorMessage = error.stack ?? error; - console.error(`Command Error: ${metadata.header} (${metadata.args.join(", ")})\n${errorMessage}`); - - return `There was an error while trying to execute that command!\`\`\`${errorMessage}\`\`\``; - } - } - - return null; - } - - // Resolve the value of the current command's argument (adding it to the resolved args), - // then pass the thread of execution to whichever subcommand is valid (if any). - const isMessageLink = patterns.messageLink.test(param); - const isMessagePair = patterns.messagePair.test(param); - - if (this.subcommands.has(param)) { - metadata.symbolicArgs.push(param); - return this.subcommands.get(param)!.execute(args, menu, metadata); - } else if (this.channel && patterns.channel.test(param)) { - const id = patterns.channel.exec(param)![1]; - const channel = await getChannelByID(id); - - if (typeof channel !== "string") { - if (channel instanceof TextChannel || channel instanceof DMChannel) { - metadata.symbolicArgs.push(""); - menu.args.push(channel); - return this.channel.execute(args, menu, metadata); - } else { - return `\`${id}\` is not a valid text channel!`; - } - } else { - return channel; - } - } else if (this.role && patterns.role.test(param)) { - const id = patterns.role.exec(param)![1]; - - if (!menu.guild) { - return "You can't use role parameters in DM channels!"; - } - - const role = menu.guild.roles.cache.get(id); - - if (role) { - metadata.symbolicArgs.push(""); - menu.args.push(role); - return this.role.execute(args, menu, metadata); - } else { - return `\`${id}\` is not a valid role in this server!`; - } - } else if (this.emote && patterns.emote.test(param)) { - const id = patterns.emote.exec(param)![1]; - const emote = menu.client.emojis.cache.get(id); - - if (emote) { - metadata.symbolicArgs.push(""); - menu.args.push(emote); - return this.emote.execute(args, menu, metadata); - } else { - return `\`${id}\` isn't a valid emote!`; - } - } else if (this.message && (isMessageLink || isMessagePair)) { - let channelID = ""; - let messageID = ""; - - if (isMessageLink) { - const result = patterns.messageLink.exec(param)!; - channelID = result[1]; - messageID = result[2]; - } else if (isMessagePair) { - const result = patterns.messagePair.exec(param)!; - channelID = result[1]; - messageID = result[2]; - } - - const message = await getMessageByID(channelID, messageID); - - if (typeof message !== "string") { - metadata.symbolicArgs.push(""); - menu.args.push(message); - return this.message.execute(args, menu, metadata); - } else { - return message; - } - } else if (this.user && patterns.user.test(param)) { - const id = patterns.user.exec(param)![1]; - const user = await getUserByID(id); - - if (typeof user !== "string") { - metadata.symbolicArgs.push(""); - menu.args.push(user); - return this.user.execute(args, menu, metadata); - } else { - return user; - } - } else if (this.id && this.idType && patterns.id.test(param)) { - metadata.symbolicArgs.push(""); - const id = patterns.id.exec(param)![1]; - - // Probably modularize the findXByY code in general in libd. - // Because this part is pretty much a whole bunch of copy pastes. - switch (this.idType) { - case "channel": - const channel = await getChannelByID(id); - - if (typeof channel !== "string") { - if (channel instanceof TextChannel || channel instanceof DMChannel) { - metadata.symbolicArgs.push(""); - menu.args.push(channel); - return this.id.execute(args, menu, metadata); - } else { - return `\`${id}\` is not a valid text channel!`; - } - } else { - return channel; - } - case "role": - if (!menu.guild) { - return "You can't use role parameters in DM channels!"; - } - - const role = menu.guild.roles.cache.get(id); - - if (role) { - menu.args.push(role); - return this.id.execute(args, menu, metadata); - } else { - return `\`${id}\` isn't a valid role in this server!`; - } - case "emote": - const emote = menu.client.emojis.cache.get(id); - - if (emote) { - menu.args.push(emote); - return this.id.execute(args, menu, metadata); - } else { - return `\`${id}\` isn't a valid emote!`; - } - case "message": - const message = await getMessageByID(menu.channel, id); - - if (typeof message !== "string") { - menu.args.push(message); - return this.id.execute(args, menu, metadata); - } else { - return message; - } - case "user": - const user = await getUserByID(id); - - if (typeof user !== "string") { - menu.args.push(user); - return this.id.execute(args, menu, metadata); - } else { - return user; - } - case "guild": - const guild = getGuildByID(id); - - if (typeof guild !== "string") { - menu.args.push(guild); - return this.id.execute(args, menu, metadata); - } else { - return guild; - } - default: - requireAllCasesHandledFor(this.idType); - } - } else if (this.number && !Number.isNaN(Number(param)) && param !== "Infinity" && param !== "-Infinity") { - metadata.symbolicArgs.push(""); - menu.args.push(Number(param)); - return this.number.execute(args, menu, metadata); - } else if (this.any instanceof Command) { - metadata.symbolicArgs.push(""); - menu.args.push(param); - return this.any.execute(args, menu, metadata); - } else if (this.any instanceof RestCommand) { - metadata.symbolicArgs.push("<...>"); - args.unshift(param); - menu.args.push(...args); - return this.any.execute(args.join(" "), menu, metadata); - } else { - metadata.symbolicArgs.push(`"${param}"`); - return `No valid command sequence matching \`${metadata.header} ${metadata.symbolicArgs.join( - " " - )}\` found.`; - } - - // Note: Do NOT add a return statement here. In case one of the other sections is missing - // a return statement, there'll be a compile error to catch that. - } - - // What this does is resolve the resulting subcommand as well as the inherited properties and the available subcommands. - public resolveInfo(args: string[], header: string): CommandInfo | CommandInfoError { - return this.resolveInfoInternal(args, { - permission: 0, - nsfw: false, - channelType: CHANNEL_TYPE.ANY, - header, - args: [], - usage: "", - originalArgs: [...args] - }); - } - - private resolveInfoInternal(args: string[], metadata: CommandInfoMetadata): CommandInfo | CommandInfoError { - // Update inherited properties if the current command specifies a property. - // In case there are no initial arguments, these should go first so that it can register. - if (this.permission !== -1) metadata.permission = this.permission; - if (this.nsfw !== null) metadata.nsfw = this.nsfw; - if (this.channelType !== null) metadata.channelType = this.channelType; - if (this.usage !== "") metadata.usage = this.usage; - - // Take off the leftmost argument from the list. - const param = args.shift(); - - // If there are no arguments left, return the data or an error message. - if (param === undefined) { - const keyedSubcommandInfo = new Collection(); - const subcommandInfo = new Collection(); - - // Get all the subcommands of the current command but without aliases. - for (const [tag, command] of this.subcommands.entries()) { - // Don't capture duplicates generated from aliases. - if (tag === command.name) { - keyedSubcommandInfo.set(tag, command); - } - } - - // Then get all the generic subcommands. - if (this.channel) subcommandInfo.set("", this.channel); - if (this.role) subcommandInfo.set("", this.role); - if (this.emote) subcommandInfo.set("", this.emote); - if (this.message) subcommandInfo.set("", this.message); - if (this.user) subcommandInfo.set("", this.user); - if (this.id) subcommandInfo.set(`>`, this.id); - if (this.number) subcommandInfo.set("", this.number); - - // The special case for a possible rest command. - if (this.any) { - if (this.any instanceof Command) subcommandInfo.set("", this.any); - else subcommandInfo.set("<...>", this.any); - } - - return { - type: "info", - command: this, - keyedSubcommandInfo, - subcommandInfo, - ...metadata - }; - } - - const invalidSubcommandGenerator: () => CommandInfoError = () => ({ - type: "error", - message: `No subcommand found by the argument list: \`${metadata.originalArgs.join(" ")}\`` - }); - - // Then test if anything fits any hardcoded values, otherwise check if it's a valid keyed subcommand. - if (param === "") { - if (this.channel) { - metadata.args.push(""); - return this.channel.resolveInfoInternal(args, metadata); - } else { - return invalidSubcommandGenerator(); - } - } else if (param === "") { - if (this.role) { - metadata.args.push(""); - return this.role.resolveInfoInternal(args, metadata); - } else { - return invalidSubcommandGenerator(); - } - } else if (param === "") { - if (this.emote) { - metadata.args.push(""); - return this.emote.resolveInfoInternal(args, metadata); - } else { - return invalidSubcommandGenerator(); - } - } else if (param === "") { - if (this.message) { - metadata.args.push(""); - return this.message.resolveInfoInternal(args, metadata); - } else { - return invalidSubcommandGenerator(); - } - } else if (param === "") { - if (this.user) { - metadata.args.push(""); - return this.user.resolveInfoInternal(args, metadata); - } else { - return invalidSubcommandGenerator(); - } - } else if (param === "") { - if (this.id) { - metadata.args.push(`>`); - return this.id.resolveInfoInternal(args, metadata); - } else { - return invalidSubcommandGenerator(); - } - } else if (param === "") { - if (this.number) { - metadata.args.push(""); - return this.number.resolveInfoInternal(args, metadata); - } else { - return invalidSubcommandGenerator(); - } - } else if (param === "") { - if (this.any instanceof Command) { - metadata.args.push(""); - return this.any.resolveInfoInternal(args, metadata); - } else { - return invalidSubcommandGenerator(); - } - } else if (param === "<...>") { - if (this.any instanceof RestCommand) { - metadata.args.push("<...>"); - return this.any.resolveInfoFinale(metadata); - } else { - return invalidSubcommandGenerator(); - } - } else if (this.subcommands?.has(param)) { - metadata.args.push(param); - return this.subcommands.get(param)!.resolveInfoInternal(args, metadata); - } else { - return invalidSubcommandGenerator(); - } - } -} - -export class NamedCommand extends Command { - public readonly aliases: string[]; // This is to keep the array intact for parent Command instances to use. It'll also be used when loading top-level aliases. - private originalCommandName: string | null; // If the command is an alias, what's the original name? - - constructor(options?: NamedCommandOptions) { - super(options); - this.aliases = options?.aliases || []; - // The name override exists in case a user wants to bypass filename restrictions. - this.originalCommandName = options?.nameOverride ?? null; - } - - public get name(): string { - if (this.originalCommandName === null) throw new Error("originalCommandName must be set before accessing it!"); - else return this.originalCommandName; - } - - public set name(value: string) { - if (this.originalCommandName !== null) - throw new Error(`originalCommandName cannot be set twice! Attempted to set the value to "${value}".`); - else this.originalCommandName = value; - } - - public isNameSet(): boolean { - return this.originalCommandName !== null; - } -} - -// RestCommand is a declarative version of the common "any: args.join(' ')" pattern, basically the Command version of a rest parameter. -// This way, you avoid having extra subcommands when using this pattern. -// I'm probably not going to add a transformer function (a callback to automatically handle stuff like searching for usernames). -// I don't think the effort to figure this part out via generics or something is worth it. -export class RestCommand extends BaseCommand { - private run: (($: CommandMenu & {readonly combined: string}) => Promise) | string; - - constructor(options?: RestCommandOptions) { - super(options); - this.run = options?.run || "No action was set on this command!"; - } - - public async execute( - combined: string, - menu: CommandMenu, - metadata: ExecuteCommandMetadata - ): Promise { - // Update inherited properties if the current command specifies a property. - // In case there are no initial arguments, these should go first so that it can register. - if (this.permission !== -1) metadata.permission = this.permission; - if (this.nsfw !== null) metadata.nsfw = this.nsfw; - if (this.channelType !== null) metadata.channelType = this.channelType; - - const error = canExecute(menu, metadata); - if (error) return error; - - if (typeof this.run === "string") { - // Although I *could* add an option in the launcher to attach arbitrary variables to this var string... - // I'll just leave it like this, because instead of using var strings for user stuff, you could just make "run" a template string. - await menu.send( - parseVars( - this.run, - { - author: menu.author.toString(), - prefix: getPrefix(menu.guild), - command: `${metadata.header} ${metadata.symbolicArgs.join(", ")}` - }, - "???" - ) - ); - } else { - // Then capture any potential errors. - try { - // Args will still be kept intact. A common pattern is popping some parameters off the end then doing some branching. - // That way, you can still declaratively mark an argument list as continuing while also handling the individual args. - await this.run({...menu, args: menu.args, combined}); - } catch (error) { - const errorMessage = error.stack ?? error; - console.error(`Command Error: ${metadata.header} (${metadata.args.join(", ")})\n${errorMessage}`); - - return `There was an error while trying to execute that command!\`\`\`${errorMessage}\`\`\``; - } - } - - return null; - } - - public resolveInfoFinale(metadata: CommandInfoMetadata): CommandInfo { - if (this.permission !== -1) metadata.permission = this.permission; - if (this.nsfw !== null) metadata.nsfw = this.nsfw; - if (this.channelType !== null) metadata.channelType = this.channelType; - if (this.usage !== "") metadata.usage = this.usage; - - return { - type: "info", - command: this, - keyedSubcommandInfo: new Collection(), - subcommandInfo: new Collection(), - ...metadata - }; - } -} - -// See if there is anything that'll prevent the user from executing the command. -// Returns null if successful, otherwise returns a message with the error. -function canExecute(menu: CommandMenu, metadata: ExecuteCommandMetadata): string | null { - // 1. Does this command specify a required channel type? If so, does the channel type match? - if ( - metadata.channelType === CHANNEL_TYPE.GUILD && - (!(menu.channel instanceof GuildChannel) || menu.guild === null || menu.member === null) - ) { - return "This command must be executed in a server."; - } else if ( - metadata.channelType === CHANNEL_TYPE.DM && - (menu.channel.type !== "dm" || menu.guild !== null || menu.member !== null) - ) { - return "This command must be executed as a direct message."; - } - - // 2. Is this an NSFW command where the channel prevents such use? (DM channels bypass this requirement.) - if (metadata.nsfw && menu.channel.type !== "dm" && !menu.channel.nsfw) { - return "This command must be executed in either an NSFW channel or as a direct message."; - } - - // 3. Does the user have permission to execute the command? - if (!hasPermission(menu.author, menu.member, metadata.permission)) { - const userPermLevel = getPermissionLevel(menu.author, menu.member); - - return `You don't have access to this command! Your permission level is \`${getPermissionName( - userPermLevel - )}\` (${userPermLevel}), but this command requires a permission level of \`${getPermissionName( - metadata.permission - )}\` (${metadata.permission}).`; - } - - return null; -} diff --git a/src/core/eventListeners.ts b/src/core/eventListeners.ts deleted file mode 100644 index bad74cd..0000000 --- a/src/core/eventListeners.ts +++ /dev/null @@ -1,39 +0,0 @@ -import {Client, Permissions, Message, MessageReaction, User, PartialUser} from "discord.js"; -import {botHasPermission} from "./libd"; - -// A list of message ID and callback pairs. You get the emote name and ID of the user reacting. -// This will handle removing reactions automatically (if the bot has the right permission). -export const reactEventListeners = new Map void>(); -export const emptyReactEventListeners = new Map void>(); - -// A list of "channel-message" and callback pairs. Also, I imagine that the callback will be much more maintainable when discord.js v13 comes out with a dedicated message.referencedMessage property. -export const replyEventListeners = new Map void>(); - -export function attachEventListenersToClient(client: Client) { - client.on("messageReactionAdd", (reaction, user) => { - // The reason this is inside the call is because it's possible to switch a user's permissions halfway and suddenly throw an error. - // This will dynamically adjust for that, switching modes depending on whether it currently has the "Manage Messages" permission. - const canDeleteEmotes = botHasPermission(reaction.message.guild, Permissions.FLAGS.MANAGE_MESSAGES); - reactEventListeners.get(reaction.message.id)?.(reaction, user); - if (canDeleteEmotes && !user.partial) reaction.users.remove(user); - }); - - client.on("messageReactionRemove", (reaction, user) => { - const canDeleteEmotes = botHasPermission(reaction.message.guild, Permissions.FLAGS.MANAGE_MESSAGES); - if (!canDeleteEmotes) reactEventListeners.get(reaction.message.id)?.(reaction, user); - }); - - client.on("messageReactionRemoveAll", (message) => { - reactEventListeners.delete(message.id); - emptyReactEventListeners.get(message.id)?.(); - emptyReactEventListeners.delete(message.id); - }); - - client.on("message", (message) => { - // If there's an inline reply, fire off that event listener (if it exists). - if (message.reference) { - const reference = message.reference; - replyEventListeners.get(`${reference.channelID}-${reference.messageID}`)?.(message); - } - }); -} diff --git a/src/core/handler.ts b/src/core/handler.ts deleted file mode 100644 index c93f217..0000000 --- a/src/core/handler.ts +++ /dev/null @@ -1,138 +0,0 @@ -import {Client, Permissions, Message, TextChannel, DMChannel, NewsChannel} from "discord.js"; -import {getPrefix, loadableCommands} from "./interface"; - -// For custom message events that want to cancel the command handler on certain conditions. -const interceptRules: ((message: Message) => boolean)[] = [(message) => message.author.bot]; - -export function addInterceptRule(handler: (message: Message) => boolean) { - interceptRules.push(handler); -} - -const lastCommandInfo: { - header: string; - args: string[]; - channel: TextChannel | DMChannel | NewsChannel | null; -} = { - header: "N/A", - args: [], - channel: null -}; - -const defaultMetadata = { - permission: 0, - nsfw: false, - channelType: 0 // CHANNEL_TYPE.ANY, apparently isn't initialized at this point yet -}; - -// Note: client.user is only undefined before the bot logs in, so by this point, client.user cannot be undefined. -// Note: guild.available will never need to be checked because the message starts in either a DM channel or an already-available guild. -export function attachMessageHandlerToClient(client: Client) { - client.on("message", async (message) => { - for (const shouldIntercept of interceptRules) { - if (shouldIntercept(message)) { - return; - } - } - - const commands = await loadableCommands; - const {author, channel, content, guild, member} = message; - const send = channel.send.bind(channel); - const text = content; - const menu = { - author, - channel, - client, - guild, - member, - message, - args: [], - send - }; - - // Execute a dedicated block for messages in DM channels. - if (channel.type === "dm") { - // In a DM channel, simply forget about the prefix and execute any message as a command. - const [header, ...args] = text.split(/ +/); - - if (commands.has(header)) { - const command = commands.get(header)!; - - // Set last command info in case of unhandled rejections. - lastCommandInfo.header = header; - lastCommandInfo.args = [...args]; - lastCommandInfo.channel = channel; - - // Send the arguments to the command to resolve and execute. - const result = await command.execute(args, menu, { - header, - args: [...args], - ...defaultMetadata, - symbolicArgs: [] - }); - - // If something went wrong, let the user know (like if they don't have permission to use a command). - if (result) { - send(result); - } - } else { - send( - `I couldn't find the command or alias that starts with \`${header}\`. To see the list of commands, type \`help\`` - ); - } - } - // Continue if the bot has permission to send messages in this channel. - else if (channel.permissionsFor(client.user!)!.has(Permissions.FLAGS.SEND_MESSAGES)) { - const prefix = getPrefix(guild); - - // First, test if the message is just a ping to the bot. - if (new RegExp(`^<@!?${client.user!.id}>$`).test(text)) { - send(`${author}, my prefix on this server is \`${prefix}\`.`); - } - // Then check if it's a normal command. - else if (text.startsWith(prefix)) { - const [header, ...args] = text.substring(prefix.length).split(/ +/); - - if (commands.has(header)) { - const command = commands.get(header)!; - - // Set last command info in case of unhandled rejections. - lastCommandInfo.header = header; - lastCommandInfo.args = [...args]; - lastCommandInfo.channel = channel; - - // Send the arguments to the command to resolve and execute. - const result = await command.execute(args, menu, { - header, - args: [...args], - ...defaultMetadata, - symbolicArgs: [] - }); - - // If something went wrong, let the user know (like if they don't have permission to use a command). - if (result) { - send(result); - } - } - } - } - // Otherwise, let the sender know that the bot doesn't have permission to send messages. - else { - author.send( - `I don't have permission to send messages in ${channel}. ${ - member!.hasPermission(Permissions.FLAGS.ADMINISTRATOR) - ? "Because you're a server admin, you have the ability to change that channel's permissions to match if that's what you intended." - : "Try using a different channel or contacting a server admin to change permissions of that channel if you think something's wrong." - }` - ); - } - }); -} - -process.on("unhandledRejection", (reason: any) => { - if (reason?.name === "DiscordAPIError") { - console.error(`Command Error: ${lastCommandInfo.header} (${lastCommandInfo.args.join(", ")})\n${reason.stack}`); - lastCommandInfo.channel?.send( - `There was an error while trying to execute that command!\`\`\`${reason.stack}\`\`\`` - ); - } -}); diff --git a/src/core/index.ts b/src/core/index.ts deleted file mode 100644 index f80074a..0000000 --- a/src/core/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -// Onion Lasers Command Handler // -export {Command, NamedCommand, RestCommand, CHANNEL_TYPE} from "./command"; -export {addInterceptRule} from "./handler"; -export {launch} from "./interface"; -export * from "./libd"; -export {getCommandList, getCommandInfo} from "./loader"; -export {hasPermission, getPermissionLevel, getPermissionName} from "./permissions"; diff --git a/src/core/interface.ts b/src/core/interface.ts deleted file mode 100644 index f6ead8f..0000000 --- a/src/core/interface.ts +++ /dev/null @@ -1,55 +0,0 @@ -import {Collection, Client, User, GuildMember, Guild} from "discord.js"; -import {attachMessageHandlerToClient} from "./handler"; -import {attachEventListenersToClient} from "./eventListeners"; -import {NamedCommand} from "./command"; -import {loadCommands} from "./loader"; - -interface PermissionLevel { - name: string; - check: (user: User, member: GuildMember | null) => boolean; -} - -type PrefixResolver = (guild: Guild | null) => string; -type CategoryTransformer = (text: string) => string; - -// One potential option is to let the user customize system messages such as "This command must be executed in a guild." -// I decided not to do that because I don't think it'll be worth the trouble. -interface LaunchSettings { - permissionLevels?: PermissionLevel[]; - getPrefix?: PrefixResolver; - categoryTransformer?: CategoryTransformer; -} - -// One alternative to putting everything in launch(client, ...) is to create an object then set each individual aspect, such as OnionCore.setPermissions(...). -// That way, you can split different pieces of logic into different files, then do OnionCore.launch(client). -// Additionally, each method would return the object so multiple methods could be chained, such as OnionCore.setPermissions(...).setPrefixResolver(...).launch(client). -// I decided to not do this because creating a class then having a bunch of boilerplate around it just wouldn't really be worth it. -// commandsDirectory requires an absolute path to work, so use __dirname. -export async function launch(newClient: Client, commandsDirectory: string, settings?: LaunchSettings) { - // Core Launch Parameters // - client.destroy(); // Release any resources/connections being used by the placeholder client. - client = newClient; - loadableCommands = loadCommands(commandsDirectory); - attachMessageHandlerToClient(newClient); - attachEventListenersToClient(newClient); - - // Additional Configuration // - if (settings?.permissionLevels) { - if (settings.permissionLevels.length > 0) permissionLevels = settings.permissionLevels; - else console.warn("permissionLevels must have at least one element to work!"); - } - if (settings?.getPrefix) getPrefix = settings.getPrefix; - if (settings?.categoryTransformer) categoryTransformer = settings.categoryTransformer; -} - -// Placeholder until properly loaded by the user. -export let loadableCommands = (async () => new Collection())(); -export let client = new Client(); -export let permissionLevels: PermissionLevel[] = [ - { - name: "User", - check: () => true - } -]; -export let getPrefix: PrefixResolver = () => "."; -export let categoryTransformer: CategoryTransformer = (text) => text; diff --git a/src/core/libd.ts b/src/core/libd.ts deleted file mode 100644 index aed4a07..0000000 --- a/src/core/libd.ts +++ /dev/null @@ -1,390 +0,0 @@ -// Library for Discord-specific functions -import { - Message, - Guild, - GuildMember, - TextChannel, - DMChannel, - NewsChannel, - MessageOptions, - Channel, - GuildChannel, - User, - APIMessageContentResolvable, - MessageAdditions, - SplitOptions, - APIMessage, - StringResolvable, - EmojiIdentifierResolvable, - MessageReaction, - PartialUser -} from "discord.js"; -import {reactEventListeners, emptyReactEventListeners, replyEventListeners} from "./eventListeners"; -import {client} from "./interface"; - -export type SingleMessageOptions = MessageOptions & {split?: false}; - -export type SendFunction = (( - content: APIMessageContentResolvable | (MessageOptions & {split?: false}) | MessageAdditions -) => Promise) & - ((options: MessageOptions & {split: true | SplitOptions}) => Promise) & - ((options: MessageOptions | APIMessage) => Promise) & - ((content: StringResolvable, options: (MessageOptions & {split?: false}) | MessageAdditions) => Promise) & - ((content: StringResolvable, options: MessageOptions & {split: true | SplitOptions}) => Promise) & - ((content: StringResolvable, options: MessageOptions) => Promise); - -interface PaginateOptions { - multiPageSize?: number; - idleTimeout?: number; -} - -// Pagination function that allows for customization via a callback. -// Define your own pages outside the function because this only manages the actual turning of pages. -/** - * Takes a message and some additional parameters and makes a reaction page with it. All the pagination logic is taken care of but nothing more, the page index is returned and you have to send a callback to do something with it. - * - * Returns the page number the user left off on in case you want to implement a return to page function. - */ -export function paginate( - send: SendFunction, - listenTo: string | null, - totalPages: number, - onTurnPage: (page: number, hasMultiplePages: boolean) => SingleMessageOptions, - options?: PaginateOptions -): Promise { - if (totalPages < 1) throw new Error(`totalPages on paginate() must be 1 or more, ${totalPages} given.`); - - return new Promise(async (resolve) => { - const hasMultiplePages = totalPages > 1; - const message = await send(onTurnPage(0, hasMultiplePages)); - - if (hasMultiplePages) { - const multiPageSize = options?.multiPageSize ?? 5; - const idleTimeout = options?.idleTimeout ?? 60000; - let page = 0; - - const turn = (amount: number) => { - page += amount; - - if (page >= totalPages) { - page %= totalPages; - } else if (page < 0) { - // Assuming 3 total pages, it's a bit tricker, but if we just take the modulo of the absolute value (|page| % total), we get (1 2 0 ...), and we just need the pattern (2 1 0 ...). It needs to reverse order except for when it's 0. I want to find a better solution, but for the time being... total - (|page| % total) unless (|page| % total) = 0, then return 0. - const flattened = Math.abs(page) % totalPages; - if (flattened !== 0) page = totalPages - flattened; - } - - message.edit(onTurnPage(page, true)); - }; - - let stack: {[emote: string]: number} = { - "⬅️": -1, - "➡️": 1 - }; - - if (totalPages > multiPageSize) { - stack = { - "⏪": -multiPageSize, - ...stack, - "⏩": multiPageSize - }; - } - - const handle = (reaction: MessageReaction, user: User | PartialUser) => { - if (user.id === listenTo || (listenTo === null && user.id !== client.user!.id)) { - // Turn the page - const emote = reaction.emoji.name; - if (emote in stack) turn(stack[emote]); - - // Reset the timer - client.clearTimeout(timeout); - timeout = client.setTimeout(destroy, idleTimeout); - } - }; - - // When time's up, remove the bot's own reactions. - const destroy = () => { - reactEventListeners.delete(message.id); - for (const emote of message.reactions.cache.values()) emote.users.remove(message.author); - resolve(page); - }; - - // Start the reactions and call the handler. - reactInOrder(message, Object.keys(stack)); - reactEventListeners.set(message.id, handle); - emptyReactEventListeners.set(message.id, destroy); - let timeout = client.setTimeout(destroy, idleTimeout); - } - }); -} - -export async function poll(message: Message, emotes: string[], duration = 60000): Promise<{[emote: string]: number}> { - if (emotes.length === 0) throw new Error("poll() was called without any emotes."); - - reactInOrder(message, emotes); - const reactions = await message.awaitReactions( - (reaction: MessageReaction) => emotes.includes(reaction.emoji.name), - {time: duration} - ); - const reactionsByCount: {[emote: string]: number} = {}; - - for (const emote of emotes) { - const reaction = reactions.get(emote); - - if (reaction) { - const hasBot = reaction.users.cache.has(client.user!.id); // Apparently, reaction.me doesn't work properly. - - if (reaction.count !== null) { - const difference = hasBot ? 1 : 0; - reactionsByCount[emote] = reaction.count - difference; - } else { - reactionsByCount[emote] = 0; - } - } else { - reactionsByCount[emote] = 0; - } - } - - return reactionsByCount; -} - -export function confirm(message: Message, senderID: string, timeout = 30000): Promise { - return generateOneTimePrompt( - message, - { - "✅": true, - "❌": false - }, - senderID, - timeout - ); -} - -// This MUST be split into an array. These emojis are made up of several characters each, adding up to 29 in length. -const multiNumbers = ["1️⃣", "2️⃣", "3️⃣", "4️⃣", "5️⃣", "6️⃣", "7️⃣", "8️⃣", "9️⃣", "🔟"]; - -// This will bring up an option to let the user choose between one option out of many. -// This definitely needs a single callback alternative, because using the numerical version isn't actually that uncommon of a pattern. -export async function askMultipleChoice( - message: Message, - senderID: string, - choices: number, - timeout = 90000 -): Promise { - if (choices > multiNumbers.length) - throw new Error( - `askMultipleChoice only accepts up to ${multiNumbers.length} options, ${choices} was provided.` - ); - const numbers: {[emote: string]: number} = {}; - for (let i = 0; i < choices; i++) numbers[multiNumbers[i]] = i; - return generateOneTimePrompt(message, numbers, senderID, timeout); -} - -// Asks the user for some input using the inline reply feature. The message here is a message you send beforehand. -// If the reply is rejected, reply with an error message (when stable support comes from discord.js). -export function askForReply(message: Message, listenTo: string, timeout?: number): Promise { - return new Promise((resolve) => { - const referenceID = `${message.channel.id}-${message.id}`; - - replyEventListeners.set(referenceID, (reply) => { - if (reply.author.id === listenTo) { - message.delete(); - replyEventListeners.delete(referenceID); - resolve(reply); - } - }); - - if (timeout) { - client.setTimeout(() => { - if (!message.deleted) message.delete(); - replyEventListeners.delete(referenceID); - resolve(null); - }, timeout); - } - }); -} - -// Returns null if timed out, otherwise, returns the value. -export function generateOneTimePrompt( - message: Message, - stack: {[emote: string]: T}, - listenTo: string | null = null, - duration = 60000 -): Promise { - return new Promise(async (resolve) => { - // First, start reacting to the message in order. - reactInOrder(message, Object.keys(stack)); - - // Then setup the reaction listener in parallel. - await message.awaitReactions( - (reaction: MessageReaction, user: User) => { - if (user.id === listenTo || listenTo === null) { - const emote = reaction.emoji.name; - - if (emote in stack) { - resolve(stack[emote]); - message.delete(); - } - } - - // CollectorFilter requires a boolean to be returned. - // My guess is that the return value of awaitReactions can be altered by making a boolean filter. - // However, because that's not my concern with this command, I don't have to worry about it. - // May as well just set it to false because I'm not concerned with collecting any reactions. - return false; - }, - {time: duration} - ); - - if (!message.deleted) { - message.delete(); - resolve(null); - } - }); -} - -// Start a parallel chain of ordered reactions, allowing a collector to end early. -// Check if the collector ended early by seeing if the message is already deleted. -// Though apparently, message.deleted doesn't seem to update fast enough, so just put a try catch block on message.react(). -async function reactInOrder(message: Message, emotes: EmojiIdentifierResolvable[]): Promise { - for (const emote of emotes) { - try { - await message.react(emote); - } catch { - return; - } - } -} - -/** - * Tests if a bot has a certain permission in a specified guild. - */ -export function botHasPermission(guild: Guild | null, permission: number): boolean { - return !!guild?.me?.hasPermission(permission); -} - -// For "get x by y" methods: -// Caching: All guilds, channels, and roles are fully cached, while the caches for messages, users, and members aren't complete. -// It's more reliable to get users/members by fetching their IDs. fetch() will searching through the cache anyway. -// For guilds, do an extra check to make sure there isn't an outage (guild.available). - -export function getGuildByID(id: string): Guild | string { - const guild = client.guilds.cache.get(id); - - if (guild) { - if (guild.available) return guild; - else return `The guild \`${guild.name}\` (ID: \`${id}\`) is unavailable due to an outage.`; - } else { - return `No guild found by the ID of \`${id}\`!`; - } -} - -export function getGuildByName(name: string): Guild | string { - const query = name.toLowerCase(); - const guild = client.guilds.cache.find((guild) => guild.name.toLowerCase().includes(query)); - - if (guild) { - if (guild.available) return guild; - else return `The guild \`${guild.name}\` (ID: \`${guild.id}\`) is unavailable due to an outage.`; - } else { - return `No guild found by the name of \`${name}\`!`; - } -} - -export async function getChannelByID(id: string): Promise { - try { - return await client.channels.fetch(id); - } catch { - return `No channel found by the ID of \`${id}\`!`; - } -} - -// Only go through the cached channels (non-DM channels). Plus, searching DM channels by name wouldn't really make sense, nor do they have names to search anyway. -export function getChannelByName(name: string): GuildChannel | string { - const query = name.toLowerCase(); - const channel = client.channels.cache.find( - (channel) => channel instanceof GuildChannel && channel.name.toLowerCase().includes(query) - ) as GuildChannel | undefined; - if (channel) return channel; - else return `No channel found by the name of \`${name}\`!`; -} - -export async function getMessageByID( - channel: TextChannel | DMChannel | NewsChannel | string, - id: string -): Promise { - if (typeof channel === "string") { - const targetChannel = await getChannelByID(channel); - if (targetChannel instanceof TextChannel || targetChannel instanceof DMChannel) channel = targetChannel; - else if (targetChannel instanceof Channel) return `\`${id}\` isn't a valid text-based channel!`; - else return targetChannel; - } - - try { - return await channel.messages.fetch(id); - } catch { - return `\`${id}\` isn't a valid message of the channel ${channel}!`; - } -} - -export async function getUserByID(id: string): Promise { - try { - return await client.users.fetch(id); - } catch { - return `No user found by the ID of \`${id}\`!`; - } -} - -// Also check tags (if provided) to narrow down users. -export function getUserByName(name: string): User | string { - let query = name.toLowerCase(); - const tagMatch = /^(.+?)#(\d{4})$/.exec(name); - let tag: string | null = null; - - if (tagMatch) { - query = tagMatch[1].toLowerCase(); - tag = tagMatch[2]; - } - - const user = client.users.cache.find((user) => { - const hasUsernameMatch = user.username.toLowerCase().includes(query); - if (tag) return hasUsernameMatch && user.discriminator === tag; - else return hasUsernameMatch; - }); - - if (user) return user; - else return `No user found by the name of \`${name}\`!`; -} - -export async function getMemberByID(guild: Guild, id: string): Promise { - try { - return await guild.members.fetch(id); - } catch { - return `No member found by the ID of \`${id}\`!`; - } -} - -// First checks if a member can be found by that nickname, then check if a member can be found by that username. -export async function getMemberByName(guild: Guild, name: string): Promise { - const member = ( - await guild.members.fetch({ - query: name, - limit: 1 - }) - ).first(); - - // Search by username if no member is found, then resolve the user into a member if possible. - if (member) { - return member; - } else { - const user = getUserByName(name); - - if (user instanceof User) { - const member = guild.members.resolve(user); - if (member) return member; - else return `The user \`${user.tag}\` isn't in this guild!`; - } else { - return `No member found by the name of \`${name}\`!`; - } - } -} diff --git a/src/core/loader.ts b/src/core/loader.ts deleted file mode 100644 index 9939519..0000000 --- a/src/core/loader.ts +++ /dev/null @@ -1,148 +0,0 @@ -import {Collection} from "discord.js"; -import glob from "glob"; -import path from "path"; -import {NamedCommand, CommandInfo} from "./command"; -import {loadableCommands, categoryTransformer} from "./interface"; - -// Internally, it'll keep its original capitalization. It's up to you to convert it to title case when you make a help command. -const categories = new Collection(); - -// This will go through all the .js files and import them. Because the import has to be .js (and cannot be .ts), there's no need for a custom filename checker in the launch settings. -// This will avoid the problems of being a node module by requiring absolute imports, which the user will pass in as a launch parameter. -export async function loadCommands(commandsDir: string): Promise> { - // Add a trailing separator so that the reduced filename list will reliably cut off the starting part. - // "C:/some/path/to/commands" --> "C:/some/path/to/commands/" (and likewise for \) - commandsDir = path.normalize(commandsDir); - if (!commandsDir.endsWith(path.sep)) commandsDir += path.sep; - - const commands = new Collection(); - // Include all .ts files recursively in "src/commands/". - const files = await globP(path.join(commandsDir, "**", "*.js")); // This stage filters out source maps (.js.map). - // Because glob will use / regardless of platform, the following regex pattern can rely on / being the case. - const filesClean = files.map((filename) => filename.substring(commandsDir.length)); - // Extract the usable parts from commands directory if: - // - The path is 1 to 2 subdirectories (a or a/b, not a/b/c) - // - Any leading directory isn't "modules" - // - The filename doesn't end in .test.js (for jest testing) - // - The filename cannot be the hardcoded top-level "template.js", reserved for generating templates - const pattern = /^(?!template\.js)(?!modules\/)(\w+(?:\/\w+)?)(?:test\.)?\.js$/; - const lists: {[category: string]: string[]} = {}; - - for (let i = 0; i < files.length; i++) { - const match = pattern.exec(filesClean[i]); - if (!match) continue; - const commandID = match[1]; // e.g. "utilities/info" - const slashIndex = commandID.indexOf("/"); - const isMiscCommand = slashIndex !== -1; - const category = isMiscCommand ? commandID.substring(0, slashIndex) : "miscellaneous"; - const commandName = isMiscCommand ? commandID.substring(slashIndex + 1) : commandID; // e.g. "info" - - // This try-catch block MUST be here or Node.js' dynamic require() will silently fail. - try { - // If the dynamic import works, it must be an object at the very least. Then, just test to see if it's a proper instance. - const command = (await import(files[i])).default as unknown; - - if (command instanceof NamedCommand) { - const isNameOverridden = command.isNameSet(); - if (!isNameOverridden) command.name = commandName; - const header = command.name; - - if (commands.has(header)) { - console.warn( - `Command "${header}" already exists! Make sure to make each command uniquely identifiable across categories!` - ); - } else { - commands.set(header, command); - } - - for (const alias of command.aliases) { - if (commands.has(alias)) { - console.warn( - `Top-level alias "${alias}" from command "${commandID}" already exists either as a command or alias!` - ); - } else { - commands.set(alias, command); - } - } - - if (!(category in lists)) lists[category] = []; - lists[category].push(header); - - if (isNameOverridden) console.log(`Loaded Command: "${commandID}" as "${header}"`); - else console.log(`Loaded Command: ${commandID}`); - } else { - console.warn(`Command "${commandID}" has no default export which is a NamedCommand instance!`); - } - } catch (error) { - console.error(error); - } - } - - for (const category in lists) { - categories.set(category, lists[category]); - } - - return commands; -} - -function globP(path: string) { - return new Promise((resolve, reject) => { - glob(path, (error, files) => { - if (error) { - reject(error); - } else { - resolve(files); - } - }); - }); -} - -/** - * Returns a list of categories and their associated commands. - */ -export async function getCommandList(): Promise> { - const list = new Collection(); - const commands = await loadableCommands; - - for (const [category, headers] of categories) { - const commandList: NamedCommand[] = []; - for (const header of headers.filter((header) => header !== "test")) commandList.push(commands.get(header)!); - // Ignore empty categories like "miscellaneous" (if it's empty). - if (commandList.length > 0) list.set(categoryTransformer(category), commandList); - } - - return list; -} - -/** - * Resolves a command based on the arguments given. - * - Returns a string if there was an error. - * - Returns a CommandInfo/category tuple if it was a success. - */ -export async function getCommandInfo(args: string[]): Promise<[CommandInfo, string] | string> { - // Use getCommandList() instead if you're just getting the list of all commands. - if (args.length === 0) return "No arguments were provided!"; - - // Setup the root command - const commands = await loadableCommands; - let header = args.shift()!; - const command = commands.get(header); - if (!command || header === "test") return `No command found by the name \`${header}\`.`; - if (!(command instanceof NamedCommand)) return "Command is not a proper instance of NamedCommand."; - // If it's an alias, set the header to the original command name. - if (command.name) header = command.name; - - // Search categories - let category = "Unknown"; - for (const [referenceCategory, headers] of categories) { - if (headers.includes(header)) { - category = categoryTransformer(referenceCategory); - break; - } - } - - // Gather info - const result = command.resolveInfo(args, header); - if (result.type === "error") return result.message; - else return [result, category]; -} diff --git a/src/core/permissions.ts b/src/core/permissions.ts deleted file mode 100644 index f6b515e..0000000 --- a/src/core/permissions.ts +++ /dev/null @@ -1,26 +0,0 @@ -// Contains all the permission roles and checking functions. -import {User, GuildMember} from "discord.js"; -import {permissionLevels} from "./interface"; - -/** - * Checks if a `Member` has a certain permission. - */ -export function hasPermission(user: User, member: GuildMember | null, permission: number): boolean { - if (permissionLevels.length === 0) return true; - for (let i = permissionLevels.length - 1; i >= permission; i--) - if (permissionLevels[i].check(user, member)) return true; - return false; -} - -/** - * Gets a `Member`'s permission level according to the permissions enum defined in the file. - */ -export function getPermissionLevel(user: User, member: GuildMember | null): number { - for (let i = permissionLevels.length - 1; i >= 0; i--) if (permissionLevels[i].check(user, member)) return i; - return 0; -} - -export function getPermissionName(level: number) { - if (level > permissionLevels.length || level < 0 || permissionLevels.length === 0) return "N/A"; - else return permissionLevels[level].name; -} diff --git a/src/index.ts b/src/index.ts index 863569e..156aeb4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -6,7 +6,7 @@ import path from "path"; // This of course won't actually do anything until the setup process is complete and it logs in. export const client = new Client(); -import {launch} from "./core"; +import {launch} from "onion-lasers"; import setup from "./modules/setup"; import {Config, getPrefix} from "./structures"; import {toTitleCase} from "./lib"; diff --git a/src/modules/messageEmbed.ts b/src/modules/messageEmbed.ts index 6776bee..107b28d 100644 --- a/src/modules/messageEmbed.ts +++ b/src/modules/messageEmbed.ts @@ -1,7 +1,7 @@ import {client} from "../index"; import {MessageEmbed} from "discord.js"; import {getPrefix} from "../structures"; -import {getMessageByID} from "../core"; +import {getMessageByID} from "onion-lasers"; client.on("message", async (message) => { // Only execute if the message is from a user and isn't a command. diff --git a/src/modules/setup.ts b/src/modules/setup.ts index 9b44eda..f1df71a 100644 --- a/src/modules/setup.ts +++ b/src/modules/setup.ts @@ -1,19 +1,8 @@ -import {existsSync as exists, readFileSync as read, writeFile as write} from "fs"; +import {existsSync as exists} from "fs"; import inquirer from "inquirer"; -import Storage, {generateHandler} from "./storage"; +import Storage from "./storage"; import {Config} from "../structures"; -// The template should be built with a reductionist mentality. -// Provide everything the user needs and then let them remove whatever they want. -// That way, they aren't focusing on what's missing, but rather what they need for their command. -if (IS_DEV_MODE && !exists("src/commands/test.ts")) { - write( - "src/commands/test.ts", - read("src/commands/template.ts"), - generateHandler('"test.ts" (testing/template command) successfully generated.') - ); -} - // A generic process handler is set to catch unhandled rejections other than the ones from Lavalink and Discord. process.on("unhandledRejection", (reason: any) => { const isLavalinkError = reason?.code === "ECONNREFUSED"; From 564a419b40f9ac988b655e5dd81febb8331e0681 Mon Sep 17 00:00:00 2001 From: WatDuhHekBro Date: Thu, 15 Apr 2021 18:14:21 -0500 Subject: [PATCH 177/178] Shamelessly added myself to the whois registry --- src/commands/fun/whois.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/commands/fun/whois.ts b/src/commands/fun/whois.ts index 86d7360..761e049 100644 --- a/src/commands/fun/whois.ts +++ b/src/commands/fun/whois.ts @@ -37,7 +37,10 @@ const registry: {[id: string]: string} = { "367439475153829892": "A weeb.", "760375501775700038": "˙qǝǝʍ ∀", "389178357302034442": "In his dreams, he is the star. its him. <:itsMe:808174425253871657>", - "606395763404046349": "Me." + "606395763404046349": "Me.", + "237359961842253835": "Good question.", + "689538764950994990": + "The slayer of memes, a vigilante of the voidborn, and the self-proclaimed prophet of Xereptheí.\n> And thus, I shall remain dormant once more. For when judgement day arrives, those whose names are sung shall pierce the heavens." }; export default new NamedCommand({ @@ -59,7 +62,7 @@ export default new NamedCommand({ const id = user.id; if (id in registry) { - send(`\`${user.username}\` - ${registry[id]}`); + send(registry[id]); } else { send(`\`${user.tag}\` hasn't been added to the registry yet!`); } @@ -72,7 +75,7 @@ export default new NamedCommand({ if (typeof member !== "string") { if (member.id in registry) { - send(`\`${member.nickname ?? member.user.username}\` - ${registry[member.id]}`); + send(registry[member.id]); } else { send(`\`${member.nickname ?? member.user.username}\` hasn't been added to the registry yet!`); } From 8094dbd6c82ee4370f059572bd5f0163b02aa1de Mon Sep 17 00:00:00 2001 From: WatDuhHekBro Date: Sat, 17 Apr 2021 10:21:17 -0500 Subject: [PATCH 178/178] Improved searching for users by name --- CHANGELOG.md | 1 + package-lock.json | 18 +++++++++--------- package.json | 4 ++-- src/commands/fun/eco.ts | 8 ++++---- src/commands/fun/modules/eco-core.ts | 24 +++++++++++------------- src/commands/fun/whois.ts | 15 +++++++-------- src/commands/utility/info.ts | 22 ++++++++++++++++------ src/commands/utility/time.ts | 8 ++++---- 8 files changed, 54 insertions(+), 46 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ca44f4e..879eb14 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ - Reworked `poll` - Extended stream notifications feature - Fixed various bugs +- Improved searching for users by name # 3.2.1 - `vaporwave`: Transforms input into full-width text diff --git a/package-lock.json b/package-lock.json index ea19ad8..b04fc46 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "travebot", - "version": "3.2.1", + "version": "3.2.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "travebot", - "version": "3.2.1", + "version": "3.2.2", "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -20,7 +20,7 @@ "mathjs": "^9.3.0", "moment": "^2.29.1", "ms": "^2.1.3", - "onion-lasers": "^1.0.0", + "onion-lasers": "^1.1.0", "relevant-urban": "^2.0.0", "translate-google": "^1.4.3", "weather-js": "^2.0.0" @@ -5406,9 +5406,9 @@ } }, "node_modules/onion-lasers": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/onion-lasers/-/onion-lasers-1.0.0.tgz", - "integrity": "sha512-vVpywipeVUBMffvhxTeq8i+kuIO3zV+t2RQcPFo7XiUjfARR6Kq4CGyPhi9EhVXmylzwSDH+HzBzxZcdfajqng==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/onion-lasers/-/onion-lasers-1.1.0.tgz", + "integrity": "sha512-mwdRwvWTsDbiMkYGRskn05fqxvmJm+Mft10rjF5WwHZUm0wpSzD/nZrsrlkQ5KlCzDQJfKD5du8ZM0VX/35DQA==", "dependencies": { "discord.js": "^12.5.3", "glob": "^7.1.6" @@ -12425,9 +12425,9 @@ } }, "onion-lasers": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/onion-lasers/-/onion-lasers-1.0.0.tgz", - "integrity": "sha512-vVpywipeVUBMffvhxTeq8i+kuIO3zV+t2RQcPFo7XiUjfARR6Kq4CGyPhi9EhVXmylzwSDH+HzBzxZcdfajqng==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/onion-lasers/-/onion-lasers-1.1.0.tgz", + "integrity": "sha512-mwdRwvWTsDbiMkYGRskn05fqxvmJm+Mft10rjF5WwHZUm0wpSzD/nZrsrlkQ5KlCzDQJfKD5du8ZM0VX/35DQA==", "requires": { "discord.js": "^12.5.3", "glob": "^7.1.6" diff --git a/package.json b/package.json index d4bc26f..89552ef 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "travebot", - "version": "3.2.1", + "version": "3.2.2", "description": "TravBot Discord bot.", "main": "dist/index.js", "scripts": { @@ -23,7 +23,7 @@ "mathjs": "^9.3.0", "moment": "^2.29.1", "ms": "^2.1.3", - "onion-lasers": "^1.0.0", + "onion-lasers": "^1.1.0", "relevant-urban": "^2.0.0", "translate-google": "^1.4.3", "weather-js": "^2.0.0" diff --git a/src/commands/fun/eco.ts b/src/commands/fun/eco.ts index 0ece25e..6bf5d94 100644 --- a/src/commands/fun/eco.ts +++ b/src/commands/fun/eco.ts @@ -1,4 +1,4 @@ -import {Command, NamedCommand, getMemberByName, RestCommand} from "onion-lasers"; +import {Command, NamedCommand, getUserByNickname, RestCommand} from "onion-lasers"; import {isAuthorized, getMoneyEmbed} from "./modules/eco-utils"; import {DailyCommand, PayCommand, GuildCommand, LeaderboardCommand} from "./modules/eco-core"; import {BuyCommand, ShopCommand} from "./modules/eco-shop"; @@ -36,9 +36,9 @@ export default new NamedCommand({ description: "See how much money someone else has by using their username.", async run({send, guild, channel, combined}) { if (isAuthorized(guild, channel)) { - const member = await getMemberByName(guild!, combined); - if (typeof member !== "string") send(getMoneyEmbed(member.user)); - else send(member); + const user = await getUserByNickname(combined, guild); + if (typeof user !== "string") send(getMoneyEmbed(user)); + else send(user); } } }) diff --git a/src/commands/fun/modules/eco-core.ts b/src/commands/fun/modules/eco-core.ts index ce95785..e3432b2 100644 --- a/src/commands/fun/modules/eco-core.ts +++ b/src/commands/fun/modules/eco-core.ts @@ -1,4 +1,4 @@ -import {Command, getMemberByName, NamedCommand, confirm, RestCommand} from "onion-lasers"; +import {Command, getUserByNickname, NamedCommand, confirm, RestCommand} from "onion-lasers"; import {pluralise} from "../../../lib"; import {Storage} from "../../../structures"; import {isAuthorized, getMoneyEmbed, getSendEmbed, ECO_EMBED_COLOR} from "./eco-utils"; @@ -156,20 +156,18 @@ export const PayCommand = new NamedCommand({ else if (!guild) return send("You have to use this in a server if you want to send Mons with a username!"); - const member = await getMemberByName(guild, combined); - if (typeof member === "string") return send(member); - else if (member.user.id === author.id) return send("You can't send Mons to yourself!"); - else if (member.user.bot && process.argv[2] !== "dev") return send("You can't send Mons to a bot!"); + const user = await getUserByNickname(combined, guild); + if (typeof user === "string") return send(user); + else if (user.id === author.id) return send("You can't send Mons to yourself!"); + else if (user.bot && !IS_DEV_MODE) return send("You can't send Mons to a bot!"); - const target = member.user; - - const result = await confirm( + const confirmed = await confirm( await send(`Are you sure you want to send ${pluralise(amount, "Mon", "s")} to this person?`, { embed: { color: ECO_EMBED_COLOR, author: { - name: target.tag, - icon_url: target.displayAvatarURL({ + name: user.tag, + icon_url: user.displayAvatarURL({ format: "png", dynamic: true }) @@ -179,12 +177,12 @@ export const PayCommand = new NamedCommand({ author.id ); - if (result) { - const receiver = Storage.getUser(target.id); + if (confirmed) { + const receiver = Storage.getUser(user.id); sender.money -= amount; receiver.money += amount; Storage.save(); - send(getSendEmbed(author, target, amount)); + send(getSendEmbed(author, user, amount)); } } diff --git a/src/commands/fun/whois.ts b/src/commands/fun/whois.ts index 761e049..7950ed6 100644 --- a/src/commands/fun/whois.ts +++ b/src/commands/fun/whois.ts @@ -1,5 +1,5 @@ import {User} from "discord.js"; -import {Command, NamedCommand, getMemberByName, CHANNEL_TYPE, RestCommand} from "onion-lasers"; +import {Command, NamedCommand, getUserByNickname, RestCommand} from "onion-lasers"; // Quotes must be used here or the numbers will change const registry: {[id: string]: string} = { @@ -69,18 +69,17 @@ export default new NamedCommand({ } }), any: new RestCommand({ - channelType: CHANNEL_TYPE.GUILD, async run({send, guild, combined}) { - const member = await getMemberByName(guild!, combined); + const user = await getUserByNickname(combined, guild); - if (typeof member !== "string") { - if (member.id in registry) { - send(registry[member.id]); + if (typeof user !== "string") { + if (user.id in registry) { + send(registry[user.id]); } else { - send(`\`${member.nickname ?? member.user.username}\` hasn't been added to the registry yet!`); + send(`\`${user.tag}\` hasn't been added to the registry yet!`); } } else { - send(member); + send(user); } } }) diff --git a/src/commands/utility/info.ts b/src/commands/utility/info.ts index 9dbbc09..fa037e9 100644 --- a/src/commands/utility/info.ts +++ b/src/commands/utility/info.ts @@ -1,7 +1,7 @@ import {MessageEmbed, version as djsversion, Guild, User, GuildMember} from "discord.js"; import ms from "ms"; import os from "os"; -import {Command, NamedCommand, getMemberByName, CHANNEL_TYPE, getGuildByName, RestCommand} from "onion-lasers"; +import {Command, NamedCommand, getUserByNickname, CHANNEL_TYPE, getGuildByName, RestCommand} from "onion-lasers"; import {formatBytes, trimArray} from "../../lib"; import {verificationLevels, filterLevels, regions} from "../../defs/info"; import moment, {utc} from "moment"; @@ -34,17 +34,17 @@ export default new NamedCommand({ description: "Shows another user's avatar by searching their name", channelType: CHANNEL_TYPE.GUILD, async run({send, guild, combined}) { - const member = await getMemberByName(guild!, combined); + const user = await getUserByNickname(combined, guild); - if (typeof member !== "string") { + if (typeof user !== "string") { send( - member.user.displayAvatarURL({ + user.displayAvatarURL({ dynamic: true, size: 2048 }) ); } else { - send(member); + send(user); } } }) @@ -125,9 +125,19 @@ export default new NamedCommand({ async run({send, guild, args}) { const user = args[0] as User; // Transforms the User object into a GuildMember object of the current guild. - const member = guild?.members.resolve(args[0]); + const member = guild?.members.resolve(user); send(await getUserInfo(user, member)); } + }), + any: new RestCommand({ + description: "Displays info about a user by their nickname or username.", + async run({send, guild, combined}) { + const user = await getUserByNickname(combined, guild); + // Transforms the User object into a GuildMember object of the current guild. + const member = guild?.members.resolve(user); + if (typeof user !== "string") send(await getUserInfo(user, member)); + else send(user); + } }) }); diff --git a/src/commands/utility/time.ts b/src/commands/utility/time.ts index 5d6bf83..d55e37d 100644 --- a/src/commands/utility/time.ts +++ b/src/commands/utility/time.ts @@ -4,7 +4,7 @@ import { askForReply, confirm, askMultipleChoice, - getMemberByName, + getUserByNickname, RestCommand } from "onion-lasers"; import {Storage} from "../../structures"; @@ -393,9 +393,9 @@ export default new NamedCommand({ any: new RestCommand({ description: "See what time it is for someone else (by their username).", async run({send, guild, combined}) { - const member = await getMemberByName(guild!, combined); - if (typeof member !== "string") send(getTimeEmbed(member.user)); - else send(member); + const user = await getUserByNickname(combined, guild); + if (typeof user !== "string") send(getTimeEmbed(user)); + else send(user); } }) });