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/.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/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. 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/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..9a64ccf --- /dev/null +++ b/docs/Specifications.md @@ -0,0 +1,33 @@ +# 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. + - `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. +- `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. + +# 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 index 7a3968f..b60a197 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,45 +4,6 @@ "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", @@ -61,9 +22,42 @@ "@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==", + "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", @@ -72,191 +66,72 @@ "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 + "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, + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", "requires": { - "color-convert": "^1.9.0" + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" } }, - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "requires": { - "sprintf-js": "~1.0.2" - } - }, - "astral-regex": { - "version": "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, + "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" - }, - "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 + "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==", - "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 + "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": "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, + "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.3" + "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==" }, "combined-stream": { "version": "1.0.8", @@ -266,12 +141,6 @@ "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", @@ -283,21 +152,6 @@ "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", @@ -316,159 +170,51 @@ "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==" + } } }, - "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" - } + "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==", - "dev": true + "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=", - "dev": true + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" }, - "eslint": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.1.0.tgz", - "integrity": "sha512-DfS3b8iHMK5z/YLSme8K5cge168I8j8o1uiVmFCgnnjxZQbCGyraF8bMl7Ju4yfBmCuxD7shOF7eqGkcuIHfsA==", + "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": { - "@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" + "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" } }, - "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", @@ -478,256 +224,63 @@ "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==", + "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 - }, - "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 + "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==", - "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, + "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": "^3.0.0", + "chalk": "^4.1.0", "cli-cursor": "^3.1.0", - "cli-width": "^2.0.0", + "cli-width": "^3.0.0", "external-editor": "^3.0.3", "figures": "^3.0.0", - "lodash": "^4.17.15", + "lodash": "^4.17.16", "mute-stream": "0.0.8", "run-async": "^2.4.0", - "rxjs": "^6.5.3", + "rxjs": "^6.6.0", "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" - } + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" }, "isexe": { "version": "2.0.0", @@ -735,48 +288,15 @@ "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.19", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz", - "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==", + "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": { @@ -795,53 +315,17 @@ "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==" + "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==", - "dev": true + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==" }, - "natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "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": { @@ -849,58 +333,18 @@ "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 + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" }, "path-key": { "version": "3.1.1", @@ -908,71 +352,42 @@ "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 + "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" + } }, - "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 + "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==", - "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 + "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==" }, "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, + "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" } @@ -980,14 +395,7 @@ "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 + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "setimmediate": { "version": "1.0.5", @@ -1012,39 +420,36 @@ "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 + "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==" }, - "slice-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", - "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", + "split": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/split/-/split-0.3.3.tgz", + "integrity": "sha1-zQ7qXmOiEd//frDwkcQTPi0N0o8=", "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 - } + "through": "2" } }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "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==", - "dev": true, "requires": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -1055,138 +460,63 @@ "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, + "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": "^3.0.0" + "has-flag": "^4.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 + "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==", - "dev": true, "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==", - "dev": true + "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-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 + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.11.0.tgz", + "integrity": "sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==" }, - "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==", + "typescript": { + "version": "3.9.6", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.6.tgz", + "integrity": "sha512-Pspx3oKAPJtjNwE92YS05HQoY7z2SFyOpHo9MqJor3BXAGNaPUs83CuVp9VISFkSjyRfiTpmKuAYGJB7S7hOxw==", "dev": true }, "which": { @@ -1197,32 +527,6 @@ "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 index 7e0e79b..e92a1e8 100644 --- a/package.json +++ b/package.json @@ -2,23 +2,30 @@ "name": "d.js-v12-bot", "version": "0.0.1", "description": "A Discord bot built on Discord.JS v12", - "main": "src/index", + "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": { - "test": "echo \"Error: no test specified\" && exit 1" + "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": "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" - } + "license": "MIT" } 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/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/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 new file mode 100644 index 0000000..61db18d --- /dev/null +++ b/src/core/lib.ts @@ -0,0 +1,372 @@ +import {GenericWrapper, NumberWrapper, ArrayWrapper} from "./wrappers"; +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 +{ + // 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: "" +}; + +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 = (...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}`; +} + +// 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); +}; + +// 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..922e0fe --- /dev/null +++ b/src/core/storage.ts @@ -0,0 +1,126 @@ +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 + { + 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; + }, + 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) +{ + 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/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.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(); diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..f6114da --- /dev/null +++ b/src/index.ts @@ -0,0 +1,15 @@ +import {Client} from "discord.js"; +import setup from "./setup"; +import FileManager from "./core/storage"; +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. +// 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); +}); \ No newline at end of file diff --git a/src/setup.ts b/src/setup.ts new file mode 100644 index 0000000..c373a72 --- /dev/null +++ b/src/setup.ts @@ -0,0 +1,49 @@ +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: "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."); + 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 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