├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── AUTHORS ├── LICENSE ├── README.md ├── package-lock.json ├── package.json ├── prod.env ├── src ├── commands │ ├── ping │ │ └── cmd.ping.ts │ └── role │ │ └── cmd.role.ts ├── loadCommands.ts ├── loadMiddleware.ts ├── loadSettings.ts ├── middleware │ └── antispam │ │ ├── README.md │ │ ├── heuristics │ │ ├── heuristic.rapidMessages.ts │ │ └── heuristic.repeatingMessages.ts │ │ └── mw.antispam.ts └── tunneler.ts └── tsconfig.json /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: '@typescript-eslint/parser', 4 | env: { 5 | node: true 6 | }, 7 | plugins: [ 8 | '@typescript-eslint', 9 | ], 10 | extends: [ 11 | 'eslint:recommended', 12 | 'plugin:@typescript-eslint/recommended', 13 | 'prettier', 14 | ], 15 | }; 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | dev.env 4 | *.log 5 | *.settings -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Ari Höysniemi -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Ari Höysniemi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Tunneler 2 | 3 | **Version 1 now released!** 4 | 5 | A super lightweight and robust Discord bot platform for your various Discord-needs. 6 | 7 | Forget session handling, message handling, permissions and such. _Focus on your core feature needs._ 8 | 9 | Comes with several ready-to-use commands and middleware. Like a role assigner and an antispam guard. Use as-is or build _your_ dream bot. 10 | 11 | # How to install 12 | 13 | ### Requirements 14 | 15 | - NodeJs 16 | - Tunneler has been developed against Node 14. 17 | - Basic knowledge about npm and running node applications. 18 | - Basic knowledge about handling Discord applications. 19 | 20 | ### Installation Steps 21 | 22 | 1. [Register a new application for the bot](https://discord.com/developers/applications) 23 | 2. Download the most recent release from [releases](https://github.com/ahoys/discaptcha/releases). 24 | 3. Extract the package and run `npm install` in the extraction folder. 25 | 4. Open .env in a text editor and configure the bot (the following are mandatory): 26 | 27 | ``` 28 | discord.token=Your bot's application token. 29 | discord.application_id=Your bot's application id. 30 | discord.owner_id=Your own Discord client id. 31 | ``` 32 | 33 | You can use this same file to give default settings for the bot (see below, commands and middleware). 34 | 35 | You are all set. You can run the bot now with a `node tunneler` command. Make sure you give the bot suitable permissions to execute its tasks. 36 | 37 | # Commands and middleware 38 | 39 | ## What are they? 40 | 41 | All the functionality the bot has is based on commands and middleware. **A command** is something that is given as a command to the bot. **A middleware** is applied to all messages. 42 | 43 | You can enable or disable these functionalities in your .env-file: 44 | 45 | `cmd.ping=true` ping command enabled. 46 | 47 | `cmd.ping=false` ping command disabled. 48 | 49 | ## List of features 50 | 51 | ### cmd.ping 52 | 53 | A simple ping command that returns the current latency the bot experiences towards the Discord services. Useful for investigating what's going on with Discord. 54 | 55 | Usage: `@Tunneler ping` 56 | 57 | ### cmd.role 58 | 59 | The user can request a specific role with the role command. 60 | 61 | Usage: `@Tunneler role example` 62 | 63 | ### mw.antispam 64 | 65 | Detects and bans spammers. Makes moderator's day a lot nicer. 66 | 67 | [Read more.](https://github.com/ahoys/tunnelerjs/blob/ts-build/src/middleware/antispam/README.md) 68 | 69 | ## How to configure commands or middleware? 70 | 71 | Some commands and middleware do allow special configuration. See their corresponding folder for a README file to learn more: 72 | 73 | For example `src/middleware/antispam/README.md` 74 | 75 | You can either set **global settings** or **guild specific** settings. Use the .env file to set global settings and separate guild files for guild specific settings. 76 | 77 | `.settings` or `123456789.settings` is the naming syntax for the guild file. It must be in the same folder as the .env file. 78 | 79 | All settings missing from the guild settings file will be read from the global settings instead. If global setting is missing, a default value is used. 80 | 81 | # Making custom commands and middleware for the bot 82 | 83 | **Important**: Do not develop with a release build. Instead clone this repository with Git and apply `npm install` to it. 84 | 85 | ## Technical background 86 | 87 | Tunneler will give you a fast access to developing your own commands and middleware by allowing you to skip all the "how to connect Discord, handle messages, etc." hurdles. 88 | 89 | Commands are something that always require two things: 90 | 91 | - The bot is either mentioned or the command is given in a direct channel. 92 | - The command is called by its name. 93 | - For example: `@tunneler ping` where ping is the name of the command. 94 | 95 | Middleware apply to all messages with a one deliberate restriction: 96 | 97 | - Direct messages are not monitored. 98 | 99 | All commands and middleware are under a specific folder structure. Commands are in `src/commands` and middleware in `src/middleware`. 100 | 101 | ## Creating files 102 | 103 | All of the following instructions apply to middleware as well. Just replace "command" with "middleware". 104 | 105 | 1. Create your command folder under `src/commands` 106 | 2. Name your index file with a following syntax: `..ts`, for example `cmd.ping.ts`. Js-files may also be supported, not tested and using ts is recommended. 107 | 108 | ## Coding your function 109 | 110 | See `cmd.ping.ts` or `mw.antispam.ts` to get an idea of the ideal end result. 111 | 112 | - The given parameters are following: `(client: Client, message: Message, flags: IFlags) => void` 113 | - The function must have a default export. For example: `export default ping` 114 | - The function can't return anything. 115 | - [Client](https://discord.js.org/#/docs/main/stable/class/Client) holds the bot's client. 116 | - [Message](https://discord.js.org/#/docs/main/stable/class/Message) holds the message that triggered the command. 117 | 118 | Tip: see the flags interface (IFlags) to learn about utility variables that are in your disposal. 119 | 120 | ## Enabling your command/middleware 121 | 122 | Enabling features is simple in Tunneler. Just add `.=true` into your .env-file. For example: `cmd.ping=true` 123 | 124 | ## Time to build 125 | 126 | Run `npm run dist` to build production ready code. The development build can be started with a `npm start` 127 | 128 | ## Other tips 129 | 130 | - Create a `dev.env` file to specify development environment specific variables. 131 | - Tunneler won't trigger your command if the command name wasn't mentioned. No need to double-check it in your function. 132 | - Using the given flags is a really handy way to make sure not everyone can execute mission critical commands. 133 | - Tunneler doesn't react to its own messages. 134 | - Use VS Code with ESLint and Prettier extensions. 135 | 136 | # Migrating from Tunneler 0.x to 1.x 137 | 138 | ### How to proceed? 139 | 140 | Tunneler has been 100% rewritten for this new release. This means that the backwards compatibility is broken. If you have your own custom commands made, see the chapter above about making your own commands to migrate your custom commands and middleware to 1.x and above. 141 | 142 | It is highly recommended to remove the old Tunneler and everything related before using the new one. 143 | 144 | You can re-use the Discord application (id and token). 145 | 146 | ### Slash commands 147 | 148 | Slash commands will emerge after the Discord API matures a bit more. 149 | 150 | ### Why so drastic update? 151 | 152 | After years of using Tunneler, the exact needs for it have become more clear. The original Tunneler was way over-engineered and had tons of easy pitfalls. It was coded fast to serve a specific purpose and the end result wasn't as well thought as one'd desire. The new 1.x is basically an "how it should've been made" update. 153 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tunnelerjs", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@babel/code-frame": { 8 | "version": "7.12.11", 9 | "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", 10 | "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", 11 | "dev": true, 12 | "requires": { 13 | "@babel/highlight": "^7.10.4" 14 | } 15 | }, 16 | "@babel/helper-validator-identifier": { 17 | "version": "7.14.5", 18 | "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.5.tgz", 19 | "integrity": "sha512-5lsetuxCLilmVGyiLEfoHBRX8UCFD+1m2x3Rj97WrW3V7H3u4RWRXA4evMjImCsin2J2YT0QaVDGf+z8ondbAg==", 20 | "dev": true 21 | }, 22 | "@babel/highlight": { 23 | "version": "7.14.5", 24 | "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz", 25 | "integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==", 26 | "dev": true, 27 | "requires": { 28 | "@babel/helper-validator-identifier": "^7.14.5", 29 | "chalk": "^2.0.0", 30 | "js-tokens": "^4.0.0" 31 | }, 32 | "dependencies": { 33 | "chalk": { 34 | "version": "2.4.2", 35 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", 36 | "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", 37 | "dev": true, 38 | "requires": { 39 | "ansi-styles": "^3.2.1", 40 | "escape-string-regexp": "^1.0.5", 41 | "supports-color": "^5.3.0" 42 | } 43 | }, 44 | "escape-string-regexp": { 45 | "version": "1.0.5", 46 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", 47 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", 48 | "dev": true 49 | } 50 | } 51 | }, 52 | "@discordjs/collection": { 53 | "version": "0.1.6", 54 | "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-0.1.6.tgz", 55 | "integrity": "sha512-utRNxnd9kSS2qhyivo9lMlt5qgAUasH2gb7BEOn6p0efFh24gjGomHzWKMAPn2hEReOPQZCJaRKoURwRotKucQ==" 56 | }, 57 | "@discordjs/form-data": { 58 | "version": "3.0.1", 59 | "resolved": "https://registry.npmjs.org/@discordjs/form-data/-/form-data-3.0.1.tgz", 60 | "integrity": "sha512-ZfFsbgEXW71Rw/6EtBdrP5VxBJy4dthyC0tpQKGKmYFImlmmrykO14Za+BiIVduwjte0jXEBlhSKf0MWbFp9Eg==", 61 | "requires": { 62 | "asynckit": "^0.4.0", 63 | "combined-stream": "^1.0.8", 64 | "mime-types": "^2.1.12" 65 | } 66 | }, 67 | "@eslint/eslintrc": { 68 | "version": "0.4.2", 69 | "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.2.tgz", 70 | "integrity": "sha512-8nmGq/4ycLpIwzvhI4tNDmQztZ8sp+hI7cyG8i1nQDhkAbRzHpXPidRAHlNvCZQpJTKw5ItIpMw9RSToGF00mg==", 71 | "dev": true, 72 | "requires": { 73 | "ajv": "^6.12.4", 74 | "debug": "^4.1.1", 75 | "espree": "^7.3.0", 76 | "globals": "^13.9.0", 77 | "ignore": "^4.0.6", 78 | "import-fresh": "^3.2.1", 79 | "js-yaml": "^3.13.1", 80 | "minimatch": "^3.0.4", 81 | "strip-json-comments": "^3.1.1" 82 | }, 83 | "dependencies": { 84 | "ignore": { 85 | "version": "4.0.6", 86 | "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", 87 | "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", 88 | "dev": true 89 | } 90 | } 91 | }, 92 | "@nodelib/fs.scandir": { 93 | "version": "2.1.5", 94 | "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", 95 | "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", 96 | "dev": true, 97 | "requires": { 98 | "@nodelib/fs.stat": "2.0.5", 99 | "run-parallel": "^1.1.9" 100 | } 101 | }, 102 | "@nodelib/fs.stat": { 103 | "version": "2.0.5", 104 | "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", 105 | "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", 106 | "dev": true 107 | }, 108 | "@nodelib/fs.walk": { 109 | "version": "1.2.7", 110 | "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.7.tgz", 111 | "integrity": "sha512-BTIhocbPBSrRmHxOAJFtR18oLhxTtAFDAvL8hY1S3iU8k+E60W/YFs4jrixGzQjMpF4qPXxIQHcjVD9dz1C2QA==", 112 | "dev": true, 113 | "requires": { 114 | "@nodelib/fs.scandir": "2.1.5", 115 | "fastq": "^1.6.0" 116 | } 117 | }, 118 | "@types/json-schema": { 119 | "version": "7.0.7", 120 | "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.7.tgz", 121 | "integrity": "sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA==", 122 | "dev": true 123 | }, 124 | "@types/node": { 125 | "version": "15.12.2", 126 | "resolved": "https://registry.npmjs.org/@types/node/-/node-15.12.2.tgz", 127 | "integrity": "sha512-zjQ69G564OCIWIOHSXyQEEDpdpGl+G348RAKY0XXy9Z5kU9Vzv1GMNnkar/ZJ8dzXB3COzD9Mo9NtRZ4xfgUww==" 128 | }, 129 | "@types/ws": { 130 | "version": "7.4.4", 131 | "resolved": "https://registry.npmjs.org/@types/ws/-/ws-7.4.4.tgz", 132 | "integrity": "sha512-d/7W23JAXPodQNbOZNXvl2K+bqAQrCMwlh/nuQsPSQk6Fq0opHoPrUw43aHsvSbIiQPr8Of2hkFbnz1XBFVyZQ==", 133 | "requires": { 134 | "@types/node": "*" 135 | } 136 | }, 137 | "@typescript-eslint/eslint-plugin": { 138 | "version": "4.26.1", 139 | "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.26.1.tgz", 140 | "integrity": "sha512-aoIusj/8CR+xDWmZxARivZjbMBQTT9dImUtdZ8tVCVRXgBUuuZyM5Of5A9D9arQPxbi/0rlJLcuArclz/rCMJw==", 141 | "dev": true, 142 | "requires": { 143 | "@typescript-eslint/experimental-utils": "4.26.1", 144 | "@typescript-eslint/scope-manager": "4.26.1", 145 | "debug": "^4.3.1", 146 | "functional-red-black-tree": "^1.0.1", 147 | "lodash": "^4.17.21", 148 | "regexpp": "^3.1.0", 149 | "semver": "^7.3.5", 150 | "tsutils": "^3.21.0" 151 | } 152 | }, 153 | "@typescript-eslint/experimental-utils": { 154 | "version": "4.26.1", 155 | "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.26.1.tgz", 156 | "integrity": "sha512-sQHBugRhrXzRCs9PaGg6rowie4i8s/iD/DpTB+EXte8OMDfdCG5TvO73XlO9Wc/zi0uyN4qOmX9hIjQEyhnbmQ==", 157 | "dev": true, 158 | "requires": { 159 | "@types/json-schema": "^7.0.7", 160 | "@typescript-eslint/scope-manager": "4.26.1", 161 | "@typescript-eslint/types": "4.26.1", 162 | "@typescript-eslint/typescript-estree": "4.26.1", 163 | "eslint-scope": "^5.1.1", 164 | "eslint-utils": "^3.0.0" 165 | } 166 | }, 167 | "@typescript-eslint/parser": { 168 | "version": "4.26.1", 169 | "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.26.1.tgz", 170 | "integrity": "sha512-q7F3zSo/nU6YJpPJvQveVlIIzx9/wu75lr6oDbDzoeIRWxpoc/HQ43G4rmMoCc5my/3uSj2VEpg/D83LYZF5HQ==", 171 | "dev": true, 172 | "requires": { 173 | "@typescript-eslint/scope-manager": "4.26.1", 174 | "@typescript-eslint/types": "4.26.1", 175 | "@typescript-eslint/typescript-estree": "4.26.1", 176 | "debug": "^4.3.1" 177 | } 178 | }, 179 | "@typescript-eslint/scope-manager": { 180 | "version": "4.26.1", 181 | "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.26.1.tgz", 182 | "integrity": "sha512-TW1X2p62FQ8Rlne+WEShyd7ac2LA6o27S9i131W4NwDSfyeVlQWhw8ylldNNS8JG6oJB9Ha9Xyc+IUcqipvheQ==", 183 | "dev": true, 184 | "requires": { 185 | "@typescript-eslint/types": "4.26.1", 186 | "@typescript-eslint/visitor-keys": "4.26.1" 187 | } 188 | }, 189 | "@typescript-eslint/types": { 190 | "version": "4.26.1", 191 | "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.26.1.tgz", 192 | "integrity": "sha512-STyMPxR3cS+LaNvS8yK15rb8Y0iL0tFXq0uyl6gY45glyI7w0CsyqyEXl/Fa0JlQy+pVANeK3sbwPneCbWE7yg==", 193 | "dev": true 194 | }, 195 | "@typescript-eslint/typescript-estree": { 196 | "version": "4.26.1", 197 | "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.26.1.tgz", 198 | "integrity": "sha512-l3ZXob+h0NQzz80lBGaykdScYaiEbFqznEs99uwzm8fPHhDjwaBFfQkjUC/slw6Sm7npFL8qrGEAMxcfBsBJUg==", 199 | "dev": true, 200 | "requires": { 201 | "@typescript-eslint/types": "4.26.1", 202 | "@typescript-eslint/visitor-keys": "4.26.1", 203 | "debug": "^4.3.1", 204 | "globby": "^11.0.3", 205 | "is-glob": "^4.0.1", 206 | "semver": "^7.3.5", 207 | "tsutils": "^3.21.0" 208 | } 209 | }, 210 | "@typescript-eslint/visitor-keys": { 211 | "version": "4.26.1", 212 | "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.26.1.tgz", 213 | "integrity": "sha512-IGouNSSd+6x/fHtYRyLOM6/C+QxMDzWlDtN41ea+flWuSF9g02iqcIlX8wM53JkfljoIjP0U+yp7SiTS1onEkw==", 214 | "dev": true, 215 | "requires": { 216 | "@typescript-eslint/types": "4.26.1", 217 | "eslint-visitor-keys": "^2.0.0" 218 | } 219 | }, 220 | "abort-controller": { 221 | "version": "3.0.0", 222 | "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", 223 | "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", 224 | "requires": { 225 | "event-target-shim": "^5.0.0" 226 | } 227 | }, 228 | "acorn": { 229 | "version": "7.4.1", 230 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", 231 | "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", 232 | "dev": true 233 | }, 234 | "acorn-jsx": { 235 | "version": "5.3.1", 236 | "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.1.tgz", 237 | "integrity": "sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==", 238 | "dev": true 239 | }, 240 | "ajv": { 241 | "version": "6.12.6", 242 | "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", 243 | "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", 244 | "dev": true, 245 | "requires": { 246 | "fast-deep-equal": "^3.1.1", 247 | "fast-json-stable-stringify": "^2.0.0", 248 | "json-schema-traverse": "^0.4.1", 249 | "uri-js": "^4.2.2" 250 | } 251 | }, 252 | "ansi-colors": { 253 | "version": "4.1.1", 254 | "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", 255 | "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", 256 | "dev": true 257 | }, 258 | "ansi-regex": { 259 | "version": "5.0.0", 260 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", 261 | "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", 262 | "dev": true 263 | }, 264 | "ansi-styles": { 265 | "version": "3.2.1", 266 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", 267 | "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", 268 | "dev": true, 269 | "requires": { 270 | "color-convert": "^1.9.0" 271 | } 272 | }, 273 | "argparse": { 274 | "version": "1.0.10", 275 | "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", 276 | "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", 277 | "dev": true, 278 | "requires": { 279 | "sprintf-js": "~1.0.2" 280 | } 281 | }, 282 | "array-union": { 283 | "version": "2.1.0", 284 | "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", 285 | "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", 286 | "dev": true 287 | }, 288 | "astral-regex": { 289 | "version": "2.0.0", 290 | "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", 291 | "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", 292 | "dev": true 293 | }, 294 | "asynckit": { 295 | "version": "0.4.0", 296 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", 297 | "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" 298 | }, 299 | "balanced-match": { 300 | "version": "1.0.2", 301 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 302 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", 303 | "dev": true 304 | }, 305 | "brace-expansion": { 306 | "version": "1.1.11", 307 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 308 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 309 | "dev": true, 310 | "requires": { 311 | "balanced-match": "^1.0.0", 312 | "concat-map": "0.0.1" 313 | } 314 | }, 315 | "braces": { 316 | "version": "3.0.2", 317 | "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", 318 | "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", 319 | "dev": true, 320 | "requires": { 321 | "fill-range": "^7.0.1" 322 | } 323 | }, 324 | "callsites": { 325 | "version": "3.1.0", 326 | "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", 327 | "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", 328 | "dev": true 329 | }, 330 | "chalk": { 331 | "version": "4.1.1", 332 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", 333 | "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", 334 | "dev": true, 335 | "requires": { 336 | "ansi-styles": "^4.1.0", 337 | "supports-color": "^7.1.0" 338 | }, 339 | "dependencies": { 340 | "ansi-styles": { 341 | "version": "4.3.0", 342 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", 343 | "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", 344 | "dev": true, 345 | "requires": { 346 | "color-convert": "^2.0.1" 347 | } 348 | }, 349 | "color-convert": { 350 | "version": "2.0.1", 351 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", 352 | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", 353 | "dev": true, 354 | "requires": { 355 | "color-name": "~1.1.4" 356 | } 357 | }, 358 | "color-name": { 359 | "version": "1.1.4", 360 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", 361 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", 362 | "dev": true 363 | }, 364 | "has-flag": { 365 | "version": "4.0.0", 366 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", 367 | "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", 368 | "dev": true 369 | }, 370 | "supports-color": { 371 | "version": "7.2.0", 372 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", 373 | "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", 374 | "dev": true, 375 | "requires": { 376 | "has-flag": "^4.0.0" 377 | } 378 | } 379 | } 380 | }, 381 | "color-convert": { 382 | "version": "1.9.3", 383 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", 384 | "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", 385 | "dev": true, 386 | "requires": { 387 | "color-name": "1.1.3" 388 | } 389 | }, 390 | "color-name": { 391 | "version": "1.1.3", 392 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", 393 | "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", 394 | "dev": true 395 | }, 396 | "combined-stream": { 397 | "version": "1.0.8", 398 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", 399 | "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", 400 | "requires": { 401 | "delayed-stream": "~1.0.0" 402 | } 403 | }, 404 | "concat-map": { 405 | "version": "0.0.1", 406 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 407 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", 408 | "dev": true 409 | }, 410 | "cross-spawn": { 411 | "version": "7.0.3", 412 | "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", 413 | "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", 414 | "dev": true, 415 | "requires": { 416 | "path-key": "^3.1.0", 417 | "shebang-command": "^2.0.0", 418 | "which": "^2.0.1" 419 | } 420 | }, 421 | "debug": { 422 | "version": "4.3.1", 423 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", 424 | "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", 425 | "dev": true, 426 | "requires": { 427 | "ms": "2.1.2" 428 | } 429 | }, 430 | "deep-is": { 431 | "version": "0.1.3", 432 | "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", 433 | "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", 434 | "dev": true 435 | }, 436 | "delayed-stream": { 437 | "version": "1.0.0", 438 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", 439 | "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" 440 | }, 441 | "dir-glob": { 442 | "version": "3.0.1", 443 | "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", 444 | "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", 445 | "dev": true, 446 | "requires": { 447 | "path-type": "^4.0.0" 448 | } 449 | }, 450 | "discord.js": { 451 | "version": "12.5.3", 452 | "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-12.5.3.tgz", 453 | "integrity": "sha512-D3nkOa/pCkNyn6jLZnAiJApw2N9XrIsXUAdThf01i7yrEuqUmDGc7/CexVWwEcgbQR97XQ+mcnqJpmJ/92B4Aw==", 454 | "requires": { 455 | "@discordjs/collection": "^0.1.6", 456 | "@discordjs/form-data": "^3.0.1", 457 | "abort-controller": "^3.0.0", 458 | "node-fetch": "^2.6.1", 459 | "prism-media": "^1.2.9", 460 | "setimmediate": "^1.0.5", 461 | "tweetnacl": "^1.0.3", 462 | "ws": "^7.4.4" 463 | } 464 | }, 465 | "doctrine": { 466 | "version": "3.0.0", 467 | "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", 468 | "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", 469 | "dev": true, 470 | "requires": { 471 | "esutils": "^2.0.2" 472 | } 473 | }, 474 | "dotenv": { 475 | "version": "10.0.0", 476 | "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz", 477 | "integrity": "sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==" 478 | }, 479 | "emoji-regex": { 480 | "version": "8.0.0", 481 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", 482 | "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", 483 | "dev": true 484 | }, 485 | "enquirer": { 486 | "version": "2.3.6", 487 | "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", 488 | "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", 489 | "dev": true, 490 | "requires": { 491 | "ansi-colors": "^4.1.1" 492 | } 493 | }, 494 | "escape-string-regexp": { 495 | "version": "4.0.0", 496 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", 497 | "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", 498 | "dev": true 499 | }, 500 | "eslint": { 501 | "version": "7.28.0", 502 | "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.28.0.tgz", 503 | "integrity": "sha512-UMfH0VSjP0G4p3EWirscJEQ/cHqnT/iuH6oNZOB94nBjWbMnhGEPxsZm1eyIW0C/9jLI0Fow4W5DXLjEI7mn1g==", 504 | "dev": true, 505 | "requires": { 506 | "@babel/code-frame": "7.12.11", 507 | "@eslint/eslintrc": "^0.4.2", 508 | "ajv": "^6.10.0", 509 | "chalk": "^4.0.0", 510 | "cross-spawn": "^7.0.2", 511 | "debug": "^4.0.1", 512 | "doctrine": "^3.0.0", 513 | "enquirer": "^2.3.5", 514 | "escape-string-regexp": "^4.0.0", 515 | "eslint-scope": "^5.1.1", 516 | "eslint-utils": "^2.1.0", 517 | "eslint-visitor-keys": "^2.0.0", 518 | "espree": "^7.3.1", 519 | "esquery": "^1.4.0", 520 | "esutils": "^2.0.2", 521 | "fast-deep-equal": "^3.1.3", 522 | "file-entry-cache": "^6.0.1", 523 | "functional-red-black-tree": "^1.0.1", 524 | "glob-parent": "^5.1.2", 525 | "globals": "^13.6.0", 526 | "ignore": "^4.0.6", 527 | "import-fresh": "^3.0.0", 528 | "imurmurhash": "^0.1.4", 529 | "is-glob": "^4.0.0", 530 | "js-yaml": "^3.13.1", 531 | "json-stable-stringify-without-jsonify": "^1.0.1", 532 | "levn": "^0.4.1", 533 | "lodash.merge": "^4.6.2", 534 | "minimatch": "^3.0.4", 535 | "natural-compare": "^1.4.0", 536 | "optionator": "^0.9.1", 537 | "progress": "^2.0.0", 538 | "regexpp": "^3.1.0", 539 | "semver": "^7.2.1", 540 | "strip-ansi": "^6.0.0", 541 | "strip-json-comments": "^3.1.0", 542 | "table": "^6.0.9", 543 | "text-table": "^0.2.0", 544 | "v8-compile-cache": "^2.0.3" 545 | }, 546 | "dependencies": { 547 | "eslint-utils": { 548 | "version": "2.1.0", 549 | "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", 550 | "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", 551 | "dev": true, 552 | "requires": { 553 | "eslint-visitor-keys": "^1.1.0" 554 | }, 555 | "dependencies": { 556 | "eslint-visitor-keys": { 557 | "version": "1.3.0", 558 | "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", 559 | "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", 560 | "dev": true 561 | } 562 | } 563 | }, 564 | "ignore": { 565 | "version": "4.0.6", 566 | "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", 567 | "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", 568 | "dev": true 569 | } 570 | } 571 | }, 572 | "eslint-config-prettier": { 573 | "version": "8.3.0", 574 | "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.3.0.tgz", 575 | "integrity": "sha512-BgZuLUSeKzvlL/VUjx/Yb787VQ26RU3gGjA3iiFvdsp/2bMfVIWUVP7tjxtjS0e+HP409cPlPvNkQloz8C91ew==", 576 | "dev": true 577 | }, 578 | "eslint-scope": { 579 | "version": "5.1.1", 580 | "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", 581 | "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", 582 | "dev": true, 583 | "requires": { 584 | "esrecurse": "^4.3.0", 585 | "estraverse": "^4.1.1" 586 | } 587 | }, 588 | "eslint-utils": { 589 | "version": "3.0.0", 590 | "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", 591 | "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", 592 | "dev": true, 593 | "requires": { 594 | "eslint-visitor-keys": "^2.0.0" 595 | } 596 | }, 597 | "eslint-visitor-keys": { 598 | "version": "2.1.0", 599 | "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", 600 | "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", 601 | "dev": true 602 | }, 603 | "espree": { 604 | "version": "7.3.1", 605 | "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", 606 | "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", 607 | "dev": true, 608 | "requires": { 609 | "acorn": "^7.4.0", 610 | "acorn-jsx": "^5.3.1", 611 | "eslint-visitor-keys": "^1.3.0" 612 | }, 613 | "dependencies": { 614 | "eslint-visitor-keys": { 615 | "version": "1.3.0", 616 | "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", 617 | "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", 618 | "dev": true 619 | } 620 | } 621 | }, 622 | "esprima": { 623 | "version": "4.0.1", 624 | "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", 625 | "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", 626 | "dev": true 627 | }, 628 | "esquery": { 629 | "version": "1.4.0", 630 | "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", 631 | "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", 632 | "dev": true, 633 | "requires": { 634 | "estraverse": "^5.1.0" 635 | }, 636 | "dependencies": { 637 | "estraverse": { 638 | "version": "5.2.0", 639 | "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", 640 | "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", 641 | "dev": true 642 | } 643 | } 644 | }, 645 | "esrecurse": { 646 | "version": "4.3.0", 647 | "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", 648 | "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", 649 | "dev": true, 650 | "requires": { 651 | "estraverse": "^5.2.0" 652 | }, 653 | "dependencies": { 654 | "estraverse": { 655 | "version": "5.2.0", 656 | "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", 657 | "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", 658 | "dev": true 659 | } 660 | } 661 | }, 662 | "estraverse": { 663 | "version": "4.3.0", 664 | "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", 665 | "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", 666 | "dev": true 667 | }, 668 | "esutils": { 669 | "version": "2.0.3", 670 | "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", 671 | "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", 672 | "dev": true 673 | }, 674 | "event-target-shim": { 675 | "version": "5.0.1", 676 | "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", 677 | "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==" 678 | }, 679 | "fast-deep-equal": { 680 | "version": "3.1.3", 681 | "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", 682 | "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", 683 | "dev": true 684 | }, 685 | "fast-glob": { 686 | "version": "3.2.5", 687 | "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.5.tgz", 688 | "integrity": "sha512-2DtFcgT68wiTTiwZ2hNdJfcHNke9XOfnwmBRWXhmeKM8rF0TGwmC/Qto3S7RoZKp5cilZbxzO5iTNTQsJ+EeDg==", 689 | "dev": true, 690 | "requires": { 691 | "@nodelib/fs.stat": "^2.0.2", 692 | "@nodelib/fs.walk": "^1.2.3", 693 | "glob-parent": "^5.1.0", 694 | "merge2": "^1.3.0", 695 | "micromatch": "^4.0.2", 696 | "picomatch": "^2.2.1" 697 | } 698 | }, 699 | "fast-json-stable-stringify": { 700 | "version": "2.1.0", 701 | "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", 702 | "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", 703 | "dev": true 704 | }, 705 | "fast-levenshtein": { 706 | "version": "2.0.6", 707 | "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", 708 | "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", 709 | "dev": true 710 | }, 711 | "fastq": { 712 | "version": "1.11.0", 713 | "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.11.0.tgz", 714 | "integrity": "sha512-7Eczs8gIPDrVzT+EksYBcupqMyxSHXXrHOLRRxU2/DicV8789MRBRR8+Hc2uWzUupOs4YS4JzBmBxjjCVBxD/g==", 715 | "dev": true, 716 | "requires": { 717 | "reusify": "^1.0.4" 718 | } 719 | }, 720 | "file-entry-cache": { 721 | "version": "6.0.1", 722 | "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", 723 | "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", 724 | "dev": true, 725 | "requires": { 726 | "flat-cache": "^3.0.4" 727 | } 728 | }, 729 | "fill-range": { 730 | "version": "7.0.1", 731 | "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", 732 | "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", 733 | "dev": true, 734 | "requires": { 735 | "to-regex-range": "^5.0.1" 736 | } 737 | }, 738 | "flat-cache": { 739 | "version": "3.0.4", 740 | "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", 741 | "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", 742 | "dev": true, 743 | "requires": { 744 | "flatted": "^3.1.0", 745 | "rimraf": "^3.0.2" 746 | } 747 | }, 748 | "flatted": { 749 | "version": "3.1.1", 750 | "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.1.1.tgz", 751 | "integrity": "sha512-zAoAQiudy+r5SvnSw3KJy5os/oRJYHzrzja/tBDqrZtNhUw8bt6y8OBzMWcjWr+8liV8Eb6yOhw8WZ7VFZ5ZzA==", 752 | "dev": true 753 | }, 754 | "fs.realpath": { 755 | "version": "1.0.0", 756 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 757 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", 758 | "dev": true 759 | }, 760 | "functional-red-black-tree": { 761 | "version": "1.0.1", 762 | "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", 763 | "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", 764 | "dev": true 765 | }, 766 | "glob": { 767 | "version": "7.1.7", 768 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", 769 | "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", 770 | "dev": true, 771 | "requires": { 772 | "fs.realpath": "^1.0.0", 773 | "inflight": "^1.0.4", 774 | "inherits": "2", 775 | "minimatch": "^3.0.4", 776 | "once": "^1.3.0", 777 | "path-is-absolute": "^1.0.0" 778 | } 779 | }, 780 | "glob-parent": { 781 | "version": "5.1.2", 782 | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", 783 | "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", 784 | "dev": true, 785 | "requires": { 786 | "is-glob": "^4.0.1" 787 | } 788 | }, 789 | "globals": { 790 | "version": "13.9.0", 791 | "resolved": "https://registry.npmjs.org/globals/-/globals-13.9.0.tgz", 792 | "integrity": "sha512-74/FduwI/JaIrr1H8e71UbDE+5x7pIPs1C2rrwC52SszOo043CsWOZEMW7o2Y58xwm9b+0RBKDxY5n2sUpEFxA==", 793 | "dev": true, 794 | "requires": { 795 | "type-fest": "^0.20.2" 796 | } 797 | }, 798 | "globby": { 799 | "version": "11.0.3", 800 | "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.3.tgz", 801 | "integrity": "sha512-ffdmosjA807y7+lA1NM0jELARVmYul/715xiILEjo3hBLPTcirgQNnXECn5g3mtR8TOLCVbkfua1Hpen25/Xcg==", 802 | "dev": true, 803 | "requires": { 804 | "array-union": "^2.1.0", 805 | "dir-glob": "^3.0.1", 806 | "fast-glob": "^3.1.1", 807 | "ignore": "^5.1.4", 808 | "merge2": "^1.3.0", 809 | "slash": "^3.0.0" 810 | } 811 | }, 812 | "has-flag": { 813 | "version": "3.0.0", 814 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", 815 | "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", 816 | "dev": true 817 | }, 818 | "ignore": { 819 | "version": "5.1.8", 820 | "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", 821 | "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==", 822 | "dev": true 823 | }, 824 | "import-fresh": { 825 | "version": "3.3.0", 826 | "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", 827 | "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", 828 | "dev": true, 829 | "requires": { 830 | "parent-module": "^1.0.0", 831 | "resolve-from": "^4.0.0" 832 | } 833 | }, 834 | "imurmurhash": { 835 | "version": "0.1.4", 836 | "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", 837 | "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", 838 | "dev": true 839 | }, 840 | "inflight": { 841 | "version": "1.0.6", 842 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 843 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 844 | "dev": true, 845 | "requires": { 846 | "once": "^1.3.0", 847 | "wrappy": "1" 848 | } 849 | }, 850 | "inherits": { 851 | "version": "2.0.4", 852 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 853 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", 854 | "dev": true 855 | }, 856 | "is-extglob": { 857 | "version": "2.1.1", 858 | "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", 859 | "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", 860 | "dev": true 861 | }, 862 | "is-fullwidth-code-point": { 863 | "version": "3.0.0", 864 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", 865 | "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", 866 | "dev": true 867 | }, 868 | "is-glob": { 869 | "version": "4.0.1", 870 | "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", 871 | "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", 872 | "dev": true, 873 | "requires": { 874 | "is-extglob": "^2.1.1" 875 | } 876 | }, 877 | "is-number": { 878 | "version": "7.0.0", 879 | "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", 880 | "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", 881 | "dev": true 882 | }, 883 | "isexe": { 884 | "version": "2.0.0", 885 | "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", 886 | "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", 887 | "dev": true 888 | }, 889 | "js-tokens": { 890 | "version": "4.0.0", 891 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", 892 | "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", 893 | "dev": true 894 | }, 895 | "js-yaml": { 896 | "version": "3.14.1", 897 | "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", 898 | "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", 899 | "dev": true, 900 | "requires": { 901 | "argparse": "^1.0.7", 902 | "esprima": "^4.0.0" 903 | } 904 | }, 905 | "json-schema-traverse": { 906 | "version": "0.4.1", 907 | "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", 908 | "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", 909 | "dev": true 910 | }, 911 | "json-stable-stringify-without-jsonify": { 912 | "version": "1.0.1", 913 | "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", 914 | "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", 915 | "dev": true 916 | }, 917 | "levn": { 918 | "version": "0.4.1", 919 | "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", 920 | "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", 921 | "dev": true, 922 | "requires": { 923 | "prelude-ls": "^1.2.1", 924 | "type-check": "~0.4.0" 925 | } 926 | }, 927 | "lodash": { 928 | "version": "4.17.21", 929 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", 930 | "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", 931 | "dev": true 932 | }, 933 | "lodash.clonedeep": { 934 | "version": "4.5.0", 935 | "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", 936 | "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", 937 | "dev": true 938 | }, 939 | "lodash.merge": { 940 | "version": "4.6.2", 941 | "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", 942 | "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", 943 | "dev": true 944 | }, 945 | "lodash.truncate": { 946 | "version": "4.4.2", 947 | "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", 948 | "integrity": "sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=", 949 | "dev": true 950 | }, 951 | "logscribe": { 952 | "version": "2.0.2", 953 | "resolved": "https://registry.npmjs.org/logscribe/-/logscribe-2.0.2.tgz", 954 | "integrity": "sha512-MBdO3v9xMpD5Vk6hsmgzgN9zScUuyOn5MkK/pEYllXzX0OIfUKtSBldzW7aFtfZGdccy/hGz0W82/UrGrIAXTg==" 955 | }, 956 | "lru-cache": { 957 | "version": "6.0.0", 958 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", 959 | "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", 960 | "dev": true, 961 | "requires": { 962 | "yallist": "^4.0.0" 963 | } 964 | }, 965 | "merge2": { 966 | "version": "1.4.1", 967 | "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", 968 | "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", 969 | "dev": true 970 | }, 971 | "micromatch": { 972 | "version": "4.0.4", 973 | "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", 974 | "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", 975 | "dev": true, 976 | "requires": { 977 | "braces": "^3.0.1", 978 | "picomatch": "^2.2.3" 979 | } 980 | }, 981 | "mime-db": { 982 | "version": "1.48.0", 983 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.48.0.tgz", 984 | "integrity": "sha512-FM3QwxV+TnZYQ2aRqhlKBMHxk10lTbMt3bBkMAp54ddrNeVSfcQYOOKuGuy3Ddrm38I04If834fOUSq1yzslJQ==" 985 | }, 986 | "mime-types": { 987 | "version": "2.1.31", 988 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.31.tgz", 989 | "integrity": "sha512-XGZnNzm3QvgKxa8dpzyhFTHmpP3l5YNusmne07VUOXxou9CqUqYa/HBy124RqtVh/O2pECas/MOcsDgpilPOPg==", 990 | "requires": { 991 | "mime-db": "1.48.0" 992 | } 993 | }, 994 | "minimatch": { 995 | "version": "3.0.4", 996 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 997 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 998 | "dev": true, 999 | "requires": { 1000 | "brace-expansion": "^1.1.7" 1001 | } 1002 | }, 1003 | "ms": { 1004 | "version": "2.1.2", 1005 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 1006 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", 1007 | "dev": true 1008 | }, 1009 | "natural-compare": { 1010 | "version": "1.4.0", 1011 | "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", 1012 | "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", 1013 | "dev": true 1014 | }, 1015 | "node-fetch": { 1016 | "version": "2.6.1", 1017 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", 1018 | "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==" 1019 | }, 1020 | "once": { 1021 | "version": "1.4.0", 1022 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 1023 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 1024 | "dev": true, 1025 | "requires": { 1026 | "wrappy": "1" 1027 | } 1028 | }, 1029 | "optionator": { 1030 | "version": "0.9.1", 1031 | "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", 1032 | "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", 1033 | "dev": true, 1034 | "requires": { 1035 | "deep-is": "^0.1.3", 1036 | "fast-levenshtein": "^2.0.6", 1037 | "levn": "^0.4.1", 1038 | "prelude-ls": "^1.2.1", 1039 | "type-check": "^0.4.0", 1040 | "word-wrap": "^1.2.3" 1041 | } 1042 | }, 1043 | "parent-module": { 1044 | "version": "1.0.1", 1045 | "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", 1046 | "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", 1047 | "dev": true, 1048 | "requires": { 1049 | "callsites": "^3.0.0" 1050 | } 1051 | }, 1052 | "path-is-absolute": { 1053 | "version": "1.0.1", 1054 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 1055 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", 1056 | "dev": true 1057 | }, 1058 | "path-key": { 1059 | "version": "3.1.1", 1060 | "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", 1061 | "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", 1062 | "dev": true 1063 | }, 1064 | "path-type": { 1065 | "version": "4.0.0", 1066 | "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", 1067 | "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", 1068 | "dev": true 1069 | }, 1070 | "picomatch": { 1071 | "version": "2.3.0", 1072 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", 1073 | "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", 1074 | "dev": true 1075 | }, 1076 | "prelude-ls": { 1077 | "version": "1.2.1", 1078 | "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", 1079 | "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", 1080 | "dev": true 1081 | }, 1082 | "prism-media": { 1083 | "version": "1.3.1", 1084 | "resolved": "https://registry.npmjs.org/prism-media/-/prism-media-1.3.1.tgz", 1085 | "integrity": "sha512-nyYAa3KB4qteJIqdguKmwxTJgy55xxUtkJ3uRnOvO5jO+frci+9zpRXw6QZVcfDeva3S654fU9+26P2OSTzjHw==" 1086 | }, 1087 | "progress": { 1088 | "version": "2.0.3", 1089 | "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", 1090 | "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", 1091 | "dev": true 1092 | }, 1093 | "punycode": { 1094 | "version": "2.1.1", 1095 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", 1096 | "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", 1097 | "dev": true 1098 | }, 1099 | "queue-microtask": { 1100 | "version": "1.2.3", 1101 | "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", 1102 | "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", 1103 | "dev": true 1104 | }, 1105 | "regexpp": { 1106 | "version": "3.1.0", 1107 | "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz", 1108 | "integrity": "sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==", 1109 | "dev": true 1110 | }, 1111 | "require-from-string": { 1112 | "version": "2.0.2", 1113 | "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", 1114 | "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", 1115 | "dev": true 1116 | }, 1117 | "resolve-from": { 1118 | "version": "4.0.0", 1119 | "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", 1120 | "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", 1121 | "dev": true 1122 | }, 1123 | "reusify": { 1124 | "version": "1.0.4", 1125 | "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", 1126 | "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", 1127 | "dev": true 1128 | }, 1129 | "rimraf": { 1130 | "version": "3.0.2", 1131 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", 1132 | "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", 1133 | "dev": true, 1134 | "requires": { 1135 | "glob": "^7.1.3" 1136 | } 1137 | }, 1138 | "run-parallel": { 1139 | "version": "1.2.0", 1140 | "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", 1141 | "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", 1142 | "dev": true, 1143 | "requires": { 1144 | "queue-microtask": "^1.2.2" 1145 | } 1146 | }, 1147 | "semver": { 1148 | "version": "7.3.5", 1149 | "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", 1150 | "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", 1151 | "dev": true, 1152 | "requires": { 1153 | "lru-cache": "^6.0.0" 1154 | } 1155 | }, 1156 | "setimmediate": { 1157 | "version": "1.0.5", 1158 | "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", 1159 | "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=" 1160 | }, 1161 | "shebang-command": { 1162 | "version": "2.0.0", 1163 | "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", 1164 | "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", 1165 | "dev": true, 1166 | "requires": { 1167 | "shebang-regex": "^3.0.0" 1168 | } 1169 | }, 1170 | "shebang-regex": { 1171 | "version": "3.0.0", 1172 | "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", 1173 | "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", 1174 | "dev": true 1175 | }, 1176 | "slash": { 1177 | "version": "3.0.0", 1178 | "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", 1179 | "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", 1180 | "dev": true 1181 | }, 1182 | "slice-ansi": { 1183 | "version": "4.0.0", 1184 | "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", 1185 | "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", 1186 | "dev": true, 1187 | "requires": { 1188 | "ansi-styles": "^4.0.0", 1189 | "astral-regex": "^2.0.0", 1190 | "is-fullwidth-code-point": "^3.0.0" 1191 | }, 1192 | "dependencies": { 1193 | "ansi-styles": { 1194 | "version": "4.3.0", 1195 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", 1196 | "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", 1197 | "dev": true, 1198 | "requires": { 1199 | "color-convert": "^2.0.1" 1200 | } 1201 | }, 1202 | "color-convert": { 1203 | "version": "2.0.1", 1204 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", 1205 | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", 1206 | "dev": true, 1207 | "requires": { 1208 | "color-name": "~1.1.4" 1209 | } 1210 | }, 1211 | "color-name": { 1212 | "version": "1.1.4", 1213 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", 1214 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", 1215 | "dev": true 1216 | } 1217 | } 1218 | }, 1219 | "sprintf-js": { 1220 | "version": "1.0.3", 1221 | "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", 1222 | "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", 1223 | "dev": true 1224 | }, 1225 | "string-width": { 1226 | "version": "4.2.2", 1227 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", 1228 | "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", 1229 | "dev": true, 1230 | "requires": { 1231 | "emoji-regex": "^8.0.0", 1232 | "is-fullwidth-code-point": "^3.0.0", 1233 | "strip-ansi": "^6.0.0" 1234 | } 1235 | }, 1236 | "strip-ansi": { 1237 | "version": "6.0.0", 1238 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", 1239 | "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", 1240 | "dev": true, 1241 | "requires": { 1242 | "ansi-regex": "^5.0.0" 1243 | } 1244 | }, 1245 | "strip-json-comments": { 1246 | "version": "3.1.1", 1247 | "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", 1248 | "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", 1249 | "dev": true 1250 | }, 1251 | "supports-color": { 1252 | "version": "5.5.0", 1253 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", 1254 | "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", 1255 | "dev": true, 1256 | "requires": { 1257 | "has-flag": "^3.0.0" 1258 | } 1259 | }, 1260 | "table": { 1261 | "version": "6.7.1", 1262 | "resolved": "https://registry.npmjs.org/table/-/table-6.7.1.tgz", 1263 | "integrity": "sha512-ZGum47Yi6KOOFDE8m223td53ath2enHcYLgOCjGr5ngu8bdIARQk6mN/wRMv4yMRcHnCSnHbCEha4sobQx5yWg==", 1264 | "dev": true, 1265 | "requires": { 1266 | "ajv": "^8.0.1", 1267 | "lodash.clonedeep": "^4.5.0", 1268 | "lodash.truncate": "^4.4.2", 1269 | "slice-ansi": "^4.0.0", 1270 | "string-width": "^4.2.0", 1271 | "strip-ansi": "^6.0.0" 1272 | }, 1273 | "dependencies": { 1274 | "ajv": { 1275 | "version": "8.6.0", 1276 | "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.6.0.tgz", 1277 | "integrity": "sha512-cnUG4NSBiM4YFBxgZIj/In3/6KX+rQ2l2YPRVcvAMQGWEPKuXoPIhxzwqh31jA3IPbI4qEOp/5ILI4ynioXsGQ==", 1278 | "dev": true, 1279 | "requires": { 1280 | "fast-deep-equal": "^3.1.1", 1281 | "json-schema-traverse": "^1.0.0", 1282 | "require-from-string": "^2.0.2", 1283 | "uri-js": "^4.2.2" 1284 | } 1285 | }, 1286 | "json-schema-traverse": { 1287 | "version": "1.0.0", 1288 | "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", 1289 | "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", 1290 | "dev": true 1291 | } 1292 | } 1293 | }, 1294 | "text-table": { 1295 | "version": "0.2.0", 1296 | "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", 1297 | "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", 1298 | "dev": true 1299 | }, 1300 | "to-regex-range": { 1301 | "version": "5.0.1", 1302 | "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", 1303 | "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", 1304 | "dev": true, 1305 | "requires": { 1306 | "is-number": "^7.0.0" 1307 | } 1308 | }, 1309 | "tslib": { 1310 | "version": "1.14.1", 1311 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", 1312 | "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", 1313 | "dev": true 1314 | }, 1315 | "tsutils": { 1316 | "version": "3.21.0", 1317 | "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", 1318 | "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", 1319 | "dev": true, 1320 | "requires": { 1321 | "tslib": "^1.8.1" 1322 | } 1323 | }, 1324 | "tweetnacl": { 1325 | "version": "1.0.3", 1326 | "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", 1327 | "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==" 1328 | }, 1329 | "type-check": { 1330 | "version": "0.4.0", 1331 | "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", 1332 | "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", 1333 | "dev": true, 1334 | "requires": { 1335 | "prelude-ls": "^1.2.1" 1336 | } 1337 | }, 1338 | "type-fest": { 1339 | "version": "0.20.2", 1340 | "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", 1341 | "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", 1342 | "dev": true 1343 | }, 1344 | "typescript": { 1345 | "version": "4.3.2", 1346 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.2.tgz", 1347 | "integrity": "sha512-zZ4hShnmnoVnAHpVHWpTcxdv7dWP60S2FsydQLV8V5PbS3FifjWFFRiHSWpDJahly88PRyV5teTSLoq4eG7mKw==" 1348 | }, 1349 | "uri-js": { 1350 | "version": "4.4.1", 1351 | "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", 1352 | "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", 1353 | "dev": true, 1354 | "requires": { 1355 | "punycode": "^2.1.0" 1356 | } 1357 | }, 1358 | "v8-compile-cache": { 1359 | "version": "2.3.0", 1360 | "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", 1361 | "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", 1362 | "dev": true 1363 | }, 1364 | "which": { 1365 | "version": "2.0.2", 1366 | "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", 1367 | "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", 1368 | "dev": true, 1369 | "requires": { 1370 | "isexe": "^2.0.0" 1371 | } 1372 | }, 1373 | "word-wrap": { 1374 | "version": "1.2.3", 1375 | "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", 1376 | "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", 1377 | "dev": true 1378 | }, 1379 | "wrappy": { 1380 | "version": "1.0.2", 1381 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 1382 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", 1383 | "dev": true 1384 | }, 1385 | "ws": { 1386 | "version": "7.4.6", 1387 | "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz", 1388 | "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==" 1389 | }, 1390 | "yallist": { 1391 | "version": "4.0.0", 1392 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", 1393 | "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", 1394 | "dev": true 1395 | } 1396 | } 1397 | } 1398 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tunnelerjs", 3 | "version": "1.0.0", 4 | "author": "Ari Höysniemi", 5 | "license": "MIT", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/ahoys/tunnelerjs.git" 9 | }, 10 | "scripts": { 11 | "dist": "tsc && cp package.json dist/package.json && cp package-lock.json dist/package-lock.json && cp prod.env dist/.env", 12 | "start": "tsc && cp dev.env dist/.env && node dist/tunneler", 13 | "lint": "./node_modules/.bin/tslint --project ./ --fix ./src/**" 14 | }, 15 | "devDependencies": { 16 | "@typescript-eslint/eslint-plugin": "^4.26.1", 17 | "@typescript-eslint/parser": "^4.26.1", 18 | "eslint": "^7.28.0", 19 | "eslint-config-prettier": "^8.3.0" 20 | }, 21 | "dependencies": { 22 | "@types/node": "^15.12.2", 23 | "@types/ws": "^7.4.4", 24 | "discord.js": "^12.5.3", 25 | "dotenv": "^10.0.0", 26 | "logscribe": "^2.0.2", 27 | "typescript": "^4.3.2" 28 | }, 29 | "eslintConfig": { 30 | "@typescript-eslint/no-var-requires": false 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /prod.env: -------------------------------------------------------------------------------- 1 | discord.token= 2 | discord.application_id= 3 | discord.owner_id= 4 | bot.whitelisted_roles= 5 | cmd.ping=false 6 | mw.antispam=false 7 | mw.antispam.warnings= 8 | mw.antispam.ban_days= 9 | mw.antispam.rapid_messages_avg= 10 | mw.antispam.repeating_messages_percentage= 11 | cmd.role=false 12 | cmd.role.allowed_role_ids= 13 | cmd.role.allow_multiple_roles= -------------------------------------------------------------------------------- /src/commands/ping/cmd.ping.ts: -------------------------------------------------------------------------------- 1 | import { Client, Message } from "discord.js"; 2 | import { p } from "logscribe"; 3 | import { IFlags } from "../../tunneler"; 4 | import { IGuildSettings } from "../../loadSettings"; 5 | 6 | /** 7 | * Returns the current latencies as a reply. 8 | */ 9 | const ping = ( 10 | client: Client, 11 | message: Message, 12 | settings: IGuildSettings, 13 | flags: IFlags 14 | ): void => { 15 | try { 16 | const { isDirectMessage } = flags; 17 | const apiLatency = Math.round(client.ws.ping); 18 | const messageLatency = Date.now() - message.createdTimestamp; 19 | message.reply( 20 | `${ 21 | isDirectMessage ? "M" : "m" 22 | }essage latency: ${messageLatency}ms, API-latency: ${apiLatency}ms.` 23 | ); 24 | } catch (err) { 25 | p(err); 26 | } 27 | }; 28 | 29 | export default ping; 30 | -------------------------------------------------------------------------------- /src/commands/role/cmd.role.ts: -------------------------------------------------------------------------------- 1 | import { Client, Message } from "discord.js"; 2 | import { p } from "logscribe"; 3 | import { IFlags } from "../../tunneler"; 4 | import { IGuildSettings } from "../../loadSettings"; 5 | 6 | /** 7 | * Gives the requested role for the requester, if allowed. 8 | */ 9 | const role = ( 10 | client: Client, 11 | message: Message, 12 | settings: IGuildSettings, 13 | flags: IFlags 14 | ): void => { 15 | try { 16 | const { isDirectMessage } = flags; 17 | const roles: string[] = 18 | typeof settings["cmd.role.allowed_role_ids"] === "string" 19 | ? settings["cmd.role.allowed_role_ids"].split(",") 20 | : []; 21 | const allowMultiple: boolean = 22 | typeof settings["cmd.role.allow_multiple_roles"] === "string" 23 | ? settings["cmd.role.allow_multiple_roles"] === "true" 24 | : false; 25 | if (!isDirectMessage && roles.length) { 26 | // Transform @tunneler role something something -> something something. 27 | const inArr = message.content.split(" "); 28 | inArr.shift(); 29 | inArr.shift(); 30 | const requested = inArr.join(" ").toLowerCase(); 31 | const theRole = message.guild?.roles.cache.find( 32 | (r) => r.name.toLowerCase() === requested 33 | ); 34 | if ( 35 | theRole && 36 | roles.includes(theRole.id) && 37 | !message.member?.roles.cache.some((r) => r.id === theRole.id) 38 | ) { 39 | // Remove other roles if requested. 40 | if (!allowMultiple) { 41 | roles.forEach((role) => { 42 | if (role !== theRole.id) { 43 | const duplicateRole = message.guild?.roles.cache.find( 44 | (guildRole) => guildRole.id === role 45 | ); 46 | if (duplicateRole) { 47 | message.member?.roles 48 | .remove(duplicateRole) 49 | .catch((err) => p(err)); 50 | } 51 | } 52 | }); 53 | } 54 | // Add the new role. 55 | message.member?.roles 56 | .add(theRole) 57 | .then(() => { 58 | message.reply("role set.").catch((err) => p(err)); 59 | }) 60 | .catch((err) => p(err)); 61 | } else { 62 | message.reply("invalid role requested.").catch((err) => p(err)); 63 | } 64 | } 65 | } catch (err) { 66 | p(err); 67 | } 68 | }; 69 | 70 | export default role; 71 | -------------------------------------------------------------------------------- /src/loadCommands.ts: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | import { TCmd } from "./tunneler"; 3 | import { p } from "logscribe"; 4 | 5 | /** 6 | * Returns a singular function. 7 | */ 8 | const loadCmd = async ( 9 | name: string, 10 | path: string 11 | ): Promise<{ name: string; command: TCmd } | undefined> => { 12 | if (fs.existsSync(path)) { 13 | const cmd = await import(path); 14 | if (typeof cmd.default === "function") { 15 | return { 16 | name, 17 | command: cmd.default, 18 | }; 19 | } 20 | } 21 | return undefined; 22 | }; 23 | 24 | /** 25 | * Returns all available command-functions inside the commands folder. 26 | * All commands need to follow a certain naming syntax (cmd.example.ts) 27 | * and need to be inside a folder that corresponds the name (example). 28 | * 29 | * So, for example: commands/example/cmd.example.ts 30 | */ 31 | export const loadCommands = async (): Promise< 32 | { name: string; execute: TCmd }[] 33 | > => 34 | new Promise((resolve, reject) => { 35 | try { 36 | const folders: Promise<{ name: string; command: TCmd } | undefined>[] = 37 | []; 38 | fs.readdirSync(__dirname + "/commands").forEach((folder) => { 39 | const fPath = __dirname + "/commands/" + folder; 40 | if (fs.lstatSync(fPath).isDirectory()) { 41 | folders.push(loadCmd(folder, fPath + "/cmd." + folder + ".js")); 42 | } 43 | }); 44 | Promise.all(folders).then((cmds) => { 45 | const commands: { name: string; execute: TCmd }[] = []; 46 | cmds.forEach((cmd) => { 47 | if (cmd) { 48 | commands.push({ 49 | name: cmd.name, 50 | execute: cmd.command, 51 | }); 52 | } 53 | }); 54 | resolve(commands); 55 | }); 56 | } catch (err) { 57 | p(err); 58 | reject(); 59 | } 60 | }); 61 | -------------------------------------------------------------------------------- /src/loadMiddleware.ts: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | import { TMw } from "./tunneler"; 3 | import { p } from "logscribe"; 4 | 5 | /** 6 | * Returns a singular function. 7 | */ 8 | const loadMw = async (path: string): Promise => { 9 | if (fs.existsSync(path)) { 10 | const mw = await import(path); 11 | if (typeof mw.default === "function") { 12 | return mw.default; 13 | } 14 | } 15 | return undefined; 16 | }; 17 | 18 | /** 19 | * Returns all available middleware-functions inside the middleware folder. 20 | * All middleware need to follow a certain naming syntax (mw.example.ts) 21 | * and need to be inside a folder that corresponds the name (example). 22 | * 23 | * So, for example: middleware/example/mw.example.ts 24 | */ 25 | export const loadMiddleware = async (): Promise< 26 | { name: string; execute: TMw }[] 27 | > => 28 | new Promise((resolve, reject) => { 29 | try { 30 | const folders: Promise[] = []; 31 | fs.readdirSync(__dirname + "/middleware").forEach((folder) => { 32 | const fPath = __dirname + "/middleware/" + folder; 33 | if (fs.lstatSync(fPath).isDirectory()) { 34 | folders.push(loadMw(fPath + "/mw." + folder + ".js")); 35 | } 36 | }); 37 | Promise.all(folders).then((mws) => { 38 | const onMessages: { name: string; execute: TMw }[] = []; 39 | mws.forEach((mw) => { 40 | if (mw) { 41 | onMessages.push({ 42 | name: mw.name, 43 | execute: mw, 44 | }); 45 | } 46 | }); 47 | resolve(onMessages); 48 | }); 49 | } catch (err) { 50 | p(err); 51 | reject(); 52 | } 53 | }); 54 | -------------------------------------------------------------------------------- /src/loadSettings.ts: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | import { p } from "logscribe"; 3 | 4 | const defaults = typeof process.env === "object" ? process.env : {}; 5 | 6 | export interface IGuildSettings { 7 | [key: string]: string | undefined; 8 | } 9 | 10 | export interface ISettings { 11 | [key: string]: IGuildSettings; 12 | defaults: IGuildSettings; 13 | } 14 | 15 | /** 16 | * Returns default settings for the bots and also 17 | * guild specific settings if found. 18 | * 19 | * Guild-specific settings must be defined in a .setting file, 20 | * that's located in the dist root folder. 21 | */ 22 | export const loadSettings = (): ISettings => { 23 | try { 24 | const base: ISettings = { defaults: {} }; 25 | Object.keys(defaults).forEach((key) => { 26 | if (key.startsWith("cmd.") || key.startsWith("mw.")) { 27 | base.defaults[key] = process.env[key]; 28 | } 29 | }); 30 | fs.readdirSync(__dirname).forEach((item) => { 31 | const itemPath = __dirname + "/" + item; 32 | if (fs.lstatSync(itemPath).isFile()) { 33 | const splitName = item.split("."); 34 | if ( 35 | item.endsWith(".settings") && 36 | splitName.length === 2 && 37 | splitName[0] !== "" 38 | ) { 39 | // A settings file detected. 40 | const data = fs.readFileSync(itemPath, "utf-8").toString(); 41 | const lines = data.split(/\n|\r/).filter((l) => l !== ""); 42 | const guild: IGuildSettings = { ...base.defaults }; 43 | lines.forEach((l) => { 44 | const lineSplit = l.split("="); 45 | if ( 46 | lineSplit.length === 2 && 47 | (lineSplit[0].startsWith("cmd.") || 48 | lineSplit[0].startsWith("mw.")) && 49 | lineSplit[1] !== "" 50 | ) { 51 | // A valid settings line read. 52 | guild[lineSplit[0]] = lineSplit[1]; 53 | } 54 | }); 55 | base[splitName[0]] = guild; 56 | } 57 | } 58 | }); 59 | return base; 60 | } catch (err) { 61 | p(err); 62 | return { 63 | defaults, 64 | }; 65 | } 66 | }; 67 | -------------------------------------------------------------------------------- /src/middleware/antispam/README.md: -------------------------------------------------------------------------------- 1 | ## About 2 | 3 | Antispam middleware is created to stop spam-attacks. The middleware uses various heuristics to detect spamming and if spamming is detected, acts accordingly by either warning the user or outright banning the user. 4 | 5 | --- 6 | 7 | ## Configuration 8 | 9 | Done via .env file. 10 | For example: `mw.antispam.ban_days=3` 11 | 12 | ### Environment (.env) parameters: 13 | 14 | - `mw.antispam.warnings` 15 | - How many warnings before punishment. 16 | - Default 2 17 | - `mw.antispam.ban_days` 18 | - How many days does the ban last. 19 | - Default 1 20 | - `mw.antispam.rapid_messages_avg` 21 | - How fast must the users send messages to be considered spamming (on average). 22 | - Default 1024 23 | - `mw.antispam.repeating_messages_percentage` 24 | - How much of the content written by the user needs to be equal to be considered as spam. 25 | - Give in percentages, as in 0 = 0%, 0.5 = 50%, 1 = 100%. 26 | - Default 0.5 27 | 28 | --- 29 | 30 | ## Author 31 | 32 | - Ari Höysniemi, 2021/06/12 33 | -------------------------------------------------------------------------------- /src/middleware/antispam/heuristics/heuristic.rapidMessages.ts: -------------------------------------------------------------------------------- 1 | import { p } from "logscribe"; 2 | import { IDbUser } from "../mw.antispam"; 3 | 4 | /** 5 | * Flags rapid messages heuristic if the user 6 | * has sent messages too often on average. 7 | */ 8 | export const rapidMessages = (dbUser: IDbUser, limit: number): boolean => { 9 | try { 10 | const timestamps = dbUser.timestamps.sort(); 11 | if (timestamps.length > 1) { 12 | const diffs = []; 13 | for (let i = 0; i < timestamps.length - 1; i++) { 14 | diffs[i] = timestamps[i + 1] - timestamps[i]; 15 | } 16 | const total = diffs.reduce((a, b) => a + b, 0); 17 | const avg = total / diffs.length; 18 | return avg < limit; 19 | } 20 | return false; 21 | } catch (err) { 22 | p(err); 23 | return false; 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /src/middleware/antispam/heuristics/heuristic.repeatingMessages.ts: -------------------------------------------------------------------------------- 1 | import { p } from "logscribe"; 2 | import { IDbUser } from "../mw.antispam"; 3 | 4 | /** 5 | * Returns true if the previous messages are repeating the 6 | * same content. 7 | */ 8 | export const repeatingMessages = ( 9 | dbUser: IDbUser, 10 | percentage: number, 11 | warnings = 0 12 | ): boolean => { 13 | try { 14 | const content = dbUser.content; 15 | percentage = percentage > 1 ? 1 : percentage; 16 | percentage = percentage < 0 ? 0 : percentage; 17 | if (content.length <= 2) { 18 | // Not enough content to be precise. 19 | return false; 20 | } else if (content.every((v) => v === content[0])) { 21 | // Mass spamming. 22 | return true; 23 | } 24 | // Look for duplicate content. 25 | let duplicates = 0; 26 | content.forEach((text, i) => { 27 | if (content.some((v, r) => v === text && r > i)) { 28 | duplicates += 1; 29 | } 30 | }); 31 | // We'll ease the heuristic after a warning just to give 32 | // the user a chance to make things right. 33 | const result = (duplicates - warnings) / content.length; 34 | return result > percentage; 35 | } catch (err) { 36 | p(err); 37 | return false; 38 | } 39 | }; 40 | -------------------------------------------------------------------------------- /src/middleware/antispam/mw.antispam.ts: -------------------------------------------------------------------------------- 1 | import { Client, Message } from "discord.js"; 2 | import { p } from "logscribe"; 3 | import { IFlags } from "../../tunneler"; 4 | import { rapidMessages } from "./heuristics/heuristic.rapidMessages"; 5 | import { repeatingMessages } from "./heuristics/heuristic.repeatingMessages"; 6 | import { IGuildSettings } from "../../loadSettings"; 7 | 8 | export interface IDbUser { 9 | index: number; 10 | warnings: number; 11 | timestamps: number[]; 12 | content: string[]; 13 | contentLengths: number[]; 14 | } 15 | 16 | interface IDb { 17 | [key: string]: IDbUser; 18 | } 19 | 20 | const db: IDb = {}; 21 | const MAX_DB_SIZE = 16; 22 | 23 | /** 24 | * An antispam middleware with various heuristics against 25 | * various spammers. 26 | */ 27 | const antispam = ( 28 | client: Client, 29 | message: Message, 30 | settings: IGuildSettings, 31 | flags: IFlags 32 | ): void => { 33 | try { 34 | const { isWhitelisted, isAdmin, isDevelopment } = flags; 35 | const maxWarnings: number = 36 | typeof settings["mw.antispam.warnings"] === "string" 37 | ? Number(settings["mw.antispam.warnings"]) 38 | : 2; 39 | const banDays: number = 40 | typeof settings["mw.antispam.ban_days"] === "string" 41 | ? Number(settings["mw.antispam.ban_days"]) 42 | : 1; 43 | const rapidMessagesAvg: number = 44 | typeof settings["mw.antispam.rapid_messages_avg"] === "string" 45 | ? Number(settings["mw.antispam.rapid_messages_avg"]) 46 | : 1024; 47 | const repeatingMessagesPercentage: number = 48 | typeof settings["mw.antispam.repeating_messages_percentage"] === "string" 49 | ? Number(settings["mw.antispam.repeating_messages_percentage"]) 50 | : 0.5; 51 | if ( 52 | (!isWhitelisted && !isAdmin && message.member?.bannable) || 53 | isDevelopment 54 | ) { 55 | // Load investigated user. 56 | const dbUser: IDbUser = db[message.author.id] 57 | ? { ...db[message.author.id] } 58 | : { 59 | index: 0, 60 | warnings: 0, 61 | timestamps: [], 62 | content: [], 63 | contentLengths: [], 64 | }; 65 | // Update the user with this new entry. 66 | const index = dbUser.index; 67 | const warnings = dbUser.warnings; 68 | dbUser.timestamps[index] = message.createdTimestamp; 69 | dbUser.content[index] = message.content; 70 | dbUser.contentLengths[index] = message.content.length; 71 | dbUser.index = index < MAX_DB_SIZE - 1 ? index + 1 : 0; 72 | // Investigate if the user has violated anything based on 73 | // the recorded information. 74 | if ( 75 | rapidMessages(dbUser, rapidMessagesAvg) || 76 | repeatingMessages(dbUser, repeatingMessagesPercentage, dbUser.warnings) 77 | ) { 78 | dbUser.warnings = dbUser.warnings + 1; 79 | } 80 | if (dbUser.warnings > maxWarnings) { 81 | // Kick here. 82 | dbUser.warnings = 0; 83 | message.member 84 | ?.ban({ 85 | days: banDays, 86 | reason: `Caught spamming and did not stop after ${maxWarnings} warnings.`, 87 | }) 88 | .catch((err) => { 89 | p(err); 90 | }); 91 | } else if (dbUser.warnings !== warnings) { 92 | message 93 | .reply( 94 | `please stop spamming. You have ${ 95 | maxWarnings - dbUser.warnings 96 | } warnings left.` 97 | ) 98 | .catch((err) => { 99 | p(err); 100 | }); 101 | } 102 | // Save the user. 103 | db[message.author.id] = dbUser; 104 | } 105 | } catch (err) { 106 | p(err); 107 | } 108 | }; 109 | 110 | export default antispam; 111 | -------------------------------------------------------------------------------- /src/tunneler.ts: -------------------------------------------------------------------------------- 1 | import { Client, Message } from "discord.js"; 2 | import { config } from "dotenv"; 3 | import { p } from "logscribe"; 4 | import { loadSettings } from "./loadSettings"; 5 | import { loadCommands } from "./loadCommands"; 6 | import { loadMiddleware } from "./loadMiddleware"; 7 | import { IGuildSettings } from "./loadSettings"; 8 | 9 | config({ path: __dirname + "/.env" }); 10 | 11 | const token: string = process.env["discord.token"] ?? ""; 12 | const applicationId: string = process.env["discord.application_id"] ?? ""; 13 | const ownerId: string = process.env["discord.owner_id"] ?? ""; 14 | const whitelisted: string[] = 15 | process.env["bot.whitelisted_roles"]?.split(",") ?? []; 16 | const isDevelopment: boolean = process.env["bot.development"] === "true"; 17 | 18 | export interface IFlags { 19 | isDirectMessage: boolean; // In a direct channel, not guild. 20 | isWhitelisted: boolean; // Has a whitelisted role. 21 | isAdmin: boolean; // Has administrative permissions. 22 | isOwner: boolean; // Owner of the bot. 23 | isDevelopment: boolean; // In development mode. 24 | } 25 | 26 | export type TCmd = ( 27 | client: Client, 28 | message: Message, 29 | settings: IGuildSettings, 30 | flags?: IFlags 31 | ) => void; 32 | export type TMw = ( 33 | client: Client, 34 | message: Message, 35 | settings: IGuildSettings, 36 | flags?: IFlags 37 | ) => void; 38 | 39 | const appSettings = loadSettings(); 40 | let commands: { name: string; execute: TCmd }[] = []; 41 | let middleware: { name: string; execute: TMw }[] = []; 42 | 43 | // This is the main Discord-client. 44 | const client: Client = new Client(); 45 | 46 | /** 47 | * Logs into Discord. 48 | */ 49 | let reconnect: ReturnType | null = null; 50 | const login = () => { 51 | client.login(token).catch((err) => { 52 | p(err); 53 | console.error("Failed to login."); 54 | if (reconnect) { 55 | clearTimeout(reconnect); 56 | } 57 | reconnect = setTimeout(() => { 58 | login(); 59 | }, 10240); 60 | }); 61 | }; 62 | 63 | /** 64 | * Something went wrong with the connection 65 | * or Discord. 66 | * 67 | * Try to re-login. 68 | */ 69 | client.on("error", () => { 70 | try { 71 | p("Connection issues or some other form of a failure detected."); 72 | login(); 73 | } catch (err) { 74 | p(err); 75 | } 76 | }); 77 | 78 | /** 79 | * The bot is ready to function, meaning 80 | * that we're connected to Discord. 81 | */ 82 | client.on("ready", () => { 83 | p( 84 | "Successfully connected to Discord!\n" + 85 | `Username: ${client.user?.username}\n` + 86 | `Verified: ${client.user?.verified}\n` + 87 | "Waiting for events..." 88 | ); 89 | }); 90 | 91 | /** 92 | * All new messages (from non-trusted authors) are 93 | * investigated. 94 | */ 95 | client.on("message", (message) => { 96 | try { 97 | if (client && client.user && message.author.id !== applicationId) { 98 | const isMentioned = message.mentions.has(client.user.id); 99 | const isDirectMessage = !message.guild; 100 | const command = message.content.split(" ")[isDirectMessage ? 0 : 1] ?? ""; 101 | const isAdmin = message.member?.hasPermission("ADMINISTRATOR") ?? false; 102 | const isOwner = message.author.id === ownerId; 103 | const isWhitelisted = !!message.member?.roles.cache.find((r) => 104 | whitelisted.includes(r.id) 105 | ); 106 | const settings = 107 | !message.guild || !message.guild.id 108 | ? appSettings.defaults 109 | : typeof appSettings[message.guild.id] === "object" 110 | ? appSettings[message.guild.id] 111 | : appSettings.defaults; 112 | if (isMentioned && command) { 113 | // The user seems to be asking for a command. 114 | // Look for the given command. If found, execute. 115 | const foundCmd = commands.find((cmd) => cmd.name === command); 116 | if (foundCmd && settings["cmd." + foundCmd.name] === "true") { 117 | foundCmd.execute(client, message, settings, { 118 | isDirectMessage, 119 | isWhitelisted, 120 | isAdmin, 121 | isOwner, 122 | isDevelopment, 123 | }); 124 | } 125 | } 126 | if (!isDirectMessage) { 127 | middleware.forEach((mw) => { 128 | if (settings["mw." + mw.name] === "true") { 129 | mw.execute(client, message, settings, { 130 | isDirectMessage, 131 | isWhitelisted, 132 | isAdmin, 133 | isOwner, 134 | isDevelopment, 135 | }); 136 | } 137 | }); 138 | } 139 | } 140 | } catch (err) { 141 | p(err); 142 | } 143 | }); 144 | 145 | // Load commands and then login to Discord. 146 | loadCommands() 147 | .then((cmds) => { 148 | commands = [...cmds]; 149 | loadMiddleware() 150 | .then((mws) => { 151 | middleware = [...mws]; 152 | p(appSettings); 153 | login(); 154 | }) 155 | .catch(() => { 156 | p("Failed to load middleware."); 157 | }); 158 | }) 159 | .catch(() => { 160 | p("Failed to load commands."); 161 | }); 162 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "es6", 6 | "dom", 7 | "es2017" 8 | ], 9 | "module": "commonjs", 10 | "declaration": false, 11 | "sourceMap": false, 12 | "outDir": "dist", 13 | "removeComments": true, 14 | "strict": true, 15 | "esModuleInterop": true, 16 | "types": [ 17 | "node" 18 | ], 19 | }, 20 | "include": [ 21 | "./src/**/*.ts" 22 | ], 23 | "exclude": [ 24 | "node_modules", 25 | "**/__tests__/*", 26 | ] 27 | } --------------------------------------------------------------------------------