├── .github └── workflows │ └── lint.yml ├── .gitignore ├── LICENSE ├── README.md ├── index.js ├── package-lock.json ├── package.json ├── polaris ├── client.js ├── commands │ ├── Verification │ │ ├── done.js │ │ ├── getrole.js │ │ ├── linkaccount.js │ │ └── update.js │ ├── admin │ │ ├── prefix.js │ │ ├── settings.js │ │ └── setup.js │ ├── baseCommand.js │ ├── fun │ │ ├── 8ball.js │ │ └── quote.js │ ├── index.js │ ├── info │ │ ├── help.js │ │ └── info.js │ ├── misc │ │ ├── getinfo.js │ │ ├── groupInfo.js │ │ └── ping.js │ └── owner │ │ ├── blacklist.js │ │ ├── eval.js │ │ ├── reload.js │ │ └── unblacklist.js ├── index.js └── util │ ├── Collection.js │ ├── CommandManager.js │ ├── Database.js │ ├── erisExtensions.js │ ├── index.js │ ├── ipcClient.js │ ├── linkManager.js │ └── polaris-rbx │ ├── baseClasses │ ├── group.js │ └── user.js │ ├── index.js │ └── request.js └── scripts ├── export.js └── import.js /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | # Checks for eslint compliance for v4 2 | name: Eslint check 3 | 4 | on: 5 | push: 6 | branches: 7 | - v4 8 | pull_request: 9 | branches: 10 | - v4 11 | workflow_dispatch: 12 | branches: 13 | - v4 14 | 15 | jobs: 16 | lint: 17 | 18 | runs-on: ubuntu-latest 19 | 20 | strategy: 21 | matrix: 22 | node-version: [10.x, 12.x, 14.x, 15.x] 23 | # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ 24 | 25 | steps: 26 | - uses: actions/checkout@v2 27 | - name: Use Node.js ${{ matrix.node-version }} 28 | uses: actions/setup-node@v1 29 | with: 30 | node-version: ${{ matrix.node-version }} 31 | - run: npm run lint 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | \.eslintrc\.json 3 | 4 | settings\.json 5 | 6 | node_modules/ 7 | 8 | \.idea/ 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Josh Muir 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 | # Polaris 2 | 3 | ## Polaris is shutting down on the 1st March 2022. [Read more](https://nezto.re/posts/2022/discontinuing-polaris/). 4 | 5 | [![Codacy Badge](https://api.codacy.com/project/badge/Grade/934f50cd0c354d4ebd17c0a91fc4855d)](https://app.codacy.com/app/Neztore/Polaris?utm_source=github.com&utm_medium=referral&utm_content=Neztore/Polaris&utm_campaign=Badge_Grade_Settings) 6 | [![Polaris project bot](https://img.shields.io/badge/Polaris%20Project-Roblox%20bot-2bbbad.svg)](https://polaris.codes) 7 | 8 | Polaris is an open source Roblox verification bot, created in November 2017 and open sourced in December 2018. 9 | 10 | It is currently labelled as `unmaintained` in the sense that I will not be actively improving it or fixing less critical bugs. 11 | If you'd like to take over maintaining the bot, feel free to contact me or just fork and PR some fixes! 12 | 13 | This bot is designed to work in tandem with the [Polaris-React webpanel and site](https://github.com/neztore/polaris-react). 14 | 15 | Feel free to snoop, edit, hash, contribute to and remix the bot. 16 | 17 | ## Installing 18 | 1. Clone this repo - `git clone https://github.com/neztore/polaris` 19 | 2. Navigate into new folder `cd polaris` 20 | 3. Run `npm install` in this repo 21 | 4. Create your settings.json. Set `sync` to true 22 | 5. Make sure your rethinkdb database is running 23 | 6. Run it! 24 | 25 | ## Setup / config 26 | ### Config 27 | The Polaris client takes a config object, which can contain: 28 | - `token` - The bot token to use 29 | - `erisOptions` - The options to pass to the underlying eris client 30 | - `Raven` - A Raven client for sentry. Use without this is planned, but currently untested. 31 | 32 | It also expects a `settings.json` to exist in the root, with example values as below. We also plan to stop moving this in the near future. 33 | ```json 34 | { 35 | "token": "Main bot token", 36 | "testToken": "Test bot token", 37 | "dblToken": "Discord bot list token", 38 | "sentry": "Sentry token", 39 | "specialPeople": { 40 | "93968063": "This user works for Polaris.", 41 | "66592931": "This user is the Polaris developer.", 42 | "Roblox id": "Desc string to be shown" 43 | } 44 | } 45 | ``` 46 | If you don't want any "special people" just leave that as an empty array. 47 | ## Support 48 | Join our [discord](https://discord.gg/QevWabU) for support. 49 | 50 | The main [website](https://polaris-bot.xyz/). 51 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const { Transaction } = require("@sentry/integrations"); 2 | const Sentry = require("@sentry/node"); 3 | 4 | const { name, version } = require("./package.json"); 5 | const Polaris = require("./polaris"); 6 | const settings = require("./settings"); 7 | 8 | // 9 | // Setup Sentry Configuration 10 | if (settings.sentry && settings.sentry !== "") { 11 | Sentry.init({ 12 | dsn: settings.sentry, 13 | integrations: [new Transaction()], 14 | release: `${name}@${version}` 15 | }); 16 | } 17 | 18 | const token = process.env.NODE_ENV === "production" ? settings.token : settings.testToken; 19 | const Client = new Polaris.Client({ 20 | token: `Bot ${token}`, 21 | erisSettings: { 22 | maxShards: "auto", 23 | intents: [ 24 | "guilds", 25 | "guildMembers", 26 | "guildMessages", 27 | "directMessages" 28 | ], 29 | allowedMentions: { users: true } 30 | } 31 | }); 32 | Client.on("error", console.error); 33 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "polaris-bot", 3 | "version": "3.9.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@babel/code-frame": { 8 | "version": "7.10.4", 9 | "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", 10 | "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", 11 | "dev": true, 12 | "requires": { 13 | "@babel/highlight": "^7.10.4" 14 | } 15 | }, 16 | "@babel/helper-validator-identifier": { 17 | "version": "7.10.4", 18 | "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", 19 | "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==", 20 | "dev": true 21 | }, 22 | "@babel/highlight": { 23 | "version": "7.10.4", 24 | "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", 25 | "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", 26 | "dev": true, 27 | "requires": { 28 | "@babel/helper-validator-identifier": "^7.10.4", 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 | } 45 | }, 46 | "@eslint/eslintrc": { 47 | "version": "0.2.2", 48 | "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.2.2.tgz", 49 | "integrity": "sha512-EfB5OHNYp1F4px/LI/FEnGylop7nOqkQ1LRzCM0KccA2U8tvV8w01KBv37LbO7nW4H+YhKyo2LcJhRwjjV17QQ==", 50 | "dev": true, 51 | "requires": { 52 | "ajv": "^6.12.4", 53 | "debug": "^4.1.1", 54 | "espree": "^7.3.0", 55 | "globals": "^12.1.0", 56 | "ignore": "^4.0.6", 57 | "import-fresh": "^3.2.1", 58 | "js-yaml": "^3.13.1", 59 | "lodash": "^4.17.19", 60 | "minimatch": "^3.0.4", 61 | "strip-json-comments": "^3.1.1" 62 | } 63 | }, 64 | "@sentry/apm": { 65 | "version": "5.19.2", 66 | "resolved": "https://registry.npmjs.org/@sentry/apm/-/apm-5.19.2.tgz", 67 | "integrity": "sha512-V7p5niqG/Nn1OSMAyreChiIrQFYzFHKADKNaDEvIXqC4hxFnMG8lPRqEFJH49fNjsFBFfIG9iY1rO1ZFg3S42Q==", 68 | "requires": { 69 | "@sentry/browser": "5.19.2", 70 | "@sentry/hub": "5.19.2", 71 | "@sentry/minimal": "5.19.2", 72 | "@sentry/types": "5.19.2", 73 | "@sentry/utils": "5.19.2", 74 | "tslib": "^1.9.3" 75 | } 76 | }, 77 | "@sentry/browser": { 78 | "version": "5.19.2", 79 | "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-5.19.2.tgz", 80 | "integrity": "sha512-o6Z532n+0N5ANDzgR9GN+Q6CU7zVlIJvBEW234rBiB+ZZj6XwTLS1dD+JexGr8lCo8PeXI2rypKcj1jUGLVW8w==", 81 | "requires": { 82 | "@sentry/core": "5.19.2", 83 | "@sentry/types": "5.19.2", 84 | "@sentry/utils": "5.19.2", 85 | "tslib": "^1.9.3" 86 | } 87 | }, 88 | "@sentry/core": { 89 | "version": "5.19.2", 90 | "resolved": "https://registry.npmjs.org/@sentry/core/-/core-5.19.2.tgz", 91 | "integrity": "sha512-sfbBsVXpA0WYJUichz5IhvqKD8xJUfQvsszrTsUKa7PQAMAboOmuh6bo8KquaVQnAZyZWZU08UduvlSV3tA7tw==", 92 | "requires": { 93 | "@sentry/hub": "5.19.2", 94 | "@sentry/minimal": "5.19.2", 95 | "@sentry/types": "5.19.2", 96 | "@sentry/utils": "5.19.2", 97 | "tslib": "^1.9.3" 98 | } 99 | }, 100 | "@sentry/hub": { 101 | "version": "5.19.2", 102 | "resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-5.19.2.tgz", 103 | "integrity": "sha512-2KkEYX4q9TDCOiaVEo2kQ1W0IXyZxJxZtIjDdFQyes9T4ubYlKHAbvCjTxHSQv37lDO4t7sOIApWG9rlkHzlEA==", 104 | "requires": { 105 | "@sentry/types": "5.19.2", 106 | "@sentry/utils": "5.19.2", 107 | "tslib": "^1.9.3" 108 | } 109 | }, 110 | "@sentry/integrations": { 111 | "version": "5.19.2", 112 | "resolved": "https://registry.npmjs.org/@sentry/integrations/-/integrations-5.19.2.tgz", 113 | "integrity": "sha512-kHXJlJBRMKFDT6IebLNgTciJtSygxm4nLETmwVOmE555lTOUmbbmpNEpJokk1D8f/dCy9ai1N+h6CFyLsjCXNw==", 114 | "requires": { 115 | "@sentry/types": "5.19.2", 116 | "@sentry/utils": "5.19.2", 117 | "tslib": "^1.9.3" 118 | } 119 | }, 120 | "@sentry/minimal": { 121 | "version": "5.19.2", 122 | "resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-5.19.2.tgz", 123 | "integrity": "sha512-rApEOkjy+ZmkeqEItgFvUFxe5l+dht9AumuUzq74pWp+HJqxxv9IVTusKppBsE1adjtmyhwK4O3Wr8qyc75xlw==", 124 | "requires": { 125 | "@sentry/hub": "5.19.2", 126 | "@sentry/types": "5.19.2", 127 | "tslib": "^1.9.3" 128 | } 129 | }, 130 | "@sentry/node": { 131 | "version": "5.19.2", 132 | "resolved": "https://registry.npmjs.org/@sentry/node/-/node-5.19.2.tgz", 133 | "integrity": "sha512-gbww3iTWkdvYIAhOmULbv8znKwkIpklGJ0SPtAh0orUMuaa0lVht+6HQIhRgeXp50lMzNaYC3fuzkbFfYgpS7A==", 134 | "requires": { 135 | "@sentry/apm": "5.19.2", 136 | "@sentry/core": "5.19.2", 137 | "@sentry/hub": "5.19.2", 138 | "@sentry/types": "5.19.2", 139 | "@sentry/utils": "5.19.2", 140 | "cookie": "^0.3.1", 141 | "https-proxy-agent": "^5.0.0", 142 | "lru_map": "^0.3.3", 143 | "tslib": "^1.9.3" 144 | } 145 | }, 146 | "@sentry/types": { 147 | "version": "5.19.2", 148 | "resolved": "https://registry.npmjs.org/@sentry/types/-/types-5.19.2.tgz", 149 | "integrity": "sha512-O6zkW8oM1qK5Uma9+B/UMlmlm9/gkw9MooqycWuEhIaKfDBj/yVbwb/UTiJmNkGc5VJQo0v1uXUZZQt6/Xq1GA==" 150 | }, 151 | "@sentry/utils": { 152 | "version": "5.19.2", 153 | "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-5.19.2.tgz", 154 | "integrity": "sha512-gEPkC0CJwvIWqcTcPSdIzqJkJa9N5vZzUZyBvdu1oiyJu7MfazpJEvj3whfJMysSfXJQxoJ+a1IPrA73VY23VA==", 155 | "requires": { 156 | "@sentry/types": "5.19.2", 157 | "tslib": "^1.9.3" 158 | } 159 | }, 160 | "@types/json5": { 161 | "version": "0.0.29", 162 | "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", 163 | "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", 164 | "dev": true 165 | }, 166 | "@types/node": { 167 | "version": "14.0.23", 168 | "resolved": "https://registry.npmjs.org/@types/node/-/node-14.0.23.tgz", 169 | "integrity": "sha512-Z4U8yDAl5TFkmYsZdFPdjeMa57NOvnaf1tljHzhouaPEp7LCj2JKkejpI1ODviIAQuW4CcQmxkQ77rnLsOOoKw==" 170 | }, 171 | "acorn": { 172 | "version": "7.4.1", 173 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", 174 | "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", 175 | "dev": true 176 | }, 177 | "acorn-jsx": { 178 | "version": "5.3.1", 179 | "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.1.tgz", 180 | "integrity": "sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==", 181 | "dev": true 182 | }, 183 | "agent-base": { 184 | "version": "6.0.1", 185 | "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.1.tgz", 186 | "integrity": "sha512-01q25QQDwLSsyfhrKbn8yuur+JNw0H+0Y4JiGIKd3z9aYk/w/2kxD/Upc+t2ZBBSUNff50VjPsSW2YxM8QYKVg==", 187 | "requires": { 188 | "debug": "4" 189 | } 190 | }, 191 | "ajv": { 192 | "version": "6.12.6", 193 | "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", 194 | "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", 195 | "dev": true, 196 | "requires": { 197 | "fast-deep-equal": "^3.1.1", 198 | "fast-json-stable-stringify": "^2.0.0", 199 | "json-schema-traverse": "^0.4.1", 200 | "uri-js": "^4.2.2" 201 | } 202 | }, 203 | "ansi-colors": { 204 | "version": "4.1.1", 205 | "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", 206 | "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", 207 | "dev": true 208 | }, 209 | "ansi-regex": { 210 | "version": "5.0.0", 211 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", 212 | "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", 213 | "dev": true 214 | }, 215 | "ansi-styles": { 216 | "version": "3.2.1", 217 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", 218 | "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", 219 | "dev": true, 220 | "requires": { 221 | "color-convert": "^1.9.0" 222 | } 223 | }, 224 | "argparse": { 225 | "version": "1.0.10", 226 | "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", 227 | "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", 228 | "dev": true, 229 | "requires": { 230 | "sprintf-js": "~1.0.2" 231 | } 232 | }, 233 | "array-includes": { 234 | "version": "3.1.2", 235 | "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.2.tgz", 236 | "integrity": "sha512-w2GspexNQpx+PutG3QpT437/BenZBj0M/MZGn5mzv/MofYqo0xmRHzn4lFsoDlWJ+THYsGJmFlW68WlDFx7VRw==", 237 | "dev": true, 238 | "requires": { 239 | "call-bind": "^1.0.0", 240 | "define-properties": "^1.1.3", 241 | "es-abstract": "^1.18.0-next.1", 242 | "get-intrinsic": "^1.0.1", 243 | "is-string": "^1.0.5" 244 | } 245 | }, 246 | "array.prototype.flat": { 247 | "version": "1.2.4", 248 | "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.4.tgz", 249 | "integrity": "sha512-4470Xi3GAPAjZqFcljX2xzckv1qeKPizoNkiS0+O4IoPR2ZNpcjE0pkhdihlDouK+x6QOast26B4Q/O9DJnwSg==", 250 | "dev": true, 251 | "requires": { 252 | "call-bind": "^1.0.0", 253 | "define-properties": "^1.1.3", 254 | "es-abstract": "^1.18.0-next.1" 255 | } 256 | }, 257 | "astral-regex": { 258 | "version": "1.0.0", 259 | "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", 260 | "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", 261 | "dev": true 262 | }, 263 | "balanced-match": { 264 | "version": "1.0.0", 265 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", 266 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", 267 | "dev": true 268 | }, 269 | "bluebird": { 270 | "version": "3.5.1", 271 | "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", 272 | "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==" 273 | }, 274 | "boolbase": { 275 | "version": "1.0.0", 276 | "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", 277 | "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=" 278 | }, 279 | "brace-expansion": { 280 | "version": "1.1.11", 281 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 282 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 283 | "dev": true, 284 | "requires": { 285 | "balanced-match": "^1.0.0", 286 | "concat-map": "0.0.1" 287 | } 288 | }, 289 | "call-bind": { 290 | "version": "1.0.0", 291 | "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.0.tgz", 292 | "integrity": "sha512-AEXsYIyyDY3MCzbwdhzG3Jx1R0J2wetQyUynn6dYHAO+bg8l1k7jwZtRv4ryryFs7EP+NDlikJlVe59jr0cM2w==", 293 | "dev": true, 294 | "requires": { 295 | "function-bind": "^1.1.1", 296 | "get-intrinsic": "^1.0.0" 297 | } 298 | }, 299 | "callsites": { 300 | "version": "3.1.0", 301 | "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", 302 | "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", 303 | "dev": true 304 | }, 305 | "chalk": { 306 | "version": "4.1.0", 307 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", 308 | "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", 309 | "dev": true, 310 | "requires": { 311 | "ansi-styles": "^4.1.0", 312 | "supports-color": "^7.1.0" 313 | }, 314 | "dependencies": { 315 | "ansi-styles": { 316 | "version": "4.3.0", 317 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", 318 | "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", 319 | "dev": true, 320 | "requires": { 321 | "color-convert": "^2.0.1" 322 | } 323 | }, 324 | "color-convert": { 325 | "version": "2.0.1", 326 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", 327 | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", 328 | "dev": true, 329 | "requires": { 330 | "color-name": "~1.1.4" 331 | } 332 | }, 333 | "color-name": { 334 | "version": "1.1.4", 335 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", 336 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", 337 | "dev": true 338 | }, 339 | "has-flag": { 340 | "version": "4.0.0", 341 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", 342 | "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", 343 | "dev": true 344 | }, 345 | "supports-color": { 346 | "version": "7.2.0", 347 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", 348 | "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", 349 | "dev": true, 350 | "requires": { 351 | "has-flag": "^4.0.0" 352 | } 353 | } 354 | } 355 | }, 356 | "cheerio": { 357 | "version": "1.0.0-rc.3", 358 | "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.3.tgz", 359 | "integrity": "sha512-0td5ijfUPuubwLUu0OBoe98gZj8C/AA+RW3v67GPlGOrvxWjZmBXiBCRU+I8VEiNyJzjth40POfHiz2RB3gImA==", 360 | "requires": { 361 | "css-select": "~1.2.0", 362 | "dom-serializer": "~0.1.1", 363 | "entities": "~1.1.1", 364 | "htmlparser2": "^3.9.1", 365 | "lodash": "^4.15.0", 366 | "parse5": "^3.0.1" 367 | } 368 | }, 369 | "color-convert": { 370 | "version": "1.9.3", 371 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", 372 | "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", 373 | "dev": true, 374 | "requires": { 375 | "color-name": "1.1.3" 376 | } 377 | }, 378 | "color-name": { 379 | "version": "1.1.3", 380 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", 381 | "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", 382 | "dev": true 383 | }, 384 | "concat-map": { 385 | "version": "0.0.1", 386 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 387 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", 388 | "dev": true 389 | }, 390 | "confusing-browser-globals": { 391 | "version": "1.0.10", 392 | "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.10.tgz", 393 | "integrity": "sha512-gNld/3lySHwuhaVluJUKLePYirM3QNCKzVxqAdhJII9/WXKVX5PURzMVJspS1jTslSqjeuG4KMVTSouit5YPHA==", 394 | "dev": true 395 | }, 396 | "contains-path": { 397 | "version": "0.1.0", 398 | "resolved": "https://registry.npmjs.org/contains-path/-/contains-path-0.1.0.tgz", 399 | "integrity": "sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo=", 400 | "dev": true 401 | }, 402 | "cookie": { 403 | "version": "0.3.1", 404 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", 405 | "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" 406 | }, 407 | "cross-spawn": { 408 | "version": "7.0.3", 409 | "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", 410 | "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", 411 | "dev": true, 412 | "requires": { 413 | "path-key": "^3.1.0", 414 | "shebang-command": "^2.0.0", 415 | "which": "^2.0.1" 416 | } 417 | }, 418 | "css-select": { 419 | "version": "1.2.0", 420 | "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", 421 | "integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=", 422 | "requires": { 423 | "boolbase": "~1.0.0", 424 | "css-what": "2.1", 425 | "domutils": "1.5.1", 426 | "nth-check": "~1.0.1" 427 | } 428 | }, 429 | "css-what": { 430 | "version": "2.1.3", 431 | "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.3.tgz", 432 | "integrity": "sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg==" 433 | }, 434 | "debug": { 435 | "version": "4.1.1", 436 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", 437 | "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", 438 | "requires": { 439 | "ms": "^2.1.1" 440 | } 441 | }, 442 | "deep-is": { 443 | "version": "0.1.3", 444 | "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", 445 | "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", 446 | "dev": true 447 | }, 448 | "define-properties": { 449 | "version": "1.1.3", 450 | "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", 451 | "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", 452 | "dev": true, 453 | "requires": { 454 | "object-keys": "^1.0.12" 455 | } 456 | }, 457 | "doctrine": { 458 | "version": "3.0.0", 459 | "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", 460 | "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", 461 | "dev": true, 462 | "requires": { 463 | "esutils": "^2.0.2" 464 | } 465 | }, 466 | "dom-serializer": { 467 | "version": "0.1.1", 468 | "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.1.tgz", 469 | "integrity": "sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA==", 470 | "requires": { 471 | "domelementtype": "^1.3.0", 472 | "entities": "^1.1.1" 473 | } 474 | }, 475 | "domelementtype": { 476 | "version": "1.3.1", 477 | "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", 478 | "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==" 479 | }, 480 | "domhandler": { 481 | "version": "2.4.2", 482 | "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz", 483 | "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==", 484 | "requires": { 485 | "domelementtype": "1" 486 | } 487 | }, 488 | "domutils": { 489 | "version": "1.5.1", 490 | "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", 491 | "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", 492 | "requires": { 493 | "dom-serializer": "0", 494 | "domelementtype": "1" 495 | } 496 | }, 497 | "easy-stack": { 498 | "version": "1.0.0", 499 | "resolved": "https://registry.npmjs.org/easy-stack/-/easy-stack-1.0.0.tgz", 500 | "integrity": "sha1-EskbMIWjfwuqM26UhurEv5Tj54g=" 501 | }, 502 | "emoji-regex": { 503 | "version": "7.0.3", 504 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", 505 | "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", 506 | "dev": true 507 | }, 508 | "enquirer": { 509 | "version": "2.3.6", 510 | "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", 511 | "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", 512 | "dev": true, 513 | "requires": { 514 | "ansi-colors": "^4.1.1" 515 | } 516 | }, 517 | "entities": { 518 | "version": "1.1.2", 519 | "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", 520 | "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==" 521 | }, 522 | "eris": { 523 | "version": "0.14.0", 524 | "resolved": "https://registry.npmjs.org/eris/-/eris-0.14.0.tgz", 525 | "integrity": "sha512-/W6X0SFR2swtA9oc4ga5Wh1TQcZtPgbUaDDdwYc67fvFUAtwC+V1xzWUZq2yDeJnTfB8Uot9SJWA8Lthe2sDtQ==", 526 | "requires": { 527 | "opusscript": "^0.0.7", 528 | "tweetnacl": "^1.0.1", 529 | "ws": "^7.2.1" 530 | } 531 | }, 532 | "error-ex": { 533 | "version": "1.3.2", 534 | "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", 535 | "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", 536 | "dev": true, 537 | "requires": { 538 | "is-arrayish": "^0.2.1" 539 | } 540 | }, 541 | "es-abstract": { 542 | "version": "1.18.0-next.1", 543 | "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.1.tgz", 544 | "integrity": "sha512-I4UGspA0wpZXWENrdA0uHbnhte683t3qT/1VFH9aX2dA5PPSf6QW5HHXf5HImaqPmjXaVeVk4RGWnaylmV7uAA==", 545 | "dev": true, 546 | "requires": { 547 | "es-to-primitive": "^1.2.1", 548 | "function-bind": "^1.1.1", 549 | "has": "^1.0.3", 550 | "has-symbols": "^1.0.1", 551 | "is-callable": "^1.2.2", 552 | "is-negative-zero": "^2.0.0", 553 | "is-regex": "^1.1.1", 554 | "object-inspect": "^1.8.0", 555 | "object-keys": "^1.1.1", 556 | "object.assign": "^4.1.1", 557 | "string.prototype.trimend": "^1.0.1", 558 | "string.prototype.trimstart": "^1.0.1" 559 | } 560 | }, 561 | "es-to-primitive": { 562 | "version": "1.2.1", 563 | "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", 564 | "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", 565 | "dev": true, 566 | "requires": { 567 | "is-callable": "^1.1.4", 568 | "is-date-object": "^1.0.1", 569 | "is-symbol": "^1.0.2" 570 | } 571 | }, 572 | "escape-string-regexp": { 573 | "version": "1.0.5", 574 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", 575 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", 576 | "dev": true 577 | }, 578 | "eslint": { 579 | "version": "7.15.0", 580 | "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.15.0.tgz", 581 | "integrity": "sha512-Vr64xFDT8w30wFll643e7cGrIkPEU50yIiI36OdSIDoSGguIeaLzBo0vpGvzo9RECUqq7htURfwEtKqwytkqzA==", 582 | "dev": true, 583 | "requires": { 584 | "@babel/code-frame": "^7.0.0", 585 | "@eslint/eslintrc": "^0.2.2", 586 | "ajv": "^6.10.0", 587 | "chalk": "^4.0.0", 588 | "cross-spawn": "^7.0.2", 589 | "debug": "^4.0.1", 590 | "doctrine": "^3.0.0", 591 | "enquirer": "^2.3.5", 592 | "eslint-scope": "^5.1.1", 593 | "eslint-utils": "^2.1.0", 594 | "eslint-visitor-keys": "^2.0.0", 595 | "espree": "^7.3.1", 596 | "esquery": "^1.2.0", 597 | "esutils": "^2.0.2", 598 | "file-entry-cache": "^6.0.0", 599 | "functional-red-black-tree": "^1.0.1", 600 | "glob-parent": "^5.0.0", 601 | "globals": "^12.1.0", 602 | "ignore": "^4.0.6", 603 | "import-fresh": "^3.0.0", 604 | "imurmurhash": "^0.1.4", 605 | "is-glob": "^4.0.0", 606 | "js-yaml": "^3.13.1", 607 | "json-stable-stringify-without-jsonify": "^1.0.1", 608 | "levn": "^0.4.1", 609 | "lodash": "^4.17.19", 610 | "minimatch": "^3.0.4", 611 | "natural-compare": "^1.4.0", 612 | "optionator": "^0.9.1", 613 | "progress": "^2.0.0", 614 | "regexpp": "^3.1.0", 615 | "semver": "^7.2.1", 616 | "strip-ansi": "^6.0.0", 617 | "strip-json-comments": "^3.1.0", 618 | "table": "^5.2.3", 619 | "text-table": "^0.2.0", 620 | "v8-compile-cache": "^2.0.3" 621 | } 622 | }, 623 | "eslint-config-airbnb": { 624 | "version": "18.2.1", 625 | "resolved": "https://registry.npmjs.org/eslint-config-airbnb/-/eslint-config-airbnb-18.2.1.tgz", 626 | "integrity": "sha512-glZNDEZ36VdlZWoxn/bUR1r/sdFKPd1mHPbqUtkctgNG4yT2DLLtJ3D+yCV+jzZCc2V1nBVkmdknOJBZ5Hc0fg==", 627 | "dev": true, 628 | "requires": { 629 | "eslint-config-airbnb-base": "^14.2.1", 630 | "object.assign": "^4.1.2", 631 | "object.entries": "^1.1.2" 632 | } 633 | }, 634 | "eslint-config-airbnb-base": { 635 | "version": "14.2.1", 636 | "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-14.2.1.tgz", 637 | "integrity": "sha512-GOrQyDtVEc1Xy20U7vsB2yAoB4nBlfH5HZJeatRXHleO+OS5Ot+MWij4Dpltw4/DyIkqUfqz1epfhVR5XWWQPA==", 638 | "dev": true, 639 | "requires": { 640 | "confusing-browser-globals": "^1.0.10", 641 | "object.assign": "^4.1.2", 642 | "object.entries": "^1.1.2" 643 | } 644 | }, 645 | "eslint-import-resolver-node": { 646 | "version": "0.3.4", 647 | "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.4.tgz", 648 | "integrity": "sha512-ogtf+5AB/O+nM6DIeBUNr2fuT7ot9Qg/1harBfBtaP13ekEWFQEEMP94BCB7zaNW3gyY+8SHYF00rnqYwXKWOA==", 649 | "dev": true, 650 | "requires": { 651 | "debug": "^2.6.9", 652 | "resolve": "^1.13.1" 653 | }, 654 | "dependencies": { 655 | "debug": { 656 | "version": "2.6.9", 657 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 658 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 659 | "dev": true, 660 | "requires": { 661 | "ms": "2.0.0" 662 | } 663 | }, 664 | "ms": { 665 | "version": "2.0.0", 666 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 667 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", 668 | "dev": true 669 | } 670 | } 671 | }, 672 | "eslint-module-utils": { 673 | "version": "2.6.0", 674 | "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.6.0.tgz", 675 | "integrity": "sha512-6j9xxegbqe8/kZY8cYpcp0xhbK0EgJlg3g9mib3/miLaExuuwc3n5UEfSnU6hWMbT0FAYVvDbL9RrRgpUeQIvA==", 676 | "dev": true, 677 | "requires": { 678 | "debug": "^2.6.9", 679 | "pkg-dir": "^2.0.0" 680 | }, 681 | "dependencies": { 682 | "debug": { 683 | "version": "2.6.9", 684 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 685 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 686 | "dev": true, 687 | "requires": { 688 | "ms": "2.0.0" 689 | } 690 | }, 691 | "ms": { 692 | "version": "2.0.0", 693 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 694 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", 695 | "dev": true 696 | } 697 | } 698 | }, 699 | "eslint-plugin-import": { 700 | "version": "2.22.1", 701 | "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.22.1.tgz", 702 | "integrity": "sha512-8K7JjINHOpH64ozkAhpT3sd+FswIZTfMZTjdx052pnWrgRCVfp8op9tbjpAk3DdUeI/Ba4C8OjdC0r90erHEOw==", 703 | "dev": true, 704 | "requires": { 705 | "array-includes": "^3.1.1", 706 | "array.prototype.flat": "^1.2.3", 707 | "contains-path": "^0.1.0", 708 | "debug": "^2.6.9", 709 | "doctrine": "1.5.0", 710 | "eslint-import-resolver-node": "^0.3.4", 711 | "eslint-module-utils": "^2.6.0", 712 | "has": "^1.0.3", 713 | "minimatch": "^3.0.4", 714 | "object.values": "^1.1.1", 715 | "read-pkg-up": "^2.0.0", 716 | "resolve": "^1.17.0", 717 | "tsconfig-paths": "^3.9.0" 718 | }, 719 | "dependencies": { 720 | "debug": { 721 | "version": "2.6.9", 722 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 723 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 724 | "dev": true, 725 | "requires": { 726 | "ms": "2.0.0" 727 | } 728 | }, 729 | "doctrine": { 730 | "version": "1.5.0", 731 | "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz", 732 | "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=", 733 | "dev": true, 734 | "requires": { 735 | "esutils": "^2.0.2", 736 | "isarray": "^1.0.0" 737 | } 738 | }, 739 | "ms": { 740 | "version": "2.0.0", 741 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 742 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", 743 | "dev": true 744 | } 745 | } 746 | }, 747 | "eslint-scope": { 748 | "version": "5.1.1", 749 | "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", 750 | "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", 751 | "dev": true, 752 | "requires": { 753 | "esrecurse": "^4.3.0", 754 | "estraverse": "^4.1.1" 755 | } 756 | }, 757 | "eslint-utils": { 758 | "version": "2.1.0", 759 | "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", 760 | "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", 761 | "dev": true, 762 | "requires": { 763 | "eslint-visitor-keys": "^1.1.0" 764 | }, 765 | "dependencies": { 766 | "eslint-visitor-keys": { 767 | "version": "1.3.0", 768 | "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", 769 | "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", 770 | "dev": true 771 | } 772 | } 773 | }, 774 | "eslint-visitor-keys": { 775 | "version": "2.0.0", 776 | "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz", 777 | "integrity": "sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ==", 778 | "dev": true 779 | }, 780 | "espree": { 781 | "version": "7.3.1", 782 | "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", 783 | "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", 784 | "dev": true, 785 | "requires": { 786 | "acorn": "^7.4.0", 787 | "acorn-jsx": "^5.3.1", 788 | "eslint-visitor-keys": "^1.3.0" 789 | }, 790 | "dependencies": { 791 | "eslint-visitor-keys": { 792 | "version": "1.3.0", 793 | "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", 794 | "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", 795 | "dev": true 796 | } 797 | } 798 | }, 799 | "esprima": { 800 | "version": "4.0.1", 801 | "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", 802 | "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", 803 | "dev": true 804 | }, 805 | "esquery": { 806 | "version": "1.3.1", 807 | "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.3.1.tgz", 808 | "integrity": "sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ==", 809 | "dev": true, 810 | "requires": { 811 | "estraverse": "^5.1.0" 812 | }, 813 | "dependencies": { 814 | "estraverse": { 815 | "version": "5.2.0", 816 | "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", 817 | "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", 818 | "dev": true 819 | } 820 | } 821 | }, 822 | "esrecurse": { 823 | "version": "4.3.0", 824 | "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", 825 | "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", 826 | "dev": true, 827 | "requires": { 828 | "estraverse": "^5.2.0" 829 | }, 830 | "dependencies": { 831 | "estraverse": { 832 | "version": "5.2.0", 833 | "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", 834 | "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", 835 | "dev": true 836 | } 837 | } 838 | }, 839 | "estraverse": { 840 | "version": "4.3.0", 841 | "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", 842 | "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", 843 | "dev": true 844 | }, 845 | "esutils": { 846 | "version": "2.0.3", 847 | "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", 848 | "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", 849 | "dev": true 850 | }, 851 | "event-pubsub": { 852 | "version": "4.3.0", 853 | "resolved": "https://registry.npmjs.org/event-pubsub/-/event-pubsub-4.3.0.tgz", 854 | "integrity": "sha512-z7IyloorXvKbFx9Bpie2+vMJKKx1fH1EN5yiTfp8CiLOTptSYy1g8H4yDpGlEdshL1PBiFtBHepF2cNsqeEeFQ==" 855 | }, 856 | "fast-deep-equal": { 857 | "version": "3.1.3", 858 | "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", 859 | "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", 860 | "dev": true 861 | }, 862 | "fast-json-stable-stringify": { 863 | "version": "2.1.0", 864 | "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", 865 | "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", 866 | "dev": true 867 | }, 868 | "fast-levenshtein": { 869 | "version": "2.0.6", 870 | "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", 871 | "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", 872 | "dev": true 873 | }, 874 | "file-entry-cache": { 875 | "version": "6.0.0", 876 | "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.0.tgz", 877 | "integrity": "sha512-fqoO76jZ3ZnYrXLDRxBR1YvOvc0k844kcOg40bgsPrE25LAb/PDqTY+ho64Xh2c8ZXgIKldchCFHczG2UVRcWA==", 878 | "dev": true, 879 | "requires": { 880 | "flat-cache": "^3.0.4" 881 | } 882 | }, 883 | "find-up": { 884 | "version": "2.1.0", 885 | "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", 886 | "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", 887 | "dev": true, 888 | "requires": { 889 | "locate-path": "^2.0.0" 890 | } 891 | }, 892 | "flat-cache": { 893 | "version": "3.0.4", 894 | "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", 895 | "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", 896 | "dev": true, 897 | "requires": { 898 | "flatted": "^3.1.0", 899 | "rimraf": "^3.0.2" 900 | } 901 | }, 902 | "flatted": { 903 | "version": "3.1.0", 904 | "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.1.0.tgz", 905 | "integrity": "sha512-tW+UkmtNg/jv9CSofAKvgVcO7c2URjhTdW1ZTkcAritblu8tajiYy7YisnIflEwtKssCtOxpnBRoCB7iap0/TA==", 906 | "dev": true 907 | }, 908 | "fs.realpath": { 909 | "version": "1.0.0", 910 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 911 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", 912 | "dev": true 913 | }, 914 | "function-bind": { 915 | "version": "1.1.1", 916 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", 917 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", 918 | "dev": true 919 | }, 920 | "functional-red-black-tree": { 921 | "version": "1.0.1", 922 | "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", 923 | "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", 924 | "dev": true 925 | }, 926 | "get-intrinsic": { 927 | "version": "1.0.1", 928 | "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.0.1.tgz", 929 | "integrity": "sha512-ZnWP+AmS1VUaLgTRy47+zKtjTxz+0xMpx3I52i+aalBK1QP19ggLF3Db89KJX7kjfOfP2eoa01qc++GwPgufPg==", 930 | "dev": true, 931 | "requires": { 932 | "function-bind": "^1.1.1", 933 | "has": "^1.0.3", 934 | "has-symbols": "^1.0.1" 935 | } 936 | }, 937 | "glob": { 938 | "version": "7.1.6", 939 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", 940 | "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", 941 | "dev": true, 942 | "requires": { 943 | "fs.realpath": "^1.0.0", 944 | "inflight": "^1.0.4", 945 | "inherits": "2", 946 | "minimatch": "^3.0.4", 947 | "once": "^1.3.0", 948 | "path-is-absolute": "^1.0.0" 949 | } 950 | }, 951 | "glob-parent": { 952 | "version": "5.1.1", 953 | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", 954 | "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", 955 | "dev": true, 956 | "requires": { 957 | "is-glob": "^4.0.1" 958 | } 959 | }, 960 | "globals": { 961 | "version": "12.4.0", 962 | "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", 963 | "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==", 964 | "dev": true, 965 | "requires": { 966 | "type-fest": "^0.8.1" 967 | } 968 | }, 969 | "graceful-fs": { 970 | "version": "4.2.4", 971 | "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", 972 | "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", 973 | "dev": true 974 | }, 975 | "has": { 976 | "version": "1.0.3", 977 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", 978 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", 979 | "dev": true, 980 | "requires": { 981 | "function-bind": "^1.1.1" 982 | } 983 | }, 984 | "has-flag": { 985 | "version": "3.0.0", 986 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", 987 | "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", 988 | "dev": true 989 | }, 990 | "has-symbols": { 991 | "version": "1.0.1", 992 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", 993 | "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", 994 | "dev": true 995 | }, 996 | "hosted-git-info": { 997 | "version": "2.8.9", 998 | "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", 999 | "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", 1000 | "dev": true 1001 | }, 1002 | "htmlparser2": { 1003 | "version": "3.10.1", 1004 | "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz", 1005 | "integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==", 1006 | "requires": { 1007 | "domelementtype": "^1.3.1", 1008 | "domhandler": "^2.3.0", 1009 | "domutils": "^1.5.1", 1010 | "entities": "^1.1.1", 1011 | "inherits": "^2.0.1", 1012 | "readable-stream": "^3.1.1" 1013 | } 1014 | }, 1015 | "https-proxy-agent": { 1016 | "version": "5.0.0", 1017 | "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", 1018 | "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", 1019 | "requires": { 1020 | "agent-base": "6", 1021 | "debug": "4" 1022 | } 1023 | }, 1024 | "ignore": { 1025 | "version": "4.0.6", 1026 | "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", 1027 | "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", 1028 | "dev": true 1029 | }, 1030 | "import-fresh": { 1031 | "version": "3.2.2", 1032 | "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.2.tgz", 1033 | "integrity": "sha512-cTPNrlvJT6twpYy+YmKUKrTSjWFs3bjYjAhCwm+z4EOCubZxAuO+hHpRN64TqjEaYSHs7tJAE0w1CKMGmsG/lw==", 1034 | "dev": true, 1035 | "requires": { 1036 | "parent-module": "^1.0.0", 1037 | "resolve-from": "^4.0.0" 1038 | } 1039 | }, 1040 | "imurmurhash": { 1041 | "version": "0.1.4", 1042 | "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", 1043 | "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", 1044 | "dev": true 1045 | }, 1046 | "inflight": { 1047 | "version": "1.0.6", 1048 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 1049 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 1050 | "dev": true, 1051 | "requires": { 1052 | "once": "^1.3.0", 1053 | "wrappy": "1" 1054 | } 1055 | }, 1056 | "inherits": { 1057 | "version": "2.0.4", 1058 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 1059 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 1060 | }, 1061 | "is-arrayish": { 1062 | "version": "0.2.1", 1063 | "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", 1064 | "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", 1065 | "dev": true 1066 | }, 1067 | "is-callable": { 1068 | "version": "1.2.2", 1069 | "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.2.tgz", 1070 | "integrity": "sha512-dnMqspv5nU3LoewK2N/y7KLtxtakvTuaCsU9FU50/QDmdbHNy/4/JuRtMHqRU22o3q+W89YQndQEeCVwK+3qrA==", 1071 | "dev": true 1072 | }, 1073 | "is-core-module": { 1074 | "version": "2.2.0", 1075 | "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.2.0.tgz", 1076 | "integrity": "sha512-XRAfAdyyY5F5cOXn7hYQDqh2Xmii+DEfIcQGxK/uNwMHhIkPWO0g8msXcbzLe+MpGoR951MlqM/2iIlU4vKDdQ==", 1077 | "dev": true, 1078 | "requires": { 1079 | "has": "^1.0.3" 1080 | } 1081 | }, 1082 | "is-date-object": { 1083 | "version": "1.0.2", 1084 | "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", 1085 | "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==", 1086 | "dev": true 1087 | }, 1088 | "is-extglob": { 1089 | "version": "2.1.1", 1090 | "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", 1091 | "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", 1092 | "dev": true 1093 | }, 1094 | "is-fullwidth-code-point": { 1095 | "version": "2.0.0", 1096 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", 1097 | "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", 1098 | "dev": true 1099 | }, 1100 | "is-glob": { 1101 | "version": "4.0.1", 1102 | "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", 1103 | "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", 1104 | "dev": true, 1105 | "requires": { 1106 | "is-extglob": "^2.1.1" 1107 | } 1108 | }, 1109 | "is-negative-zero": { 1110 | "version": "2.0.1", 1111 | "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz", 1112 | "integrity": "sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==", 1113 | "dev": true 1114 | }, 1115 | "is-regex": { 1116 | "version": "1.1.1", 1117 | "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", 1118 | "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", 1119 | "dev": true, 1120 | "requires": { 1121 | "has-symbols": "^1.0.1" 1122 | } 1123 | }, 1124 | "is-string": { 1125 | "version": "1.0.5", 1126 | "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.5.tgz", 1127 | "integrity": "sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==", 1128 | "dev": true 1129 | }, 1130 | "is-symbol": { 1131 | "version": "1.0.3", 1132 | "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", 1133 | "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", 1134 | "dev": true, 1135 | "requires": { 1136 | "has-symbols": "^1.0.1" 1137 | } 1138 | }, 1139 | "isarray": { 1140 | "version": "1.0.0", 1141 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", 1142 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", 1143 | "dev": true 1144 | }, 1145 | "isexe": { 1146 | "version": "2.0.0", 1147 | "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", 1148 | "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", 1149 | "dev": true 1150 | }, 1151 | "js-message": { 1152 | "version": "1.0.5", 1153 | "resolved": "https://registry.npmjs.org/js-message/-/js-message-1.0.5.tgz", 1154 | "integrity": "sha1-IwDSSxrwjondCVvBpMnJz8uJLRU=" 1155 | }, 1156 | "js-queue": { 1157 | "version": "2.0.0", 1158 | "resolved": "https://registry.npmjs.org/js-queue/-/js-queue-2.0.0.tgz", 1159 | "integrity": "sha1-NiITz4YPRo8BJfxslqvBdCUx+Ug=", 1160 | "requires": { 1161 | "easy-stack": "^1.0.0" 1162 | } 1163 | }, 1164 | "js-tokens": { 1165 | "version": "4.0.0", 1166 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", 1167 | "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", 1168 | "dev": true 1169 | }, 1170 | "js-yaml": { 1171 | "version": "3.14.1", 1172 | "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", 1173 | "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", 1174 | "dev": true, 1175 | "requires": { 1176 | "argparse": "^1.0.7", 1177 | "esprima": "^4.0.0" 1178 | } 1179 | }, 1180 | "json-schema-traverse": { 1181 | "version": "0.4.1", 1182 | "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", 1183 | "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", 1184 | "dev": true 1185 | }, 1186 | "json-stable-stringify-without-jsonify": { 1187 | "version": "1.0.1", 1188 | "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", 1189 | "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", 1190 | "dev": true 1191 | }, 1192 | "json5": { 1193 | "version": "1.0.1", 1194 | "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", 1195 | "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", 1196 | "dev": true, 1197 | "requires": { 1198 | "minimist": "^1.2.0" 1199 | } 1200 | }, 1201 | "levn": { 1202 | "version": "0.4.1", 1203 | "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", 1204 | "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", 1205 | "dev": true, 1206 | "requires": { 1207 | "prelude-ls": "^1.2.1", 1208 | "type-check": "~0.4.0" 1209 | } 1210 | }, 1211 | "load-json-file": { 1212 | "version": "2.0.0", 1213 | "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", 1214 | "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", 1215 | "dev": true, 1216 | "requires": { 1217 | "graceful-fs": "^4.1.2", 1218 | "parse-json": "^2.2.0", 1219 | "pify": "^2.0.0", 1220 | "strip-bom": "^3.0.0" 1221 | } 1222 | }, 1223 | "locate-path": { 1224 | "version": "2.0.0", 1225 | "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", 1226 | "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", 1227 | "dev": true, 1228 | "requires": { 1229 | "p-locate": "^2.0.0", 1230 | "path-exists": "^3.0.0" 1231 | } 1232 | }, 1233 | "lodash": { 1234 | "version": "4.17.21", 1235 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", 1236 | "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" 1237 | }, 1238 | "lru-cache": { 1239 | "version": "6.0.0", 1240 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", 1241 | "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", 1242 | "dev": true, 1243 | "requires": { 1244 | "yallist": "^4.0.0" 1245 | } 1246 | }, 1247 | "lru_map": { 1248 | "version": "0.3.3", 1249 | "resolved": "https://registry.npmjs.org/lru_map/-/lru_map-0.3.3.tgz", 1250 | "integrity": "sha1-tcg1G5Rky9dQM1p5ZQoOwOVhGN0=" 1251 | }, 1252 | "minimatch": { 1253 | "version": "3.0.4", 1254 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 1255 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 1256 | "dev": true, 1257 | "requires": { 1258 | "brace-expansion": "^1.1.7" 1259 | } 1260 | }, 1261 | "minimist": { 1262 | "version": "1.2.5", 1263 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", 1264 | "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", 1265 | "dev": true 1266 | }, 1267 | "ms": { 1268 | "version": "2.1.2", 1269 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 1270 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" 1271 | }, 1272 | "natural-compare": { 1273 | "version": "1.4.0", 1274 | "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", 1275 | "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", 1276 | "dev": true 1277 | }, 1278 | "node-fetch": { 1279 | "version": "2.6.1", 1280 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", 1281 | "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==" 1282 | }, 1283 | "node-ipc": { 1284 | "version": "9.1.1", 1285 | "resolved": "https://registry.npmjs.org/node-ipc/-/node-ipc-9.1.1.tgz", 1286 | "integrity": "sha512-FAyICv0sIRJxVp3GW5fzgaf9jwwRQxAKDJlmNFUL5hOy+W4X/I5AypyHoq0DXXbo9o/gt79gj++4cMr4jVWE/w==", 1287 | "requires": { 1288 | "event-pubsub": "4.3.0", 1289 | "js-message": "1.0.5", 1290 | "js-queue": "2.0.0" 1291 | } 1292 | }, 1293 | "normalize-package-data": { 1294 | "version": "2.5.0", 1295 | "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", 1296 | "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", 1297 | "dev": true, 1298 | "requires": { 1299 | "hosted-git-info": "^2.1.4", 1300 | "resolve": "^1.10.0", 1301 | "semver": "2 || 3 || 4 || 5", 1302 | "validate-npm-package-license": "^3.0.1" 1303 | }, 1304 | "dependencies": { 1305 | "semver": { 1306 | "version": "5.7.1", 1307 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", 1308 | "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", 1309 | "dev": true 1310 | } 1311 | } 1312 | }, 1313 | "nth-check": { 1314 | "version": "1.0.2", 1315 | "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", 1316 | "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", 1317 | "requires": { 1318 | "boolbase": "~1.0.0" 1319 | } 1320 | }, 1321 | "object-inspect": { 1322 | "version": "1.9.0", 1323 | "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.9.0.tgz", 1324 | "integrity": "sha512-i3Bp9iTqwhaLZBxGkRfo5ZbE07BQRT7MGu8+nNgwW9ItGp1TzCTw2DLEoWwjClxBjOFI/hWljTAmYGCEwmtnOw==", 1325 | "dev": true 1326 | }, 1327 | "object-keys": { 1328 | "version": "1.1.1", 1329 | "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", 1330 | "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", 1331 | "dev": true 1332 | }, 1333 | "object.assign": { 1334 | "version": "4.1.2", 1335 | "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", 1336 | "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", 1337 | "dev": true, 1338 | "requires": { 1339 | "call-bind": "^1.0.0", 1340 | "define-properties": "^1.1.3", 1341 | "has-symbols": "^1.0.1", 1342 | "object-keys": "^1.1.1" 1343 | } 1344 | }, 1345 | "object.entries": { 1346 | "version": "1.1.3", 1347 | "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.3.tgz", 1348 | "integrity": "sha512-ym7h7OZebNS96hn5IJeyUmaWhaSM4SVtAPPfNLQEI2MYWCO2egsITb9nab2+i/Pwibx+R0mtn+ltKJXRSeTMGg==", 1349 | "dev": true, 1350 | "requires": { 1351 | "call-bind": "^1.0.0", 1352 | "define-properties": "^1.1.3", 1353 | "es-abstract": "^1.18.0-next.1", 1354 | "has": "^1.0.3" 1355 | } 1356 | }, 1357 | "object.values": { 1358 | "version": "1.1.2", 1359 | "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.2.tgz", 1360 | "integrity": "sha512-MYC0jvJopr8EK6dPBiO8Nb9mvjdypOachO5REGk6MXzujbBrAisKo3HmdEI6kZDL6fC31Mwee/5YbtMebixeag==", 1361 | "dev": true, 1362 | "requires": { 1363 | "call-bind": "^1.0.0", 1364 | "define-properties": "^1.1.3", 1365 | "es-abstract": "^1.18.0-next.1", 1366 | "has": "^1.0.3" 1367 | } 1368 | }, 1369 | "once": { 1370 | "version": "1.4.0", 1371 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 1372 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 1373 | "dev": true, 1374 | "requires": { 1375 | "wrappy": "1" 1376 | } 1377 | }, 1378 | "optionator": { 1379 | "version": "0.9.1", 1380 | "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", 1381 | "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", 1382 | "dev": true, 1383 | "requires": { 1384 | "deep-is": "^0.1.3", 1385 | "fast-levenshtein": "^2.0.6", 1386 | "levn": "^0.4.1", 1387 | "prelude-ls": "^1.2.1", 1388 | "type-check": "^0.4.0", 1389 | "word-wrap": "^1.2.3" 1390 | } 1391 | }, 1392 | "opusscript": { 1393 | "version": "0.0.7", 1394 | "resolved": "https://registry.npmjs.org/opusscript/-/opusscript-0.0.7.tgz", 1395 | "integrity": "sha512-DcBadTdYTUuH9zQtepsLjQn4Ll6rs3dmeFvN+SD0ThPnxRBRm/WC1zXWPg+wgAJimB784gdZvUMA57gDP7FdVg==", 1396 | "optional": true 1397 | }, 1398 | "p-limit": { 1399 | "version": "1.3.0", 1400 | "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", 1401 | "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", 1402 | "dev": true, 1403 | "requires": { 1404 | "p-try": "^1.0.0" 1405 | } 1406 | }, 1407 | "p-locate": { 1408 | "version": "2.0.0", 1409 | "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", 1410 | "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", 1411 | "dev": true, 1412 | "requires": { 1413 | "p-limit": "^1.1.0" 1414 | } 1415 | }, 1416 | "p-try": { 1417 | "version": "1.0.0", 1418 | "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", 1419 | "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", 1420 | "dev": true 1421 | }, 1422 | "parent-module": { 1423 | "version": "1.0.1", 1424 | "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", 1425 | "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", 1426 | "dev": true, 1427 | "requires": { 1428 | "callsites": "^3.0.0" 1429 | } 1430 | }, 1431 | "parse-json": { 1432 | "version": "2.2.0", 1433 | "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", 1434 | "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", 1435 | "dev": true, 1436 | "requires": { 1437 | "error-ex": "^1.2.0" 1438 | } 1439 | }, 1440 | "parse5": { 1441 | "version": "3.0.3", 1442 | "resolved": "https://registry.npmjs.org/parse5/-/parse5-3.0.3.tgz", 1443 | "integrity": "sha512-rgO9Zg5LLLkfJF9E6CCmXlSE4UVceloys8JrFqCcHloC3usd/kJCyPDwH2SOlzix2j3xaP9sUX3e8+kvkuleAA==", 1444 | "requires": { 1445 | "@types/node": "*" 1446 | } 1447 | }, 1448 | "path-exists": { 1449 | "version": "3.0.0", 1450 | "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", 1451 | "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", 1452 | "dev": true 1453 | }, 1454 | "path-is-absolute": { 1455 | "version": "1.0.1", 1456 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 1457 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", 1458 | "dev": true 1459 | }, 1460 | "path-key": { 1461 | "version": "3.1.1", 1462 | "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", 1463 | "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", 1464 | "dev": true 1465 | }, 1466 | "path-parse": { 1467 | "version": "1.0.6", 1468 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", 1469 | "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", 1470 | "dev": true 1471 | }, 1472 | "path-type": { 1473 | "version": "2.0.0", 1474 | "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", 1475 | "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", 1476 | "dev": true, 1477 | "requires": { 1478 | "pify": "^2.0.0" 1479 | } 1480 | }, 1481 | "pify": { 1482 | "version": "2.3.0", 1483 | "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", 1484 | "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", 1485 | "dev": true 1486 | }, 1487 | "pkg-dir": { 1488 | "version": "2.0.0", 1489 | "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", 1490 | "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", 1491 | "dev": true, 1492 | "requires": { 1493 | "find-up": "^2.1.0" 1494 | } 1495 | }, 1496 | "polaris-quote-engine": { 1497 | "version": "1.0.2", 1498 | "resolved": "https://registry.npmjs.org/polaris-quote-engine/-/polaris-quote-engine-1.0.2.tgz", 1499 | "integrity": "sha512-0+Zxgelb1FOFvhLHUOf4UCvtYUm4SLUgGrCmMtNfCNWkPj1blTVVyg+oqR8aUPeSkIyt7xnP4/HeatNB1FAChQ==" 1500 | }, 1501 | "prelude-ls": { 1502 | "version": "1.2.1", 1503 | "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", 1504 | "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", 1505 | "dev": true 1506 | }, 1507 | "progress": { 1508 | "version": "2.0.3", 1509 | "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", 1510 | "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", 1511 | "dev": true 1512 | }, 1513 | "punycode": { 1514 | "version": "2.1.1", 1515 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", 1516 | "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", 1517 | "dev": true 1518 | }, 1519 | "read-pkg": { 1520 | "version": "2.0.0", 1521 | "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", 1522 | "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", 1523 | "dev": true, 1524 | "requires": { 1525 | "load-json-file": "^2.0.0", 1526 | "normalize-package-data": "^2.3.2", 1527 | "path-type": "^2.0.0" 1528 | } 1529 | }, 1530 | "read-pkg-up": { 1531 | "version": "2.0.0", 1532 | "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", 1533 | "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", 1534 | "dev": true, 1535 | "requires": { 1536 | "find-up": "^2.0.0", 1537 | "read-pkg": "^2.0.0" 1538 | } 1539 | }, 1540 | "readable-stream": { 1541 | "version": "3.6.0", 1542 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", 1543 | "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", 1544 | "requires": { 1545 | "inherits": "^2.0.3", 1546 | "string_decoder": "^1.1.1", 1547 | "util-deprecate": "^1.0.1" 1548 | } 1549 | }, 1550 | "regexpp": { 1551 | "version": "3.1.0", 1552 | "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz", 1553 | "integrity": "sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==", 1554 | "dev": true 1555 | }, 1556 | "resolve": { 1557 | "version": "1.19.0", 1558 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.19.0.tgz", 1559 | "integrity": "sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg==", 1560 | "dev": true, 1561 | "requires": { 1562 | "is-core-module": "^2.1.0", 1563 | "path-parse": "^1.0.6" 1564 | } 1565 | }, 1566 | "resolve-from": { 1567 | "version": "4.0.0", 1568 | "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", 1569 | "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", 1570 | "dev": true 1571 | }, 1572 | "rethinkdbdash": { 1573 | "version": "2.3.31", 1574 | "resolved": "https://registry.npmjs.org/rethinkdbdash/-/rethinkdbdash-2.3.31.tgz", 1575 | "integrity": "sha512-6nXrKFjdg2Ug0YpdmPWSvyD/2EisHnFNt4FWZ74dcXGK48ievSv+cNFTmVv+KjLi6I9CCf6w4CKZ6yCYTfMfdQ==", 1576 | "requires": { 1577 | "bluebird": ">= 3.0.1" 1578 | } 1579 | }, 1580 | "rimraf": { 1581 | "version": "3.0.2", 1582 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", 1583 | "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", 1584 | "dev": true, 1585 | "requires": { 1586 | "glob": "^7.1.3" 1587 | } 1588 | }, 1589 | "safe-buffer": { 1590 | "version": "5.2.1", 1591 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 1592 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" 1593 | }, 1594 | "semver": { 1595 | "version": "7.3.4", 1596 | "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", 1597 | "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", 1598 | "dev": true, 1599 | "requires": { 1600 | "lru-cache": "^6.0.0" 1601 | } 1602 | }, 1603 | "shebang-command": { 1604 | "version": "2.0.0", 1605 | "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", 1606 | "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", 1607 | "dev": true, 1608 | "requires": { 1609 | "shebang-regex": "^3.0.0" 1610 | } 1611 | }, 1612 | "shebang-regex": { 1613 | "version": "3.0.0", 1614 | "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", 1615 | "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", 1616 | "dev": true 1617 | }, 1618 | "slice-ansi": { 1619 | "version": "2.1.0", 1620 | "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", 1621 | "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", 1622 | "dev": true, 1623 | "requires": { 1624 | "ansi-styles": "^3.2.0", 1625 | "astral-regex": "^1.0.0", 1626 | "is-fullwidth-code-point": "^2.0.0" 1627 | } 1628 | }, 1629 | "spdx-correct": { 1630 | "version": "3.1.1", 1631 | "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", 1632 | "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", 1633 | "dev": true, 1634 | "requires": { 1635 | "spdx-expression-parse": "^3.0.0", 1636 | "spdx-license-ids": "^3.0.0" 1637 | } 1638 | }, 1639 | "spdx-exceptions": { 1640 | "version": "2.3.0", 1641 | "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", 1642 | "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", 1643 | "dev": true 1644 | }, 1645 | "spdx-expression-parse": { 1646 | "version": "3.0.1", 1647 | "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", 1648 | "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", 1649 | "dev": true, 1650 | "requires": { 1651 | "spdx-exceptions": "^2.1.0", 1652 | "spdx-license-ids": "^3.0.0" 1653 | } 1654 | }, 1655 | "spdx-license-ids": { 1656 | "version": "3.0.7", 1657 | "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.7.tgz", 1658 | "integrity": "sha512-U+MTEOO0AiDzxwFvoa4JVnMV6mZlJKk2sBLt90s7G0Gd0Mlknc7kxEn3nuDPNZRta7O2uy8oLcZLVT+4sqNZHQ==", 1659 | "dev": true 1660 | }, 1661 | "sprintf-js": { 1662 | "version": "1.0.3", 1663 | "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", 1664 | "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", 1665 | "dev": true 1666 | }, 1667 | "string-width": { 1668 | "version": "3.1.0", 1669 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", 1670 | "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", 1671 | "dev": true, 1672 | "requires": { 1673 | "emoji-regex": "^7.0.1", 1674 | "is-fullwidth-code-point": "^2.0.0", 1675 | "strip-ansi": "^5.1.0" 1676 | }, 1677 | "dependencies": { 1678 | "ansi-regex": { 1679 | "version": "4.1.0", 1680 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", 1681 | "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", 1682 | "dev": true 1683 | }, 1684 | "strip-ansi": { 1685 | "version": "5.2.0", 1686 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", 1687 | "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", 1688 | "dev": true, 1689 | "requires": { 1690 | "ansi-regex": "^4.1.0" 1691 | } 1692 | } 1693 | } 1694 | }, 1695 | "string.prototype.trimend": { 1696 | "version": "1.0.3", 1697 | "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.3.tgz", 1698 | "integrity": "sha512-ayH0pB+uf0U28CtjlLvL7NaohvR1amUvVZk+y3DYb0Ey2PUV5zPkkKy9+U1ndVEIXO8hNg18eIv9Jntbii+dKw==", 1699 | "dev": true, 1700 | "requires": { 1701 | "call-bind": "^1.0.0", 1702 | "define-properties": "^1.1.3" 1703 | } 1704 | }, 1705 | "string.prototype.trimstart": { 1706 | "version": "1.0.3", 1707 | "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.3.tgz", 1708 | "integrity": "sha512-oBIBUy5lea5tt0ovtOFiEQaBkoBBkyJhZXzJYrSmDo5IUUqbOPvVezuRs/agBIdZ2p2Eo1FD6bD9USyBLfl3xg==", 1709 | "dev": true, 1710 | "requires": { 1711 | "call-bind": "^1.0.0", 1712 | "define-properties": "^1.1.3" 1713 | } 1714 | }, 1715 | "string_decoder": { 1716 | "version": "1.3.0", 1717 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", 1718 | "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", 1719 | "requires": { 1720 | "safe-buffer": "~5.2.0" 1721 | } 1722 | }, 1723 | "strip-ansi": { 1724 | "version": "6.0.0", 1725 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", 1726 | "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", 1727 | "dev": true, 1728 | "requires": { 1729 | "ansi-regex": "^5.0.0" 1730 | } 1731 | }, 1732 | "strip-bom": { 1733 | "version": "3.0.0", 1734 | "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", 1735 | "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", 1736 | "dev": true 1737 | }, 1738 | "strip-json-comments": { 1739 | "version": "3.1.1", 1740 | "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", 1741 | "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", 1742 | "dev": true 1743 | }, 1744 | "supports-color": { 1745 | "version": "5.5.0", 1746 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", 1747 | "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", 1748 | "dev": true, 1749 | "requires": { 1750 | "has-flag": "^3.0.0" 1751 | } 1752 | }, 1753 | "table": { 1754 | "version": "5.4.6", 1755 | "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz", 1756 | "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==", 1757 | "dev": true, 1758 | "requires": { 1759 | "ajv": "^6.10.2", 1760 | "lodash": "^4.17.14", 1761 | "slice-ansi": "^2.1.0", 1762 | "string-width": "^3.0.0" 1763 | } 1764 | }, 1765 | "text-table": { 1766 | "version": "0.2.0", 1767 | "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", 1768 | "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", 1769 | "dev": true 1770 | }, 1771 | "tsconfig-paths": { 1772 | "version": "3.9.0", 1773 | "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.9.0.tgz", 1774 | "integrity": "sha512-dRcuzokWhajtZWkQsDVKbWyY+jgcLC5sqJhg2PSgf4ZkH2aHPvaOY8YWGhmjb68b5qqTfasSsDO9k7RUiEmZAw==", 1775 | "dev": true, 1776 | "requires": { 1777 | "@types/json5": "^0.0.29", 1778 | "json5": "^1.0.1", 1779 | "minimist": "^1.2.0", 1780 | "strip-bom": "^3.0.0" 1781 | } 1782 | }, 1783 | "tslib": { 1784 | "version": "1.13.0", 1785 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz", 1786 | "integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==" 1787 | }, 1788 | "tweetnacl": { 1789 | "version": "1.0.3", 1790 | "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", 1791 | "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==", 1792 | "optional": true 1793 | }, 1794 | "type-check": { 1795 | "version": "0.4.0", 1796 | "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", 1797 | "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", 1798 | "dev": true, 1799 | "requires": { 1800 | "prelude-ls": "^1.2.1" 1801 | } 1802 | }, 1803 | "type-fest": { 1804 | "version": "0.8.1", 1805 | "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", 1806 | "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", 1807 | "dev": true 1808 | }, 1809 | "uri-js": { 1810 | "version": "4.4.0", 1811 | "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.0.tgz", 1812 | "integrity": "sha512-B0yRTzYdUCCn9n+F4+Gh4yIDtMQcaJsmYBDsTSG8g/OejKBodLQ2IHfN3bM7jUsRXndopT7OIXWdYqc1fjmV6g==", 1813 | "dev": true, 1814 | "requires": { 1815 | "punycode": "^2.1.0" 1816 | } 1817 | }, 1818 | "util-deprecate": { 1819 | "version": "1.0.2", 1820 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 1821 | "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" 1822 | }, 1823 | "v8-compile-cache": { 1824 | "version": "2.2.0", 1825 | "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.2.0.tgz", 1826 | "integrity": "sha512-gTpR5XQNKFwOd4clxfnhaqvfqMpqEwr4tOtCyz4MtYZX2JYhfr1JvBFKdS+7K/9rfpZR3VLX+YWBbKoxCgS43Q==", 1827 | "dev": true 1828 | }, 1829 | "validate-npm-package-license": { 1830 | "version": "3.0.4", 1831 | "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", 1832 | "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", 1833 | "dev": true, 1834 | "requires": { 1835 | "spdx-correct": "^3.0.0", 1836 | "spdx-expression-parse": "^3.0.0" 1837 | } 1838 | }, 1839 | "which": { 1840 | "version": "2.0.2", 1841 | "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", 1842 | "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", 1843 | "dev": true, 1844 | "requires": { 1845 | "isexe": "^2.0.0" 1846 | } 1847 | }, 1848 | "word-wrap": { 1849 | "version": "1.2.3", 1850 | "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", 1851 | "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", 1852 | "dev": true 1853 | }, 1854 | "wrappy": { 1855 | "version": "1.0.2", 1856 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 1857 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", 1858 | "dev": true 1859 | }, 1860 | "ws": { 1861 | "version": "7.3.1", 1862 | "resolved": "https://registry.npmjs.org/ws/-/ws-7.3.1.tgz", 1863 | "integrity": "sha512-D3RuNkynyHmEJIpD2qrgVkc9DQ23OrN/moAwZX4L8DfvszsJxpjQuUq3LMx6HoYji9fbIOBY18XWBsAux1ZZUA==" 1864 | }, 1865 | "yallist": { 1866 | "version": "4.0.0", 1867 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", 1868 | "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", 1869 | "dev": true 1870 | } 1871 | } 1872 | } 1873 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "polaris-bot", 3 | "version": "3.9.0", 4 | "main": "index.js", 5 | "description": "Polaris is a Roblox verification bot designed to provide additional features with greater ease.", 6 | "keywords": [ 7 | "discord", 8 | "bot", 9 | "roblox", 10 | "polaris" 11 | ], 12 | "author": "Neztore", 13 | "dependencies": { 14 | "@sentry/integrations": "^5.19.2", 15 | "@sentry/node": "^5.19.2", 16 | "cheerio": "^1.0.0-rc.3", 17 | "eris": "^0.14.0", 18 | "node-fetch": "^2.6.0", 19 | "node-ipc": "^9.1.1", 20 | "polaris-quote-engine": "^1.0.2", 21 | "rethinkdbdash": "^2.3.31" 22 | }, 23 | "devDependencies": { 24 | "eslint": "^7.15.0", 25 | "eslint-config-airbnb": "^18.2.1", 26 | "eslint-plugin-import": "^2.22.1" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /polaris/client.js: -------------------------------------------------------------------------------- 1 | // Require dependencies 2 | const Eris = require("eris"); 3 | 4 | const { 5 | CommandManager, RobloxManager, Database, ErisExtensions, IPC 6 | } = require("./util"); 7 | 8 | ErisExtensions(Eris); // Adds Extensions to Eris 9 | const { 10 | withScope, captureException, Severity, captureEvent, captureMessage 11 | } = require("@sentry/node"); 12 | 13 | const Collection = require("./util/Collection.js"); 14 | 15 | const util = require("util"); 16 | 17 | const { getLink } = require("./util/linkManager"); 18 | 19 | class Polaris extends Eris.Client { 20 | constructor (options) { 21 | super(options.token, options.erisSettings); 22 | this.eris = Eris; 23 | 24 | // Queue Stuff (Really shouldn't be here) 25 | this.linkQueue = new Collection(); 26 | this.cooldown = new Set(); 27 | this.autoUpdateCooldown = new Map(); 28 | this.start(); 29 | } 30 | 31 | load () { 32 | this.CommandManager = new CommandManager(this); 33 | this.db = new Database(this); // Database Manager 34 | this.IPC = new IPC(this, { allowLog: false }); // IPC Manager 35 | this.roblox = new RobloxManager(this); // Roblox Manager 36 | this.ownerId = this.options.ownerId || "183601072344924160"; // Bot Owner Id 37 | } 38 | 39 | loadEvents () { 40 | this.on("ready", () => { 41 | console.log(`Bot now running on ${this.guilds.size} servers`); 42 | this.editStatus("online", { 43 | name: `${this.guilds.size} servers | .help`, 44 | type: 3 45 | }); 46 | }); 47 | this.on("error", e => { 48 | this.logError(e); 49 | }); 50 | if (process.env.NODE_ENV !== "production") { 51 | this.on("debug", m => { 52 | // Removes token from debug output 53 | const { token } = this; 54 | function checkObj (obj, depth = 0) { 55 | if (depth === 4) { 56 | return false; 57 | } 58 | for (const key of Object.keys(obj)) { 59 | if (key === "token" || obj[key] === token) { 60 | obj[key] = "Token removed"; 61 | } else if (typeof obj[key] === "object") { 62 | return checkObj(obj[key], depth + 1); 63 | } else if (typeof obj[key] === "string") { 64 | obj[key] = obj[key].replace(new RegExp(token), "TokenRemoved"); 65 | } 66 | } 67 | } 68 | if (typeof m === "string") { 69 | m = m.replace(new RegExp(token, "g"), "TokenRemoved"); 70 | } else if (typeof m === "object") { 71 | checkObj(m); 72 | } 73 | 74 | console.log(`DEBUG: `, util.inspect(m, { depth: 4 })); 75 | }); 76 | } 77 | 78 | this.on("disconnect", () => { 79 | captureMessage("Client Disconnect"); 80 | console.error(`Client disconnect!`); 81 | }); 82 | 83 | this.on("guildCreate", async guild => { 84 | console.log(`New guild joined: ${guild.name} (id: ${guild.id}). This guild has ${guild.memberCount} members!`); 85 | this.editStatus("online", { 86 | name: `${this.guilds.size} servers | .help`, 87 | type: 3 88 | }); 89 | }); 90 | 91 | this.on("guildDelete", async guild => { 92 | console.log(`Guild ${guild.name} has removed Polaris.`); 93 | this.editStatus("online", { 94 | name: `${this.guilds.size} servers | .help`, 95 | type: 3 96 | }); 97 | }); 98 | 99 | this.on("guildMemberAdd", async (guild, member) => { 100 | if (member.bot) return; 101 | const settings = await this.db.getSettings(guild.id); 102 | 103 | if (settings.autoVerify) { 104 | const rbxId = await getLink(member.id); 105 | if (rbxId) { 106 | const res = await this.CommandManager.commands.getrole.giveRoles(settings, member, rbxId); 107 | if (res) { 108 | if (res.error) return; 109 | res.title = `Welcome to ${guild.name}`; 110 | const dm = await member.user.getDMChannel(); 111 | dm.sendSuccess(member.user, res); 112 | } 113 | } else { 114 | await this.CommandManager.commands.getrole.verifiedRoles(false, member); 115 | } 116 | } 117 | }); 118 | 119 | this.on("error", (error, shardId) => { 120 | withScope(scope => { 121 | scope.setLevel(Severity.Fatal); 122 | scope.setExtra("shard", shardId); 123 | captureException(error); 124 | }); 125 | }); 126 | 127 | this.on("messageCreate", async message => { 128 | // Process Message 129 | await this.CommandManager.processMessage(message); 130 | }); 131 | } 132 | 133 | logError (err, obj) { 134 | withScope(scope => { 135 | scope.setLevel(Severity.Fatal); 136 | if (obj) { 137 | for (const key of Object.keys(obj)) { 138 | scope.setExtra(key, obj[key]); 139 | } 140 | } 141 | captureException(err); 142 | }); 143 | } 144 | 145 | // this shouldnt really be here 146 | async autoRole (member) { 147 | if (member.bot) return; 148 | const cooldown = this.autoUpdateCooldown.get(member.user.id); 149 | if (cooldown) { 150 | // 30min 151 | if ((cooldown + 1800000 > Date.now())) { 152 | // It hasn't expired 153 | return; 154 | } 155 | this.autoUpdateCooldown.delete(member.user.id); 156 | // 157 | } 158 | this.autoUpdateCooldown.set(member.user.id, Date.now()); 159 | const settings = await this.db.getSettings(member.guild.id); 160 | if (settings.autoVerify) { 161 | const rbxId = await getLink(member.id); 162 | if (rbxId) { 163 | // eslint-disable-next-line no-unused-vars 164 | const res = await this.CommandManager.commands.getrole.giveRoles(settings, member, rbxId); 165 | /* 166 | Got a lot of complaints regarding this 167 | if (res) { 168 | 169 | if (res.error) return; 170 | res.title = '[AUTOROLES] Roles Updated'; 171 | const dm = await member.user.getDMChannel(); 172 | dm.sendSuccess(member.user, res); 173 | } */ 174 | } else { 175 | await this.CommandManager.commands.getrole.verifiedRoles(false, member); 176 | } 177 | } 178 | } 179 | 180 | start () { 181 | this.load(); 182 | this.loadEvents(); 183 | this.connect(); 184 | } 185 | } 186 | 187 | module.exports = Polaris; 188 | -------------------------------------------------------------------------------- /polaris/commands/Verification/done.js: -------------------------------------------------------------------------------- 1 | const { checkCode } = require("../../util/linkManager"); 2 | const { getLink } = require("../../util/linkManager"); 3 | const BaseCommand = require("../baseCommand"); 4 | 5 | class doneCommand extends BaseCommand { 6 | constructor (client) { 7 | super(client); 8 | this.description = "Completes an in-progress account link by checking your Roblox profile for the code."; 9 | this.aliases = ["ihavesuccessfullyputthecodeinmydesc"]; 10 | this.group = "Roblox account verification"; 11 | } 12 | 13 | async execute (msg, _a, prefix) { 14 | // Check with Aquarius 15 | const aquariusResponse = await checkCode(msg.author.id); 16 | 17 | // Search for code 18 | if (aquariusResponse && aquariusResponse.success) { 19 | let { robloxId } = aquariusResponse; 20 | const verified = await this.client.CommandManager.commands.getrole.verifiedRoles(true, msg.member); 21 | if (verified.error) { 22 | return msg.channel.sendError(msg.author, { 23 | title: "Permission error", 24 | description: verified.error 25 | }); 26 | } 27 | if (!robloxId) { 28 | console.error(`No Roblox id returned from aquarius!`); 29 | console.log(aquariusResponse); 30 | robloxId = await getLink(msg.author.id); 31 | 32 | if (!robloxId) { 33 | throw new Error("Failed to get your account link: Please try again."); 34 | } 35 | } 36 | 37 | const settings = await this.client.db.getSettings(msg.channel.guild.id); 38 | const res = await this.client.CommandManager.commands.getrole.giveRoles(settings, msg.member, robloxId); 39 | if (!res) { 40 | return msg.channel.sendSuccess(msg.author, { 41 | title: "Successfully verified", 42 | description: `There were no ranks to give you.\nRank data is cached for up to 10 minutes: Try using \`${prefix}getroles\` in a few minutes if you think you should have roles.` 43 | }); 44 | } if (res.error) { 45 | return msg.channel.sendError(msg.author, { 46 | title: "Verified with errors", 47 | description: "Successfully verified you. I tried to fetch your roles, but encountered an error.", 48 | fields: [{ 49 | name: "Error details", 50 | value: (res && res.error) || "No response from Fetch roles." 51 | }] 52 | }); 53 | } 54 | res.title = `Verified and fetched roles`; 55 | res.description = `You have successfully verified your account!\n${res.description}`; 56 | return msg.channel.sendSuccess(msg.author, res); 57 | } 58 | 59 | if (aquariusResponse.error) { 60 | const { error } = aquariusResponse; 61 | if (error.status === 400) { 62 | // Code not found 63 | const { 64 | robloxId, 65 | robloxUsername 66 | } = error; 67 | return msg.channel.sendError(msg.author, `I couldn't find the code in your profile. Please ensure that it is in your **description** / **blurb**.\nUsername: \`${robloxUsername}\` UserID: \`${robloxId}\`\nIf still having difficulties, have a look at the [website](https://verify.neztore/dashboard)`); 68 | } if (error.status === 404) { 69 | // No link 70 | return msg.channel.sendError(msg.author, `You are not currently linking your account. Please use the \`linkaccount\` command, or head to the [website](https://verify.neztore/dashboard) to get a verification code.`); 71 | } if (error.status === 500) { 72 | // Roblox error 73 | return msg.channel.sendError(msg.author, `Roblox returned a temporary error. Please try again later.\n${error.message}`); 74 | } 75 | throw new Error(error.message); 76 | } 77 | throw new Error("Failed to check with Aquarius."); 78 | } 79 | } 80 | 81 | module.exports = doneCommand; 82 | -------------------------------------------------------------------------------- /polaris/commands/Verification/getrole.js: -------------------------------------------------------------------------------- 1 | const { getLink } = require("../../util/linkManager"); 2 | const BaseCommand = require("../baseCommand"); 3 | 4 | class getRoleCommand extends BaseCommand { 5 | constructor (client) { 6 | super(client); 7 | this.description = "Uses a previously linked account and a server's settings to give you roles according to your group rank(s)."; 8 | this.aliases = ["getroles"]; 9 | this.group = "Roblox account verification"; 10 | } 11 | 12 | async execute (msg) { 13 | // Check for settings 14 | 15 | const settings = await this.client.db.getSettings(msg.member.guild.id); 16 | if (!settings || !settings.mainGroup || (settings.mainGroup.binds === 0 && !settings.mainGroup.ranksToRoles)) { 17 | return msg.channel.sendError(msg.author, { 18 | title: "No settings", 19 | description: "This server isn't set up yet. Please ask an admin to set up Polaris.\nTry adding the main group." 20 | }); 21 | } 22 | // Check for link 23 | const rbxId = await getLink(msg.author.id); 24 | if (!rbxId) { 25 | const res = await this.verifiedRoles(false, msg.member); 26 | if (res.error) { 27 | return msg.channel.sendError(msg.author, { 28 | title: "No permissions", 29 | description: res.error 30 | }); 31 | } 32 | const prefix = settings.prefix ? settings.prefix : "."; 33 | return msg.channel.sendError(msg.author, { 34 | title: "Please link your account", 35 | description: `You need to link your account to get your roles.\nGet started with \`${prefix}linkaccount\`.` 36 | }); 37 | } 38 | const reply = await this.giveRoles(settings, msg.member, rbxId); 39 | if (reply) { 40 | if (reply.error) return msg.channel.sendError(msg.author, reply.error); 41 | msg.channel.sendSuccess(msg.author, reply); 42 | } else { 43 | msg.channel.sendError(msg.author, "I could not find any roles to give or remove from you.\nRank data is cached for up to 10 minutes. If you were ranked recently, retry later."); 44 | } 45 | } 46 | 47 | // Return true for success, false for otherwise. 48 | async giveRoles (settings, member, robloxId) { 49 | if (!robloxId) { 50 | throw new Error("getrole.giveRoles was not passed a roblox id"); 51 | } 52 | const res = await this.verifiedRoles(true, member); 53 | if (res.error) { 54 | return { 55 | title: "No permissions", 56 | description: res.error 57 | }; 58 | } 59 | const rolesToGive = {}; 60 | const rolesToRemove = {}; 61 | 62 | let addedMsg = ""; 63 | let removedMsg = ""; 64 | let failedMsg = ""; 65 | 66 | // Var to store pos of binds that relate to deleted roles. It'll only delete one broken bind per run, but that's better than none. 67 | let bindToDelete; 68 | // binds 69 | for (const current of settings.binds) { 70 | if (member.guild.roles.get(current.role)) { 71 | const group = await this.client.roblox.getGroup(current.group); 72 | if (group.error) { 73 | return { 74 | error: { 75 | title: "HTTP Error", 76 | description: `A HTTP Error has occured. Is Roblox Down?\n\`${group.error.message}\`` 77 | } 78 | }; 79 | } 80 | 81 | const rank = await group.getRank(robloxId); 82 | if (current.exclusive) { 83 | if (rank === current.rank) { 84 | rolesToGive[current.role] = current.role; 85 | } else { 86 | rolesToRemove[current.role] = current.role; 87 | } 88 | } else if (rank >= parseInt(current.rank, 10)) { 89 | rolesToGive[current.role] = current.role; 90 | } else { 91 | rolesToRemove[current.role] = current.role; 92 | } 93 | } else { 94 | bindToDelete = current; 95 | } 96 | } 97 | 98 | if (bindToDelete) { 99 | settings.binds.splice(bindToDelete, 1); 100 | this.client.db.updateSetting(member.guild.id, { binds: settings.binds }); 101 | } 102 | 103 | // ranks to roles 104 | if (settings.mainGroup.id) { 105 | await this.processGroup(rolesToGive, rolesToRemove, member, settings.mainGroup, robloxId); 106 | } 107 | 108 | // SubGroups 109 | if (settings.subGroups && settings.subGroups.length > 0) { 110 | for (const sub of settings.subGroups) { 111 | await this.processGroup(rolesToGive, rolesToRemove, member, sub, robloxId); 112 | } 113 | } 114 | // ROLE GIVER 115 | for (const roleId of Object.keys(rolesToGive)) { 116 | if (rolesToRemove[roleId]) { 117 | delete rolesToRemove[roleId]; 118 | } 119 | 120 | if (!checkForPresence(member.roles, roleId)) { 121 | const role = member.guild.roles.get(roleId); 122 | try { 123 | if (role) { 124 | await member.guild.addMemberRole(member.id, roleId); 125 | addedMsg = `${addedMsg}${role.name}\n`; 126 | } 127 | } catch (err) { 128 | const roleName = role.name; 129 | if (roleName) { 130 | failedMsg = `${failedMsg}\n${roleName}`; 131 | } 132 | } 133 | } 134 | } 135 | 136 | // ROLE Remover 137 | for (const roleId of Object.keys(rolesToRemove)) { 138 | if (!rolesToGive[roleId]) { 139 | if (checkForPresence(member.roles, roleId)) { 140 | const role = member.guild.roles.get(roleId); 141 | try { 142 | if (role) { 143 | await member.guild.removeMemberRole(member.id, roleId); 144 | removedMsg = `${removedMsg}\n${role.name}`; 145 | } 146 | } catch (err) { 147 | const roleName = role.name; 148 | if (roleName) { 149 | failedMsg = `${failedMsg}\n${roleName}`; 150 | } 151 | } 152 | } 153 | } 154 | } 155 | const embed = { 156 | title: "Successfully changed your roles:", 157 | fields: [], 158 | description: "If a role was added, removed or failed to add it will be listed below." 159 | }; 160 | 161 | if (addedMsg !== "") { 162 | embed.fields.push({ 163 | name: "Added the following roles", 164 | value: addedMsg, 165 | inline: true 166 | }); 167 | } 168 | if (removedMsg !== "") { 169 | embed.fields.push({ 170 | name: "Removed the following roles", 171 | value: removedMsg, 172 | inline: true 173 | }); 174 | } 175 | if (failedMsg !== "") { 176 | embed.fields.push({ 177 | name: "I couldn't add the following roles", 178 | value: failedMsg, 179 | inline: true 180 | }); 181 | } 182 | let nickChanged = false; 183 | const newNick = await member.guild.updateNickname(settings, member, robloxId); 184 | if (newNick) { 185 | if (!newNick.error) { 186 | nickChanged = true; 187 | } 188 | } 189 | if (newNick && newNick.error) { 190 | embed.description = `${embed.description}\nNickname error: \`${newNick.error.message}\``; 191 | } 192 | if (embed.fields.length === 0) { 193 | // Return false. No roles added. 194 | if (nickChanged) { 195 | return { 196 | title: "Changed your nickname", 197 | description: `Changed nickname to:\n\`${newNick}\`` 198 | }; 199 | } return false; 200 | } 201 | if (nickChanged) embed.description = `${embed.description}\nChanged nickname to \`${newNick}\``; 202 | return embed; 203 | } 204 | 205 | async verifiedRoles (isVerified, member) { 206 | const verifiedName = "Verified"; 207 | const unverifiedName = "Unverified"; 208 | 209 | let verifiedRole = member.guild.roles.find(role => role.name === verifiedName); 210 | let unverifiedRole = member.guild.roles.find(role => role.name === unverifiedName); 211 | // Check for roles existing. If they do not exist, create them. 212 | if (!verifiedRole) { 213 | // Create verified role 214 | try { 215 | verifiedRole = await member.guild.createRole({ name: verifiedName }); 216 | } catch (error) { 217 | return { error: "I do not have permission to create roles. Please ask a server admin to grant me this permission." }; 218 | } 219 | } 220 | if (!unverifiedRole) { 221 | // Create unverified role 222 | try { 223 | unverifiedRole = await member.guild.createRole({ name: unverifiedName }); 224 | } catch (error) { 225 | return { error: "I do not have permission to create roles. Please ask a server admin to grant me this permission." }; 226 | } 227 | } 228 | // If user verified 229 | if (isVerified) { 230 | // Check for verified role. Add if don't have. 231 | if (!checkForPresence(member.roles, verifiedRole.id)) { 232 | // Add role 233 | try { 234 | await member.guild.addMemberRole(member.id, verifiedRole.id, "User is verified"); 235 | } catch (error) { 236 | return { error: "I do not have permission to add roles. Please ask a server admin to grant me this permission, or move my role." }; 237 | } 238 | } 239 | // Check for unverified role. Remove if have. 240 | if (checkForPresence(member.roles, unverifiedRole.id)) { 241 | // Add role 242 | try { 243 | await member.guild.removeMemberRole(member.id, unverifiedRole.id, "User is verified"); 244 | } catch (error) { 245 | return { error: "I do not have permission to remove roles. Please ask a server admin to grant me this permission, or move my role." }; 246 | } 247 | } 248 | } else { 249 | // If user NOT verified 250 | // Check for verified role. Remove if have. 251 | if (checkForPresence(member.roles, verifiedRole.id)) { 252 | // Add role 253 | try { 254 | await member.guild.removeMemberRole(member.id, verifiedRole.id, "User is not verified"); 255 | } catch (error) { 256 | return { error: "I do not have permission to add roles. Please ask a server admin to grant me this permission, or move my role." }; 257 | } 258 | } 259 | // Check for unverified role. add if don't have. 260 | if (!checkForPresence(member.roles, unverifiedRole.id)) { 261 | // Add role 262 | try { 263 | await member.guild.addMemberRole(member.id, unverifiedRole.id, "User is not verified"); 264 | } catch (error) { 265 | return { error: "I do not have permission to remove roles. Please ask a server admin to grant me this permission, or move my role." }; 266 | } 267 | } 268 | } 269 | return true; 270 | } 271 | 272 | // messy: lots of params. 273 | /** 274 | * 275 | * @param rolesToGive 276 | * @param rolesToRemove 277 | * @param member 278 | * @param groupSettings 279 | * @param robloxId 280 | * @return {Promise<{error: {title: string, description: string}}>} 281 | */ 282 | async processGroup (rolesToGive, rolesToRemove, member, groupSettings, robloxId) { 283 | const group = await this.client.roblox.getGroup(groupSettings.id); 284 | if (!group || group.error) { 285 | if (!group) { 286 | return { 287 | error: { 288 | title: "HTTP Error", 289 | description: "A HTTP Error has occurred. No group obtained." 290 | } 291 | }; 292 | } 293 | this.client.logError(group.error); 294 | return { 295 | error: { 296 | title: "HTTP Error", 297 | description: `A HTTP Error has occurred. Is Roblox Down?\n\`${group.error.message}\`` 298 | } 299 | }; 300 | } 301 | // Check ranks to roles 302 | if (groupSettings.ranksToRoles) { 303 | const groupRanks = group.roles; 304 | const userRank = await group.getRole(robloxId); 305 | if (!userRank) throw new Error("User rank is not defined?"); 306 | // Give user their role if it exists. 307 | let role; 308 | 309 | if (userRank.toLowerCase) { 310 | role = member.guild.roles.find(current => current.name.toLowerCase() === userRank.toLowerCase()); 311 | } else { 312 | this.client.logError("userRank not defined", { userRank }); 313 | } 314 | 315 | if (role) { 316 | rolesToGive[role.id] = role.id; 317 | // Take out of remove list if there. 318 | if (rolesToRemove[role.id]) { 319 | delete rolesToRemove[role.id]; 320 | } 321 | } 322 | 323 | for (const thisOne of groupRanks) { 324 | const check = member.guild.roles.find(current => current.name.toLowerCase() === thisOne.name.toLowerCase()); 325 | if (check) { 326 | if (!rolesToGive[check.id]) { 327 | rolesToRemove[check.id] = check.id; 328 | } 329 | } 330 | } 331 | } 332 | // Check binds 333 | if (groupSettings.binds && groupSettings.binds.length > 0) { 334 | for (const current of groupSettings.binds) { 335 | const rank = await group.getRank(robloxId); 336 | 337 | if (current.exclusive) { 338 | if (parseInt(rank, 10) === parseInt(current.rank, 10)) { 339 | rolesToGive[current.role] = current.role; 340 | } else { 341 | rolesToRemove[current.role] = current.role; 342 | } 343 | } else if (rank >= parseInt(current.rank, 10)) { 344 | rolesToGive[current.role] = current.role; 345 | } else { 346 | rolesToRemove[current.role] = current.role; 347 | } 348 | } 349 | } 350 | } 351 | } 352 | module.exports = getRoleCommand; 353 | 354 | function checkForPresence (array, value) { 355 | for (let i = 0; i < array.length; i++) { 356 | if (array[i] === value) { 357 | return true; 358 | } 359 | } 360 | return false; 361 | } 362 | -------------------------------------------------------------------------------- /polaris/commands/Verification/linkaccount.js: -------------------------------------------------------------------------------- 1 | const { aquariusKey } = require("../../../settings.json"); 2 | const { getLink, getCode } = require("../../util/linkManager"); 3 | const BaseCommand = require("../baseCommand"); 4 | 5 | class linkAccountCommand extends BaseCommand { 6 | constructor (client) { 7 | super(client); 8 | this.description = "Creates an account link to allow for role retrieval."; 9 | this.aliases = ["verify", "link", "relink"]; 10 | this.group = "Roblox account verification"; 11 | } 12 | 13 | async execute (msg, args, prefix) { 14 | // If no username, prompt for it. 15 | if (!aquariusKey) { 16 | return msg.channel.sendInfo(msg.author, { 17 | title: "Unofficial Polaris instance", 18 | description: "Verification is provided by the main Aquarius verification system. Please go [here](https://verify.nezto.re/discord) to link your account.\nYou can link by providing a code on your profile, or by joining a game.", 19 | url: "https://verify.nezto.re/discord" 20 | }); 21 | } 22 | let username = args[0]; 23 | 24 | if (!username) { 25 | // Prompt for username 26 | const rbxMsg = await msg.channel.prompt(msg.author, { 27 | title: "I need your Roblox name", 28 | description: `Hey there \`${msg.author.username}\`! I need your Roblox name to get started. What is your Roblox username?`, 29 | fields: [{ 30 | name: "Try the web version", 31 | value: "The web version is easier to use, and allows for game verification. [Try it out](https://verify.nezto.re/discord)!" 32 | }], 33 | url: "https://verify.nezto.re/discord" 34 | }); 35 | if (!rbxMsg) return; 36 | username = rbxMsg.content; 37 | } 38 | 39 | // Roblox NAME VALIDATION 40 | if (searchForChars(username, ["?", "<", ">", "~", "|", "%", "\""])) return msg.channel.sendError(msg.author, "Roblox usernames may only contain letters, numbers or the _ symbol."); 41 | // Get Roblox userID from username, return error if not existing. Check that error is non-existant user error. 42 | const newUser = await this.client.roblox.getUserFromName(username); 43 | if (!newUser) { 44 | return msg.channel.sendError(msg.author, { 45 | title: "HTTP Error", 46 | description: `I have encountered a HTTP error. Is Roblox Down?` 47 | }); 48 | } 49 | if (newUser.error || !newUser.id) { 50 | if (newUser.error.status === 404) { 51 | return msg.channel.sendError(msg.author, { 52 | title: "User not found", 53 | description: `I could not find user \`${username}\` on Roblox.` 54 | }); 55 | } 56 | msg.channel.sendError(msg.author, { 57 | title: "HTTP Error", 58 | description: `A HTTP Error has occured. Is Roblox Down?\n\`${newUser.error.message}\`` 59 | }); 60 | return this.client.logError(newUser.error); 61 | } 62 | // ALREADY EXIST CHECK 63 | const current = await getLink(msg.author.id); 64 | if (current) { 65 | const robloxUser = await this.client.roblox.getUser(current); 66 | if (!newUser.error) { 67 | const user = robloxUser.username; 68 | if (robloxUser.username === username) { 69 | return msg.channel.sendError(msg.author, { 70 | title: "You are already linked to that account", 71 | description: `You are already linked to the Roblox account \`${robloxUser.username}\` and id \`${robloxUser.id}\`!\nDo \`${prefix}getroles\` to get started.`, 72 | url: "https://verify.nezto.re/discord" 73 | }); 74 | } 75 | const opt = await msg.channel.restrictedPrompt(msg.author, { 76 | title: "Hold up! You have already linked your account.", 77 | description: `**PLEASE READ FULLY**\nAccount details: \`${user}\` and ID \`${current}\`.\n If this the Roblox account you want to use, just do \`${prefix}getroles\`! \nDo you want to contiue?` 78 | }, ["Yes", "No"]); 79 | if (!opt) return; 80 | if (opt.content.toLowerCase() !== "yes") return msg.channel.sendInfo(msg.author, "Cancelled account re-linking."); 81 | } 82 | } 83 | 84 | const code = await getCode(msg.author.id, newUser.id); 85 | if (!code) { 86 | throw new Error("Failed to obtain code"); 87 | } 88 | msg.channel.sendInfo(msg.author, { 89 | title: "Account link code - Please read FULLY", 90 | description: `You are linking account with account \`${username}\` with UserID \`${newUser.id}\`.\nPlease place the following code in your Roblox profile - Put it in your status, accessible through [my feed](https://www.roblox.com/feeds/).`, 91 | fields: [ 92 | { 93 | name: "Code", 94 | value: `\`${code}\`` 95 | }, 96 | { 97 | name: "After you are done", 98 | value: `Once you have put the code in your profile, run the \`${prefix}done\` command! :)` 99 | }, 100 | { 101 | name: "Timeout", 102 | value: `This request will time-out in **10 minutes.** Please run \`${prefix}done\` before then, or you'll need to restart!` 103 | }, 104 | { 105 | name: "Web version", 106 | value: `We **recommend** that you use the [web version](https://verify.nezto.re/discord), and this allows you to verify by joining a verification game. See it [here](https://verify.nezto.re/discord)` 107 | } 108 | ], 109 | url: "https://verify.nezto.re/discord" 110 | }); 111 | } 112 | } 113 | module.exports = linkAccountCommand; 114 | 115 | function searchForChars (string, chars) { 116 | const arr = string.split(""); 117 | for (const count in arr) { 118 | if (chars.includes(arr[count])) { 119 | return true; 120 | } 121 | } 122 | return false; 123 | } 124 | -------------------------------------------------------------------------------- /polaris/commands/Verification/update.js: -------------------------------------------------------------------------------- 1 | const { getLink } = require("../../util/linkManager"); 2 | const BaseCommand = require("../baseCommand"); 3 | 4 | class updateRoleCommand extends BaseCommand { 5 | constructor (client) { 6 | super(client); 7 | this.description = "Uses a previously linked account and a server's settings to a user their roles according to your group rank(s)."; 8 | this.aliases = ["updateRole", "updateRoles"]; 9 | this.group = "Roblox account verification"; 10 | this.permissions = ["manageRoles"]; 11 | this.guildOnly = true; 12 | } 13 | 14 | async execute (msg) { 15 | // Check for link 16 | if (!msg.mentions[0]) { 17 | return msg.channel.sendError(msg.author, "You must mention a user.\nSupport for updateroles without tagging will be added in future."); 18 | } 19 | const mentionedUser = msg.mentions[1] || msg.mentions[0]; 20 | const mentionedMember = msg.channel.guild.members.get(mentionedUser.id); 21 | if (mentionedUser.bot) return msg.channel.sendError(msg.author, "Do you really think a bot has linked their account?! **Please mention a normal user!**"); 22 | 23 | const rbxId = await getLink(mentionedUser.id); 24 | if (!rbxId) { 25 | const res = await this.client.CommandManager.commands.getrole.verifiedRoles(false, mentionedMember); 26 | if (res.error) { 27 | return msg.channel.sendError(msg.author, { 28 | title: "No permissions", 29 | description: res.error 30 | }); 31 | } 32 | return msg.channel.sendError(msg.author, "I couldn't find that user's info. Have they linked their account?"); 33 | } 34 | 35 | // Check for settings 36 | 37 | const settings = await this.client.db.getSettings(msg.member.guild.id); 38 | if (!settings || !settings.mainGroup || (settings.mainGroup.binds === 0 && !settings.mainGroup.ranksToRoles)) { 39 | msg.channel.sendError(msg.author, { 40 | title: "No settings", 41 | description: "This server isn't set up yet. Please ask an admin to set up Polaris." 42 | }); 43 | return; 44 | } 45 | 46 | const reply = await this.client.CommandManager.commands.getrole.giveRoles(settings, mentionedMember, rbxId); 47 | if (reply) { 48 | reply.title = "Changed their roles:"; 49 | 50 | msg.channel.sendSuccess(msg.author, reply); 51 | } else { 52 | msg.channel.sendError(msg.author, `I could not find any roles to give or remove from \`${mentionedUser.username}\`.`); 53 | } 54 | } 55 | } 56 | module.exports = updateRoleCommand; 57 | -------------------------------------------------------------------------------- /polaris/commands/admin/prefix.js: -------------------------------------------------------------------------------- 1 | const BaseCommand = require("../baseCommand"); 2 | 3 | class prefixCommand extends BaseCommand { 4 | constructor (client) { 5 | super(client); 6 | this.description = "Allows you to view or edit the prefix for this server."; 7 | this.group = "Admin"; 8 | this.guildOnly = true; 9 | } 10 | 11 | async execute (msg) { 12 | if (msg.member.permissions.has("administrator")) { 13 | // user is admin 14 | const settings = await this.client.db.getSettings(msg.channel.guild.id); 15 | await this.client.CommandManager.commands.settings.changePrefix(msg, settings, this.client); 16 | } else { 17 | // user is not admin! 18 | const settings = await this.client.db.getSettings(msg.channel.guild.id); 19 | const prefix = settings.prefix ? settings.prefix : "."; 20 | await msg.channel.sendInfo(msg.author, `The prefix is currently set to ${prefix}\nYou can also mention the bot instead of using a prefix.`); 21 | } 22 | } 23 | } 24 | module.exports = prefixCommand; 25 | -------------------------------------------------------------------------------- /polaris/commands/admin/settings.js: -------------------------------------------------------------------------------- 1 | const BaseCommand = require("../baseCommand"); 2 | 3 | const COOLDOWN = new Map(); 4 | 5 | class settingsCommand extends BaseCommand { 6 | constructor (client) { 7 | super(client); 8 | this.db = this.client.db; 9 | 10 | this.description = "Allows those with the `ADMINISTRATOR` permission to change server settings, such as main group and binds."; 11 | this.aliases = ["setting", "options", "configure", "config"]; 12 | this.group = "Admin"; 13 | this.permissions = ["administrator"]; 14 | this.guildOnly = true; 15 | 16 | this.changePrefix = changePrefix; 17 | } 18 | 19 | async execute (msg, args) { 20 | const { client } = this; 21 | // Cooldown mangagement 22 | if (COOLDOWN.has(msg.author.id)) { 23 | msg.channel.sendError(msg.author, "Please complete all other prompts!\nThink this is an error? Join our Discord.(Link in `.info`)"); 24 | return; 25 | } 26 | COOLDOWN.set(msg.author.id, true); 27 | 28 | const raven = this.client.Raven; 29 | 30 | const guildSettings = this.guildSettings = await this.client.db.getSettings(msg.channel.guild.id); 31 | 32 | if (!this.guildSettings) { 33 | await this.db.setupGuild(msg.channel.guild); 34 | this.guildSettings = await this.db.getSettings(msg.channel.guild.id); 35 | console.log(`No defaults? ${msg.channel.guild.id}`); 36 | } 37 | let current = menu; 38 | let done = false; 39 | const option = args[0]; 40 | 41 | let menuName = "Main"; 42 | 43 | if (option) { 44 | const optionLowerCase = option.toLowerCase(); 45 | // Checks if their initial .settings [VALUE] is part of main menu. If it is, skips to that menu. 46 | if (current.subs[optionLowerCase] && current.subs[optionLowerCase].subs) current = current.subs[optionLowerCase]; 47 | menuName = option.toLowerCase(); 48 | menuName = capitalizeFirstLetter(menuName); 49 | } 50 | // Add global catch for settings. 51 | try { 52 | do { 53 | const embed = this.makeMenu(menuName, current); 54 | 55 | const res = await this.sendPrompt(msg, embed, current.subs); 56 | 57 | // If cancelled (returned null) 58 | if (res === "time") { 59 | // Remove from QUEUE 60 | COOLDOWN.delete(msg.author.id); 61 | done = true; 62 | return msg.channel.sendInfo(msg.author, "Prompt timed out."); 63 | } 64 | if (!res) { 65 | msg.channel.sendInfo(msg.author, "Cancelled prompt."); 66 | done = true; 67 | COOLDOWN.delete(msg.author.id); 68 | return; 69 | } 70 | current = current.subs[res]; 71 | menuName = capitalizeFirstLetter(res); 72 | 73 | if (current.func) { 74 | try { 75 | await current.func(msg, guildSettings, client); 76 | } catch (err) { 77 | done = true; 78 | throw new Error(err); 79 | } 80 | done = true; 81 | } 82 | } while (!done); 83 | COOLDOWN.delete(msg.author.id); 84 | } catch (error) { 85 | await msg.channel.sendError(msg.author, `The following error occurred during execution:\n\`${error}\``); 86 | // Remove from QUEUE 87 | COOLDOWN.delete(msg.author.id); 88 | // ALERT SENTRY 89 | raven.captureException(error); 90 | } 91 | } 92 | 93 | makeMenu (menuName, menu) { 94 | // Mangages both Info and extra. Executes if they are functions. 95 | let info = this.isFunction(menu.info) ? menu.info(this.guildSettings) : menu.info;// Executes info if it is a function, else sets it as it is. 96 | if (menu.extra) { 97 | const extra = this.isFunction(menu.extra) ? menu.extra(this.guildSettings) : menu.extra; 98 | info = `${info}\n${extra}\n`; 99 | } else { 100 | // Add a new line to make options clearer if extra isn't there. 101 | info = `${info}\n`; 102 | } 103 | 104 | if (!menu.subs) throw new Error(`Menu has no sub-menus! Name: ${menuName}`); 105 | const subMenus = Object.values(menu.subs); 106 | const subNames = Object.keys(menu.subs); 107 | 108 | for (const i in subMenus) { 109 | info = `${info}\n**${capitalizeFirstLetter(subNames[i])}** - ${subMenus[i].info}`; 110 | } 111 | 112 | const embed = { 113 | title: `${menuName} menu`, 114 | description: info 115 | 116 | }; 117 | // if (fields.length>0)embed.fields = fields; 118 | return embed; 119 | } 120 | 121 | async sendPrompt (message, embed, options) { 122 | embed.footer = { 123 | text: `Say "cancel" to cancel`, 124 | icon_url: message.author.avatarURL 125 | }; 126 | 127 | // Move all object keys to lowercase, ensures that user input will always match. 128 | var key, keys = Object.keys(options) // eslint-disable-line 129 | let n = keys.length; 130 | const newobj = {}; 131 | while (n--) { 132 | key = keys[n]; 133 | newobj[key.toLowerCase()] = options[key]; 134 | } 135 | options = newobj; 136 | 137 | const fn = msg => (options[msg.content.toLowerCase()] && msg.author.id === message.author.id) || (msg.content.toLowerCase() === "cancel" && msg.author.id === message.author.id); 138 | const m = await message.channel.sendInfo(message.author, embed); 139 | if (!m) { 140 | // If message doesn't send for whatever reason 141 | COOLDOWN.delete(message.author.id); 142 | } 143 | 144 | let res = await message.channel.awaitMessages(fn, { 145 | maxMatches: 1, 146 | time: 30000 147 | }); 148 | 149 | res = res[0]; 150 | if (!res) return "time"; 151 | res = res.content; 152 | res = res.toLowerCase(); 153 | if (res === "cancel") return; 154 | 155 | return res; 156 | } 157 | 158 | isFunction (object) { 159 | return !!(object && object.constructor && object.call && object.apply); 160 | } 161 | } 162 | module.exports = settingsCommand; 163 | 164 | function capitalizeFirstLetter (string) { 165 | return string.charAt(0).toUpperCase() + string.slice(1); 166 | } 167 | 168 | /* 169 | SETTINGS MENU. 170 | First letter is auto-capitalised. 171 | 172 | * Each page MUST HAVE a info. 173 | * Each page may have "extra". This is displayed in addition to info. 174 | * Each page MUST HAVE EITHER: 175 | subs OR func 176 | 177 | subs - Object. Contains sub-menus. 178 | func - Function to execute. 179 | */ 180 | const menu = { 181 | info: "Settings home. Please ensure you read each message both continuing.\n**Say the name of the option that you want to choose.**\n**WE RECOMMEND YOU USE OUR WEB PANEL INSTEAD:** https://polaris-bot.xyz/panel", 182 | subs: { 183 | group: { 184 | info: "Allows you to set the group ID of the main group for this server and toggle `Ranks to roles`.", 185 | extra: groupExtra, 186 | subs: { 187 | groupid: { 188 | info: "Allows you to update/set the ID of the main group for this server.", 189 | func: setMainGroupId 190 | }, 191 | 192 | "ranks to roles": { 193 | info: "Allows you to enable/disable ranks to roles.", 194 | extra: "Ranks to roles is a system that allows users to get their group rank as a discord role if the role and rank names match.", 195 | subs: { 196 | enable: { 197 | info: "Enable `ranks to roles`, allows roles which match group ranks **exactly** to be obtained with `.getroles`.", 198 | func: enableRTR 199 | }, 200 | disable: { 201 | info: "Disable `ranks to roles`, Disables matching role retrieval.", 202 | func: disableRTR 203 | } 204 | 205 | } 206 | 207 | } 208 | 209 | } 210 | 211 | }, 212 | server: { 213 | info: "Allows you to edit server settings.", 214 | subs: { 215 | autoverify: { 216 | info: "Auto verification means users will be given their roles when they join, if they are verified.", 217 | extra: settings => (settings.autoVerify ? "Currently: `Enabled`." : "Currently: `Disabled`."), 218 | subs: { 219 | enable: { 220 | info: "Enable `Auto-verification`, causing new users to be automatically given their roles if verified.", 221 | func: enableAutoVerify 222 | }, 223 | disable: { 224 | info: "Disable `Auto-verification`, meaning new users must manually get their roles.", 225 | func: disableAutoVerify 226 | } 227 | } 228 | }, 229 | nicknames: { 230 | info: "Customise the nicknames of verified users according to their group rank, Roblox name etc.", 231 | extra: settings => ((settings.nicknameTemplate && settings.nicknameTemplate !== "") ? `Nickname template is currently set to \`${settings.nicknameTemplate}\`.` : `The nickname template is **not set**! Use one from the templates, or make a custom one.`), 232 | subs: { 233 | // MAKE SURE ADD LENGTH CHECK. NICKS ARE MAX 32. Roblox NAMES CAN BE UP TO 20! 234 | templates: { 235 | info: "Choose from one of our premade templates for the most common usages.", 236 | func: nicknameTemplates 237 | }, 238 | custom: { 239 | info: "Create a custom template for nicknames.", 240 | func: customNickname 241 | }, 242 | disable: { 243 | info: "Disable nickname management", 244 | func: disableNickname 245 | } 246 | } 247 | }, 248 | prefix: { 249 | info: "Allows you to change the prefix for this server. Can be 1 or 2 characters.", 250 | func: changePrefix 251 | } 252 | 253 | } 254 | } 255 | } 256 | 257 | }; 258 | 259 | // SETTINGS FUNCTIONS. 260 | 261 | // ALL SETTINGS FUNCTIONS APPEAR HERE. THE MENU ABOVE WILL LINK TO A FUNCTION. 262 | 263 | // Group: 264 | // EXTRA: 265 | function groupExtra (settings) { 266 | if (!settings.mainGroup || !settings.mainGroup.id || settings.mainGroup.id === 0) { 267 | return "There is currently no main group set! Use `groupid` to set the ID of your Roblox Group."; 268 | } if (!settings.mainGroup.ranksToRoles && settings.mainGroup.id) { 269 | return `Group ID is currently set to ${settings.mainGroup.id}. Ranks to roles is \`Disabled\`.`; 270 | } if (settings.mainGroup.ranksToRoles && settings.mainGroup.id) { 271 | return `Ranks to roles is \`enabled\` and the group ID is set to ${settings.mainGroup.id}`; 272 | } 273 | return "Use `groupid` to set the ID of your Roblox Group!"; 274 | } 275 | 276 | async function setMainGroupId (msg, settings, client) { 277 | let newGroupId; 278 | if (settings.mainGroup.id) { 279 | newGroupId = await msg.channel.prompt(msg.author, { 280 | title: "New group ID", 281 | description: `What is the new ID for your group?\nCurrently set to \`${settings.mainGroup.id}\`.` 282 | }); 283 | } else { 284 | newGroupId = await msg.channel.prompt(msg.author, { 285 | title: "New group ID", 286 | description: "What is the ID of your group?" 287 | }); 288 | } 289 | 290 | // If cancel or timeout stop 291 | if (!newGroupId) return; 292 | 293 | newGroupId = newGroupId.content; 294 | 295 | // Group validator 296 | const group = await client.roblox.getGroup(newGroupId); 297 | if (group.error) { 298 | if (group.error.status === 404) { 299 | // Group not found. All other HTTP errors logged by Polaris-RBX 300 | return msg.channel.sendError(msg.author, "I could not find that group on Roblox.\nPlease ensure the ID is correct."); 301 | } 302 | return { 303 | error: { 304 | title: "HTTP Error", 305 | description: `A HTTP Error has occured. Is Roblox Down?\n\`${group.error.message}\`` 306 | } 307 | }; 308 | } 309 | 310 | const newSetting = settings.mainGroup || {}; 311 | newSetting.id = newGroupId; 312 | 313 | const success = await client.db.updateSetting(msg.channel.guild.id, { mainGroup: newSetting }); 314 | if (success) { 315 | return msg.channel.sendSuccess(msg.author, `Successfully set the main group id to \`${newGroupId}\`!`); 316 | } 317 | return msg.channel.sendError(msg.author, `A database error has occurred. Please try again.`); 318 | } 319 | 320 | // Enable ranksToRoles 321 | async function enableRTR (msg, settings, client) { 322 | if (!settings.mainGroup || !settings.mainGroup.id || settings.mainGroup.id === 0) { 323 | return msg.channel.sendError(msg.author, "You must set a Group ID first!"); 324 | } 325 | const newMain = settings.mainGroup; 326 | newMain.ranksToRoles = true; 327 | 328 | const success = await client.db.updateSetting(msg.channel.guild.id, { mainGroup: newMain }); 329 | if (success) { 330 | msg.channel.sendSuccess(msg.author, `Successfully enabled \`ranks to roles\`!`); 331 | } else { 332 | return msg.channel.sendError(msg.author, `A database error has occurred. Please try again.`); 333 | } 334 | } 335 | 336 | async function disableRTR (msg, settings, client) { 337 | if (!settings.mainGroup || !settings.mainGroup.id || settings.mainGroup.id === 0) { 338 | return msg.channel.sendError(msg.author, "You must set a Group ID first!"); 339 | } 340 | const newMain = settings.mainGroup; 341 | newMain.ranksToRoles = false; 342 | const success = await client.db.updateSetting(msg.channel.guild.id, { mainGroup: newMain }); 343 | if (success) { 344 | msg.channel.sendSuccess(msg.author, `Successfully disabled \`ranks to roles\`!`); 345 | } else { 346 | return msg.channel.sendError(msg.author, `A database error has occurred. Please try again.`); 347 | } 348 | } 349 | 350 | // AUTO-VERIFY 351 | async function enableAutoVerify (msg, settings, client) { 352 | if (!settings || (settings.binds.length === 0 && !settings.mainGroup.ranksToRoles)) { 353 | return msg.channel.sendError(msg.author, "You must set up binds or ranks to roles before enabling auto verification!"); 354 | } 355 | 356 | const success = await client.db.updateSetting(msg.channel.guild.id, { autoVerify: true }); 357 | if (success) { 358 | msg.channel.sendSuccess(msg.author, `Successfully enabled \`Auto-verification\`!`); 359 | } else { 360 | return msg.channel.sendError(msg.author, `A database error has occurred. Please try again.`); 361 | } 362 | } 363 | 364 | async function disableAutoVerify (msg, settings, client) { 365 | if (!settings.mainGroup || !settings.mainGroup.id || settings.mainGroup.id === 0) { 366 | return msg.channel.sendError(msg.author, "You must set a Group ID first before enabling auto-verification!"); 367 | } 368 | 369 | const success = await client.db.updateSetting(msg.channel.guild.id, { autoVerify: false }); 370 | if (success) { 371 | msg.channel.sendSuccess(msg.author, `Successfully disabled \`Auto-verification\`!`); 372 | } else { 373 | return msg.channel.sendError(msg.author, `A database error has occurred. Please try again.`); 374 | } 375 | } 376 | 377 | // BINDS FUNCTIONS 378 | async function viewBinds (msg, settings, client) { 379 | const embed = { 380 | fields: [], 381 | description: "Displaying current binds. Any binds that relate to deleted roles will be automatically removed.", 382 | title: "Current group binds" 383 | }; 384 | 385 | const initialLength = settings.binds.length; 386 | 387 | if (!settings.binds || settings.binds.length <= 0) { 388 | return msg.channel.sendError(msg.author, { 389 | title: "No binds set", 390 | description: "You don't have any group binds set! Navigate to `binds` and say `add` to get started." 391 | }); 392 | } 393 | const { binds } = settings; 394 | for (let count = 0; count < binds.length; count++) { 395 | const current = binds[count]; 396 | 397 | const role = msg.channel.guild.roles.get(current.role); 398 | if (!role) { 399 | settings.binds.splice(count, 1); 400 | 401 | embed.fields.push({ 402 | name: `Bind ${count + 1}`, 403 | value: `Deleted bind - Role removed` 404 | }); 405 | } else { 406 | embed.fields.push({ 407 | name: `Bind ${count + 1}`, 408 | value: `**Group ID:** ${current.group}\n**Group rank ID:** ${current.rank}\n**Role name:** ${role.name}\n**Exclusive:** ${current.exclusive}`, 409 | inline: true 410 | }); 411 | } 412 | } 413 | // If one or more elements has been removed: 414 | if (settings.binds.length !== initialLength) { 415 | const success = await client.db.updateSetting(msg.channel.guild.id, { autoVerify: false }); 416 | if (!success) { 417 | return msg.channel.sendError(msg.author, `A database error has occurred. Please try again.`); 418 | } 419 | } 420 | 421 | await msg.channel.sendInfo(msg.author, embed); 422 | } 423 | 424 | async function addBind (msg, settings, client) { 425 | if (settings.binds.length >= 25) { 426 | msg.channel.sendError(msg.author, "Oops! You can only have up to 25 seperate binds. Please consider using `ranksToRoles` or `sub groups`."); 427 | } 428 | // Variables 429 | let groupId = await msg.channel.prompt(msg.author, { 430 | title: "Please provide Group ID", 431 | description: "What is the Roblox group ID?" 432 | }); 433 | if (!groupId) return; 434 | groupId = groupId.content; 435 | // Validate group ID 436 | const group = await client.roblox.getGroup(groupId); 437 | if (group.error) { 438 | if (group.error.status === 404) { 439 | // Group not found. All other HTTP errors logged by Polaris-RBX 440 | return msg.channel.sendError(msg.author, "I could not find that group on Roblox.\nPlease ensure the ID is correct."); 441 | } 442 | return { 443 | error: { 444 | title: "HTTP Error", 445 | description: `A HTTP Error has occured. Is Roblox Down?\n\`${group.error.message}\`` 446 | } 447 | }; 448 | } 449 | 450 | let rankId = await msg.channel.prompt(msg.author, { 451 | title: "Please provide Rank ID", 452 | description: "What is the Roblox Rank ID for this rank? It should be a number from 0 to 255." 453 | }); 454 | if (!rankId) return; 455 | rankId = rankId.content; 456 | 457 | // RANK ID VALIDATION 458 | try { 459 | rankId = parseInt(rankId); 460 | } catch (e) { 461 | return msg.channel.sendError(msg.author, `Rank ID must be a number between 0 and 255.`); 462 | } 463 | if (rankId > 255 || rankId < 0) { 464 | return msg.channel.sendError(msg.author, `Rank ID must be a number between 0 and 255.`); 465 | } 466 | 467 | let roleName = await msg.channel.prompt(msg.author, { 468 | title: "Please provide role name or mention it", 469 | description: "What is the name of the Discord role? (Case sensitive), you can also mention the role." 470 | }); 471 | if (!roleName) return; 472 | let roleId; 473 | 474 | if (roleName.roleMentions.length !== 0) { 475 | roleId = roleName.roleMentions[0]; 476 | } 477 | roleName = roleName.content; 478 | 479 | // Validate Discord role - in guild? 480 | if (!roleId) { 481 | if (!msg.channel.guild.roles.find(a => a.name === roleName)) { 482 | return msg.channel.sendError(msg.author, "I couldn't find a role in this discord with that name! **Please note that it is case sensitive.**"); 483 | } 484 | const role = msg.channel.guild.roles.find(a => a.name === roleName); 485 | roleId = role.id; 486 | } 487 | 488 | let exclusive = await msg.channel.restrictedPrompt(msg.author, { 489 | title: "Is this bind Exclusive?", 490 | description: "Is this bind exclusive? If **Yes** then only this rank will get it.\nIf **no** all ranks above this rank will also recieve it." 491 | }, ["Yes", "No"]); 492 | if (!exclusive) return; 493 | exclusive = exclusive.content; 494 | 495 | if (exclusive.toLowerCase() === "yes") { 496 | exclusive = true; 497 | } else { 498 | exclusive = false; 499 | } 500 | 501 | const binds = settings.binds || []; 502 | // Check that there is no identical bind present 503 | for (let i = 0; i < binds.length; i++) { 504 | if (binds[i].rank === rankId && binds[i].role === roleId && binds[i].group === groupId && binds[i].exclusive === exclusive) { 505 | return msg.channel.sendError(msg.author, { 506 | title: "Action cancelled", 507 | description: "You already have a bind with those settings! If you would like to remove or edit it, remove the bind and add a new one." 508 | }); 509 | } 510 | } 511 | 512 | binds.push({ 513 | group: groupId, 514 | rank: rankId, 515 | role: roleId, 516 | exclusive 517 | 518 | }); 519 | const success = await client.db.updateSetting(msg.channel.guild.id, { binds }); 520 | 521 | if (!success) { 522 | return msg.channel.sendError(msg.author, `A database error has occurred. Please try again.`); 523 | } if (exclusive) { 524 | msg.channel.sendSuccess(msg.author, `Successfully added an exclusive new bind with Group ID \`${groupId}\`, Rank ID \`${rankId}\` and Rolename ${roleName}.`); 525 | } else { 526 | msg.channel.sendSuccess(msg.author, `Successfully added a non-exclusive bind with Group ID \`${groupId}\`, Rank ID \`${rankId}\` and Role name ${roleName}.`); 527 | } 528 | } 529 | async function rmvBind (msg, settings, client) { 530 | const embed = { 531 | fields: [], 532 | description: "Displaying current binds. Please say the `Bind ID` of the bind you would like to remove.", 533 | title: "Current group binds", 534 | footer: { 535 | icon_url: msg.author.avatarURL, 536 | text: `${msg.author.username} - Say an ID or "cancel"` 537 | } 538 | }; 539 | 540 | if (!settings.binds || settings.binds.length <= 0) { 541 | return msg.channel.sendError(msg.author, { 542 | title: "No binds set", 543 | description: "You don't have any group binds set! Navigate to `binds` and say `add` to get started." 544 | }); 545 | } 546 | 547 | for (let count = 0; count < settings.binds.length; count++) { 548 | const current = settings.binds[count]; 549 | 550 | const role = msg.channel.guild.roles.get(current.role); 551 | if (!role) { 552 | settings.binds.splice(count, 1); 553 | count -= 1; 554 | } else { 555 | embed.fields.push({ 556 | name: `Bind ${count + 1}`, 557 | value: `**Group ID:** ${current.group}\n**Group rank ID: ${current.rank}**\n**Role name:** ${role.name}\n**Exclusive:** ${current.exclusive}` 558 | }); 559 | } 560 | } 561 | 562 | const fn = m => (parseInt(m.content, 10) > 0 && parseInt(m.content, 10) <= embed.fields.length && m.author.id === msg.author.id) || (m.content.toLowerCase() === "cancel" && m.author.id === msg.author.id); 563 | const m = await msg.channel.sendInfo(msg.author, embed); 564 | if (!m) return; 565 | // CUSTOM IMPLEMENTATION/USE OF MESSAGE COLLECTOR TO ALLOW FOR ERROR MESSAGES 566 | const collector = new client.MessageCollector(msg.channel, fn, { 567 | maxMatches: 1, 568 | time: 30000 569 | }); 570 | 571 | collector.on("nonMatch", m => { 572 | if (m.author.id === msg.author.id) { 573 | msg.channel.sendError(msg.author, { 574 | title: "Please choose a valid option.", 575 | description: `Your choice of \`${m.content}\` was not valid. It must be a number, above 0 and below ${settings.binds.length}. Please try again! Content: ${m.content}` 576 | }); 577 | } 578 | }); 579 | 580 | collector.on("end", async (res, reason) => { 581 | if (reason === "time") { 582 | msg.channel.sendInfo(msg.author, { 583 | title: "Timed out", 584 | description: "Prompt timed out. You needed to say a number within 30 seconds!" 585 | }); 586 | return; 587 | } 588 | res = res[0]; 589 | 590 | if (res.content.toLowerCase() === "cancel") { 591 | res.channel.sendInfo(msg.author, { 592 | title: "Cancelled", 593 | description: "Prompt cancelled." 594 | }); 595 | return; 596 | } 597 | let loc = parseInt(res.content); 598 | // Account for the one added earlier to make numbers easier for user 599 | loc -= 1; 600 | 601 | if (!settings.binds[loc]) { 602 | msg.channel.sendError(msg.author, { 603 | title: "Oops! That wasn't meant to happen.", 604 | description: "It looks like your selection wasn't valid but was accepted. Please try again." 605 | }); 606 | throw new Error(`Invalid selection. Selection: ${loc} Array: ${JSON.stringify(settings.binds)}`); 607 | } 608 | settings.binds.splice(loc); 609 | 610 | const success = await client.db.updateSetting(msg.channel.guild.id, { binds: settings.binds }); 611 | if (success) { 612 | return msg.channel.sendSuccess(msg.author, { 613 | title: "Successfully updated", 614 | description: `Successfully removed the bind!` 615 | }); 616 | } 617 | return msg.channel.sendError(msg.author, { 618 | title: "Oops! Something went wrong.", 619 | description: `Something went wrong with the database. Please try again.` 620 | }); 621 | }); 622 | } 623 | const templates = { 624 | Standard: "{robloxName} | {rankName}", 625 | Reversed: "{rankName} | {robloxName}", 626 | "Roblox name": "{robloxName}" 627 | }; 628 | async function nicknameTemplates (msg, settings, client) { 629 | const embed = { 630 | title: "Listing templates", 631 | description: "Please select a template from the list shown. If you want to use a different one, say **cancel** and use the `custom` option. Templates:\n" 632 | }; 633 | const names = Object.keys(templates); 634 | const values = Object.values(templates); 635 | const lowercaseNames = []; 636 | for (const current in names) { 637 | embed.description = `${embed.description}\n**${names[current]}** - \`${values[current]}\``; 638 | lowercaseNames.push(names[current].toLowerCase()); 639 | } 640 | const res = await msg.channel.restrictedPrompt(msg.author, embed, lowercaseNames); 641 | if (!res) return; 642 | let newTemplate; 643 | if (res.content.toLowerCase() === "standard") { 644 | newTemplate = "{robloxName} | {rankName}"; 645 | } else if (res.content.toLowerCase() === "roblox name") { 646 | newTemplate = "{robloxName}"; 647 | } else if (res.content.toLowerCase() === "reversed") { 648 | newTemplate = "{rankName} | {robloxName}"; 649 | } else { 650 | await msg.channel.sendError(msg.author, "Invalid option! This should'nt have happened. Please join our discord."); 651 | throw new Error(`Invalid option accepted! ${res.content}`); 652 | } 653 | const success = await client.db.updateSetting(msg.channel.guild.id, { nicknameTemplate: newTemplate }); 654 | if (success) { 655 | return msg.channel.sendSuccess(msg.author, { 656 | title: "Successfully updated", 657 | description: `Successfully set the nickname template for this server to \`${newTemplate}\`` 658 | }); 659 | } 660 | return msg.channel.sendError(msg.author, { 661 | title: "Oops! Something went wrong.", 662 | description: `Something went wrong with the database. Please try again.` 663 | }); 664 | } 665 | 666 | async function customNickname (msg, settings, client) { 667 | // Get new template 668 | const embed = { 669 | title: "What would you like your new nickname template to be?", 670 | description: "Please say your new nickname template. The bot will reply with an example of what it will look like when in use, once set. Please ensure all variables are enclosed with `{}`.\n", 671 | fields: [] 672 | }; 673 | const m = "The following variables can be used within nicknames:\n`{rankName}`\n`{rankId}`\n`{robloxName}`\n`{discordName}`"; 674 | embed.fields.push({ 675 | name: "Availible variables", 676 | value: m 677 | }); 678 | embed.fields.push({ 679 | name: "Notice", 680 | value: "Please note that nicknames that will be longer than 32 characters (discord max) will be shortened." 681 | }); 682 | const res = await msg.channel.prompt(msg.author, embed); 683 | if (!res) return; 684 | const newTemplate = res.content; 685 | // Do DB 686 | const success = await client.db.updateSetting(msg.channel.guild.id, { nicknameTemplate: newTemplate }); 687 | if (success) { 688 | return msg.channel.sendSuccess(msg.author, { 689 | title: "Successfully updated the template", 690 | description: `The new nickname template is \`${newTemplate}!\`` 691 | }); 692 | } 693 | return msg.channel.sendError(msg.author, { 694 | title: "Oops! Something went wrong.", 695 | description: `Something went wrong with the database. Please try again.` 696 | }); 697 | } 698 | 699 | async function disableNickname (msg, settings, client) { 700 | const success = await client.db.updateSetting(msg.channel.guild.id, { nicknameTemplate: "" }); 701 | if (success) { 702 | return msg.channel.sendSuccess(msg.author, { 703 | title: "Successfully disabled", 704 | description: `Successfully disabled nickname management!` 705 | }); 706 | } 707 | return msg.channel.sendError(msg.author, { 708 | title: "Oops! Something went wrong.", 709 | description: `Something went wrong with the database. Please try again.` 710 | }); 711 | } 712 | async function changePrefix (msg, settings, client) { 713 | const prefix = settings.prefix ? settings.prefix : "."; 714 | const res = await msg.channel.prompt(msg.author, { 715 | title: "Changing prefix", 716 | description: `The prefix for this server is currently set to \`${prefix}\`!`, 717 | fields: [{ 718 | name: "What would you like to set the prefix to?", 719 | value: "It can be anything! Up to 2 characters.\nPlease do not use a character such as \"a\" which will be used in normal conversation." 720 | }] 721 | }, true); 722 | if (!res) return; 723 | if (res.content.length > 2 || res.content.length <= 0) return msg.channel.sendError(msg.author, "Prefix must be 1 or 2 characters."); 724 | 725 | const success = await client.db.updateSetting(msg.channel.guild.id, { prefix: res.content }); 726 | if (success) { 727 | return msg.channel.sendSuccess(msg.author, { 728 | title: "Successfully updated the prefix", 729 | description: `The new command prefix for this server is \`${res.content}\`! Users should use this new prefix to interact with Polaris.` 730 | }); 731 | } 732 | return msg.channel.sendError(msg.author, { 733 | title: "Oops! Something went wrong.", 734 | description: `Something went wrong with the database. Please try again.` 735 | }); 736 | } 737 | 738 | function viewSettings (msg, settings) { 739 | const sendMessage = { 740 | title: "current settings", 741 | fields: [] 742 | }; 743 | sendMessage.description = `**Nickname management**: ${settings.nicknameTemplate || "Not set"}\n**Prefix**:${settings.prefix || "`.`"}\n**Auto verification**:\`${settings.autoVerify}\``; 744 | if (settings.binds) { 745 | let bindString = ``; 746 | for (const bind of settings.binds) { 747 | bindString += `Group: ${bind.group}, rank: ${bind.rank}, role: ${bind.role}\n`; 748 | } 749 | if (bindString !== "") { 750 | sendMessage.fields.push({ 751 | name: "Old binds", 752 | value: bindString 753 | }); 754 | } 755 | } 756 | 757 | sendMessage.fields.push({ 758 | name: "Main group", 759 | value: doGroup(settings.mainGroup) 760 | }); 761 | if (settings.subGroups) { 762 | for (let counter = 0; counter < settings.subGroups.length; counter++) { 763 | const sub = settings.subGroups[counter]; 764 | sendMessage.fields.push({ 765 | name: `Sub group ${counter + 1}`, 766 | value: doGroup(sub) 767 | }); 768 | } 769 | } 770 | msg.channel.sendInfo(msg.author, sendMessage); 771 | function doGroup (obj) { 772 | let returnString = ""; 773 | if (obj.id) { 774 | returnString = `**Group id**: ${obj.id}\n`; 775 | } 776 | if (obj.ranksToRoles !== undefined) { 777 | returnString += `**Ranks to roles**: ${obj.ranksToRoles}\n`; 778 | } 779 | if (obj.binds) { 780 | returnString += "**Binds**: \n"; 781 | for (const bind of obj.binds) { 782 | returnString += `Rank: ${bind.rank}, role: ${bind.role}\n`; 783 | } 784 | } 785 | 786 | return returnString; 787 | } 788 | } 789 | -------------------------------------------------------------------------------- /polaris/commands/admin/setup.js: -------------------------------------------------------------------------------- 1 | const BaseCommand = require("../baseCommand"); 2 | 3 | class setupCommand extends BaseCommand { 4 | constructor (client) { 5 | super(client); 6 | this.description = "Helps you to set up your server. Builds discord roles from your Group."; 7 | this.group = "Admin"; 8 | this.permissions = ["administrator"]; 9 | this.guildOnly = true; 10 | } 11 | 12 | async execute (msg, args) { 13 | const botMember = msg.channel.guild.members.get(this.client.user.id); 14 | if (!botMember.permissions.has("manageRoles")) { 15 | return msg.channel.sendError(msg.author, { 16 | title: "I don't have permission!", 17 | description: 18 | "I don't have permission to create roles. Please grant me this and re-run the command." 19 | }); 20 | } 21 | let groupId; 22 | if (args[0]) { 23 | if (!isNaN(args[0])) { 24 | groupId = args[0]; 25 | } // is NaN checks if NOT a number, so it is inverted. 26 | } 27 | const settings = await this.client.db.getSettings(msg.channel.guild.id); 28 | if (settings.mainGroup && settings.mainGroup.id && !groupId) { 29 | const res = await msg.channel.restrictedPrompt( 30 | msg.author, 31 | { 32 | title: "Do you want to use this Group id?", 33 | description: `The current Group Id in settings is \`${ 34 | settings.mainGroup.id 35 | }\`. Do you want to use this, or a different one?` 36 | }, 37 | ["This one", "Different"] 38 | ); 39 | if (!res) { 40 | return; 41 | } if (res.content.toLowerCase() === "this one") { 42 | groupId = settings.mainGroup.id; 43 | } 44 | } 45 | if (!groupId) { 46 | // Get new group ID 47 | const newId = await msg.channel.prompt(msg.author, { 48 | title: "What group ID Do you want to use?", 49 | description: 50 | "What group id do you want to use? This is the group which I will fetch ranks from." 51 | }); 52 | if (!newId) return; 53 | if (isNaN(newId.content)) { return msg.channel.sendError(msg.author, `Group id must be a number!`); } 54 | groupId = newId.content; 55 | } 56 | const group = await this.client.roblox.getGroup(groupId); 57 | if (group.error) { 58 | if (group.error.status === 404) { 59 | msg.channel.sendError(msg.author, { 60 | title: "Group not found", 61 | description: `I couldn't find that group on Roblox.` 62 | }); 63 | } else { 64 | msg.channel.sendError(msg.author, { 65 | title: "HTTP error", 66 | description: `A HTTP error has occurred. Is Roblox down?` 67 | }); 68 | } 69 | 70 | return; 71 | } 72 | const msgToBeEdited = await msg.channel.send( 73 | "Generating roles.. Please wait." 74 | ); 75 | if (!msgToBeEdited) return; 76 | 77 | const roles = group.roles.reverse(); 78 | let createdRoles = ""; 79 | for (var current of roles) { 80 | // Check for role with same name. 81 | if (!msg.channel.guild.roles.find(a => a.name === current.name)) { 82 | await msg.channel.guild.createRole({ name: current.name }, `Set-up command run by ${msg.author.username}#${msg.author.discriminator}`); 83 | createdRoles = `${createdRoles}**-** ${current.name}\n`; 84 | } 85 | } 86 | if (createdRoles === "") { 87 | msgToBeEdited.edit({ 88 | content: `<@${msg.author.id}>`, 89 | embed: { 90 | title: "No roles to make!", 91 | color: 0xb3000a, 92 | timestamp: new Date(), 93 | description: 94 | "There were no roles to create.\nIf there is already a discord role with the same name, I will not create another role with that name." 95 | } 96 | }); 97 | return; 98 | } 99 | msgToBeEdited.edit({ 100 | content: `<@${msg.author.id}>`, 101 | embed: { 102 | title: "Created the following roles!", 103 | description: createdRoles, 104 | color: 0x23ff9f, 105 | timestamp: new Date(), 106 | fields: [ 107 | { 108 | name: "Missing role?", 109 | value: "If a role with a rank name already exists, the bot will not create a new one." 110 | }, 111 | { 112 | name: "What next?", 113 | value: "You can now colour and order them as you please. Permissions are default." 114 | } 115 | ] 116 | } 117 | }); 118 | } 119 | } 120 | module.exports = setupCommand; 121 | -------------------------------------------------------------------------------- /polaris/commands/baseCommand.js: -------------------------------------------------------------------------------- 1 | const { configureScope, captureException } = require("@sentry/node"); 2 | 3 | class BaseCommand { 4 | constructor (client) { 5 | this.client = client; 6 | this.name = "Unidentified command"; 7 | this.description = "This command does not have a description"; 8 | this.group = "Misc"; 9 | this.permissions = []; 10 | this.aliases = []; 11 | 12 | this.guildOnly = true; 13 | this.hidden = false; 14 | } 15 | 16 | async process (message, args, prefix) { 17 | if (this.hidden && message.author.id !== this.client.ownerId) return; 18 | if (!message.author || message.author.bot) return; 19 | const blacklist = await this.client.db.blacklist.get(message.author.id); 20 | if (blacklist) { 21 | return message.channel.sendError(message.author, { 22 | title: "Blacklisted", 23 | description: `You are blacklisted from using Polaris. This is likely due to a violation of our Terms of service, or some other equally grievous action.\n**Reason: **${blacklist.reason ? blacklist.reason : "None provided."}\n**Date: **${blacklist.time}` 24 | }); 25 | } 26 | const commandName = this.name; 27 | configureScope(scope => { 28 | const { username, discriminator, id } = message.author; 29 | scope.setUser({ 30 | username, 31 | discriminator, 32 | id 33 | }); 34 | scope.setTag("command", commandName); 35 | if (message.channel && message.channel.guild) { 36 | scope.setExtra("guild", message.channel.guild.id); 37 | } 38 | }); 39 | 40 | if (message.channel.guild) { 41 | const serverBlacklist = await this.client.db.blacklist.get(message.channel.guild.id); 42 | if (serverBlacklist) { 43 | await message.channel.sendError(message.author, { 44 | title: "Blacklisted!", 45 | description: `This server is blacklisted from using Polaris. This is likely due to a violation of our Terms of service, or some other equally grievous action.\n**Reason: **${serverBlacklist.reason ? serverBlacklist.reason : "None provided."}\n**Date: **${serverBlacklist.time}`, 46 | fields: [{ 47 | name: "Leaving server", 48 | value: "I am now leaving the server. Please do not re-invite me." 49 | }] 50 | }); 51 | message.channel.guild.leave(); 52 | return; 53 | } 54 | 55 | if (!message.member) { 56 | await message.channel.guild.fetchAllMembers(9000); 57 | if (message.channel.guild.members.has(message.author.id)) { 58 | // eslint-disable-next-line require-atomic-updates 59 | message.member = message.channel.guild.members.get(message.author.id); 60 | } else { 61 | return; 62 | } 63 | } 64 | } else if (this.permissions.length !== 0 || this.guildOnly) return message.channel.sendError(message.author, "This command **can only be ran in a server!**"); 65 | 66 | if (this.client.cooldown.has(message.author.id)) { 67 | return message.channel.send(":x: - There is a command cooldown of 3 seconds. Please wait!"); 68 | } 69 | if (message.author.id !== this.client.ownerId) { 70 | const permissionFails = []; 71 | this.permissions.forEach(permission => { 72 | if (!message.member.permissions.has(permission)) { 73 | permissionFails.push(permission); 74 | } 75 | }); 76 | if (permissionFails.length !== 0) { 77 | return message.channel.sendError(message.author, { 78 | title: "Error", 79 | description: `The \`${this.name}\` command requires \`${permissionFails.join(", ")}\` permission(s)` 80 | }); 81 | } 82 | } 83 | if (!message.member && this.guildOnly) { 84 | console.log(`MEMBER IS NULL. Content: ${message.content} id: ${message.id}`); 85 | return message.channel.sendError(message.author, "I couldn't find your guildMember. Please switch to `Online`, `Idle` or `DnD`. If issue persists, join our discord."); 86 | } 87 | 88 | // ERROR cATCHING 89 | try { 90 | const { cooldown } = this.client; 91 | cooldown.add(message.author.id); 92 | setTimeout(() => cooldown.delete(message.author.id), 3000); 93 | await this.execute(message, args, prefix); 94 | } catch (e) { 95 | await message.channel.sendError(message.author, { 96 | title: "Oops! An error has occured.", 97 | description: `Polaris has encounted an unexpected and fatal error. We're right on it! You may want to join to join our [discord](https://discord.gg/QevWabU) to help with fixing this.\n \`\`\` ${e.message} \`\`\`` 98 | }); 99 | captureException(e); 100 | } 101 | } 102 | } 103 | 104 | module.exports = BaseCommand; 105 | -------------------------------------------------------------------------------- /polaris/commands/fun/8ball.js: -------------------------------------------------------------------------------- 1 | const good = ["It is certain", "It is decidedly so", "Without a doubt", "Yes definitely", "You may rely on it", "As I see it, yes", "Most likely", "Outlook good", "Yes", "Signs point to yes"]; 2 | const none = ["Reply hazy try again", "Ask again later", "Better not tell you now", "Cannot predict now", "Concentrate and ask again"]; 3 | const bad = ["Don't count on it", "My reply is no", "My sources say no", "Outlook not so good", "Very doubtful"]; 4 | 5 | const BaseCommand = require("../baseCommand"); 6 | 7 | class BallCommand extends BaseCommand { 8 | constructor (client) { 9 | super(client); 10 | this.description = "A magic 8ball. Pretty simple, right?"; 11 | this.group = "Fun"; 12 | this.guildOnly = false; 13 | } 14 | 15 | async execute (msg) { 16 | const main = this.getRandomIntInclusive(1, 3); 17 | if (main === 1) { 18 | msg.channel.sendSuccess(msg.author, this.goodChoose()); 19 | } else if (main === 2) { 20 | msg.channel.sendError(msg.author, this.badChoose()); 21 | } else { 22 | msg.channel.sendInfo(msg.author, this.noneChoose()); 23 | } 24 | } 25 | 26 | goodChoose () { 27 | const g = this.getRandomIntInclusive(0, good.length - 1); 28 | const text = good[g]; 29 | return { 30 | title: "Magic 8ball", 31 | description: `:8ball: ${text} :8ball:` 32 | }; 33 | } 34 | 35 | badChoose () { 36 | const g = this.getRandomIntInclusive(0, bad.length - 1); 37 | const text = bad[g]; 38 | return { 39 | title: "Magic 8ball", 40 | description: `:8ball: ${text} :8ball:` 41 | }; 42 | } 43 | 44 | noneChoose () { 45 | const g = this.getRandomIntInclusive(0, none.length - 1); 46 | const text = none[g]; 47 | return { 48 | title: "Magic 8ball", 49 | description: `:8ball: ${text} :8ball:` 50 | }; 51 | } 52 | 53 | getRandomIntInclusive (min, max) { 54 | return Math.floor(Math.random() * max) + 1; 55 | } 56 | } 57 | module.exports = BallCommand; 58 | -------------------------------------------------------------------------------- /polaris/commands/fun/quote.js: -------------------------------------------------------------------------------- 1 | const quoteEngine = require("polaris-quote-engine"); 2 | 3 | const BaseCommand = require("../baseCommand"); 4 | 5 | class quoteCommand extends BaseCommand { 6 | constructor (client) { 7 | super(client); 8 | this.description = "Produces a random quote from a person of great wisdom."; 9 | this.group = "Fun"; 10 | this.guildOnly = false; 11 | } 12 | 13 | async execute (msg) { 14 | const text = quoteEngine(); 15 | msg.channel.sendInfo(msg.author, { 16 | title: " ", 17 | author: { name: text[1] }, 18 | description: `:scroll: ${text[0]}` 19 | }); 20 | } 21 | } 22 | module.exports = quoteCommand; 23 | -------------------------------------------------------------------------------- /polaris/commands/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { BaseCommand: require("./baseCommand") }; 2 | -------------------------------------------------------------------------------- /polaris/commands/info/help.js: -------------------------------------------------------------------------------- 1 | const BaseCommand = require("../baseCommand"); 2 | 3 | class helpCommand extends BaseCommand { 4 | constructor (client) { 5 | super(client); 6 | this.group = "Misc"; 7 | this.description = "Displays help page and all of Polaris' Commands"; 8 | this.aliases = ["cmds", "commands"]; 9 | this.guildOnly = false; 10 | } 11 | 12 | async execute (msg, args, prefix) { 13 | const DMChannel = await msg.author.getDMChannel(); 14 | if (args[0]) { 15 | // Provide specific help info 16 | const command = this.client.CommandManager.commands[args[0]] 17 | || this.client.CommandManager.commands[this.client.CommandManager.commands.aliases[args[0]]]; 18 | if (!command) { 19 | if (!this._assembled) this.assemble(); 20 | 21 | await DMChannel.sendInfo(msg.author, this._assembled); 22 | 23 | return; 24 | } 25 | if (command.hidden) return; // It's a hidden command. It doesn't exist. 26 | // assemble command-specific help 27 | const obj = {}; 28 | // Add in command desc & command group 29 | obj.description = `**Description**: ${command.description}\n**Command group**: ${command.group}`; 30 | 31 | if (command.guildOnly) { 32 | obj.description = `${obj.description}\n**Works in DMs**: No`; 33 | } else { 34 | obj.description = `${obj.description}\n**Works in DMs**: Yes`; 35 | } 36 | 37 | // Add in aliases and permissions in seperate fields 38 | 39 | if (command.aliases.length !== 0) { 40 | if (!obj.fields)obj.fields = []; 41 | let aliases = ``; 42 | command.aliases.forEach(element => aliases = `${aliases} \`${element}\`,`); 43 | obj.fields.push({ 44 | name: "Aliases", 45 | value: aliases, 46 | inline: true 47 | }); 48 | } 49 | if (command.permissions.length !== 0) { 50 | if (!obj.fields)obj.fields = []; 51 | let perms = ``; 52 | command.permissions.forEach(element => perms = `${perms} \`${element}\`,`); 53 | obj.fields.push({ 54 | name: "Required permissions", 55 | value: perms, 56 | inline: true 57 | }); 58 | } 59 | 60 | obj.title = `${this.capitalizeFirstLetter(command.name)} help`; 61 | 62 | DMChannel.sendInfo(msg.author, obj); 63 | return; 64 | } 65 | 66 | if (!this._assembled) this.assemble(prefix); 67 | await DMChannel.sendInfo(msg.author, this._assembled); 68 | } 69 | 70 | assemble (prefix) { 71 | const organised = {}; 72 | const commands = Object.values(this.client.CommandManager.commands); 73 | commands.forEach(command => { 74 | if (!command.client) return; // This means that it is the "aliases" object and not a command. 75 | if (command.hidden) return; // this means that it is a HIDDEN COMMAND 76 | if (organised[command.group]) { 77 | organised[command.group].push(command); 78 | } else { 79 | organised[command.group] = [command]; 80 | } 81 | }); 82 | // Turn that into an embed. 83 | const embed = { 84 | title: "Help menu", 85 | description: `Listed below is all of Polaris' commands. You can also see them on our [website](http://polaris-bot.xyz/commands). Need more help? Join our [Discord](https://discord.gg/QevWabU). Do \`${prefix}settings\` to set up the bot.`, 86 | fields: [] 87 | }; 88 | 89 | const groups = Object.values(organised); 90 | groups.forEach(group => { 91 | let groupContent = ``; 92 | group.forEach(command => groupContent = `${groupContent}**${command.name}** - ${command.description}\n`); 93 | embed.fields.push({ 94 | name: group[0].group, 95 | value: groupContent 96 | }); 97 | }); 98 | embed.footer = { 99 | text: "Polaris bot", 100 | icon_url: this.client.user.avatarURL 101 | }; 102 | this._assembled = embed; 103 | 104 | return embed; 105 | } 106 | 107 | capitalizeFirstLetter (string) { 108 | return string.charAt(0).toUpperCase() + string.slice(1); 109 | } 110 | } 111 | module.exports = helpCommand; 112 | -------------------------------------------------------------------------------- /polaris/commands/info/info.js: -------------------------------------------------------------------------------- 1 | const packageJson = require("../../../package.json"); 2 | const BaseCommand = require("../baseCommand"); 3 | 4 | class InfoCommand extends BaseCommand { 5 | constructor (client) { 6 | super(client); 7 | this.description = "Get information about the bot, and some important links."; 8 | this.aliases = ["invite", "support"]; 9 | this.group = "Misc"; 10 | this.guildOnly = false; 11 | } 12 | 13 | async execute (msg, _a, prefix) { 14 | let upTime = Math.round(this.client.uptime / 60000); 15 | let suffix = "Minutes"; 16 | if (upTime > 60) { 17 | // It's > hr 18 | upTime /= 60; 19 | upTime = Math.round(upTime * 10) / 10; 20 | suffix = "Hours"; 21 | } else if (upTime > 1440) { 22 | upTime /= 1440; 23 | upTime = Math.round(upTime * 10) / 10; 24 | suffix = "Days"; 25 | } 26 | msg.channel.sendInfo(msg.author, 27 | { 28 | title: "Bot information", 29 | description: `**Polaris** is a Roblox verification bot created by \`Neztore#6998\`. The bot is always undergoing improvement. \nCurrent version: ${packageJson.version}\nPolaris is open source.`, 30 | timestamp: new Date(), 31 | fields: [ 32 | { 33 | name: "Bot info", 34 | value: `Polaris was created by Neztore. It is written in Node.js, and recently became open source. If you wish to view the bot commands, please do \`${prefix}help\`.` 35 | }, 36 | { 37 | name: "Our website", 38 | value: "Polaris now has a website, with our terms of use and commands. By using the bot, you agree to our [terms of use](https://polaris-bot.xyz/terms). You can see our site [here](https://polaris-bot.xyz/)." 39 | }, 40 | { 41 | name: "Github", 42 | value: "You can snoop, fork, edit and contribute to the source code here:\n - [Bot](https://github.com/Neztore/Polaris)\n - [Website](https://github.com/Neztore/Polaris-React)" 43 | }, 44 | { 45 | name: "Discord invite", 46 | value: "For support, join our [discord](https://discord.gg/QevWabU). This is also where you can make suggestions." 47 | }, 48 | { 49 | name: "Bot invite", 50 | value: "To invite the bot to your server, use [this](https://discordapp.com/oauth2/authorize?client_id=375408313724043278&scope=bot&permissions=470281408) link." 51 | }, 52 | { 53 | name: "Donate", 54 | value: "Do you enjoy using Polaris? Please donate to the running of the bot [here](https://www.paypal.me/Neztore)." 55 | }, 56 | { 57 | name: "Discord bot list", 58 | value: "Why not check us out on [Discord bot list](https://discordbots.org/bot/375408313724043278)? You can also vote for Polaris, but we don't mind!" 59 | }, 60 | { 61 | name: "Servers", 62 | value: this.client.guilds.size, 63 | inline: true 64 | }, 65 | { 66 | name: "Uptime", 67 | value: `${upTime} ${suffix}`, 68 | inline: true 69 | } 70 | 71 | ] 72 | }); 73 | } 74 | } 75 | module.exports = InfoCommand; 76 | -------------------------------------------------------------------------------- /polaris/commands/misc/getinfo.js: -------------------------------------------------------------------------------- 1 | const BaseCommand = require("../baseCommand"); 2 | const settings = require("../../../settings.json"); 3 | 4 | const { specialPeople } = settings; 5 | const { getLink } = require("../../util/linkManager"); 6 | 7 | class getinfoCommand extends BaseCommand { 8 | constructor (client) { 9 | super(client); 10 | this.description = "Retrieves the Roblox information of a user."; 11 | this.aliases = ["whois", "who"]; 12 | this.group = "Roblox"; 13 | this.guildOnly = true; 14 | } 15 | 16 | async execute (msg, args) { 17 | let robloxUser; 18 | 19 | let mentionedUser; 20 | 21 | if (msg.mentions.length === 0) { 22 | // Isn't a mention 23 | if (args.length !== 0) { 24 | const findStr = args.join(" ").toLowerCase(); 25 | const user = msg.channel.guild.members.find(member => member.username.toLowerCase().startsWith(findStr)); 26 | const userTwo = msg.channel.guild.members.get(args[0]); 27 | try{ 28 | if (user) { 29 | mentionedUser = user; 30 | } else if (userTwo) { 31 | mentionedUser = userTwo; 32 | } else if (!isNaN(Number(args[0]))){ 33 | let tempUser = await this.client.roblox.getUser(Number(args[0])); 34 | if (!tempUser.error){ 35 | robloxUser = tempUser 36 | } else if (args[0]) { 37 | robloxUser = await this.client.roblox.getUserFromName(args[0]); 38 | } 39 | } else if (args[0]) { 40 | robloxUser = await this.client.roblox.getUserFromName(args[0]); 41 | } 42 | } catch (e) { 43 | return msg.channel.sendError(msg.author, "Invalid argument: Please try again."); 44 | } 45 | 46 | } 47 | } else { 48 | mentionedUser = msg.mentions[0]; 49 | } 50 | 51 | if (!robloxUser) { 52 | mentionedUser = mentionedUser || msg.author; 53 | if (mentionedUser.bot) return msg.channel.sendError(msg.author, "Do you really think a bot has linked their account?! **Please mention a normal user!**"); 54 | const rbxId = await getLink(mentionedUser.id); 55 | if (!rbxId) { 56 | return msg.channel.sendError(msg.author, "I couldn't find that user's info. Have they linked their account?"); 57 | } 58 | robloxUser = await this.client.roblox.getUser(rbxId); 59 | } 60 | 61 | if (robloxUser.error) { 62 | if (robloxUser.error.status === 404) { 63 | return msg.channel.sendError(msg.author, { 64 | title: `I couldn't find that user on Roblox.`, 65 | description: `I couldn't find user \`${args[0]}\` on Roblox.` 66 | }); 67 | } 68 | await msg.channel.sendError(msg.author, { 69 | title: "HTTP Error", 70 | description: `A HTTP Error has occured. Is Roblox Down?\n\`${robloxUser.error.message}\`` 71 | }); 72 | return this.client.logError(robloxUser.error); 73 | } 74 | 75 | const sentMsg = await msg.channel.sendInfo(msg.author, "Getting user info... please wait."); 76 | 77 | if (!sentMsg) return; // No perm to send msg. 78 | const playerInfo = await robloxUser.getInfo(); 79 | if (playerInfo.error) { 80 | return sentMsg.edit({ 81 | embed: { 82 | title: "HTTP Error", 83 | description: `A HTTP Error has occured. Is Roblox Down?\n\`${playerInfo.error.message}\``, 84 | timestamp: new Date(), 85 | color: 0xb3000a 86 | } 87 | }); 88 | } 89 | const robloxId = robloxUser.id; 90 | const { joinDate } = playerInfo; 91 | const date = `${(joinDate.getDate())}/${(joinDate.getMonth() + 1)}/${(joinDate.getFullYear())} (D/M/Y)`; 92 | let text = `**Username:** ${playerInfo.username}\n**User ID:** ${robloxId}\n**Join date**: ${date}\n**Player age:** ${playerInfo.age}\n**Player status:** "${playerInfo.status}"`; 93 | if (playerInfo.blurb.length <= 200) { 94 | text = `${text}\n**Blurb:** ${playerInfo.blurb}`; 95 | } else { 96 | text = `${text}\n**Blurb:** ${playerInfo.blurb.substring(0, 200)}**...**`; 97 | } 98 | const toSend = { 99 | embed: { 100 | title: "Player info", 101 | description: text, 102 | url: `https://www.roblox.com/users/${robloxId}/profile`, 103 | thumbnail: { 104 | url: `https://www.roblox.com/Thumbs/Avatar.ashx?x=100&y=100&userId=${robloxId}`, 105 | height: 100, 106 | width: 100 107 | }, 108 | timestamp: new Date(), 109 | color: 0x03b212 110 | } 111 | }; 112 | 113 | // Extra bit for affiliates 114 | if (specialPeople[`${robloxUser.id}`]) { 115 | toSend.embed.fields = [ 116 | { 117 | name: "Polaris affiliate", 118 | value: specialPeople[`${robloxUser.id}`] 119 | } 120 | ]; 121 | } 122 | 123 | await sentMsg.edit(toSend); 124 | } 125 | } 126 | module.exports = getinfoCommand; 127 | -------------------------------------------------------------------------------- /polaris/commands/misc/groupInfo.js: -------------------------------------------------------------------------------- 1 | const BaseCommand = require("../baseCommand"); 2 | 3 | class getGroupCommand extends BaseCommand { 4 | constructor (client) { 5 | super(client); 6 | this.description = "Shows information about the Group Id provided, or the main group currently set."; 7 | this.aliases = ["getGroupInfo", "group", "getGroup", "groupInfo"]; 8 | this.group = "Roblox"; 9 | this.guildOnly = false; 10 | } 11 | 12 | async execute (msg, args, p) { 13 | let groupId; 14 | let fromSettings = false; 15 | if (args[0]) { 16 | if (!isNaN(args[0])) { 17 | groupId = args[0]; 18 | } else { 19 | const grp = await this.client.roblox.getGroupByName(args[0]); 20 | groupId = grp.id; 21 | if (!groupId) return msg.channel.sendError(msg.author, "Oops! Group not found."); 22 | } 23 | } else if (msg.channel.guild) { 24 | const settings = await this.client.db.servers.get(msg.channel.guild.id); 25 | if (settings.mainGroup && settings.mainGroup.id) { 26 | groupId = settings.mainGroup.id; 27 | fromSettings = true; 28 | } else { 29 | return msg.channel.sendError(msg.author, { 30 | title: `Must provide group id or set main group id`, 31 | description: `Please **either**:\n * Set Group ID in \`${p}settings\` or\n * Provide a group id to get info from.` 32 | }); 33 | } 34 | } else { 35 | return msg.channel.sendError(msg.author, { 36 | title: `Must provide group id or set main group id`, 37 | description: `Please **either**:\n * Set Group ID in \`${p}settings\` or\n * Provide a group id to get info from.` 38 | }); 39 | } 40 | 41 | const sentMsg = await msg.channel.sendInfo(msg.author, "Getting group info... please wait."); 42 | 43 | if (!sentMsg) return; // No perm to send msg. 44 | const groupInfo = await this.client.roblox.getGroup(groupId); 45 | if (groupInfo.error) { 46 | if (groupInfo.error.status === 404) { 47 | return sentMsg.edit({ 48 | embed: { 49 | title: "Group not found", 50 | description: "I couldn't find that group on Roblox. Please check the id, and try again.", 51 | timestamp: new Date(), 52 | color: 0xb3000a 53 | } 54 | }); 55 | } 56 | return sentMsg.edit({ 57 | embed: { 58 | title: "HTTP Error", 59 | description: `A HTTP Error has occurred. Is Roblox Down?\n\`${groupInfo.error.message}\``, 60 | timestamp: new Date(), 61 | color: 0xb3000a 62 | } 63 | }); 64 | } 65 | 66 | // EMBED PART 67 | 68 | const title = fromSettings ? `Main group information` : `Group information for ${groupInfo.name}`; 69 | // Build rank test bit 70 | let ranksText = ""; 71 | for (let r = 0; r < groupInfo.roles.length; r++) { 72 | const role = groupInfo.roles[r]; 73 | ranksText += `${r + 1}: **Name**: \`${role.name}\` - **Rank Id:**: \`${role.rank}\`\n`; 74 | } 75 | 76 | // Build fields. Only show desc if it exists. 77 | const fields = []; 78 | if (groupInfo.description !== "") { 79 | fields.push({ 80 | name: `Group description`, 81 | value: groupInfo.description.length > 400 ? groupInfo.description.substring(0, 400) : groupInfo.description 82 | }); 83 | } 84 | if (ranksText.length < 1024) { 85 | fields.push({ 86 | name: `Group ranks`, 87 | value: ranksText 88 | }); 89 | } else { 90 | // regen: cut in half 91 | let ranksText1 = ""; 92 | for (let r = 0; r < groupInfo.roles.length / 2; r++) { 93 | const role = groupInfo.roles[r]; 94 | ranksText1 += `${r + 1}: **Name**: \`${role.name}\` - **Id:**: \`${role.rank}\`\n`; 95 | } 96 | let ranksText2 = ""; 97 | for (let r = Math.floor(groupInfo.roles.length / 2); r < groupInfo.roles.length; r++) { 98 | const role = groupInfo.roles[r]; 99 | ranksText2 += `${r + 1}: **Name**: \`${role.name}\` - **Id:**: \`${role.rank}\`\n`; 100 | } 101 | fields.push({ 102 | name: `Group ranks 1`, 103 | value: ranksText1 104 | }); 105 | fields.push({ 106 | name: `Group ranks 2`, 107 | value: ranksText2 108 | }); 109 | } 110 | 111 | if (groupInfo.shout) { 112 | fields.push({ 113 | name: "Group shout", 114 | value: `${groupInfo.shout.message}\n **By**: ${groupInfo.shout.postedBy}`, 115 | inline: true 116 | }); 117 | } 118 | fields.push({ 119 | name: "Members", 120 | value: groupInfo.memberCount, 121 | inline: true 122 | }); 123 | 124 | let ownerText; 125 | if (groupInfo.owner) { 126 | ownerText = `[${groupInfo.owner.username}](https://www.roblox.com/users/${groupInfo.owner.userId}/profile)`; 127 | } else { 128 | ownerText = `Unowned`; 129 | } 130 | sentMsg.edit({ 131 | embed: { 132 | title, 133 | color: 0x168776, 134 | description: `**Group name**: ${groupInfo.name}\n**Group ID:**: ${groupInfo.id}\n**Owned by**: ${ownerText}`, 135 | thumbnail: { 136 | url: groupInfo.emblemUrl, 137 | height: 100, 138 | width: 100 139 | }, 140 | timestamp: new Date(), 141 | url: `https://www.roblox.com/Groups/group.aspx?gid=${groupInfo.id}`, 142 | fields 143 | } 144 | }); 145 | } 146 | } 147 | module.exports = getGroupCommand; 148 | -------------------------------------------------------------------------------- /polaris/commands/misc/ping.js: -------------------------------------------------------------------------------- 1 | const BaseCommand = require("../baseCommand"); 2 | 3 | class pingCommand extends BaseCommand { 4 | constructor (client) { 5 | super(client); 6 | this.description = "Simple ping command to retrieve bot latency."; 7 | this.aliases = ["pong"]; 8 | this.group = "Misc"; 9 | this.guildOnly = false; 10 | } 11 | 12 | async execute (msg) { 13 | const m = await msg.channel.send("Pong..."); 14 | if (!m) return; // no perm 15 | m.edit(`Pong! Latency is ${m.timestamp - msg.timestamp}ms.`); 16 | } 17 | } 18 | module.exports = pingCommand; 19 | -------------------------------------------------------------------------------- /polaris/commands/owner/blacklist.js: -------------------------------------------------------------------------------- 1 | const BaseCommand = require("../baseCommand"); 2 | 3 | class blacklistCommand extends BaseCommand { 4 | constructor (client) { 5 | super(client); 6 | this.description = "Simple ping command to retrieve bot latency."; 7 | this.aliases = []; 8 | this.group = "OWNER"; 9 | this.guildOnly = false; 10 | this.hidden = true; 11 | } 12 | 13 | async execute (msg, args) { 14 | if (msg.author.id !== this.client.ownerId) return; 15 | let [id, ...reason] = args; 16 | reason = reason.join(" "); 17 | if (msg.mentions.length !== 0) id = msg.mentions[0].id; 18 | if (!id) return msg.channel.sendError(msg.author, "You must provide an ID to blacklist!"); 19 | const { blacklist } = this.client.db; 20 | if (id === this.client.ownerId) return msg.channel.sendError(msg.author, "You cannot blacklist yourself!"); 21 | if (await blacklist.get(id)) { 22 | await blacklist.get(id).update({ 23 | reason, 24 | time: new Date() 25 | }); 26 | msg.channel.sendSuccess(msg.author, `Successfully updated the blacklist!\nNew reason: ${reason}`); 27 | } else { 28 | await blacklist.insert({ 29 | id, 30 | reason, 31 | time: new Date() 32 | }); 33 | msg.channel.sendSuccess(msg.author, `Successfully added the blacklist!\n**Reason:** ${reason}`); 34 | } 35 | } 36 | } 37 | module.exports = blacklistCommand; 38 | -------------------------------------------------------------------------------- /polaris/commands/owner/eval.js: -------------------------------------------------------------------------------- 1 | const BaseCommand = require("../baseCommand"); 2 | 3 | class evalCommand extends BaseCommand { 4 | constructor (client) { 5 | super(client); 6 | this.description = "EVAL COMMAND. OWNER ONLY. YOU SHOULD NOT BE ABLE TO SEE THIS."; 7 | this.group = "OWNER"; 8 | this.hidden = true; 9 | this.guildOnly = false; 10 | } 11 | 12 | async execute (msg, string) { 13 | if (msg.author.id !== "183601072344924160") return; 14 | console.log(`EVAL RAN BY ${msg.author.username}! User ID: ${msg.author.id}`); 15 | try { 16 | const code = string.join(" "); 17 | let evaled = eval(code); 18 | 19 | if (typeof evaled !== "string") { 20 | evaled = require("util").inspect(evaled); 21 | } 22 | if (evaled.length > 2040) evaled = `${evaled.substring(0, 2035)} ...`; 23 | msg.channel.sendInfo(msg.author, { 24 | title: "Eval complete", 25 | description: `\`\`\`${evaled}\`\`\`` 26 | }); 27 | } catch (err) { 28 | msg.channel.sendError(msg.author, { description: `\`\`\`xl\n${clean(err)}\n\`\`\`` }); 29 | } 30 | } 31 | } 32 | module.exports = evalCommand; 33 | 34 | function clean (text) { 35 | if (typeof text === "string") { 36 | return text.replace(/`/g, `\`${String.fromCharCode(8203)}`).replace(/@/g, `@${String.fromCharCode(8203)}`); 37 | } 38 | return text; 39 | } 40 | -------------------------------------------------------------------------------- /polaris/commands/owner/reload.js: -------------------------------------------------------------------------------- 1 | const BaseCommand = require("../baseCommand"); 2 | 3 | class reloadCommand extends BaseCommand { 4 | constructor (client) { 5 | super(client); 6 | this.description = "Reloads a command file"; 7 | this.group = "OWNER"; 8 | this.hidden = true; 9 | this.guildOnly = false; 10 | } 11 | 12 | async execute (msg, args) { 13 | if (msg.author.id !== "183601072344924160") return; 14 | if (!args[0]) return msg.channel.sendError(msg.author, `Command name is required!`); 15 | let command = args[0]; 16 | if (this.client.CommandManager.commands.aliases[command]) { 17 | command = this.client.CommandManager.commands.aliases[command]; 18 | } else if (!this.client.CommandManager.commands[command]) { 19 | return msg.channel.sendError(msg.author, `Invalid command name!`); 20 | } 21 | delete require.cache[require.resolve(this.client.CommandManager.commands[command].url)]; 22 | msg.channel.sendSuccess(msg.author, `Successfully reloaded the \`${command}\` command.`); 23 | } 24 | } 25 | module.exports = reloadCommand; 26 | -------------------------------------------------------------------------------- /polaris/commands/owner/unblacklist.js: -------------------------------------------------------------------------------- 1 | const BaseCommand = require("../baseCommand"); 2 | 3 | class unblacklistCommand extends BaseCommand { 4 | constructor (client) { 5 | super(client); 6 | this.description = ""; 7 | this.aliases = []; 8 | this.group = "OWNER"; 9 | this.guildOnly = false; 10 | this.hidden = true; 11 | } 12 | 13 | async execute (msg, args) { 14 | if (msg.author.id !== this.client.ownerId) return; 15 | let id = args[0]; 16 | if (msg.mentions.length !== 0) id = msg.mentions[0].id; 17 | if (!id) return msg.channel.sendError(msg.author, "You must provide an ID to unblacklist!"); 18 | const { blacklist } = this.client.db; 19 | const blacklistRecord = await blacklist.get(id); 20 | if (blacklistRecord) { 21 | await blacklist.get(id).delete(); 22 | msg.channel.sendSuccess(msg.author, "Successfully removed the blacklist!"); 23 | } else { 24 | return msg.channel.sendError(msg.author, "There is no blacklist set for that user/id!"); 25 | } 26 | } 27 | } 28 | module.exports = unblacklistCommand; 29 | -------------------------------------------------------------------------------- /polaris/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { Client: require("./client") }; 2 | -------------------------------------------------------------------------------- /polaris/util/Collection.js: -------------------------------------------------------------------------------- 1 | module.exports = class Collection extends Map { 2 | constructor (iterable) { 3 | super(iterable); 4 | } 5 | 6 | /** 7 | * Obtains the first value(s) in this collection. 8 | * @param {number} [amount] Amount of values to obtain from the beginning 9 | * @returns {*|Array<*>} A single value if no amount is provided or an array of values, starting from the end if 10 | * amount is negative 11 | */ 12 | first (amount) { 13 | if (typeof amount === "undefined") return this.values().next().value; 14 | if (amount < 0) return this.last(amount * -1); 15 | amount = Math.min(this.size, amount); 16 | const arr = new Array(amount); 17 | const iter = this.values(); 18 | for (let i = 0; i < amount; i++) arr[i] = iter.next().value; 19 | return arr; 20 | } 21 | 22 | /** 23 | * Obtains the first key(s) in this collection. 24 | * @param {number} [amount] Amount of keys to obtain from the beginning 25 | * @returns {*|Array<*>} A single key if no amount is provided or an array of keys, starting from the end if 26 | * amount is negative 27 | */ 28 | firstKey (amount) { 29 | if (typeof amount === "undefined") return this.keys().next().value; 30 | if (amount < 0) return this.lastKey(amount * -1); 31 | amount = Math.min(this.size, amount); 32 | const arr = new Array(amount); 33 | const iter = this.keys(); 34 | for (let i = 0; i < amount; i++) arr[i] = iter.next().value; 35 | return arr; 36 | } 37 | 38 | /** 39 | * Searches for all items where their specified property's value is identical to the given value 40 | * (`item[prop] === value`). 41 | * @param {string} prop The property to test against 42 | * @param {*} value The expected value 43 | * @returns {Array} 44 | * @example 45 | * collection.findAll('username', 'Bob'); 46 | */ 47 | findAll (prop, value) { 48 | if (typeof prop !== "string") throw new TypeError("Key must be a string."); 49 | if (typeof value === "undefined") throw new Error("Value must be specified."); 50 | const results = []; 51 | for (const item of this.values()) { 52 | if (item[prop] === value) results.push(item); 53 | } 54 | return results; 55 | } 56 | 57 | /** 58 | * Searches for a single item where its specified property's value is identical to the given value 59 | * (`item[prop] === value`), or the given function returns a truthy value. In the latter case, this is identical to 60 | * [Array.find()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find). 61 | * All collections used in Discord.js are mapped using their `id` property, and if you want to find by id you 62 | * should use the `get` method. See 63 | * [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/get) for details. 64 | * @param {string|Function} propOrFn The property to test against, or the function to test with 65 | * @param {*} [value] The expected value - only applicable and required if using a property for the first argument 66 | * @returns {*} 67 | * @example 68 | * collection.find('username', 'Bob'); 69 | * @example 70 | * collection.find(val => val.username === 'Bob'); 71 | */ 72 | find (propOrFn, value) { 73 | if (typeof propOrFn === "string") { 74 | if (typeof value === "undefined") throw new Error("Value must be specified."); 75 | for (const item of this.values()) { 76 | if (item[propOrFn] === value) return item; 77 | } 78 | return undefined; 79 | } if (typeof propOrFn === "function") { 80 | for (const [key, val] of this) { 81 | if (propOrFn(val, key, this)) return val; 82 | } 83 | return undefined; 84 | } 85 | throw new Error("First argument must be a property string or a function."); 86 | } 87 | 88 | /* eslint-disable max-len */ 89 | /** 90 | * Searches for the key of a single item where its specified property's value is identical to the given value 91 | * (`item[prop] === value`), or the given function returns a truthy value. In the latter case, this is identical to 92 | * [Array.findIndex()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/findIndex). 93 | * @param {string|Function} propOrFn The property to test against, or the function to test with 94 | * @param {*} [value] The expected value - only applicable and required if using a property for the first argument 95 | * @returns {*} 96 | * @example 97 | * collection.findKey('username', 'Bob'); 98 | * @example 99 | * collection.findKey(val => val.username === 'Bob'); 100 | */ 101 | /* eslint-enable max-len */ 102 | findKey (propOrFn, value) { 103 | if (typeof propOrFn === "string") { 104 | if (typeof value === "undefined") throw new Error("Value must be specified."); 105 | for (const [key, val] of this) { 106 | if (val[propOrFn] === value) return key; 107 | } 108 | return undefined; 109 | } if (typeof propOrFn === "function") { 110 | for (const [key, val] of this) { 111 | if (propOrFn(val, key, this)) return key; 112 | } 113 | return undefined; 114 | } 115 | throw new Error("First argument must be a property string or a function."); 116 | } 117 | 118 | /** 119 | * Searches for the existence of a single item where its specified property's value is identical to the given value 120 | * (`item[prop] === value`), or the given function returns a truthy value. 121 | * Do not use this to check for an item by its ID. Instead, use `collection.has(id)`. See 122 | * [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/has) for details. 123 | * @param {string|Function} propOrFn The property to test against, or the function to test with 124 | * @param {*} [value] The expected value - only applicable and required if using a property for the first argument 125 | * @returns {boolean} 126 | * @example 127 | * if (collection.exists('username', 'Bob')) { 128 | * console.log('user here!'); 129 | * } 130 | * @example 131 | * if (collection.exists(user => user.username === 'Bob')) { 132 | * console.log('user here!'); 133 | * } 134 | */ 135 | exists (propOrFn, value) { 136 | return Boolean(this.find(propOrFn, value)); 137 | } 138 | 139 | filter (fn, thisArg) { 140 | if (thisArg) fn = fn.bind(thisArg); 141 | const results = new Collection(); 142 | for (const [key, val] of this) { 143 | if (fn(val, key, this)) results.set(key, val); 144 | } 145 | return results; 146 | } 147 | 148 | /** 149 | * Identical to 150 | * [Array.every()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/every). 151 | * @param {Function} fn Function used to test (should return a boolean) 152 | * @param {Object} [thisArg] Value to use as `this` when executing function 153 | * @returns {boolean} 154 | */ 155 | every (fn, thisArg) { 156 | if (thisArg) fn = fn.bind(thisArg); 157 | for (const [key, val] of this) { 158 | if (!fn(val, key, this)) return false; 159 | } 160 | return true; 161 | } 162 | 163 | clone () { 164 | return new this.constructor(this); 165 | } 166 | }; 167 | -------------------------------------------------------------------------------- /polaris/util/CommandManager.js: -------------------------------------------------------------------------------- 1 | const { readdirSync, statSync } = require("fs"); 2 | const { join } = require("path"); 3 | 4 | class CommandManager { 5 | constructor (client) { 6 | this.client = client; 7 | this.commands = { aliases: {} }; 8 | 9 | this.prefix = "."; 10 | this.client.once("ready", () => this.prefixMention = new RegExp(`^<@!?${this.client.user.id}> `)); 11 | 12 | this.loadCommands(join(__dirname, "../commands")); 13 | } 14 | 15 | loadCommands (directory) { 16 | const ignoredFiles = ["baseCommand.js", "index.js"]; 17 | readdirSync(directory).forEach(file => { 18 | if (!ignoredFiles.includes(file)) { 19 | const stat = statSync(join(directory, file)); 20 | 21 | if (stat.isFile()) { 22 | const commandFile = require(`${directory}/${file}`); 23 | const command = new commandFile(this.client); 24 | const commandName = file.replace(".js", ""); 25 | command.name = commandName; 26 | command.url = `${directory}/${file}`; 27 | 28 | this.commands[commandName] = command; 29 | if (command.aliases) { 30 | command.aliases.forEach(alias => { 31 | this.commands.aliases[alias.toLowerCase()] = commandName; 32 | }); 33 | } 34 | } else if (stat.isDirectory()) { 35 | this.loadCommands(join(directory, file)); 36 | } 37 | } 38 | }); 39 | } 40 | 41 | async processMessage (message) { 42 | if (message.author.bot || !message.author) return; 43 | if (message.channel.guild && (message.channel.guild.id === "264445053596991498" || message.channel.guild.id === "110373943822540800")) return; // Block Server List Guilds 44 | let prefix = "."; 45 | let prefixMsg = prefix; 46 | 47 | if (message.channel.guild) { // Handle Custom Prefix - It's in a guild. 48 | const { guild } = message.channel; 49 | let guildSettings = await this.client.db.getSettings(guild.id); 50 | if (!guildSettings) { 51 | console.log(`Guild ${guild.id} has no settings. Resolving.`); 52 | await this.client.db.setupGuild(guild); 53 | guildSettings = await this.client.db.getSettings(guild.id); 54 | } 55 | if (guildSettings.autoVerify) { 56 | this.client.autoRole(message.member); 57 | } 58 | if (guildSettings.prefix && guildSettings.prefix !== "") { 59 | prefix = guildSettings.prefix; 60 | prefixMsg = prefix; 61 | } 62 | } 63 | if (this.prefixMention) { 64 | if (message.content.match(this.prefixMention)) { 65 | prefix = message.content.match(this.prefixMention)[0]; 66 | prefixMsg = `@${this.client.user.username}#${this.client.user.discriminator} `; 67 | } 68 | } 69 | 70 | if (!message.content.startsWith(prefix)) return; 71 | 72 | const args = message.content.slice(prefix.length).trim().split(/ +/g); 73 | let command = args.shift().toLowerCase(); 74 | if (this.commands.aliases[command]) command = this.commands.aliases[command]; 75 | 76 | if (this.commands[command]) { 77 | return this.commands[command].process(message, args, prefixMsg); 78 | } 79 | } 80 | } 81 | 82 | module.exports = CommandManager; 83 | -------------------------------------------------------------------------------- /polaris/util/Database.js: -------------------------------------------------------------------------------- 1 | /* 2 | USERS: 3 | discordId: DISCORD ID 4 | robloxId: Roblox ID 5 | 6 | Servers: 7 | Id: guildID 8 | binds: array of binds [{role: discord role ID, rank: roblox rank ID, group: roblox group ID, exclusive: boolean}] 9 | mainGroup: {id: ID for main group, ranksToRoles: boolean} 10 | autoverify: false (boolean) 11 | */ 12 | 13 | class Database { 14 | constructor (client) { 15 | this.client = client; 16 | this._r = require("rethinkdbdash")({ db: "main" }); 17 | 18 | this.servers = this._r.table("servers"); 19 | this.blacklist = this._r.table("blacklist"); 20 | 21 | this.defaults = { 22 | binds: [], 23 | mainGroup: {}, 24 | autoVerify: false 25 | }; 26 | this.client.on("guildCreate", this.setupGuild.bind(this)); 27 | } 28 | 29 | async setupGuild (guild) { 30 | console.log("Setting up Defaults."); 31 | try { 32 | const current = await this.servers.get(guild.id); 33 | if (!current) { 34 | const insertOBJ = this.defaults; 35 | insertOBJ.id = guild.id; 36 | await this.servers.insert(insertOBJ).run(); 37 | } 38 | } catch (error) { 39 | this.client.logError(error, { 40 | ID: guild.id, 41 | type: "Database - Setup" 42 | }); 43 | } 44 | } 45 | 46 | async getSettings (id) { 47 | return this.servers.get(id).run(); 48 | } 49 | 50 | async updateSetting (id, newValue) { 51 | let current = this.servers.get(id); 52 | if (!current) { 53 | console.log(`Update guild settings but no bind? ${id}`); 54 | await this.setupGuild({ id }); 55 | current = this.servers.get(id); 56 | } 57 | 58 | const res = await current.update(newValue).run(); 59 | if (res.errors !== 0) { 60 | this.client.logError(res.first_error, { type: "Database error. UpdateSettings." }); 61 | return false; 62 | } 63 | return true; 64 | } 65 | } 66 | 67 | module.exports = Database; 68 | -------------------------------------------------------------------------------- /polaris/util/erisExtensions.js: -------------------------------------------------------------------------------- 1 | // awaitMessages message collector 2 | const { EventEmitter } = require("events"); 3 | 4 | class MessageCollector extends EventEmitter { 5 | constructor (channel, filter, options = {}) { 6 | // wrongFun called every time an incorrect attempt is found. 7 | super(); 8 | this.filter = filter; 9 | this.channel = channel; 10 | this.options = options; 11 | this.ended = false; 12 | this.collected = []; 13 | this.bot = channel.guild ? channel.guild.shard.client : channel._client; 14 | 15 | this.listener = message => this.verify(message); 16 | this.bot.on("messageCreate", this.listener); 17 | if (options.time) setTimeout(() => this.stop("time"), options.time); 18 | } 19 | 20 | verify (message) { 21 | if (this.channel.id !== message.channel.id) return false; 22 | if (this.filter(message)) { 23 | this.collected.push(message); 24 | 25 | this.emit("message", message); 26 | if (this.collected.length >= this.options.maxMatches) this.stop("maxMatches"); 27 | return true; 28 | } 29 | this.emit("nonMatch", message); 30 | 31 | return false; 32 | } 33 | 34 | stop (reason) { 35 | if (this.ended) return; 36 | this.ended = true; 37 | this.bot.removeListener("messageCreate", this.listener); 38 | 39 | this.emit("end", this.collected, reason); 40 | } 41 | } 42 | 43 | // SEND FUNCTIONS FOR INTERACTING WITH CHANNELS. 44 | async function sendSuccess (author, content) { 45 | if (!author || !content) return console.log(`You forgot author or content! ${author} - ${content}`); 46 | const embed = addParts(content, author, "Success"); 47 | embed.timestamp = new Date(); 48 | embed.color = 0x2ECC71; 49 | const allowedMentions = { users: [author.id] }; 50 | try { 51 | return await this.client.createMessage(this.id, { 52 | content: `<@${author.id}> `, 53 | embed, 54 | allowedMentions 55 | }); 56 | } catch (err) { 57 | console.log(`Couldn't send message to ${this.id}`); 58 | return null; 59 | } 60 | } 61 | 62 | async function sendError (author, content) { 63 | if (!author || !content) return console.log(`You forgot author or content! ${author} - ${content}`); 64 | const embed = addParts(content, author, "Error"); 65 | embed.timestamp = new Date(); 66 | embed.color = 0xE74C3C; 67 | const allowedMentions = { users: [author.id] }; 68 | try { 69 | return await this.client.createMessage(this.id, { 70 | content: `<@${author.id}> `, 71 | embed, 72 | allowedMentions 73 | }); 74 | } catch (err) { 75 | console.log(`Couldn't send message to ${this.id}`); 76 | return null; 77 | } 78 | } 79 | 80 | async function sendInfo (author, content) { 81 | if (!author || !content) return console.log(`You forgot author or content! ${author} - ${content}`); 82 | const embed = addParts(content, author, "Info"); 83 | embed.timestamp = new Date(); 84 | embed.color = 0x168776; 85 | const allowedMentions = { users: [author.id] }; 86 | try { 87 | return await this.client.createMessage(this.id, { 88 | content: `<@${author.id}> `, 89 | embed, 90 | allowedMentions 91 | }); 92 | } catch (err) { 93 | console.log(`Couldn't send message to ${this.id}`); 94 | console.log(err); 95 | return null; 96 | } 97 | } 98 | 99 | async function send (content) { 100 | if (!content) return; 101 | try { 102 | return await this.client.createMessage(this.id, { content }); 103 | } catch (err) { 104 | console.log(`Couldn't send message to ${this.id}`); 105 | return null; 106 | } 107 | } 108 | 109 | // ASSEMBLE PROTOTYPES ONTO ERIS 110 | module.exports = Eris => { 111 | Eris.Channel.prototype.awaitMessages = function (filter, options) { 112 | const collector = new MessageCollector(this, filter, options); 113 | return new Promise(resolve => collector.on("end", resolve)); 114 | }; 115 | 116 | Eris.Client.prototype.MessageCollector = MessageCollector; 117 | // To track those currently using prompts, and stop them from starting more. 118 | const promptUsers = new Set(); 119 | // Restricted prompt function. Input = Embed message or a string. Must provide author. Options is array of strings. Returns null if cancelled or timed out. 120 | Eris.Channel.prototype.restrictedPrompt = function (author, msgOrObj, options) { 121 | const channel = this; 122 | if (promptUsers.has(author.id)) { 123 | this.sendError(author, "Please finish all other prompts before starting a new one!\nThink this is an error? Join our Discord."); 124 | return; 125 | } 126 | promptUsers.add(author.id); 127 | // Fill embed object & assemble options 128 | if (typeof msgOrObj === "string")msgOrObj = { description: msgOrObj }; 129 | let optDesc = ``; 130 | options.forEach(element => optDesc = `${optDesc}- ${element}\n`); // eslint-disable-line 131 | optDesc = `${optDesc}\nto cancel this prompt, say **cancel**.`; 132 | 133 | msgOrObj.title = msgOrObj.title || "Question prompt"; 134 | msgOrObj.fields = [{ 135 | name: "Please select one of the following options.", 136 | value: optDesc 137 | }]; 138 | 139 | this.sendInfo(author, msgOrObj); 140 | // Make it so that options are capitalisation insensitive for user ease. 141 | options = options.join("¬").toLowerCase().split("¬"); 142 | 143 | const fn = msg => (options.includes(msg.content.toLowerCase()) && msg.author.id === author.id) || (msg.content.toLowerCase() === "cancel" && msg.author.id === author.id); 144 | const collector = new MessageCollector(this, fn, { 145 | maxMatches: 1, 146 | time: 30000 147 | }); 148 | 149 | return new Promise(resolve => { 150 | collector.on("end", (msg, reason) => { 151 | if (reason === "time") { 152 | channel.sendInfo(author, "Prompt timed out"); 153 | promptUsers.delete(author.id); 154 | return resolve(null); 155 | } 156 | 157 | if (msg[0].content.toLowerCase() === "cancel") { 158 | channel.sendInfo(author, "Prompt cancelled"); 159 | promptUsers.delete(author.id); 160 | return resolve(null); 161 | } 162 | promptUsers.delete(author.id); 163 | resolve(msg[0], reason); 164 | }); 165 | }); 166 | }; 167 | 168 | // Message prompt function. Input = Embed message or a string. Must provide author. RETURNS NULL IF TIMED OUT OR CANCELLED. 169 | Eris.Channel.prototype.prompt = function (author, msgOrObj, allowDot) { 170 | const channel = this; 171 | if (promptUsers.has(author.id)) { 172 | this.sendError(author, "Please finish all other prompts before starting a new one!\nThink this is an error? Join our Discord."); 173 | return; 174 | } 175 | promptUsers.add(author.id); 176 | 177 | if (typeof msgOrObj === "string")msgOrObj = { description: msgOrObj }; 178 | 179 | msgOrObj.description = `${msgOrObj.description}\nTo cancel this prompt, say **cancel**.`; 180 | 181 | msgOrObj.title = msgOrObj.title || "Message Prompt"; 182 | 183 | this.sendInfo(author, msgOrObj); 184 | // If allow dot don't check for . at start of msg. 185 | const fn = allowDot ? (msg => msg.author.id === author.id) : (msg => msg.author.id === author.id && !msg.content.startsWith(".")); 186 | 187 | const collector = new MessageCollector(this, fn, { 188 | maxMatches: 1, 189 | time: 30000 190 | }); 191 | return new Promise(resolve => { 192 | collector.on("end", (msg, reason) => { 193 | if (reason === "time") { 194 | channel.sendInfo(author, "Prompt timed out"); 195 | promptUsers.delete(author.id); 196 | return resolve(null); 197 | } 198 | 199 | if (msg[0].content.toLowerCase() === "cancel") { 200 | channel.sendInfo(author, "Prompt cancelled"); 201 | promptUsers.delete(author.id); 202 | return resolve(null); 203 | } 204 | promptUsers.delete(author.id); 205 | resolve(msg[0], reason); 206 | }); 207 | }); 208 | }; 209 | async function updateNickname (settings, member, robloxId) { 210 | const { client } = this.shard; 211 | if (member.canEdit(this.members.get(client.user.id))) { 212 | if (settings.mainGroup.id && settings.nicknameTemplate && settings.nicknameTemplate !== "") { 213 | // Group is set and it nickname management is enabled 214 | let template = `${settings.nicknameTemplate}`; 215 | if (template.includes("{rankName}")) { 216 | // Make rankName request 217 | const group = await client.roblox.getGroup(settings.mainGroup.id); 218 | if (group.error) return group; // Return error. Can be accessed with returnedValue.error 219 | 220 | const rank = await group.getRole(robloxId); 221 | if (rank.error) return rank; // Return error. Can be accessed with returnedValue.error 222 | // Replace 223 | template = template.replace(/{rankName}/g, rank); 224 | } 225 | 226 | if (template.includes("{rankId}")) { 227 | const group = await client.roblox.getGroup(settings.mainGroup.id); 228 | if (group.error) return group; // Return error. Can be accessed with returnedValue.error 229 | 230 | const rankId = await group.getRank(robloxId); 231 | if (rankId.error) return rankId; // Return error. Can be accessed with returnedValue.error 232 | // Replace 233 | template = template.replace(/{rankId}/g, rankId); 234 | } 235 | return await finishNickname(this, template); 236 | } if (settings.nicknameTemplate && settings.nicknameTemplate !== "") { 237 | return await finishNickname(this, `${settings.nicknameTemplate}`); 238 | } 239 | } 240 | // Does any user-specific stuff that doesn't require a group. 241 | async function finishNickname (guild, template) { 242 | // User will be required in virtually every case. Idek why people wouldn't use it 243 | const user = await client.roblox.getUser(robloxId); 244 | if (user.error) return user; 245 | template = template.replace(/{robloxName}/g, user.username); 246 | template = template.replace(/{robloxId}/g, user.id); 247 | 248 | template = template.replace(/{discordName}/g, member.user.username); 249 | 250 | if (template.length > 32) { 251 | template = template.substring(0, 32); 252 | } 253 | 254 | if (member.nick !== template) { 255 | try { 256 | await guild.editMember(member.id, { nick: template }); 257 | } catch (e) { 258 | // we dont care :Sunglasses: 259 | } 260 | } 261 | 262 | return template; 263 | } 264 | } 265 | // Editor: Member 266 | function canEdit (editor) { 267 | const { guild } = this; 268 | 269 | // Check if owner 270 | if (this.id === guild.ownerID) { 271 | return false; // User owns guild. Cannot edit! 272 | } 273 | // Get target's highest role 274 | const targetRoles = this.roles; 275 | let highestTargetPos = 0; 276 | for (const currentRoleId of targetRoles) { 277 | const currentRole = guild.roles.get(currentRoleId); 278 | if (currentRole) { 279 | if (currentRole.position > highestTargetPos) highestTargetPos = currentRole.position; 280 | } 281 | } 282 | // Get bot's highest role 283 | const editorRoles = editor.roles; 284 | let highestEditorPos = 0; 285 | for (const currentRoleId of editorRoles) { 286 | const currentRole = guild.roles.get(currentRoleId); 287 | // Shouldn't be undefined but sometimes is 288 | if (currentRole) { 289 | if (currentRole.position > highestEditorPos) highestEditorPos = currentRole.position; 290 | } 291 | } 292 | // is user below editor 293 | return highestTargetPos < highestEditorPos; 294 | } 295 | 296 | Eris.Channel.prototype.sendInfo = sendInfo; 297 | Eris.Channel.prototype.sendError = sendError; 298 | Eris.Channel.prototype.sendSuccess = sendSuccess; 299 | Eris.Channel.prototype.send = send; 300 | 301 | Eris.Guild.prototype.updateNickname = updateNickname; 302 | Eris.Member.prototype.canEdit = canEdit; 303 | }; 304 | 305 | // Adds in fields that are left undefined when the send message functions are run for embeds. 306 | function addParts (content, author, type) { 307 | if (typeof content === "string") { 308 | return { 309 | title: type, 310 | description: content, 311 | footer: { 312 | text: `Sent for: ${author.username}`, 313 | icon_url: author.avatarURL 314 | }, 315 | timestamp: new Date() 316 | }; 317 | } 318 | if (!content.title) { 319 | content.title = type; 320 | } 321 | if (!content.description) { 322 | content.description = "Empty description"; 323 | } 324 | if (!content.footer) { 325 | content.footer = { 326 | text: `Sent for: ${author.username}`, 327 | icon_url: author.avatarURL 328 | }; 329 | } 330 | 331 | return content; 332 | } 333 | -------------------------------------------------------------------------------- /polaris/util/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | CommandManager: require("./CommandManager"), 3 | RobloxManager: require("./polaris-rbx"), 4 | IPC: require("./ipcClient"), 5 | 6 | ErisExtensions: require("./erisExtensions"), 7 | 8 | Database: require("./Database"), 9 | Collection: require("./Collection"), 10 | linkManager: require("./linkManager") 11 | }; 12 | -------------------------------------------------------------------------------- /polaris/util/ipcClient.js: -------------------------------------------------------------------------------- 1 | const IPC = require("node-ipc"); 2 | 3 | class IPCClient { 4 | constructor (client, config) { 5 | config = config || {}; 6 | this.allowLog = config.allowLog !== undefined ? config.allowLog : true; 7 | this.client = client; 8 | IPC.config.id = config.id || "polarisClient"; 9 | IPC.config.retry = config.retry || 3000; 10 | IPC.config.silent = config.silent || true; 11 | this.client.on("ready", this.start.bind(this)); 12 | } 13 | 14 | start () { 15 | this.client.removeListener("ready", this.start); 16 | IPC.connectTo("polarisServer", this.setup.bind(this)); 17 | } 18 | 19 | setup () { 20 | const ws = IPC.of.polarisServer; 21 | this._ws = ws; 22 | 23 | ws.on("connect", this.connected.bind(this)); 24 | ws.on("disconnect", this.disconnect.bind(this)); 25 | ws.on("error", this.error.bind(this)); 26 | 27 | ws.on("botCheck", this.botCheck.bind(this)); 28 | ws.on("getRoles", this.getRoles.bind(this)); 29 | } 30 | 31 | connected () { 32 | this.log("Connected!"); 33 | } 34 | 35 | disconnect () { 36 | this.log(`Disconnected! Attempting re-connection.`); 37 | } 38 | 39 | error (error) { 40 | if (error.code === "ENOENT") { 41 | this.log(`Server has disconnected. Error caught.`); 42 | } else { 43 | throw new Error(error); 44 | } 45 | } 46 | 47 | // Checks if bot is in server 48 | botCheck (msg) { 49 | msg = JSON.parse(msg); 50 | const guild = this.client.guilds.get(msg.data.guildId); 51 | if (guild) { 52 | this.sendObject("botCheckRes", { 53 | data: true, 54 | _id: msg._id 55 | }); 56 | } else { 57 | this.sendObject("botCheckRes", { 58 | data: false, 59 | _id: msg._id 60 | }); 61 | } 62 | } 63 | 64 | getRoles (msg) { 65 | msg = JSON.parse(msg); 66 | const guild = this.client.guilds.get(msg.data.guildId); 67 | if (!guild) { 68 | // return some type of error 69 | this.sendObject("getRolesRes", { 70 | data: { 71 | error: { 72 | status: 401, 73 | message: "Polaris is not in guild." 74 | } 75 | }, 76 | _id: msg._id 77 | }); 78 | } 79 | const roles = []; 80 | guild.roles.forEach(role => { 81 | roles.push({ 82 | id: role.id, 83 | name: role.name, 84 | color: role.color, 85 | position: role.position 86 | }); 87 | }); 88 | this.sendObject("getRolesRes", { 89 | data: roles, 90 | _id: msg._id 91 | }); 92 | } 93 | 94 | get ws () { 95 | return this._ws; 96 | } 97 | 98 | log (...args) { 99 | if (this.allowLog) { 100 | console.log(`IPC: `, ...args); 101 | } 102 | } 103 | 104 | sendObject (type, object) { this.ws.emit(type, JSON.stringify(object)); } 105 | } 106 | module.exports = IPCClient; 107 | -------------------------------------------------------------------------------- /polaris/util/linkManager.js: -------------------------------------------------------------------------------- 1 | const fetch = require("node-fetch"); 2 | 3 | const cache = new Map(); 4 | const { aquariusKey, aquariusUrl = "https://verify.nezto.re" } = require("../../settings.json"); 5 | 6 | async function getLink (discordId) { 7 | if (cache.has(discordId)) { 8 | const c = cache.get(discordId); 9 | // 15 second per user 10 | if (Date.now() - c.setAt < 15000) { 11 | return c.robloxId; 12 | } 13 | } 14 | try { 15 | const json = await makeRequest(`/api/roblox/${discordId}`); 16 | cache.set(discordId, { 17 | setAt: Date.now(), 18 | robloxId: json.robloxId 19 | }); 20 | return json.robloxId; 21 | } catch (e) { 22 | return undefined; 23 | } 24 | } 25 | /* 26 | The following functions are 'internal'. Or rather, they make use of the internal Aquarius API. 27 | They do not work if the correct authentication key is not supplied. The open source version of Polaris does not 28 | have this authentication key - Polaris will automatically direct users to use the web version instead. 29 | */ 30 | 31 | async function getCode (discordId, robloxId) { 32 | try { 33 | const json = await makeRequest(`/verification/code/${discordId}/${robloxId}`); 34 | if (json.code && json.robloxId === robloxId) { 35 | return json.code; 36 | } if (json.error) { 37 | console.log(json.error.message); 38 | } 39 | console.log(json); 40 | throw new Error("Failed id validation"); 41 | } catch (e) { 42 | console.log(e); 43 | return undefined; 44 | } 45 | } 46 | 47 | async function checkCode (discordId) { 48 | try { 49 | const json = await makeRequest(`/verification/check`, { 50 | body: { discordId }, 51 | method: "POST" 52 | }); 53 | if (json.success && json.discordId === discordId) { 54 | return { 55 | success: true, 56 | updated: json.updated 57 | }; 58 | } if (json.error) { 59 | console.log(json.error); 60 | return json; 61 | } 62 | throw new Error("Failed id validation"); 63 | } catch (e) { 64 | return undefined; 65 | } 66 | } 67 | // Clear once every 10 minutes to prevent leaks 68 | setTimeout(() => { 69 | cache.clear(); 70 | }, 600000); 71 | 72 | async function makeRequest (url, options = {}) { 73 | if (!options.headers) options.headers = {}; 74 | 75 | if (aquariusKey) { 76 | options.headers.Authorization = aquariusKey; 77 | } 78 | 79 | if (options.body) { 80 | options.headers["Content-Type"] = "application/json"; 81 | options.body = JSON.stringify(options.body); 82 | } 83 | 84 | const req = await fetch(`${aquariusUrl}${url}`, options); 85 | return req.json(); 86 | } 87 | 88 | module.exports = { 89 | getLink, 90 | getCode, 91 | checkCode 92 | }; 93 | -------------------------------------------------------------------------------- /polaris/util/polaris-rbx/baseClasses/group.js: -------------------------------------------------------------------------------- 1 | const request = require("../request"); 2 | 3 | class Group { 4 | constructor (groupId) { 5 | this.id = groupId; 6 | this.users = new Map(); 7 | } 8 | 9 | clearCache () { 10 | // Clear rank cache 11 | this.users.clear(); 12 | } 13 | 14 | async _getRankObject (id) { 15 | let res = await request(`https://groups.roblox.com/v2/users/${id}/groups/roles`); 16 | res = await res.json(); 17 | const b = res.data.filter(i => this.id === i.group.id); 18 | return b && b.length !== 0 ? b[0].role : {}; 19 | } 20 | 21 | async getRank (userIdOrUserClass) { 22 | if (!userIdOrUserClass) return 0; 23 | const id = typeof userIdOrUserClass === "number" || typeof userIdOrUserClass === "string" ? userIdOrUserClass : userIdOrUserClass.id; 24 | if (this.users.get(id)) { 25 | // Possible cache hit 26 | if (this.users.get(id).rank !== undefined) return this.users.get(id).rank; 27 | } 28 | try { 29 | const roleObject = await this._getRankObject(id); 30 | const rank = roleObject.rank || 0; 31 | 32 | if (this.users.get(id)) { 33 | const cache = this.users.get(id); 34 | cache.rank = rank; 35 | this.users.set(id, cache); 36 | } else { 37 | this.users.set(id, { rank }); 38 | } 39 | return rank; 40 | } catch (error) { 41 | if (error.status === 404 || error.status === 400) { 42 | return { 43 | error: { 44 | status: 404, 45 | message: "User or group not found" 46 | } 47 | }; 48 | } 49 | if (error.status === 503 || error.status === 500) { 50 | return { 51 | error: { 52 | status: 503, 53 | message: "Service Unavailable - Roblox is down." 54 | } 55 | }; 56 | } 57 | if (error.text) { 58 | throw new Error(await error.text()); 59 | } else { 60 | throw new Error(error); 61 | } 62 | } 63 | } 64 | 65 | async getRole (userIdOrUserClass) { 66 | if (!userIdOrUserClass) throw new Error("Getrole was not passed a user"); 67 | const id = typeof userIdOrUserClass === "number" || typeof userIdOrUserClass === "string" ? userIdOrUserClass : userIdOrUserClass.id; 68 | const cache = this.users.get(id); 69 | if (cache && cache.role) { 70 | // Possible cache hi 71 | return cache.role; 72 | } 73 | try { 74 | const roleObject = await this._getRankObject(id); 75 | const role = roleObject.name || "Guest"; 76 | 77 | if (this.users.get(id)) { 78 | this.users.get(id).role = role; 79 | } else { 80 | this.users.set(id, { role }); 81 | } 82 | return role; 83 | } catch (error) { 84 | if (error.status === 404 || error.status === 400) { 85 | return { 86 | error: { 87 | status: 404, 88 | message: "User or group not found" 89 | } 90 | }; 91 | } 92 | if (error.status === 503 || error.status === 500) { 93 | return { 94 | error: { 95 | status: 503, 96 | message: "Service Unavailable - Roblox is down." 97 | } 98 | }; 99 | } 100 | if (error.text) { 101 | throw new Error(await error.text()); 102 | } else { 103 | throw new Error(error); 104 | } 105 | } 106 | } 107 | } 108 | 109 | module.exports = Group; 110 | -------------------------------------------------------------------------------- /polaris/util/polaris-rbx/baseClasses/user.js: -------------------------------------------------------------------------------- 1 | const request = require("../request"); 2 | /* INFO WITH USER CLASS: 3 | * User.id: User ID. Always set 4 | * User.username: Roblox Name. Always set. 5 | * User.age, User.blurb, User.status, User.joinDate: Roblox Profile info. *Not* always set. 6 | */ 7 | class User { 8 | constructor (Roblox, userId) { 9 | this.roblox = Roblox; 10 | this.id = userId; 11 | } 12 | 13 | async getUsername () { 14 | if (this.username) { 15 | return this.username; 16 | } 17 | await this.updateUsername(); 18 | return this.username; 19 | } 20 | 21 | // RETURNS: USERNAME OR NULL FOR FAIL. 22 | async updateUsername () { 23 | const user = this; 24 | let res = await request(`https://users.roblox.com/v1/users/${this.id}`); 25 | res = await res.json(); 26 | user.username = res.Username; 27 | user.id = res.id || user.id; 28 | } 29 | 30 | async getInfo () { 31 | // Check for player info fetched 32 | if (this.age) { 33 | return { 34 | age: this.age, 35 | blurb: this.blurb, 36 | status: this.status, 37 | username: this.username, 38 | joinDate: this.joinDate 39 | }; 40 | } 41 | return this.updateInfo(); 42 | } 43 | 44 | async updateInfo () { 45 | if (!this.id) { 46 | return { 47 | error: { 48 | message: "Id is not defined. Please try again - We're onto it.", 49 | status: 400 50 | } 51 | }; 52 | } 53 | const user = this; 54 | 55 | try { 56 | const resStatus = await request(`https://users.roblox.com/v1/users/${user.id}`); 57 | if (resStatus) { 58 | const jsonStatus = await resStatus.json(); 59 | user.blurb = jsonStatus.description; 60 | user.joinDate = new Date(jsonStatus.created); 61 | } else { 62 | return { 63 | error: { 64 | message: "The user id is invalid.", 65 | status: 404 66 | } 67 | }; 68 | } 69 | } catch (error) { 70 | return { 71 | error: { 72 | message: error.message, 73 | status: 500, 74 | robloxId: user.id, 75 | userName: user.username 76 | } 77 | }; 78 | throw new Error(error); 79 | } 80 | 81 | // user status 82 | try { 83 | const resStatus = await request(`https://users.roblox.com/v1/users/${user.id}/status`); 84 | if (resStatus) { 85 | const jsonStatus = await resStatus.json(); 86 | user.status = jsonStatus.status; 87 | } else { 88 | return { 89 | error: { 90 | message: "Invalid user", 91 | status: 400 92 | } 93 | }; 94 | } 95 | } catch (error) { 96 | return { 97 | error: { 98 | message: error.message, 99 | status: 500, 100 | robloxId: user.id, 101 | userName: user.username 102 | } 103 | }; 104 | throw new Error(error); 105 | } 106 | 107 | const currentTime = new Date(); 108 | user.age = Math.round(Math.abs((user.joinDate.getTime() - currentTime.getTime()) / (24 * 60 * 60 * 1000))); 109 | 110 | const obj = { 111 | username: user.username, 112 | status: user.status, 113 | blurb: user.blurb, 114 | joinDate: user.joinDate, 115 | age: user.age 116 | }; 117 | return obj; 118 | } 119 | } 120 | 121 | module.exports = User; 122 | -------------------------------------------------------------------------------- /polaris/util/polaris-rbx/index.js: -------------------------------------------------------------------------------- 1 | const groupClass = require("./baseClasses/group.js"); 2 | const userClass = require("./baseClasses/user.js"); 3 | const request = require("./request"); 4 | 5 | const THUMBNAILS_API_URL = "https://thumbnails.roblox.com"; 6 | 7 | class Roblox { 8 | constructor (client) { 9 | this.client = client; 10 | this._userCache = new Map(); 11 | this._groupCache = new Map(); 12 | 13 | // base classes: 14 | this._user = userClass; 15 | this._group = groupClass; 16 | 17 | // Cache clear timers 18 | const Roblox = this; 19 | 20 | // Clear group cache (Group info & ranks - everything.) 21 | setInterval(() => { Roblox._groupCache.clear(); }, 3600000); 22 | setInterval(() => { Roblox._userCache.clear(); }, 7200000); 23 | // Clear group RANK cache 24 | setInterval(() => { 25 | Roblox.clearRanks(); 26 | }, 600000); 27 | this.clearRanks = () => Roblox._groupCache.forEach(group => group.clearCache()); 28 | } 29 | 30 | async _createUser (id) { 31 | const roblox = this; 32 | try { 33 | let res = await request(`https://users.roblox.com/v1/users/${id}`); 34 | if (res) { 35 | const newUser = new roblox._user(this, id); 36 | res = await res.json(); 37 | newUser.username = res.name; 38 | newUser.created = new Date(res.created); 39 | newUser.name = res.name; 40 | newUser.id = res.id || id; 41 | newUser.isBanned = res.isBanned; 42 | newUser.blurb = res.description; 43 | 44 | roblox._userCache.set(id, newUser); 45 | return newUser; 46 | } return { 47 | error: { 48 | status: 404, 49 | message: "User does not exist" 50 | } 51 | }; 52 | } catch (err) { 53 | if (err.status === 404) { 54 | return { 55 | error: { 56 | status: 404, 57 | message: "User does not exist" 58 | } 59 | }; 60 | } 61 | if (err.status === 503) { 62 | return { 63 | error: { 64 | status: 503, 65 | message: "Service Unavailible - Roblox is down." 66 | } 67 | }; 68 | } 69 | // Not 404, put to sentry in future 70 | throw Error(err); 71 | } 72 | } 73 | 74 | async getUser (id) { 75 | if (!this._userCache.get(id)) { 76 | return this._createUser(id); 77 | } 78 | return this._userCache.get(id); 79 | } 80 | 81 | async getUserFromName (name) { 82 | try { 83 | let res = await request(`https://users.roblox.com/v1/usernames/users`, { 84 | method: "POST", 85 | body: JSON.stringify({ usernames: [name] }), 86 | headers: { "Content-Type": "application/json" } 87 | }); 88 | if (res) { 89 | res = await res.json(); 90 | if (res.data.length === 0) { 91 | return { 92 | error: { 93 | status: 404, 94 | message: "User not found" 95 | } 96 | }; 97 | } 98 | const { id, name: username } = res.data[0]; 99 | const newUser = new this._user(this, id); 100 | 101 | newUser.username = username; 102 | this._userCache.set(id, newUser); 103 | return newUser; 104 | } 105 | return false; 106 | } catch (err) { 107 | if (err.status === 404) { 108 | return { 109 | error: { 110 | status: 404, 111 | message: "User does not exist" 112 | } 113 | }; 114 | } 115 | if (err.status === 503) { 116 | return { 117 | error: { 118 | status: 503, 119 | message: "Service Unavailable - Roblox is down." 120 | } 121 | }; 122 | } 123 | this.client.logError(err); 124 | return false; 125 | } 126 | } 127 | 128 | async getGroup (id) { 129 | if (!id) { 130 | return { 131 | error: { 132 | status: 400, 133 | message: "Group id is required" 134 | } 135 | }; 136 | } 137 | if (this._groupCache.get(id)) { 138 | return this._groupCache.get(id); 139 | } 140 | const roblox = this; 141 | // Group does not already exist! 142 | try { 143 | const res = request(`https://groups.roblox.com/v1/groups/${id}`); 144 | const emblemPromise = request(`${THUMBNAILS_API_URL}/v1/groups/icons?groupIds=${id}&size=150x150&format=Png`); 145 | const rolesPromise = request(`https://groups.roblox.com/v1/groups/${id}/roles`); 146 | 147 | const [groupRes, emblemRes, rolesRes] = await Promise.all([res, emblemPromise, rolesPromise]); 148 | const [groupInfo, emblem, roles] = await Promise.all([groupRes.json(), emblemRes.json(), rolesRes.json()]); 149 | 150 | const newGroup = new roblox._group(groupInfo.id); 151 | newGroup.name = groupInfo.name; 152 | newGroup.description = groupInfo.description; 153 | newGroup.owner = groupInfo.owner; 154 | newGroup.memberCount = groupInfo.memberCount; 155 | 156 | newGroup.emblemUrl = emblem.data ? emblem.data[0].imageUrl : ""; 157 | newGroup.roles = roles.roles; 158 | 159 | if (groupInfo.shout) { 160 | newGroup.shout = { 161 | message: groupInfo.shout.body, 162 | postedBy: groupInfo.shout.poster.username 163 | }; 164 | } 165 | roblox._groupCache.set(id, newGroup); 166 | return newGroup; 167 | } catch (error) { 168 | if (error.status === 404 || error.status === 500) { 169 | return { 170 | error: { 171 | status: 404, 172 | message: "Group not found" 173 | } 174 | }; 175 | } 176 | if (error.status === 503) { 177 | return { 178 | error: { 179 | status: 503, 180 | message: "Group info not available" 181 | } 182 | }; 183 | } 184 | // Not 404 185 | this.client.logError(error); 186 | return error; 187 | } 188 | } 189 | 190 | async getGroupByName (name) { 191 | if (!name) return false; 192 | name = encodeURIComponent(name); 193 | try { 194 | let res = await request(`https://grousp.roblox.com/v1/groups/search?keyword=${name}&prioritizeExactMatch=true&limit=10`); 195 | res = await res.json(); 196 | return res.data[0]; 197 | } catch (error) { 198 | if (error.status === 404 || error.status === 400) { 199 | return { 200 | error: { 201 | status: 404, 202 | message: "User or group not found" 203 | } 204 | }; 205 | } 206 | if (error.status === 503) { 207 | return { 208 | error: { 209 | status: 503, 210 | message: "Service Unavailable - Roblox is down." 211 | } 212 | }; 213 | } 214 | throw new Error(error); 215 | } 216 | } 217 | 218 | // API FUNCTIONS. Return simple things, not classes. 219 | async getIdFromName (Username) { 220 | const res = await this.getUserFromName(Username); 221 | if (res.error) return res.error; 222 | return res.id; 223 | } 224 | 225 | async getNameFromId (id) { 226 | const res = this.getUser(id); 227 | if (res.error) return res.error; 228 | return res.id; 229 | } 230 | } 231 | module.exports = Roblox; 232 | -------------------------------------------------------------------------------- /polaris/util/polaris-rbx/request.js: -------------------------------------------------------------------------------- 1 | const fetch = require("node-fetch"); 2 | 3 | module.exports = async function request (url, opt) { 4 | const resp = await fetch(url, opt); 5 | if (resp.ok) { 6 | return resp; 7 | } 8 | const text = await resp.text(); 9 | throw new Error(text); 10 | }; 11 | -------------------------------------------------------------------------------- /scripts/export.js: -------------------------------------------------------------------------------- 1 | // Export.js 2 | const fs = require("fs"); 3 | const r = require("rethinkdbdash")(); 4 | 5 | const db = r.db("main"); 6 | 7 | async function exportTable (tbl) { 8 | const table = await db.table(tbl); 9 | const fileName = `${tbl}.json`; 10 | 11 | const data = JSON.stringify(table, null, 2); 12 | 13 | fs.writeFile(fileName, data, err => { 14 | if (err) throw err; 15 | console.log(`${tbl} table successfully written to file as ${fileName}`); 16 | }); 17 | } 18 | 19 | exportTable("servers"); 20 | -------------------------------------------------------------------------------- /scripts/import.js: -------------------------------------------------------------------------------- 1 | // Import.js 2 | const r = require("rethinkdbdash")(); 3 | 4 | const db = r.db("main"); 5 | 6 | async function importTable (tbl) { 7 | const t = db.table(tbl); 8 | let queries = 0; 9 | const table = { 10 | get (...args) { 11 | console.log(`Performing GET: ${queries}`); 12 | queries++; 13 | return t.get(...args); 14 | }, 15 | update (...args) { 16 | console.log(`Performing UPDATE: ${queries}`); 17 | return t.update(...args); 18 | }, 19 | insert (...args) { 20 | console.log(`Performing INSERT: ${queries}`); 21 | return t.insert(...args); 22 | } 23 | }; 24 | const fileName = `${tbl}.json`; 25 | 26 | const data = require(`./${fileName}`); 27 | const num = 0; 28 | for (let counter = 0; counter < data.length; counter++) { 29 | const item = data[counter]; 30 | 31 | const id = item.id ? item.id : item.discordId; 32 | const existing = await table.get(id); 33 | if (existing) { 34 | // Update 35 | try { 36 | await table.update(item); 37 | } catch (err) { 38 | console.log(`FAILED to update item in ${tbl} ${id}`); 39 | return; 40 | } 41 | console.log(`Updated item in ${tbl} ${id}`); 42 | } else { 43 | try { 44 | await table.insert(item); 45 | } catch (err) { 46 | console.log(`FAILED to add item to ${tbl} ${id}`); 47 | return; 48 | } 49 | console.log(`Added item to ${tbl} ${id}`); 50 | } 51 | } 52 | 53 | console.log(`Performed ${num} reads...`); 54 | } 55 | importTable("servers"); 56 | --------------------------------------------------------------------------------