diff --git a/common/.eslintignore b/common/.eslintignore new file mode 100644 index 000000000..1642c05af --- /dev/null +++ b/common/.eslintignore @@ -0,0 +1,2 @@ +node_modules +js \ No newline at end of file diff --git a/common/.eslintrc b/common/.eslintrc new file mode 100644 index 000000000..661751a32 --- /dev/null +++ b/common/.eslintrc @@ -0,0 +1,34 @@ +{ + "plugins": [ + "@typescript-eslint", + "import", + "cadence" + ], + "extends": [ + "plugin:cadence/recommended" + ], + "rules": { + "@typescript-eslint/no-non-null-assertion": "off", + "no-inline-comments": "off" + }, + "overrides": [ + { + "files": [ + "**/*.test.ts" + ], + "rules": { + // Our test files break these rules *all* the time, and there's + // no point refactoring. Our tests deliberately play with the + // dynamic nature of TS to more accurately test arbitrary input. + "@typescript-eslint/no-floating-promises": "off", + "@typescript-eslint/no-unsafe-member-access": "off", + "@typescript-eslint/no-unsafe-assignment": "off", + "@typescript-eslint/no-unsafe-return": "off", + "@typescript-eslint/no-unsafe-argument": "off", + "@typescript-eslint/no-unsafe-call": "off", + // we abuse this one for faking data, but we really shouldn't. + "@typescript-eslint/consistent-type-assertions": "warn" + } + } + ] +} \ No newline at end of file diff --git a/common/.gitignore b/common/.gitignore new file mode 100644 index 000000000..e041943cc --- /dev/null +++ b/common/.gitignore @@ -0,0 +1,7 @@ +/node_modules +/.vscode + +.nyc_output +coverage + +/js \ No newline at end of file diff --git a/common/.taprc b/common/.taprc new file mode 100644 index 000000000..112fbd71a --- /dev/null +++ b/common/.taprc @@ -0,0 +1,10 @@ +files: "src/**/*.test.ts" +test-env: NODE_ENV=test +timeout: 120 # might need to be increased +ts: true +check-coverage: true +branches: 80 +functions: 80 +lines: 80 +statements: 80 +reporter: terse \ No newline at end of file diff --git a/common/LICENSE b/common/LICENSE new file mode 100644 index 000000000..ae481f07a --- /dev/null +++ b/common/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 zkldi + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/common/README.md b/common/README.md new file mode 100644 index 000000000..86515059e --- /dev/null +++ b/common/README.md @@ -0,0 +1,3 @@ +# Tachi-common + +Common Types (and code) for Tachi. These are shared between the client and the server implementations. diff --git a/common/package.json b/common/package.json new file mode 100644 index 000000000..36e82ae15 --- /dev/null +++ b/common/package.json @@ -0,0 +1,45 @@ +{ + "name": "tachi-common", + "version": "0.15.2", + "description": "Common Types & Utilities for Tachi", + "main": "js/index.js", + "files": [ + "js/**/*" + ], + "scripts": { + "test": "tap", + "build": "tsc", + "prepublish": "tsc", + "lint": "eslint . --ext .ts --fix" + }, + "author": "zkldi", + "license": "MIT", + "devDependencies": { + "@types/node": "17.0.8", + "@types/tap": "15.0.3", + "eslint": "8", + "eslint-plugin-cadence": "0.0.3", + "tap": "15.0.9", + "ts-node": "10.0.0", + "typescript": "4.6.3" + }, + "dependencies": { + "@types/express-serve-static-core": "^4.17.28", + "@types/mongodb": "3.6.18", + "prudence": "^0.9.8" + }, + "nyc": { + "reporter": [ + "html", + "lcov" + ], + "all": true, + "include": [ + "src/*/!(test-utils)/**/*.ts" + ], + "exclude": [ + "src/**/*.test.ts", + "src/external/mongo/schemas.ts" + ] + } +} \ No newline at end of file diff --git a/common/pnpm-lock.yaml b/common/pnpm-lock.yaml new file mode 100644 index 000000000..e6dc7bf60 --- /dev/null +++ b/common/pnpm-lock.yaml @@ -0,0 +1,3240 @@ +lockfileVersion: 5.3 + +specifiers: + '@types/express-serve-static-core': ^4.17.28 + '@types/mongodb': 3.6.18 + '@types/node': 17.0.8 + '@types/tap': 15.0.3 + eslint: '8' + eslint-plugin-cadence: 0.0.3 + prudence: ^0.9.8 + tap: 15.0.9 + ts-node: 10.0.0 + typescript: 4.6.3 + +dependencies: + '@types/express-serve-static-core': 4.17.28 + '@types/mongodb': 3.6.18 + prudence: 0.9.8 + +devDependencies: + '@types/node': 17.0.8 + '@types/tap': 15.0.3 + eslint: 8.18.0 + eslint-plugin-cadence: 0.0.3_eslint@8.18.0+typescript@4.6.3 + tap: 15.0.9_ts-node@10.0.0+typescript@4.6.3 + ts-node: 10.0.0_22ec373cc6e5c139fea80a5e1bbc5c61 + typescript: 4.6.3 + +packages: + + /@babel/code-frame/7.14.5: + resolution: {integrity: sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/highlight': 7.14.5 + dev: true + + /@babel/compat-data/7.14.5: + resolution: {integrity: sha512-kixrYn4JwfAVPa0f2yfzc2AWti6WRRyO3XjWW5PJAvtE11qhSayrrcrEnee05KAtNaPC+EwehE8Qt1UedEVB8w==} + engines: {node: '>=6.9.0'} + dev: true + + /@babel/core/7.14.6: + resolution: {integrity: sha512-gJnOEWSqTk96qG5BoIrl5bVtc23DCycmIePPYnamY9RboYdI4nFy5vAQMSl81O5K/W0sLDWfGysnOECC+KUUCA==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/code-frame': 7.14.5 + '@babel/generator': 7.14.5 + '@babel/helper-compilation-targets': 7.14.5_@babel+core@7.14.6 + '@babel/helper-module-transforms': 7.14.5 + '@babel/helpers': 7.14.6 + '@babel/parser': 7.14.6 + '@babel/template': 7.14.5 + '@babel/traverse': 7.14.5 + '@babel/types': 7.14.5 + convert-source-map: 1.8.0 + debug: 4.3.1 + gensync: 1.0.0-beta.2 + json5: 2.2.0 + semver: 6.3.0 + source-map: 0.5.7 + transitivePeerDependencies: + - supports-color + dev: true + + /@babel/generator/7.14.5: + resolution: {integrity: sha512-y3rlP+/G25OIX3mYKKIOlQRcqj7YgrvHxOLbVmyLJ9bPmi5ttvUmpydVjcFjZphOktWuA7ovbx91ECloWTfjIA==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.14.5 + jsesc: 2.5.2 + source-map: 0.5.7 + dev: true + + /@babel/helper-annotate-as-pure/7.14.5: + resolution: {integrity: sha512-EivH9EgBIb+G8ij1B2jAwSH36WnGvkQSEC6CkX/6v6ZFlw5fVOHvsgGF4uiEHO2GzMvunZb6tDLQEQSdrdocrA==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.14.5 + dev: true + + /@babel/helper-compilation-targets/7.14.5_@babel+core@7.14.6: + resolution: {integrity: sha512-v+QtZqXEiOnpO6EYvlImB6zCD2Lel06RzOPzmkz/D/XgQiUu3C/Jb1LOqSt/AIA34TYi/Q+KlT8vTQrgdxkbLw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/compat-data': 7.14.5 + '@babel/core': 7.14.6 + '@babel/helper-validator-option': 7.14.5 + browserslist: 4.16.6 + semver: 6.3.0 + dev: true + + /@babel/helper-function-name/7.14.5: + resolution: {integrity: sha512-Gjna0AsXWfFvrAuX+VKcN/aNNWonizBj39yGwUzVDVTlMYJMK2Wp6xdpy72mfArFq5uK+NOuexfzZlzI1z9+AQ==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/helper-get-function-arity': 7.14.5 + '@babel/template': 7.14.5 + '@babel/types': 7.14.5 + dev: true + + /@babel/helper-get-function-arity/7.14.5: + resolution: {integrity: sha512-I1Db4Shst5lewOM4V+ZKJzQ0JGGaZ6VY1jYvMghRjqs6DWgxLCIyFt30GlnKkfUeFLpJt2vzbMVEXVSXlIFYUg==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.14.5 + dev: true + + /@babel/helper-hoist-variables/7.14.5: + resolution: {integrity: sha512-R1PXiz31Uc0Vxy4OEOm07x0oSjKAdPPCh3tPivn/Eo8cvz6gveAeuyUUPB21Hoiif0uoPQSSdhIPS3352nvdyQ==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.14.5 + dev: true + + /@babel/helper-member-expression-to-functions/7.14.5: + resolution: {integrity: sha512-UxUeEYPrqH1Q/k0yRku1JE7dyfyehNwT6SVkMHvYvPDv4+uu627VXBckVj891BO8ruKBkiDoGnZf4qPDD8abDQ==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.14.5 + dev: true + + /@babel/helper-module-imports/7.14.5: + resolution: {integrity: sha512-SwrNHu5QWS84XlHwGYPDtCxcA0hrSlL2yhWYLgeOc0w7ccOl2qv4s/nARI0aYZW+bSwAL5CukeXA47B/1NKcnQ==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.14.5 + dev: true + + /@babel/helper-module-transforms/7.14.5: + resolution: {integrity: sha512-iXpX4KW8LVODuAieD7MzhNjmM6dzYY5tfRqT+R9HDXWl0jPn/djKmA+G9s/2C2T9zggw5tK1QNqZ70USfedOwA==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/helper-module-imports': 7.14.5 + '@babel/helper-replace-supers': 7.14.5 + '@babel/helper-simple-access': 7.14.5 + '@babel/helper-split-export-declaration': 7.14.5 + '@babel/helper-validator-identifier': 7.14.5 + '@babel/template': 7.14.5 + '@babel/traverse': 7.14.5 + '@babel/types': 7.14.5 + transitivePeerDependencies: + - supports-color + dev: true + + /@babel/helper-optimise-call-expression/7.14.5: + resolution: {integrity: sha512-IqiLIrODUOdnPU9/F8ib1Fx2ohlgDhxnIDU7OEVi+kAbEZcyiF7BLU8W6PfvPi9LzztjS7kcbzbmL7oG8kD6VA==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.14.5 + dev: true + + /@babel/helper-plugin-utils/7.14.5: + resolution: {integrity: sha512-/37qQCE3K0vvZKwoK4XU/irIJQdIfCJuhU5eKnNxpFDsOkgFaUAwbv+RYw6eYgsC0E4hS7r5KqGULUogqui0fQ==} + engines: {node: '>=6.9.0'} + dev: true + + /@babel/helper-replace-supers/7.14.5: + resolution: {integrity: sha512-3i1Qe9/8x/hCHINujn+iuHy+mMRLoc77b2nI9TB0zjH1hvn9qGlXjWlggdwUcju36PkPCy/lpM7LLUdcTyH4Ow==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/helper-member-expression-to-functions': 7.14.5 + '@babel/helper-optimise-call-expression': 7.14.5 + '@babel/traverse': 7.14.5 + '@babel/types': 7.14.5 + transitivePeerDependencies: + - supports-color + dev: true + + /@babel/helper-simple-access/7.14.5: + resolution: {integrity: sha512-nfBN9xvmCt6nrMZjfhkl7i0oTV3yxR4/FztsbOASyTvVcoYd0TRHh7eMLdlEcCqobydC0LAF3LtC92Iwxo0wyw==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.14.5 + dev: true + + /@babel/helper-split-export-declaration/7.14.5: + resolution: {integrity: sha512-hprxVPu6e5Kdp2puZUmvOGjaLv9TCe58E/Fl6hRq4YiVQxIcNvuq6uTM2r1mT/oPskuS9CgR+I94sqAYv0NGKA==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.14.5 + dev: true + + /@babel/helper-validator-identifier/7.14.5: + resolution: {integrity: sha512-5lsetuxCLilmVGyiLEfoHBRX8UCFD+1m2x3Rj97WrW3V7H3u4RWRXA4evMjImCsin2J2YT0QaVDGf+z8ondbAg==} + engines: {node: '>=6.9.0'} + dev: true + + /@babel/helper-validator-option/7.14.5: + resolution: {integrity: sha512-OX8D5eeX4XwcroVW45NMvoYaIuFI+GQpA2a8Gi+X/U/cDUIRsV37qQfF905F0htTRCREQIB4KqPeaveRJUl3Ow==} + engines: {node: '>=6.9.0'} + dev: true + + /@babel/helpers/7.14.6: + resolution: {integrity: sha512-yesp1ENQBiLI+iYHSJdoZKUtRpfTlL1grDIX9NRlAVppljLw/4tTyYupIB7uIYmC3stW/imAv8EqaKaS/ibmeA==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/template': 7.14.5 + '@babel/traverse': 7.14.5 + '@babel/types': 7.14.5 + transitivePeerDependencies: + - supports-color + dev: true + + /@babel/highlight/7.14.5: + resolution: {integrity: sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/helper-validator-identifier': 7.14.5 + chalk: 2.4.2 + js-tokens: 4.0.0 + dev: true + + /@babel/parser/7.14.6: + resolution: {integrity: sha512-oG0ej7efjEXxb4UgE+klVx+3j4MVo+A2vCzm7OUN4CLo6WhQ+vSOD2yJ8m7B+DghObxtLxt3EfgMWpq+AsWehQ==} + engines: {node: '>=6.0.0'} + hasBin: true + dev: true + + /@babel/plugin-proposal-object-rest-spread/7.14.5_@babel+core@7.14.6: + resolution: {integrity: sha512-VzMyY6PWNPPT3pxc5hi9LloKNr4SSrVCg7Yr6aZpW4Ym07r7KqSU/QXYwjXLVxqwSv0t/XSXkFoKBPUkZ8vb2A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/compat-data': 7.14.5 + '@babel/core': 7.14.6 + '@babel/helper-compilation-targets': 7.14.5_@babel+core@7.14.6 + '@babel/helper-plugin-utils': 7.14.5 + '@babel/plugin-syntax-object-rest-spread': 7.8.3_@babel+core@7.14.6 + '@babel/plugin-transform-parameters': 7.14.5_@babel+core@7.14.6 + dev: true + + /@babel/plugin-syntax-jsx/7.14.5_@babel+core@7.14.6: + resolution: {integrity: sha512-ohuFIsOMXJnbOMRfX7/w7LocdR6R7whhuRD4ax8IipLcLPlZGJKkBxgHp++U4N/vKyU16/YDQr2f5seajD3jIw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.14.6 + '@babel/helper-plugin-utils': 7.14.5 + dev: true + + /@babel/plugin-syntax-object-rest-spread/7.8.3_@babel+core@7.14.6: + resolution: {integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.14.6 + '@babel/helper-plugin-utils': 7.14.5 + dev: true + + /@babel/plugin-transform-destructuring/7.14.5_@babel+core@7.14.6: + resolution: {integrity: sha512-wU9tYisEbRMxqDezKUqC9GleLycCRoUsai9ddlsq54r8QRLaeEhc+d+9DqCG+kV9W2GgQjTZESPTpn5bAFMDww==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.14.6 + '@babel/helper-plugin-utils': 7.14.5 + dev: true + + /@babel/plugin-transform-parameters/7.14.5_@babel+core@7.14.6: + resolution: {integrity: sha512-Tl7LWdr6HUxTmzQtzuU14SqbgrSKmaR77M0OKyq4njZLQTPfOvzblNKyNkGwOfEFCEx7KeYHQHDI0P3F02IVkA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.14.6 + '@babel/helper-plugin-utils': 7.14.5 + dev: true + + /@babel/plugin-transform-react-jsx/7.14.5_@babel+core@7.14.6: + resolution: {integrity: sha512-7RylxNeDnxc1OleDm0F5Q/BSL+whYRbOAR+bwgCxIr0L32v7UFh/pz1DLMZideAUxKT6eMoS2zQH6fyODLEi8Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.14.6 + '@babel/helper-annotate-as-pure': 7.14.5 + '@babel/helper-module-imports': 7.14.5 + '@babel/helper-plugin-utils': 7.14.5 + '@babel/plugin-syntax-jsx': 7.14.5_@babel+core@7.14.6 + '@babel/types': 7.14.5 + dev: true + + /@babel/template/7.14.5: + resolution: {integrity: sha512-6Z3Po85sfxRGachLULUhOmvAaOo7xCvqGQtxINai2mEGPFm6pQ4z5QInFnUrRpfoSV60BnjyF5F3c+15fxFV1g==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/code-frame': 7.14.5 + '@babel/parser': 7.14.6 + '@babel/types': 7.14.5 + dev: true + + /@babel/traverse/7.14.5: + resolution: {integrity: sha512-G3BiS15vevepdmFqmUc9X+64y0viZYygubAMO8SvBmKARuF6CPSZtH4Ng9vi/lrWlZFGe3FWdXNy835akH8Glg==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/code-frame': 7.14.5 + '@babel/generator': 7.14.5 + '@babel/helper-function-name': 7.14.5 + '@babel/helper-hoist-variables': 7.14.5 + '@babel/helper-split-export-declaration': 7.14.5 + '@babel/parser': 7.14.6 + '@babel/types': 7.14.5 + debug: 4.3.1 + globals: 11.12.0 + transitivePeerDependencies: + - supports-color + dev: true + + /@babel/types/7.14.5: + resolution: {integrity: sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/helper-validator-identifier': 7.14.5 + to-fast-properties: 2.0.0 + dev: true + + /@eslint/eslintrc/1.3.0: + resolution: {integrity: sha512-UWW0TMTmk2d7hLcWD1/e2g5HDM/HQ3csaLSqXCfqwh4uNDuNqlaKWXmEsL4Cs41Z0KnILNvwbHAah3C2yt06kw==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + ajv: 6.12.6 + debug: 4.3.4 + espree: 9.3.2 + globals: 13.15.0 + ignore: 5.2.0 + import-fresh: 3.3.0 + js-yaml: 4.1.0 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + dev: true + + /@humanwhocodes/config-array/0.9.5: + resolution: {integrity: sha512-ObyMyWxZiCu/yTisA7uzx81s40xR2fD5Cg/2Kq7G02ajkNubJf6BopgDTmDyc3U7sXpNKM8cYOw7s7Tyr+DnCw==} + engines: {node: '>=10.10.0'} + dependencies: + '@humanwhocodes/object-schema': 1.2.1 + debug: 4.3.4 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + dev: true + + /@humanwhocodes/object-schema/1.2.1: + resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==} + dev: true + + /@istanbuljs/load-nyc-config/1.1.0: + resolution: {integrity: sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==} + engines: {node: '>=8'} + dependencies: + camelcase: 5.3.1 + find-up: 4.1.0 + get-package-type: 0.1.0 + js-yaml: 3.14.1 + resolve-from: 5.0.0 + dev: true + + /@istanbuljs/schema/0.1.3: + resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==} + engines: {node: '>=8'} + dev: true + + /@nodelib/fs.scandir/2.1.5: + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + dev: true + + /@nodelib/fs.stat/2.0.5: + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + dev: true + + /@nodelib/fs.walk/1.2.8: + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.13.0 + dev: true + + /@tsconfig/node10/1.0.8: + resolution: {integrity: sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==} + dev: true + + /@tsconfig/node12/1.0.9: + resolution: {integrity: sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw==} + dev: true + + /@tsconfig/node14/1.0.1: + resolution: {integrity: sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==} + dev: true + + /@tsconfig/node16/1.0.1: + resolution: {integrity: sha512-FTgBI767POY/lKNDNbIzgAX6miIDBs6NTCbdlDb8TrWovHsSvaVIZDlTqym29C6UqhzwcJx4CYr+AlrMywA0cA==} + dev: true + + /@types/bson/4.0.3: + resolution: {integrity: sha512-mVRvYnTOZJz3ccpxhr3wgxVmSeiYinW+zlzQz3SXWaJmD1DuL05Jeq7nKw3SnbKmbleW5qrLG5vdyWe/A9sXhw==} + dependencies: + '@types/node': 17.0.8 + dev: false + + /@types/express-serve-static-core/4.17.28: + resolution: {integrity: sha512-P1BJAEAW3E2DJUlkgq4tOL3RyMunoWXqbSCygWo5ZIWTjUgN1YnaXWW4VWl/oc8vs/XoYibEGBKP0uZyF4AHig==} + dependencies: + '@types/node': 17.0.8 + '@types/qs': 6.9.7 + '@types/range-parser': 1.2.4 + dev: false + + /@types/json-schema/7.0.11: + resolution: {integrity: sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==} + dev: true + + /@types/json5/0.0.29: + resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} + dev: true + + /@types/mongodb/3.6.18: + resolution: {integrity: sha512-JSVFt9p0rTfZ4EgzXmVHUB3ue00xe3CRbQho8nXfImzEDDM4O7I3po1bwbWl/EIbLENxUreZxqLOc8lvcnLVPA==} + dependencies: + '@types/bson': 4.0.3 + '@types/node': 17.0.8 + dev: false + + /@types/node/17.0.8: + resolution: {integrity: sha512-YofkM6fGv4gDJq78g4j0mMuGMkZVxZDgtU0JRdx6FgiJDG+0fY0GKVolOV8WqVmEhLCXkQRjwDdKyPxJp/uucg==} + + /@types/qs/6.9.7: + resolution: {integrity: sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==} + dev: false + + /@types/range-parser/1.2.4: + resolution: {integrity: sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==} + dev: false + + /@types/tap/15.0.3: + resolution: {integrity: sha512-usOLtmMm1jfjPdkt9R9HMTU/J6m9QNYjgFs5U2tFiOzQSd7lDd/aAMprExYLnJnZb6tM4ujmGv7ITe+1smIRgg==} + dependencies: + '@types/node': 17.0.8 + dev: true + + /@typescript-eslint/eslint-plugin/5.22.0_3692bfa38c76dfb409a7f758aafdbd9d: + resolution: {integrity: sha512-YCiy5PUzpAeOPGQ7VSGDEY2NeYUV1B0swde2e0HzokRsHBYjSdF6DZ51OuRZxVPHx0032lXGLvOMls91D8FXlg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + '@typescript-eslint/parser': ^5.0.0 + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/parser': 5.22.0_eslint@8.18.0+typescript@4.6.3 + '@typescript-eslint/scope-manager': 5.22.0 + '@typescript-eslint/type-utils': 5.22.0_eslint@8.18.0+typescript@4.6.3 + '@typescript-eslint/utils': 5.22.0_eslint@8.18.0+typescript@4.6.3 + debug: 4.3.4 + eslint: 8.18.0 + functional-red-black-tree: 1.0.1 + ignore: 5.2.0 + regexpp: 3.2.0 + semver: 7.3.7 + tsutils: 3.21.0_typescript@4.6.3 + typescript: 4.6.3 + transitivePeerDependencies: + - supports-color + dev: true + + /@typescript-eslint/parser/5.22.0_eslint@8.18.0+typescript@4.6.3: + resolution: {integrity: sha512-piwC4krUpRDqPaPbFaycN70KCP87+PC5WZmrWs+DlVOxxmF+zI6b6hETv7Quy4s9wbkV16ikMeZgXsvzwI3icQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/scope-manager': 5.22.0 + '@typescript-eslint/types': 5.22.0 + '@typescript-eslint/typescript-estree': 5.22.0_typescript@4.6.3 + debug: 4.3.4 + eslint: 8.18.0 + typescript: 4.6.3 + transitivePeerDependencies: + - supports-color + dev: true + + /@typescript-eslint/scope-manager/5.22.0: + resolution: {integrity: sha512-yA9G5NJgV5esANJCO0oF15MkBO20mIskbZ8ijfmlKIvQKg0ynVKfHZ15/nhAJN5m8Jn3X5qkwriQCiUntC9AbA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + '@typescript-eslint/types': 5.22.0 + '@typescript-eslint/visitor-keys': 5.22.0 + dev: true + + /@typescript-eslint/type-utils/5.22.0_eslint@8.18.0+typescript@4.6.3: + resolution: {integrity: sha512-iqfLZIsZhK2OEJ4cQ01xOq3NaCuG5FQRKyHicA3xhZxMgaxQazLUHbH/B2k9y5i7l3+o+B5ND9Mf1AWETeMISA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: '*' + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/utils': 5.22.0_eslint@8.18.0+typescript@4.6.3 + debug: 4.3.4 + eslint: 8.18.0 + tsutils: 3.21.0_typescript@4.6.3 + typescript: 4.6.3 + transitivePeerDependencies: + - supports-color + dev: true + + /@typescript-eslint/types/5.22.0: + resolution: {integrity: sha512-T7owcXW4l0v7NTijmjGWwWf/1JqdlWiBzPqzAWhobxft0SiEvMJB56QXmeCQjrPuM8zEfGUKyPQr/L8+cFUBLw==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dev: true + + /@typescript-eslint/typescript-estree/5.22.0_typescript@4.6.3: + resolution: {integrity: sha512-EyBEQxvNjg80yinGE2xdhpDYm41so/1kOItl0qrjIiJ1kX/L/L8WWGmJg8ni6eG3DwqmOzDqOhe6763bF92nOw==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/types': 5.22.0 + '@typescript-eslint/visitor-keys': 5.22.0 + debug: 4.3.4 + globby: 11.1.0 + is-glob: 4.0.3 + semver: 7.3.7 + tsutils: 3.21.0_typescript@4.6.3 + typescript: 4.6.3 + transitivePeerDependencies: + - supports-color + dev: true + + /@typescript-eslint/utils/5.22.0_eslint@8.18.0+typescript@4.6.3: + resolution: {integrity: sha512-HodsGb037iobrWSUMS7QH6Hl1kppikjA1ELiJlNSTYf/UdMEwzgj0WIp+lBNb6WZ3zTwb0tEz51j0Wee3iJ3wQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + dependencies: + '@types/json-schema': 7.0.11 + '@typescript-eslint/scope-manager': 5.22.0 + '@typescript-eslint/types': 5.22.0 + '@typescript-eslint/typescript-estree': 5.22.0_typescript@4.6.3 + eslint: 8.18.0 + eslint-scope: 5.1.1 + eslint-utils: 3.0.0_eslint@8.18.0 + transitivePeerDependencies: + - supports-color + - typescript + dev: true + + /@typescript-eslint/visitor-keys/5.22.0: + resolution: {integrity: sha512-DbgTqn2Dv5RFWluG88tn0pP6Ex0ROF+dpDO1TNNZdRtLjUr6bdznjA6f/qNqJLjd2PgguAES2Zgxh/JzwzETDg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + '@typescript-eslint/types': 5.22.0 + eslint-visitor-keys: 3.3.0 + dev: true + + /acorn-jsx/5.3.2_acorn@8.7.1: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + dependencies: + acorn: 8.7.1 + dev: true + + /acorn/8.7.1: + resolution: {integrity: sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==} + engines: {node: '>=0.4.0'} + hasBin: true + dev: true + + /aggregate-error/3.1.0: + resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} + engines: {node: '>=8'} + dependencies: + clean-stack: 2.2.0 + indent-string: 4.0.0 + dev: true + + /ajv/6.12.6: + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + dev: true + + /ansi-regex/2.1.1: + resolution: {integrity: sha1-w7M6te42DYbg5ijwRorn7yfWVN8=} + engines: {node: '>=0.10.0'} + dev: true + + /ansi-regex/3.0.0: + resolution: {integrity: sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=} + engines: {node: '>=4'} + dev: true + + /ansi-regex/5.0.0: + resolution: {integrity: sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==} + engines: {node: '>=8'} + dev: true + + /ansi-regex/5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + dev: true + + /ansi-styles/3.2.1: + resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} + engines: {node: '>=4'} + dependencies: + color-convert: 1.9.3 + dev: true + + /ansi-styles/4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + dependencies: + color-convert: 2.0.1 + dev: true + + /anymatch/3.1.2: + resolution: {integrity: sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==} + engines: {node: '>= 8'} + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.0 + dev: true + + /append-transform/2.0.0: + resolution: {integrity: sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg==} + engines: {node: '>=8'} + dependencies: + default-require-extensions: 3.0.0 + dev: true + + /archy/1.0.0: + resolution: {integrity: sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=} + dev: true + + /arg/4.1.3: + resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} + dev: true + + /argparse/1.0.10: + resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} + dependencies: + sprintf-js: 1.0.3 + dev: true + + /argparse/2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + dev: true + + /array-includes/3.1.5: + resolution: {integrity: sha512-iSDYZMMyTPkiFasVqfuAQnWAYcvO/SeBSCGKePoEthjp4LEMTe4uLc7b025o4jAZpHhihh8xPo99TNWUWWkGDQ==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.1.4 + es-abstract: 1.20.1 + get-intrinsic: 1.1.2 + is-string: 1.0.7 + dev: true + + /array-union/2.1.0: + resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} + engines: {node: '>=8'} + dev: true + + /array.prototype.flat/1.3.0: + resolution: {integrity: sha512-12IUEkHsAhA4DY5s0FPgNXIdc8VRSqD9Zp78a5au9abH/SOBrsp082JOWFNTjkMozh8mqcdiKuaLGhPeYztxSw==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.1.4 + es-abstract: 1.20.1 + es-shim-unscopables: 1.0.0 + dev: true + + /asn1/0.2.4: + resolution: {integrity: sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==} + dependencies: + safer-buffer: 2.1.2 + dev: true + + /assert-plus/1.0.0: + resolution: {integrity: sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=} + engines: {node: '>=0.8'} + dev: true + + /async-hook-domain/2.0.3: + resolution: {integrity: sha512-MadiLLDEZRZzZwcm0dgS+K99qXZ4H2saAUwUgwzFulbAkXrKi3AX5FvWS3FFTQtLMwrqcGqAJe6o12KrObejQA==} + engines: {node: '>=10'} + dev: true + + /asynckit/0.4.0: + resolution: {integrity: sha1-x57Zf380y48robyXkLzDZkdLS3k=} + dev: true + + /aws-sign2/0.7.0: + resolution: {integrity: sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=} + dev: true + + /aws4/1.11.0: + resolution: {integrity: sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==} + dev: true + + /balanced-match/1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + dev: true + + /bcrypt-pbkdf/1.0.2: + resolution: {integrity: sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=} + dependencies: + tweetnacl: 0.14.5 + dev: true + + /binary-extensions/2.2.0: + resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} + engines: {node: '>=8'} + dev: true + + /bind-obj-methods/3.0.0: + resolution: {integrity: sha512-nLEaaz3/sEzNSyPWRsN9HNsqwk1AUyECtGj+XwGdIi3xABnEqecvXtIJ0wehQXuuER5uZ/5fTs2usONgYjG+iw==} + engines: {node: '>=10'} + dev: true + + /brace-expansion/1.1.11: + resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + dev: true + + /braces/3.0.2: + resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} + engines: {node: '>=8'} + dependencies: + fill-range: 7.0.1 + dev: true + + /browserslist/4.16.6: + resolution: {integrity: sha512-Wspk/PqO+4W9qp5iUTJsa1B/QrYn1keNCcEP5OvP7WBwT4KaDly0uONYmC6Xa3Z5IqnUgS0KcgLYu1l74x0ZXQ==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + dependencies: + caniuse-lite: 1.0.30001238 + colorette: 1.2.2 + electron-to-chromium: 1.3.752 + escalade: 3.1.1 + node-releases: 1.1.73 + dev: true + + /buffer-from/1.1.1: + resolution: {integrity: sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==} + dev: true + + /caching-transform/4.0.0: + resolution: {integrity: sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA==} + engines: {node: '>=8'} + dependencies: + hasha: 5.2.2 + make-dir: 3.1.0 + package-hash: 4.0.0 + write-file-atomic: 3.0.3 + dev: true + + /call-bind/1.0.2: + resolution: {integrity: sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==} + dependencies: + function-bind: 1.1.1 + get-intrinsic: 1.1.2 + dev: true + + /caller-callsite/2.0.0: + resolution: {integrity: sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ=} + engines: {node: '>=4'} + dependencies: + callsites: 2.0.0 + dev: true + + /caller-path/2.0.0: + resolution: {integrity: sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ=} + engines: {node: '>=4'} + dependencies: + caller-callsite: 2.0.0 + dev: true + + /callsites/2.0.0: + resolution: {integrity: sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=} + engines: {node: '>=4'} + dev: true + + /callsites/3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + dev: true + + /camelcase/5.3.1: + resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} + engines: {node: '>=6'} + dev: true + + /caniuse-lite/1.0.30001238: + resolution: {integrity: sha512-bZGam2MxEt7YNsa2VwshqWQMwrYs5tR5WZQRYSuFxsBQunWjBuXhN4cS9nV5FFb1Z9y+DoQcQ0COyQbv6A+CKw==} + dev: true + + /caseless/0.12.0: + resolution: {integrity: sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=} + dev: true + + /chalk/2.4.2: + resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} + engines: {node: '>=4'} + dependencies: + ansi-styles: 3.2.1 + escape-string-regexp: 1.0.5 + supports-color: 5.5.0 + dev: true + + /chalk/4.1.1: + resolution: {integrity: sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==} + engines: {node: '>=10'} + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + dev: true + + /chokidar/3.5.2: + resolution: {integrity: sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==} + engines: {node: '>= 8.10.0'} + dependencies: + anymatch: 3.1.2 + braces: 3.0.2 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.1 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.2 + dev: true + + /clean-stack/2.2.0: + resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} + engines: {node: '>=6'} + dev: true + + /cliui/4.1.0: + resolution: {integrity: sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==} + dependencies: + string-width: 2.1.1 + strip-ansi: 4.0.0 + wrap-ansi: 2.1.0 + dev: true + + /cliui/6.0.0: + resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==} + dependencies: + string-width: 4.2.2 + strip-ansi: 6.0.0 + wrap-ansi: 6.2.0 + dev: true + + /code-point-at/1.1.0: + resolution: {integrity: sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=} + engines: {node: '>=0.10.0'} + dev: true + + /color-convert/1.9.3: + resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} + dependencies: + color-name: 1.1.3 + dev: true + + /color-convert/2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + dependencies: + color-name: 1.1.4 + dev: true + + /color-name/1.1.3: + resolution: {integrity: sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=} + dev: true + + /color-name/1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + dev: true + + /color-support/1.1.3: + resolution: {integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==} + hasBin: true + dev: true + + /colorette/1.2.2: + resolution: {integrity: sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==} + dev: true + + /combined-stream/1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + dependencies: + delayed-stream: 1.0.0 + dev: true + + /commondir/1.0.1: + resolution: {integrity: sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=} + dev: true + + /concat-map/0.0.1: + resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=} + dev: true + + /convert-source-map/1.8.0: + resolution: {integrity: sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==} + dependencies: + safe-buffer: 5.1.2 + dev: true + + /core-util-is/1.0.2: + resolution: {integrity: sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=} + dev: true + + /coveralls/3.1.0: + resolution: {integrity: sha512-sHxOu2ELzW8/NC1UP5XVLbZDzO4S3VxfFye3XYCznopHy02YjNkHcj5bKaVw2O7hVaBdBjEdQGpie4II1mWhuQ==} + engines: {node: '>=6'} + hasBin: true + dependencies: + js-yaml: 3.14.1 + lcov-parse: 1.0.0 + log-driver: 1.2.7 + minimist: 1.2.5 + request: 2.88.2 + dev: true + + /create-require/1.1.1: + resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} + dev: true + + /cross-spawn/7.0.3: + resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} + engines: {node: '>= 8'} + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + dev: true + + /dashdash/1.14.1: + resolution: {integrity: sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=} + engines: {node: '>=0.10'} + dependencies: + assert-plus: 1.0.0 + dev: true + + /debug/2.6.9: + resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} + dependencies: + ms: 2.0.0 + dev: true + + /debug/3.2.7: + resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} + dependencies: + ms: 2.1.2 + dev: true + + /debug/4.3.1: + resolution: {integrity: sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + dependencies: + ms: 2.1.2 + dev: true + + /debug/4.3.4: + resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + dependencies: + ms: 2.1.2 + dev: true + + /decamelize/1.2.0: + resolution: {integrity: sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=} + engines: {node: '>=0.10.0'} + dev: true + + /deep-is/0.1.3: + resolution: {integrity: sha512-GtxAN4HvBachZzm4OnWqc45ESpUCMwkYcsjnsPs23FwJbsO+k4t0k9bQCgOmzIlpHO28+WPK/KRbRk0DDHuuDw==} + dev: true + + /default-require-extensions/3.0.0: + resolution: {integrity: sha512-ek6DpXq/SCpvjhpFsLFRVtIxJCRw6fUR42lYMVZuUMK7n8eMz4Uh5clckdBjEpLhn/gEBZo7hDJnJcwdKLKQjg==} + engines: {node: '>=8'} + dependencies: + strip-bom: 4.0.0 + dev: true + + /define-properties/1.1.4: + resolution: {integrity: sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==} + engines: {node: '>= 0.4'} + dependencies: + has-property-descriptors: 1.0.0 + object-keys: 1.1.1 + dev: true + + /delayed-stream/1.0.0: + resolution: {integrity: sha1-3zrhmayt+31ECqrgsp4icrJOxhk=} + engines: {node: '>=0.4.0'} + dev: true + + /diff/4.0.2: + resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} + engines: {node: '>=0.3.1'} + dev: true + + /dir-glob/3.0.1: + resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} + engines: {node: '>=8'} + dependencies: + path-type: 4.0.0 + dev: true + + /doctrine/2.1.0: + resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} + engines: {node: '>=0.10.0'} + dependencies: + esutils: 2.0.3 + dev: true + + /doctrine/3.0.0: + resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} + engines: {node: '>=6.0.0'} + dependencies: + esutils: 2.0.3 + dev: true + + /ecc-jsbn/0.1.2: + resolution: {integrity: sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=} + dependencies: + jsbn: 0.1.1 + safer-buffer: 2.1.2 + dev: true + + /electron-to-chromium/1.3.752: + resolution: {integrity: sha512-2Tg+7jSl3oPxgsBsWKh5H83QazTkmWG/cnNwJplmyZc7KcN61+I10oUgaXSVk/NwfvN3BdkKDR4FYuRBQQ2v0A==} + dev: true + + /emoji-regex/8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + dev: true + + /es-abstract/1.20.1: + resolution: {integrity: sha512-WEm2oBhfoI2sImeM4OF2zE2V3BYdSF+KnSi9Sidz51fQHd7+JuF8Xgcj9/0o+OWeIeIS/MiuNnlruQrJf16GQA==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + es-to-primitive: 1.2.1 + function-bind: 1.1.1 + function.prototype.name: 1.1.5 + get-intrinsic: 1.1.2 + get-symbol-description: 1.0.0 + has: 1.0.3 + has-property-descriptors: 1.0.0 + has-symbols: 1.0.3 + internal-slot: 1.0.3 + is-callable: 1.2.4 + is-negative-zero: 2.0.2 + is-regex: 1.1.4 + is-shared-array-buffer: 1.0.2 + is-string: 1.0.7 + is-weakref: 1.0.2 + object-inspect: 1.12.2 + object-keys: 1.1.1 + object.assign: 4.1.2 + regexp.prototype.flags: 1.4.3 + string.prototype.trimend: 1.0.5 + string.prototype.trimstart: 1.0.5 + unbox-primitive: 1.0.2 + dev: true + + /es-shim-unscopables/1.0.0: + resolution: {integrity: sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==} + dependencies: + has: 1.0.3 + dev: true + + /es-to-primitive/1.2.1: + resolution: {integrity: sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==} + engines: {node: '>= 0.4'} + dependencies: + is-callable: 1.2.4 + is-date-object: 1.0.5 + is-symbol: 1.0.4 + dev: true + + /es6-error/4.1.1: + resolution: {integrity: sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==} + dev: true + + /escalade/3.1.1: + resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==} + engines: {node: '>=6'} + dev: true + + /escape-string-regexp/1.0.5: + resolution: {integrity: sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=} + engines: {node: '>=0.8.0'} + dev: true + + /escape-string-regexp/2.0.0: + resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==} + engines: {node: '>=8'} + dev: true + + /escape-string-regexp/4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + dev: true + + /eslint-config-prettier/8.5.0_eslint@8.18.0: + resolution: {integrity: sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q==} + hasBin: true + peerDependencies: + eslint: '>=7.0.0' + dependencies: + eslint: 8.18.0 + dev: true + + /eslint-import-resolver-node/0.3.6: + resolution: {integrity: sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw==} + dependencies: + debug: 3.2.7 + resolve: 1.22.1 + dev: true + + /eslint-module-utils/2.7.3: + resolution: {integrity: sha512-088JEC7O3lDZM9xGe0RerkOMd0EjFl+Yvd1jPWIkMT5u3H9+HC34mWWPnqPrN13gieT9pBOO+Qt07Nb/6TresQ==} + engines: {node: '>=4'} + dependencies: + debug: 3.2.7 + find-up: 2.1.0 + dev: true + + /eslint-plugin-cadence/0.0.3_eslint@8.18.0+typescript@4.6.3: + resolution: {integrity: sha512-RQz1d4wU2UZe3Ztt8UPCZffvxZFuL6WkleIxcR5yW3KTwilXOePjpmJR69989L2IIlN+JcKlPjtPPairUV1E9g==} + engines: {node: 12.x || 14.x || >= 16} + peerDependencies: + eslint: '>=8' + dependencies: + '@typescript-eslint/eslint-plugin': 5.22.0_3692bfa38c76dfb409a7f758aafdbd9d + '@typescript-eslint/parser': 5.22.0_eslint@8.18.0+typescript@4.6.3 + eslint: 8.18.0 + eslint-config-prettier: 8.5.0_eslint@8.18.0 + eslint-plugin-eslint-plugin: 4.1.0_eslint@8.18.0 + eslint-plugin-import: 2.26.0_eslint@8.18.0 + eslint-plugin-node: 11.1.0_eslint@8.18.0 + eslint-plugin-prettier: 4.0.0_ee517d95c7d3128de95595e9379dd540 + prettier: 2.6.2 + transitivePeerDependencies: + - supports-color + - typescript + dev: true + + /eslint-plugin-es/3.0.1_eslint@8.18.0: + resolution: {integrity: sha512-GUmAsJaN4Fc7Gbtl8uOBlayo2DqhwWvEzykMHSCZHU3XdJ+NSzzZcVhXh3VxX5icqQ+oQdIEawXX8xkR3mIFmQ==} + engines: {node: '>=8.10.0'} + peerDependencies: + eslint: '>=4.19.1' + dependencies: + eslint: 8.18.0 + eslint-utils: 2.1.0 + regexpp: 3.2.0 + dev: true + + /eslint-plugin-eslint-plugin/4.1.0_eslint@8.18.0: + resolution: {integrity: sha512-QJVw+WYXJuG2469gx5G929bz7crfxySDlK1i569FkuT6dpeHDeP7MmDrKaswCx17snG25LRFD6wmVX+AO5x7Qg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: '>=6.0.0' + dependencies: + eslint: 8.18.0 + eslint-utils: 3.0.0_eslint@8.18.0 + estraverse: 5.2.0 + dev: true + + /eslint-plugin-import/2.26.0_eslint@8.18.0: + resolution: {integrity: sha512-hYfi3FXaM8WPLf4S1cikh/r4IxnO6zrhZbEGz2b660EJRbuxgpDS5gkCuYgGWg2xxh2rBuIr4Pvhve/7c31koA==} + engines: {node: '>=4'} + peerDependencies: + eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 + dependencies: + array-includes: 3.1.5 + array.prototype.flat: 1.3.0 + debug: 2.6.9 + doctrine: 2.1.0 + eslint: 8.18.0 + eslint-import-resolver-node: 0.3.6 + eslint-module-utils: 2.7.3 + has: 1.0.3 + is-core-module: 2.9.0 + is-glob: 4.0.3 + minimatch: 3.1.2 + object.values: 1.1.5 + resolve: 1.22.1 + tsconfig-paths: 3.14.1 + dev: true + + /eslint-plugin-node/11.1.0_eslint@8.18.0: + resolution: {integrity: sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g==} + engines: {node: '>=8.10.0'} + peerDependencies: + eslint: '>=5.16.0' + dependencies: + eslint: 8.18.0 + eslint-plugin-es: 3.0.1_eslint@8.18.0 + eslint-utils: 2.1.0 + ignore: 5.2.0 + minimatch: 3.1.2 + resolve: 1.22.1 + semver: 6.3.0 + dev: true + + /eslint-plugin-prettier/4.0.0_ee517d95c7d3128de95595e9379dd540: + resolution: {integrity: sha512-98MqmCJ7vJodoQK359bqQWaxOE0CS8paAz/GgjaZLyex4TTk3g9HugoO89EqWCrFiOqn9EVvcoo7gZzONCWVwQ==} + engines: {node: '>=6.0.0'} + peerDependencies: + eslint: '>=7.28.0' + eslint-config-prettier: '*' + prettier: '>=2.0.0' + peerDependenciesMeta: + eslint-config-prettier: + optional: true + dependencies: + eslint: 8.18.0 + eslint-config-prettier: 8.5.0_eslint@8.18.0 + prettier: 2.6.2 + prettier-linter-helpers: 1.0.0 + dev: true + + /eslint-scope/5.1.1: + resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} + engines: {node: '>=8.0.0'} + dependencies: + esrecurse: 4.3.0 + estraverse: 4.3.0 + dev: true + + /eslint-scope/7.1.1: + resolution: {integrity: sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + esrecurse: 4.3.0 + estraverse: 5.2.0 + dev: true + + /eslint-utils/2.1.0: + resolution: {integrity: sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==} + engines: {node: '>=6'} + dependencies: + eslint-visitor-keys: 1.3.0 + dev: true + + /eslint-utils/3.0.0_eslint@8.18.0: + resolution: {integrity: sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==} + engines: {node: ^10.0.0 || ^12.0.0 || >= 14.0.0} + peerDependencies: + eslint: '>=5' + dependencies: + eslint: 8.18.0 + eslint-visitor-keys: 2.0.0 + dev: true + + /eslint-visitor-keys/1.3.0: + resolution: {integrity: sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==} + engines: {node: '>=4'} + dev: true + + /eslint-visitor-keys/2.0.0: + resolution: {integrity: sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ==} + engines: {node: '>=10'} + dev: true + + /eslint-visitor-keys/3.3.0: + resolution: {integrity: sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dev: true + + /eslint/8.18.0: + resolution: {integrity: sha512-As1EfFMVk7Xc6/CvhssHUjsAQSkpfXvUGMFC3ce8JDe6WvqCgRrLOBQbVpsBFr1X1V+RACOadnzVvcUS5ni2bA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + hasBin: true + dependencies: + '@eslint/eslintrc': 1.3.0 + '@humanwhocodes/config-array': 0.9.5 + ajv: 6.12.6 + chalk: 4.1.1 + cross-spawn: 7.0.3 + debug: 4.3.4 + doctrine: 3.0.0 + escape-string-regexp: 4.0.0 + eslint-scope: 7.1.1 + eslint-utils: 3.0.0_eslint@8.18.0 + eslint-visitor-keys: 3.3.0 + espree: 9.3.2 + esquery: 1.4.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 6.0.1 + functional-red-black-tree: 1.0.1 + glob-parent: 6.0.2 + globals: 13.15.0 + ignore: 5.2.0 + import-fresh: 3.3.0 + imurmurhash: 0.1.4 + is-glob: 4.0.1 + js-yaml: 4.1.0 + json-stable-stringify-without-jsonify: 1.0.1 + levn: 0.4.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.1 + regexpp: 3.2.0 + strip-ansi: 6.0.1 + strip-json-comments: 3.1.1 + text-table: 0.2.0 + v8-compile-cache: 2.3.0 + transitivePeerDependencies: + - supports-color + dev: true + + /espree/9.3.2: + resolution: {integrity: sha512-D211tC7ZwouTIuY5x9XnS0E9sWNChB7IYKX/Xp5eQj3nFXhqmiUDB9q27y76oFl8jTg3pXcQx/bpxMfs3CIZbA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + acorn: 8.7.1 + acorn-jsx: 5.3.2_acorn@8.7.1 + eslint-visitor-keys: 3.3.0 + dev: true + + /esprima/4.0.1: + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + engines: {node: '>=4'} + hasBin: true + dev: true + + /esquery/1.4.0: + resolution: {integrity: sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==} + engines: {node: '>=0.10'} + dependencies: + estraverse: 5.2.0 + dev: true + + /esrecurse/4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + dependencies: + estraverse: 5.2.0 + dev: true + + /estraverse/4.3.0: + resolution: {integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==} + engines: {node: '>=4.0'} + dev: true + + /estraverse/5.2.0: + resolution: {integrity: sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==} + engines: {node: '>=4.0'} + dev: true + + /esutils/2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + dev: true + + /events-to-array/1.1.2: + resolution: {integrity: sha1-LUH1Y+H+QA7Uli/hpNXGp1Od9/Y=} + dev: true + + /extend/3.0.2: + resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + dev: true + + /extsprintf/1.3.0: + resolution: {integrity: sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=} + engines: {'0': node >=0.6.0} + dev: true + + /fast-deep-equal/3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + dev: true + + /fast-diff/1.2.0: + resolution: {integrity: sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==} + dev: true + + /fast-glob/3.2.11: + resolution: {integrity: sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==} + engines: {node: '>=8.6.0'} + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.5 + dev: true + + /fast-json-stable-stringify/2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + dev: true + + /fast-levenshtein/2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + dev: true + + /fastq/1.13.0: + resolution: {integrity: sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==} + dependencies: + reusify: 1.0.4 + dev: true + + /file-entry-cache/6.0.1: + resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} + engines: {node: ^10.12.0 || >=12.0.0} + dependencies: + flat-cache: 3.0.4 + dev: true + + /fill-range/7.0.1: + resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} + engines: {node: '>=8'} + dependencies: + to-regex-range: 5.0.1 + dev: true + + /find-cache-dir/3.3.1: + resolution: {integrity: sha512-t2GDMt3oGC/v+BMwzmllWDuJF/xcDtE5j/fCGbqDD7OLuJkj0cfh1YSA5VKPvwMeLFLNDBkwOKZ2X85jGLVftQ==} + engines: {node: '>=8'} + dependencies: + commondir: 1.0.1 + make-dir: 3.1.0 + pkg-dir: 4.2.0 + dev: true + + /find-up/2.1.0: + resolution: {integrity: sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==} + engines: {node: '>=4'} + dependencies: + locate-path: 2.0.0 + dev: true + + /find-up/4.1.0: + resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} + engines: {node: '>=8'} + dependencies: + locate-path: 5.0.0 + path-exists: 4.0.0 + dev: true + + /findit/2.0.0: + resolution: {integrity: sha1-ZQnwEmr0wXhVHPqZOU4DLhOk1W4=} + dev: true + + /flat-cache/3.0.4: + resolution: {integrity: sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==} + engines: {node: ^10.12.0 || >=12.0.0} + dependencies: + flatted: 3.1.1 + rimraf: 3.0.2 + dev: true + + /flatted/3.1.1: + resolution: {integrity: sha512-zAoAQiudy+r5SvnSw3KJy5os/oRJYHzrzja/tBDqrZtNhUw8bt6y8OBzMWcjWr+8liV8Eb6yOhw8WZ7VFZ5ZzA==} + dev: true + + /foreground-child/2.0.0: + resolution: {integrity: sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==} + engines: {node: '>=8.0.0'} + dependencies: + cross-spawn: 7.0.3 + signal-exit: 3.0.3 + dev: true + + /forever-agent/0.6.1: + resolution: {integrity: sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=} + dev: true + + /form-data/2.3.3: + resolution: {integrity: sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==} + engines: {node: '>= 0.12'} + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + mime-types: 2.1.31 + dev: true + + /fromentries/1.3.2: + resolution: {integrity: sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg==} + dev: true + + /fs-exists-cached/1.0.0: + resolution: {integrity: sha1-zyVVTKBQ3EmuZla0HeQiWJidy84=} + dev: true + + /fs.realpath/1.0.0: + resolution: {integrity: sha1-FQStJSMVjKpA20onh8sBQRmU6k8=} + dev: true + + /fsevents/2.3.2: + resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /function-bind/1.1.1: + resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==} + dev: true + + /function-loop/2.0.1: + resolution: {integrity: sha512-ktIR+O6i/4h+j/ZhZJNdzeI4i9lEPeEK6UPR2EVyTVBqOwcU3Za9xYKLH64ZR9HmcROyRrOkizNyjjtWJzDDkQ==} + dev: true + + /function.prototype.name/1.1.5: + resolution: {integrity: sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.1.4 + es-abstract: 1.20.1 + functions-have-names: 1.2.3 + dev: true + + /functional-red-black-tree/1.0.1: + resolution: {integrity: sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=} + dev: true + + /functions-have-names/1.2.3: + resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} + dev: true + + /gensync/1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + dev: true + + /get-caller-file/2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + dev: true + + /get-intrinsic/1.1.2: + resolution: {integrity: sha512-Jfm3OyCxHh9DJyc28qGk+JmfkpO41A4XkneDSujN9MDXrm4oDKdHvndhZ2dN94+ERNfkYJWDclW6k2L/ZGHjXA==} + dependencies: + function-bind: 1.1.1 + has: 1.0.3 + has-symbols: 1.0.3 + dev: true + + /get-package-type/0.1.0: + resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==} + engines: {node: '>=8.0.0'} + dev: true + + /get-symbol-description/1.0.0: + resolution: {integrity: sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + get-intrinsic: 1.1.2 + dev: true + + /getpass/0.1.7: + resolution: {integrity: sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=} + dependencies: + assert-plus: 1.0.0 + dev: true + + /glob-parent/5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + dependencies: + is-glob: 4.0.3 + dev: true + + /glob-parent/6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + dependencies: + is-glob: 4.0.3 + dev: true + + /glob/7.1.7: + resolution: {integrity: sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==} + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.0.4 + once: 1.4.0 + path-is-absolute: 1.0.1 + dev: true + + /globals/11.12.0: + resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} + engines: {node: '>=4'} + dev: true + + /globals/13.15.0: + resolution: {integrity: sha512-bpzcOlgDhMG070Av0Vy5Owklpv1I6+j96GhUI7Rh7IzDCKLzboflLrrfqMu8NquDbiR4EOQk7XzJwqVJxicxog==} + engines: {node: '>=8'} + dependencies: + type-fest: 0.20.2 + dev: true + + /globby/11.1.0: + resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} + engines: {node: '>=10'} + dependencies: + array-union: 2.1.0 + dir-glob: 3.0.1 + fast-glob: 3.2.11 + ignore: 5.2.0 + merge2: 1.4.1 + slash: 3.0.0 + dev: true + + /graceful-fs/4.2.6: + resolution: {integrity: sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==} + dev: true + + /har-schema/2.0.0: + resolution: {integrity: sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=} + engines: {node: '>=4'} + dev: true + + /har-validator/5.1.5: + resolution: {integrity: sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==} + engines: {node: '>=6'} + deprecated: this library is no longer supported + dependencies: + ajv: 6.12.6 + har-schema: 2.0.0 + dev: true + + /has-bigints/1.0.2: + resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==} + dev: true + + /has-flag/3.0.0: + resolution: {integrity: sha1-tdRU3CGZriJWmfNGfloH87lVuv0=} + engines: {node: '>=4'} + dev: true + + /has-flag/4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + dev: true + + /has-property-descriptors/1.0.0: + resolution: {integrity: sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==} + dependencies: + get-intrinsic: 1.1.2 + dev: true + + /has-symbols/1.0.3: + resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} + engines: {node: '>= 0.4'} + dev: true + + /has-tostringtag/1.0.0: + resolution: {integrity: sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==} + engines: {node: '>= 0.4'} + dependencies: + has-symbols: 1.0.3 + dev: true + + /has/1.0.3: + resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==} + engines: {node: '>= 0.4.0'} + dependencies: + function-bind: 1.1.1 + dev: true + + /hasha/5.2.2: + resolution: {integrity: sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ==} + engines: {node: '>=8'} + dependencies: + is-stream: 2.0.0 + type-fest: 0.8.1 + dev: true + + /html-escaper/2.0.2: + resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + dev: true + + /http-signature/1.2.0: + resolution: {integrity: sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=} + engines: {node: '>=0.8', npm: '>=1.3.7'} + dependencies: + assert-plus: 1.0.0 + jsprim: 1.4.1 + sshpk: 1.16.1 + dev: true + + /ignore/5.2.0: + resolution: {integrity: sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==} + engines: {node: '>= 4'} + dev: true + + /import-fresh/3.3.0: + resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} + engines: {node: '>=6'} + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + dev: true + + /import-jsx/4.0.0: + resolution: {integrity: sha512-CnjJ2BZFJzbFDmYG5S47xPQjMlSbZLyLJuG4znzL4TdPtJBxHtFP1xVmR+EYX4synFSldiY3B6m00XkPM3zVnA==} + engines: {node: '>=10'} + dependencies: + '@babel/core': 7.14.6 + '@babel/plugin-proposal-object-rest-spread': 7.14.5_@babel+core@7.14.6 + '@babel/plugin-transform-destructuring': 7.14.5_@babel+core@7.14.6 + '@babel/plugin-transform-react-jsx': 7.14.5_@babel+core@7.14.6 + caller-path: 2.0.0 + find-cache-dir: 3.3.1 + make-dir: 3.1.0 + resolve-from: 3.0.0 + rimraf: 3.0.2 + transitivePeerDependencies: + - supports-color + dev: true + + /imurmurhash/0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + dev: true + + /indent-string/4.0.0: + resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} + engines: {node: '>=8'} + dev: true + + /inflight/1.0.6: + resolution: {integrity: sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=} + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + dev: true + + /inherits/2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + dev: true + + /internal-slot/1.0.3: + resolution: {integrity: sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==} + engines: {node: '>= 0.4'} + dependencies: + get-intrinsic: 1.1.2 + has: 1.0.3 + side-channel: 1.0.4 + dev: true + + /is-bigint/1.0.4: + resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==} + dependencies: + has-bigints: 1.0.2 + dev: true + + /is-binary-path/2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + dependencies: + binary-extensions: 2.2.0 + dev: true + + /is-boolean-object/1.1.2: + resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + has-tostringtag: 1.0.0 + dev: true + + /is-callable/1.2.4: + resolution: {integrity: sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==} + engines: {node: '>= 0.4'} + dev: true + + /is-core-module/2.9.0: + resolution: {integrity: sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==} + dependencies: + has: 1.0.3 + dev: true + + /is-date-object/1.0.5: + resolution: {integrity: sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==} + engines: {node: '>= 0.4'} + dependencies: + has-tostringtag: 1.0.0 + dev: true + + /is-extglob/2.1.1: + resolution: {integrity: sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=} + engines: {node: '>=0.10.0'} + dev: true + + /is-fullwidth-code-point/1.0.0: + resolution: {integrity: sha1-754xOG8DGn8NZDr4L95QxFfvAMs=} + engines: {node: '>=0.10.0'} + dependencies: + number-is-nan: 1.0.1 + dev: true + + /is-fullwidth-code-point/2.0.0: + resolution: {integrity: sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=} + engines: {node: '>=4'} + dev: true + + /is-fullwidth-code-point/3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + dev: true + + /is-glob/4.0.1: + resolution: {integrity: sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==} + engines: {node: '>=0.10.0'} + dependencies: + is-extglob: 2.1.1 + dev: true + + /is-glob/4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + dependencies: + is-extglob: 2.1.1 + dev: true + + /is-negative-zero/2.0.2: + resolution: {integrity: sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==} + engines: {node: '>= 0.4'} + dev: true + + /is-number-object/1.0.7: + resolution: {integrity: sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==} + engines: {node: '>= 0.4'} + dependencies: + has-tostringtag: 1.0.0 + dev: true + + /is-number/7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + dev: true + + /is-regex/1.1.4: + resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + has-tostringtag: 1.0.0 + dev: true + + /is-shared-array-buffer/1.0.2: + resolution: {integrity: sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==} + dependencies: + call-bind: 1.0.2 + dev: true + + /is-stream/2.0.0: + resolution: {integrity: sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==} + engines: {node: '>=8'} + dev: true + + /is-string/1.0.7: + resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==} + engines: {node: '>= 0.4'} + dependencies: + has-tostringtag: 1.0.0 + dev: true + + /is-symbol/1.0.4: + resolution: {integrity: sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==} + engines: {node: '>= 0.4'} + dependencies: + has-symbols: 1.0.3 + dev: true + + /is-typedarray/1.0.0: + resolution: {integrity: sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=} + dev: true + + /is-weakref/1.0.2: + resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==} + dependencies: + call-bind: 1.0.2 + dev: true + + /is-windows/1.0.2: + resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==} + engines: {node: '>=0.10.0'} + dev: true + + /isexe/2.0.0: + resolution: {integrity: sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=} + dev: true + + /isstream/0.1.2: + resolution: {integrity: sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=} + dev: true + + /istanbul-lib-coverage/3.0.0: + resolution: {integrity: sha512-UiUIqxMgRDET6eR+o5HbfRYP1l0hqkWOs7vNxC/mggutCMUIhWMm8gAHb8tHlyfD3/l6rlgNA5cKdDzEAf6hEg==} + engines: {node: '>=8'} + dev: true + + /istanbul-lib-hook/3.0.0: + resolution: {integrity: sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ==} + engines: {node: '>=8'} + dependencies: + append-transform: 2.0.0 + dev: true + + /istanbul-lib-instrument/4.0.3: + resolution: {integrity: sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==} + engines: {node: '>=8'} + dependencies: + '@babel/core': 7.14.6 + '@istanbuljs/schema': 0.1.3 + istanbul-lib-coverage: 3.0.0 + semver: 6.3.0 + transitivePeerDependencies: + - supports-color + dev: true + + /istanbul-lib-processinfo/2.0.2: + resolution: {integrity: sha512-kOwpa7z9hme+IBPZMzQ5vdQj8srYgAtaRqeI48NGmAQ+/5yKiHLV0QbYqQpxsdEF0+w14SoB8YbnHKcXE2KnYw==} + engines: {node: '>=8'} + dependencies: + archy: 1.0.0 + cross-spawn: 7.0.3 + istanbul-lib-coverage: 3.0.0 + make-dir: 3.1.0 + p-map: 3.0.0 + rimraf: 3.0.2 + uuid: 3.4.0 + dev: true + + /istanbul-lib-report/3.0.0: + resolution: {integrity: sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==} + engines: {node: '>=8'} + dependencies: + istanbul-lib-coverage: 3.0.0 + make-dir: 3.1.0 + supports-color: 7.2.0 + dev: true + + /istanbul-lib-source-maps/4.0.0: + resolution: {integrity: sha512-c16LpFRkR8vQXyHZ5nLpY35JZtzj1PQY1iZmesUbf1FZHbIupcWfjgOXBY9YHkLEQ6puz1u4Dgj6qmU/DisrZg==} + engines: {node: '>=8'} + dependencies: + debug: 4.3.1 + istanbul-lib-coverage: 3.0.0 + source-map: 0.6.1 + transitivePeerDependencies: + - supports-color + dev: true + + /istanbul-reports/3.0.2: + resolution: {integrity: sha512-9tZvz7AiR3PEDNGiV9vIouQ/EAcqMXFmkcA1CDFTwOB98OZVDL0PH9glHotf5Ugp6GCOTypfzGWI/OqjWNCRUw==} + engines: {node: '>=8'} + dependencies: + html-escaper: 2.0.2 + istanbul-lib-report: 3.0.0 + dev: true + + /jackspeak/1.4.0: + resolution: {integrity: sha512-VDcSunT+wcccoG46FtzuBAyQKlzhHjli4q31e1fIHGOsRspqNUFjVzGb+7eIFDlTvqLygxapDHPHS0ouT2o/tw==} + engines: {node: '>=8'} + dependencies: + cliui: 4.1.0 + dev: true + + /js-tokens/4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + dev: true + + /js-yaml/3.14.1: + resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==} + hasBin: true + dependencies: + argparse: 1.0.10 + esprima: 4.0.1 + dev: true + + /js-yaml/4.1.0: + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + hasBin: true + dependencies: + argparse: 2.0.1 + dev: true + + /jsbn/0.1.1: + resolution: {integrity: sha1-peZUwuWi3rXyAdls77yoDA7y9RM=} + dev: true + + /jsesc/2.5.2: + resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==} + engines: {node: '>=4'} + hasBin: true + dev: true + + /json-schema-traverse/0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + dev: true + + /json-schema/0.2.3: + resolution: {integrity: sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=} + dev: true + + /json-stable-stringify-without-jsonify/1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + dev: true + + /json-stringify-safe/5.0.1: + resolution: {integrity: sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=} + dev: true + + /json5/1.0.1: + resolution: {integrity: sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==} + hasBin: true + dependencies: + minimist: 1.2.6 + dev: true + + /json5/2.2.0: + resolution: {integrity: sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==} + engines: {node: '>=6'} + hasBin: true + dependencies: + minimist: 1.2.5 + dev: true + + /jsprim/1.4.1: + resolution: {integrity: sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=} + engines: {'0': node >=0.6.0} + dependencies: + assert-plus: 1.0.0 + extsprintf: 1.3.0 + json-schema: 0.2.3 + verror: 1.10.0 + dev: true + + /lcov-parse/1.0.0: + resolution: {integrity: sha1-6w1GtUER68VhrLTECO+TY73I9+A=} + hasBin: true + dev: true + + /levn/0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + dev: true + + /libtap/1.1.1: + resolution: {integrity: sha512-Fye8fh1+G7E8qqmjQaY+pXGxy7HM0S6bqCCJFLa16+g2jODBByxbJFDpjbDNF69wfRVyvJ+foLZc1WTIv7dx+g==} + engines: {node: '>=10'} + dependencies: + async-hook-domain: 2.0.3 + bind-obj-methods: 3.0.0 + diff: 4.0.2 + function-loop: 2.0.1 + minipass: 3.1.3 + own-or: 1.0.0 + own-or-env: 1.0.1 + signal-exit: 3.0.3 + stack-utils: 2.0.3 + tap-parser: 10.1.0 + tap-yaml: 1.0.0 + tcompare: 5.0.6 + trivial-deferred: 1.0.1 + yapool: 1.0.0 + dev: true + + /locate-path/2.0.0: + resolution: {integrity: sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==} + engines: {node: '>=4'} + dependencies: + p-locate: 2.0.0 + path-exists: 3.0.0 + dev: true + + /locate-path/5.0.0: + resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} + engines: {node: '>=8'} + dependencies: + p-locate: 4.1.0 + dev: true + + /lodash.flattendeep/4.4.0: + resolution: {integrity: sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=} + dev: true + + /lodash.merge/4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + dev: true + + /log-driver/1.2.7: + resolution: {integrity: sha512-U7KCmLdqsGHBLeWqYlFA0V0Sl6P08EE1ZrmA9cxjUE0WVqT9qnyVDPz1kzpFEP0jdJuFnasWIfSd7fsaNXkpbg==} + engines: {node: '>=0.8.6'} + dev: true + + /loose-envify/1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true + dependencies: + js-tokens: 4.0.0 + dev: true + + /lru-cache/6.0.0: + resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} + engines: {node: '>=10'} + dependencies: + yallist: 4.0.0 + dev: true + + /make-dir/3.1.0: + resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==} + engines: {node: '>=8'} + dependencies: + semver: 6.3.0 + dev: true + + /make-error/1.3.6: + resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} + dev: true + + /merge2/1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + dev: true + + /micromatch/4.0.5: + resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} + engines: {node: '>=8.6'} + dependencies: + braces: 3.0.2 + picomatch: 2.3.1 + dev: true + + /mime-db/1.48.0: + resolution: {integrity: sha512-FM3QwxV+TnZYQ2aRqhlKBMHxk10lTbMt3bBkMAp54ddrNeVSfcQYOOKuGuy3Ddrm38I04If834fOUSq1yzslJQ==} + engines: {node: '>= 0.6'} + dev: true + + /mime-types/2.1.31: + resolution: {integrity: sha512-XGZnNzm3QvgKxa8dpzyhFTHmpP3l5YNusmne07VUOXxou9CqUqYa/HBy124RqtVh/O2pECas/MOcsDgpilPOPg==} + engines: {node: '>= 0.6'} + dependencies: + mime-db: 1.48.0 + dev: true + + /minimatch/3.0.4: + resolution: {integrity: sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==} + dependencies: + brace-expansion: 1.1.11 + dev: true + + /minimatch/3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + dependencies: + brace-expansion: 1.1.11 + dev: true + + /minimist/1.2.5: + resolution: {integrity: sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==} + dev: true + + /minimist/1.2.6: + resolution: {integrity: sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==} + dev: true + + /minipass/3.1.3: + resolution: {integrity: sha512-Mgd2GdMVzY+x3IJ+oHnVM+KG3lA5c8tnabyJKmHSaG2kAGpudxuOf8ToDkhumF7UzME7DecbQE9uOZhNm7PuJg==} + engines: {node: '>=8'} + dependencies: + yallist: 4.0.0 + dev: true + + /mkdirp/1.0.4: + resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} + engines: {node: '>=10'} + hasBin: true + dev: true + + /ms/2.0.0: + resolution: {integrity: sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=} + dev: true + + /ms/2.1.2: + resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} + dev: true + + /natural-compare/1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + dev: true + + /node-preload/0.2.1: + resolution: {integrity: sha512-RM5oyBy45cLEoHqCeh+MNuFAxO0vTFBLskvQbOKnEE7YTTSN4tbN8QWDIPQ6L+WvKsB/qLEGpYe2ZZ9d4W9OIQ==} + engines: {node: '>=8'} + dependencies: + process-on-spawn: 1.0.0 + dev: true + + /node-releases/1.1.73: + resolution: {integrity: sha512-uW7fodD6pyW2FZNZnp/Z3hvWKeEW1Y8R1+1CnErE8cXFXzl5blBOoVB41CvMer6P6Q0S5FXDwcHgFd1Wj0U9zg==} + dev: true + + /normalize-path/3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + dev: true + + /number-is-nan/1.0.1: + resolution: {integrity: sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=} + engines: {node: '>=0.10.0'} + dev: true + + /nyc/15.1.0: + resolution: {integrity: sha512-jMW04n9SxKdKi1ZMGhvUTHBN0EICCRkHemEoE5jm6mTYcqcdas0ATzgUgejlQUHMvpnOZqGB5Xxsv9KxJW1j8A==} + engines: {node: '>=8.9'} + hasBin: true + dependencies: + '@istanbuljs/load-nyc-config': 1.1.0 + '@istanbuljs/schema': 0.1.3 + caching-transform: 4.0.0 + convert-source-map: 1.8.0 + decamelize: 1.2.0 + find-cache-dir: 3.3.1 + find-up: 4.1.0 + foreground-child: 2.0.0 + get-package-type: 0.1.0 + glob: 7.1.7 + istanbul-lib-coverage: 3.0.0 + istanbul-lib-hook: 3.0.0 + istanbul-lib-instrument: 4.0.3 + istanbul-lib-processinfo: 2.0.2 + istanbul-lib-report: 3.0.0 + istanbul-lib-source-maps: 4.0.0 + istanbul-reports: 3.0.2 + make-dir: 3.1.0 + node-preload: 0.2.1 + p-map: 3.0.0 + process-on-spawn: 1.0.0 + resolve-from: 5.0.0 + rimraf: 3.0.2 + signal-exit: 3.0.3 + spawn-wrap: 2.0.0 + test-exclude: 6.0.0 + yargs: 15.4.1 + transitivePeerDependencies: + - supports-color + dev: true + + /oauth-sign/0.9.0: + resolution: {integrity: sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==} + dev: true + + /object-assign/4.1.1: + resolution: {integrity: sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=} + engines: {node: '>=0.10.0'} + dev: true + + /object-inspect/1.12.2: + resolution: {integrity: sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==} + dev: true + + /object-keys/1.1.1: + resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} + engines: {node: '>= 0.4'} + dev: true + + /object.assign/4.1.2: + resolution: {integrity: sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.1.4 + has-symbols: 1.0.3 + object-keys: 1.1.1 + dev: true + + /object.values/1.1.5: + resolution: {integrity: sha512-QUZRW0ilQ3PnPpbNtgdNV1PDbEqLIiSFB3l+EnGtBQ/8SUTLj1PZwtQHABZtLgwpJZTSZhuGLOGk57Drx2IvYg==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.1.4 + es-abstract: 1.20.1 + dev: true + + /once/1.4.0: + resolution: {integrity: sha1-WDsap3WWHUsROsF9nFC6753Xa9E=} + dependencies: + wrappy: 1.0.2 + dev: true + + /opener/1.5.2: + resolution: {integrity: sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==} + hasBin: true + dev: true + + /optionator/0.9.1: + resolution: {integrity: sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==} + engines: {node: '>= 0.8.0'} + dependencies: + deep-is: 0.1.3 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.3 + dev: true + + /own-or-env/1.0.1: + resolution: {integrity: sha512-y8qULRbRAlL6x2+M0vIe7jJbJx/kmUTzYonRAa2ayesR2qWLswninkVyeJe4x3IEXhdgoNodzjQRKAoEs6Fmrw==} + dependencies: + own-or: 1.0.0 + dev: true + + /own-or/1.0.0: + resolution: {integrity: sha1-Tod/vtqaLsgAD7wLyuOWRe6L+Nw=} + dev: true + + /p-limit/1.3.0: + resolution: {integrity: sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==} + engines: {node: '>=4'} + dependencies: + p-try: 1.0.0 + dev: true + + /p-limit/2.3.0: + resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} + engines: {node: '>=6'} + dependencies: + p-try: 2.2.0 + dev: true + + /p-locate/2.0.0: + resolution: {integrity: sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==} + engines: {node: '>=4'} + dependencies: + p-limit: 1.3.0 + dev: true + + /p-locate/4.1.0: + resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} + engines: {node: '>=8'} + dependencies: + p-limit: 2.3.0 + dev: true + + /p-map/3.0.0: + resolution: {integrity: sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==} + engines: {node: '>=8'} + dependencies: + aggregate-error: 3.1.0 + dev: true + + /p-try/1.0.0: + resolution: {integrity: sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==} + engines: {node: '>=4'} + dev: true + + /p-try/2.2.0: + resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} + engines: {node: '>=6'} + dev: true + + /package-hash/4.0.0: + resolution: {integrity: sha512-whdkPIooSu/bASggZ96BWVvZTRMOFxnyUG5PnTSGKoJE2gd5mbVNmR2Nj20QFzxYYgAXpoqC+AiXzl+UMRh7zQ==} + engines: {node: '>=8'} + dependencies: + graceful-fs: 4.2.6 + hasha: 5.2.2 + lodash.flattendeep: 4.4.0 + release-zalgo: 1.0.0 + dev: true + + /parent-module/1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + dependencies: + callsites: 3.1.0 + dev: true + + /path-exists/3.0.0: + resolution: {integrity: sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==} + engines: {node: '>=4'} + dev: true + + /path-exists/4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + dev: true + + /path-is-absolute/1.0.1: + resolution: {integrity: sha1-F0uSaHNVNP+8es5r9TpanhtcX18=} + engines: {node: '>=0.10.0'} + dev: true + + /path-key/3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + dev: true + + /path-parse/1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + dev: true + + /path-type/4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + dev: true + + /performance-now/2.1.0: + resolution: {integrity: sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=} + dev: true + + /picomatch/2.3.0: + resolution: {integrity: sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==} + engines: {node: '>=8.6'} + dev: true + + /picomatch/2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + dev: true + + /pkg-dir/4.2.0: + resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} + engines: {node: '>=8'} + dependencies: + find-up: 4.1.0 + dev: true + + /prelude-ls/1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + dev: true + + /prettier-linter-helpers/1.0.0: + resolution: {integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==} + engines: {node: '>=6.0.0'} + dependencies: + fast-diff: 1.2.0 + dev: true + + /prettier/2.6.2: + resolution: {integrity: sha512-PkUpF+qoXTqhOeWL9fu7As8LXsIUZ1WYaJiY/a7McAQzxjk82OF0tibkFXVCDImZtWxbvojFjerkiLb0/q8mew==} + engines: {node: '>=10.13.0'} + hasBin: true + dev: true + + /process-on-spawn/1.0.0: + resolution: {integrity: sha512-1WsPDsUSMmZH5LeMLegqkPDrsGgsWwk1Exipy2hvB0o/F0ASzbpIctSCcZIK1ykJvtTJULEH+20WOFjMvGnCTg==} + engines: {node: '>=8'} + dependencies: + fromentries: 1.3.2 + dev: true + + /prop-types/15.7.2: + resolution: {integrity: sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==} + dependencies: + loose-envify: 1.4.0 + object-assign: 4.1.1 + react-is: 16.13.1 + dev: true + + /prudence/0.9.8: + resolution: {integrity: sha512-my6HEhcTW3aKKjAx8j6jgUj3x5Kc71NEv3DySRXun2Zspw6Fdo5zDnbpXv/j4tyk9f9XXrpu1grWVBX4WU+1Tg==} + dev: false + + /psl/1.8.0: + resolution: {integrity: sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==} + dev: true + + /punycode/2.1.1: + resolution: {integrity: sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==} + engines: {node: '>=6'} + dev: true + + /qs/6.5.2: + resolution: {integrity: sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==} + engines: {node: '>=0.6'} + dev: true + + /queue-microtask/1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + dev: true + + /react-is/16.13.1: + resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} + dev: true + + /react/16.14.0: + resolution: {integrity: sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g==} + engines: {node: '>=0.10.0'} + dependencies: + loose-envify: 1.4.0 + object-assign: 4.1.1 + prop-types: 15.7.2 + dev: true + + /readdirp/3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + dependencies: + picomatch: 2.3.0 + dev: true + + /regexp.prototype.flags/1.4.3: + resolution: {integrity: sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.1.4 + functions-have-names: 1.2.3 + dev: true + + /regexpp/3.2.0: + resolution: {integrity: sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==} + engines: {node: '>=8'} + dev: true + + /release-zalgo/1.0.0: + resolution: {integrity: sha1-CXALflB0Mpc5Mw5TXFqQ+2eFFzA=} + engines: {node: '>=4'} + dependencies: + es6-error: 4.1.1 + dev: true + + /request/2.88.2: + resolution: {integrity: sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==} + engines: {node: '>= 6'} + deprecated: request has been deprecated, see https://github.com/request/request/issues/3142 + dependencies: + aws-sign2: 0.7.0 + aws4: 1.11.0 + caseless: 0.12.0 + combined-stream: 1.0.8 + extend: 3.0.2 + forever-agent: 0.6.1 + form-data: 2.3.3 + har-validator: 5.1.5 + http-signature: 1.2.0 + is-typedarray: 1.0.0 + isstream: 0.1.2 + json-stringify-safe: 5.0.1 + mime-types: 2.1.31 + oauth-sign: 0.9.0 + performance-now: 2.1.0 + qs: 6.5.2 + safe-buffer: 5.2.1 + tough-cookie: 2.5.0 + tunnel-agent: 0.6.0 + uuid: 3.4.0 + dev: true + + /require-directory/2.1.1: + resolution: {integrity: sha1-jGStX9MNqxyXbiNE/+f3kqam30I=} + engines: {node: '>=0.10.0'} + dev: true + + /require-main-filename/2.0.0: + resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==} + dev: true + + /resolve-from/3.0.0: + resolution: {integrity: sha1-six699nWiBvItuZTM17rywoYh0g=} + engines: {node: '>=4'} + dev: true + + /resolve-from/4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + dev: true + + /resolve-from/5.0.0: + resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} + engines: {node: '>=8'} + dev: true + + /resolve/1.22.1: + resolution: {integrity: sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==} + hasBin: true + dependencies: + is-core-module: 2.9.0 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + dev: true + + /reusify/1.0.4: + resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + dev: true + + /rimraf/3.0.2: + resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} + hasBin: true + dependencies: + glob: 7.1.7 + dev: true + + /run-parallel/1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + dependencies: + queue-microtask: 1.2.3 + dev: true + + /safe-buffer/5.1.2: + resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} + dev: true + + /safe-buffer/5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + dev: true + + /safer-buffer/2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + dev: true + + /semver/6.3.0: + resolution: {integrity: sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==} + hasBin: true + dev: true + + /semver/7.3.7: + resolution: {integrity: sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==} + engines: {node: '>=10'} + hasBin: true + dependencies: + lru-cache: 6.0.0 + dev: true + + /set-blocking/2.0.0: + resolution: {integrity: sha1-BF+XgtARrppoA93TgrJDkrPYkPc=} + dev: true + + /shebang-command/2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + dependencies: + shebang-regex: 3.0.0 + dev: true + + /shebang-regex/3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + dev: true + + /side-channel/1.0.4: + resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==} + dependencies: + call-bind: 1.0.2 + get-intrinsic: 1.1.2 + object-inspect: 1.12.2 + dev: true + + /signal-exit/3.0.3: + resolution: {integrity: sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==} + dev: true + + /slash/3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} + dev: true + + /source-map-support/0.5.19: + resolution: {integrity: sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==} + dependencies: + buffer-from: 1.1.1 + source-map: 0.6.1 + dev: true + + /source-map/0.5.7: + resolution: {integrity: sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=} + engines: {node: '>=0.10.0'} + dev: true + + /source-map/0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + dev: true + + /spawn-wrap/2.0.0: + resolution: {integrity: sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg==} + engines: {node: '>=8'} + dependencies: + foreground-child: 2.0.0 + is-windows: 1.0.2 + make-dir: 3.1.0 + rimraf: 3.0.2 + signal-exit: 3.0.3 + which: 2.0.2 + dev: true + + /sprintf-js/1.0.3: + resolution: {integrity: sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=} + dev: true + + /sshpk/1.16.1: + resolution: {integrity: sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==} + engines: {node: '>=0.10.0'} + hasBin: true + dependencies: + asn1: 0.2.4 + assert-plus: 1.0.0 + bcrypt-pbkdf: 1.0.2 + dashdash: 1.14.1 + ecc-jsbn: 0.1.2 + getpass: 0.1.7 + jsbn: 0.1.1 + safer-buffer: 2.1.2 + tweetnacl: 0.14.5 + dev: true + + /stack-utils/2.0.3: + resolution: {integrity: sha512-gL//fkxfWUsIlFL2Tl42Cl6+HFALEaB1FU76I/Fy+oZjRreP7OPMXFlGbxM7NQsI0ZpUfw76sHnv0WNYuTb7Iw==} + engines: {node: '>=10'} + dependencies: + escape-string-regexp: 2.0.0 + dev: true + + /string-width/1.0.2: + resolution: {integrity: sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=} + engines: {node: '>=0.10.0'} + dependencies: + code-point-at: 1.1.0 + is-fullwidth-code-point: 1.0.0 + strip-ansi: 3.0.1 + dev: true + + /string-width/2.1.1: + resolution: {integrity: sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==} + engines: {node: '>=4'} + dependencies: + is-fullwidth-code-point: 2.0.0 + strip-ansi: 4.0.0 + dev: true + + /string-width/4.2.2: + resolution: {integrity: sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==} + engines: {node: '>=8'} + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.0 + dev: true + + /string.prototype.trimend/1.0.5: + resolution: {integrity: sha512-I7RGvmjV4pJ7O3kdf+LXFpVfdNOxtCW/2C8f6jNiW4+PQchwxkCDzlk1/7p+Wl4bqFIZeF47qAHXLuHHWKAxog==} + dependencies: + call-bind: 1.0.2 + define-properties: 1.1.4 + es-abstract: 1.20.1 + dev: true + + /string.prototype.trimstart/1.0.5: + resolution: {integrity: sha512-THx16TJCGlsN0o6dl2o6ncWUsdgnLRSA23rRE5pyGBw/mLr3Ej/R2LaqCtgP8VNMGZsvMWnf9ooZPyY2bHvUFg==} + dependencies: + call-bind: 1.0.2 + define-properties: 1.1.4 + es-abstract: 1.20.1 + dev: true + + /strip-ansi/3.0.1: + resolution: {integrity: sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=} + engines: {node: '>=0.10.0'} + dependencies: + ansi-regex: 2.1.1 + dev: true + + /strip-ansi/4.0.0: + resolution: {integrity: sha1-qEeQIusaw2iocTibY1JixQXuNo8=} + engines: {node: '>=4'} + dependencies: + ansi-regex: 3.0.0 + dev: true + + /strip-ansi/6.0.0: + resolution: {integrity: sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==} + engines: {node: '>=8'} + dependencies: + ansi-regex: 5.0.0 + dev: true + + /strip-ansi/6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + dependencies: + ansi-regex: 5.0.1 + dev: true + + /strip-bom/3.0.0: + resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} + engines: {node: '>=4'} + dev: true + + /strip-bom/4.0.0: + resolution: {integrity: sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==} + engines: {node: '>=8'} + dev: true + + /strip-json-comments/3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + dev: true + + /supports-color/5.5.0: + resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} + engines: {node: '>=4'} + dependencies: + has-flag: 3.0.0 + dev: true + + /supports-color/7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + dependencies: + has-flag: 4.0.0 + dev: true + + /supports-preserve-symlinks-flag/1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + dev: true + + /tap-mocha-reporter/5.0.1: + resolution: {integrity: sha512-1knFWOwd4khx/7uSEnUeaP9IPW3w+sqTgJMhrwah6t46nZ8P25atOKAjSvVDsT67lOPu0nfdOqUwoyKn+3E5pA==} + engines: {node: '>= 8'} + hasBin: true + dependencies: + color-support: 1.1.3 + debug: 4.3.1 + diff: 4.0.2 + escape-string-regexp: 2.0.0 + glob: 7.1.7 + tap-parser: 10.1.0 + tap-yaml: 1.0.0 + unicode-length: 2.0.2 + transitivePeerDependencies: + - supports-color + dev: true + + /tap-parser/10.1.0: + resolution: {integrity: sha512-FujQeciDaOiOvaIVGS1Rpb0v4R6XkOjvWCWowlz5oKuhPkEJ8U6pxgqt38xuzYhPt8dWEnfHn2jqpZdJEkW7pA==} + engines: {node: '>= 8'} + hasBin: true + dependencies: + events-to-array: 1.1.2 + minipass: 3.1.3 + tap-yaml: 1.0.0 + dev: true + + /tap-yaml/1.0.0: + resolution: {integrity: sha512-Rxbx4EnrWkYk0/ztcm5u3/VznbyFJpyXO12dDBHKWiDVxy7O2Qw6MRrwO5H6Ww0U5YhRY/4C/VzWmFPhBQc4qQ==} + dependencies: + yaml: 1.10.2 + dev: true + + /tap/15.0.9_ts-node@10.0.0+typescript@4.6.3: + resolution: {integrity: sha512-bqY5SxEqYKRd37PIUfKBf9HMs/hklyl/fGXkuStr9rYTIGa0/icpSLsm6IVOmx2qT0/TliPNJ6OvS5kddJYHdg==} + engines: {node: '>=10'} + hasBin: true + peerDependencies: + flow-remove-types: '>=2.112.0' + ts-node: '>=8.5.2' + typescript: '>=3.7.2' + peerDependenciesMeta: + flow-remove-types: + optional: true + ts-node: + optional: true + typescript: + optional: true + dependencies: + chokidar: 3.5.2 + coveralls: 3.1.0 + findit: 2.0.0 + foreground-child: 2.0.0 + fs-exists-cached: 1.0.0 + glob: 7.1.7 + import-jsx: 4.0.0 + isexe: 2.0.0 + istanbul-lib-processinfo: 2.0.2 + jackspeak: 1.4.0 + libtap: 1.1.1 + minipass: 3.1.3 + mkdirp: 1.0.4 + nyc: 15.1.0 + opener: 1.5.2 + react: 16.14.0 + rimraf: 3.0.2 + signal-exit: 3.0.3 + source-map-support: 0.5.19 + tap-mocha-reporter: 5.0.1 + tap-parser: 10.1.0 + tap-yaml: 1.0.0 + tcompare: 5.0.6 + ts-node: 10.0.0_22ec373cc6e5c139fea80a5e1bbc5c61 + typescript: 4.6.3 + which: 2.0.2 + transitivePeerDependencies: + - supports-color + dev: true + bundledDependencies: + - ink + - treport + - '@types/react' + + /tcompare/5.0.6: + resolution: {integrity: sha512-OvO7omN/wkdsKzmOqr3sQFfLbghs/2X5mwSkcfgRiXZshfPnTsAs3IRf1RixR/Pff26qG/r9ogcZMpV0YdeGXg==} + engines: {node: '>=10'} + dependencies: + diff: 4.0.2 + dev: true + + /test-exclude/6.0.0: + resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} + engines: {node: '>=8'} + dependencies: + '@istanbuljs/schema': 0.1.3 + glob: 7.1.7 + minimatch: 3.0.4 + dev: true + + /text-table/0.2.0: + resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} + dev: true + + /to-fast-properties/2.0.0: + resolution: {integrity: sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=} + engines: {node: '>=4'} + dev: true + + /to-regex-range/5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + dependencies: + is-number: 7.0.0 + dev: true + + /tough-cookie/2.5.0: + resolution: {integrity: sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==} + engines: {node: '>=0.8'} + dependencies: + psl: 1.8.0 + punycode: 2.1.1 + dev: true + + /trivial-deferred/1.0.1: + resolution: {integrity: sha1-N21NKdlR1jaKb3oK6FwvTV4GWPM=} + dev: true + + /ts-node/10.0.0_22ec373cc6e5c139fea80a5e1bbc5c61: + resolution: {integrity: sha512-ROWeOIUvfFbPZkoDis0L/55Fk+6gFQNZwwKPLinacRl6tsxstTF1DbAcLKkovwnpKMVvOMHP1TIbnwXwtLg1gg==} + engines: {node: '>=12.0.0'} + hasBin: true + peerDependencies: + '@swc/core': '>=1.2.45' + '@swc/wasm': '>=1.2.45' + '@types/node': '*' + typescript: '>=2.7' + peerDependenciesMeta: + '@swc/core': + optional: true + '@swc/wasm': + optional: true + dependencies: + '@tsconfig/node10': 1.0.8 + '@tsconfig/node12': 1.0.9 + '@tsconfig/node14': 1.0.1 + '@tsconfig/node16': 1.0.1 + '@types/node': 17.0.8 + arg: 4.1.3 + create-require: 1.1.1 + diff: 4.0.2 + make-error: 1.3.6 + source-map-support: 0.5.19 + typescript: 4.6.3 + yn: 3.1.1 + dev: true + + /tsconfig-paths/3.14.1: + resolution: {integrity: sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==} + dependencies: + '@types/json5': 0.0.29 + json5: 1.0.1 + minimist: 1.2.6 + strip-bom: 3.0.0 + dev: true + + /tslib/1.14.1: + resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} + dev: true + + /tsutils/3.21.0_typescript@4.6.3: + resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} + engines: {node: '>= 6'} + peerDependencies: + typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta' + dependencies: + tslib: 1.14.1 + typescript: 4.6.3 + dev: true + + /tunnel-agent/0.6.0: + resolution: {integrity: sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=} + dependencies: + safe-buffer: 5.2.1 + dev: true + + /tweetnacl/0.14.5: + resolution: {integrity: sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=} + dev: true + + /type-check/0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + dependencies: + prelude-ls: 1.2.1 + dev: true + + /type-fest/0.20.2: + resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} + engines: {node: '>=10'} + dev: true + + /type-fest/0.8.1: + resolution: {integrity: sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==} + engines: {node: '>=8'} + dev: true + + /typedarray-to-buffer/3.1.5: + resolution: {integrity: sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==} + dependencies: + is-typedarray: 1.0.0 + dev: true + + /typescript/4.6.3: + resolution: {integrity: sha512-yNIatDa5iaofVozS/uQJEl3JRWLKKGJKh6Yaiv0GLGSuhpFJe7P3SbHZ8/yjAHRQwKRoA6YZqlfjXWmVzoVSMw==} + engines: {node: '>=4.2.0'} + hasBin: true + dev: true + + /unbox-primitive/1.0.2: + resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==} + dependencies: + call-bind: 1.0.2 + has-bigints: 1.0.2 + has-symbols: 1.0.3 + which-boxed-primitive: 1.0.2 + dev: true + + /unicode-length/2.0.2: + resolution: {integrity: sha512-Ph/j1VbS3/r77nhoY2WU0GWGjVYOHL3xpKp0y/Eq2e5r0mT/6b649vm7KFO6RdAdrZkYLdxphYVgvODxPB+Ebg==} + dependencies: + punycode: 2.1.1 + strip-ansi: 3.0.1 + dev: true + + /uri-js/4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + dependencies: + punycode: 2.1.1 + dev: true + + /uuid/3.4.0: + resolution: {integrity: sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==} + deprecated: Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details. + hasBin: true + dev: true + + /v8-compile-cache/2.3.0: + resolution: {integrity: sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==} + dev: true + + /verror/1.10.0: + resolution: {integrity: sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=} + engines: {'0': node >=0.6.0} + dependencies: + assert-plus: 1.0.0 + core-util-is: 1.0.2 + extsprintf: 1.3.0 + dev: true + + /which-boxed-primitive/1.0.2: + resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} + dependencies: + is-bigint: 1.0.4 + is-boolean-object: 1.1.2 + is-number-object: 1.0.7 + is-string: 1.0.7 + is-symbol: 1.0.4 + dev: true + + /which-module/2.0.0: + resolution: {integrity: sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=} + dev: true + + /which/2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + dependencies: + isexe: 2.0.0 + dev: true + + /word-wrap/1.2.3: + resolution: {integrity: sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==} + engines: {node: '>=0.10.0'} + dev: true + + /wrap-ansi/2.1.0: + resolution: {integrity: sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=} + engines: {node: '>=0.10.0'} + dependencies: + string-width: 1.0.2 + strip-ansi: 3.0.1 + dev: true + + /wrap-ansi/6.2.0: + resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} + engines: {node: '>=8'} + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.2 + strip-ansi: 6.0.0 + dev: true + + /wrappy/1.0.2: + resolution: {integrity: sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=} + dev: true + + /write-file-atomic/3.0.3: + resolution: {integrity: sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==} + dependencies: + imurmurhash: 0.1.4 + is-typedarray: 1.0.0 + signal-exit: 3.0.3 + typedarray-to-buffer: 3.1.5 + dev: true + + /y18n/4.0.3: + resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==} + dev: true + + /yallist/4.0.0: + resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + dev: true + + /yaml/1.10.2: + resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} + engines: {node: '>= 6'} + dev: true + + /yapool/1.0.0: + resolution: {integrity: sha1-9pPymjFbUNmp2iZGp6ZkXJaYW2o=} + dev: true + + /yargs-parser/18.1.3: + resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==} + engines: {node: '>=6'} + dependencies: + camelcase: 5.3.1 + decamelize: 1.2.0 + dev: true + + /yargs/15.4.1: + resolution: {integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==} + engines: {node: '>=8'} + dependencies: + cliui: 6.0.0 + decamelize: 1.2.0 + find-up: 4.1.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + require-main-filename: 2.0.0 + set-blocking: 2.0.0 + string-width: 4.2.2 + which-module: 2.0.0 + y18n: 4.0.3 + yargs-parser: 18.1.3 + dev: true + + /yn/3.1.1: + resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} + engines: {node: '>=6'} + dev: true diff --git a/common/src/config/config.test.ts b/common/src/config/config.test.ts new file mode 100644 index 000000000..3c254a815 --- /dev/null +++ b/common/src/config/config.test.ts @@ -0,0 +1,41 @@ +import { GetGameConfig, GetGamePTConfig } from "./config"; +import { allSupportedGames } from "./static-config"; +import t from "tap"; + +t.test("#GetGameConfig", (t) => { + t.plan(allSupportedGames.length); + + for (const game of allSupportedGames) { + t.equal(GetGameConfig(game).internalName, game); + } +}); + +// i have no idea why this one doesn't need t.end() +// and this one does +t.test("#GetGamePTConfig", (t) => { + for (const game of allSupportedGames) { + const gameConfig = GetGameConfig(game); + + for (const playtype of gameConfig.validPlaytypes) { + const conf = GetGamePTConfig(game, playtype); + + t.equal( + conf.idString, + `${game}:${playtype}`, + `Should return the right GamePTConfig (${game} ${playtype})` + ); + + if (conf.gradeBoundaries === null) { + continue; + } + + t.equal( + conf.gradeBoundaries.length, + conf.grades.length, + `Should have the same amount of grades as grade boundaries. (${game} ${playtype})` + ); + } + } + + t.end(); +}); diff --git a/common/src/config/config.ts b/common/src/config/config.ts new file mode 100644 index 000000000..e9ac7059c --- /dev/null +++ b/common/src/config/config.ts @@ -0,0 +1,2408 @@ +import { FormatInt } from ".."; +import { + BMSGenocideDans, + BMSLNDans, + BMSScratchDans, + BMSStSlDans, + CHUNITHMColours, + DDRDans, + GitadoraColours, + IIDXDans, + JubeatColours, + PMSDans, + + // PMSDans, + PopnClasses, + SDVXDans, + SDVXVFClasses, + WaccaColours, + WaccaStageUps, +} from "../game-classes"; +import { FormatSieglindeBMS, FormatSieglindePMS } from "../utils/util"; +import type { GPTTierlists } from ".."; +import type { ClassInfo, GameClassSets } from "../game-classes"; +import type { ESDJudgementFormat } from "../lib/esd"; +import type { + Playtypes, + Game, + Difficulties, + IDStrings, + ScoreCalculatedDataLookup, + SessionCalculatedDataLookup, + UGSRatingsLookup, + Grades, + Lamps, + JudgementLookup, + GPTSupportedVersions, +} from "../types"; + +export interface GameConfig { + internalName: string; + name: string; + defaultPlaytype: Playtypes[G]; + validPlaytypes: Array; +} + +interface BaseGamePTConfig { + idString: I; + + percentMax: number; + + defaultScoreRatingAlg: ScoreCalculatedDataLookup[I]; + defaultSessionRatingAlg: SessionCalculatedDataLookup[I]; + defaultProfileRatingAlg: UGSRatingsLookup[I]; + + scoreRatingAlgs: Array; + sessionRatingAlgs: Array; + profileRatingAlgs: Array; + + scoreRatingAlgDescriptions: Record; + sessionRatingAlgDescriptions: Record; + profileRatingAlgDescriptions: Record; + + scoreRatingAlgFormatters: Partial string>>; + sessionRatingAlgFormatters: Partial< + Record string> + >; + profileRatingAlgFormatters: Partial string>>; + + difficulties: Array; + shortDifficulties: Partial>; + defaultDifficulty: Difficulties[I]; + difficultyColours: Record; + + grades: Array; + gradeColours: Record; + clearGrade: Grades[I]; + gradeBoundaries: Array | null; + + lamps: Array; + lampColours: Record; + clearLamp: Lamps[I]; + + classHumanisedFormat: Record>; + + judgements: Array; + + scoreBucket: "grade" | "lamp"; + + supportedVersions: Array; + + tierlists: Array; + tierlistDescriptions: Record; +} + +interface GamePTConfigWithESD extends BaseGamePTConfig { + supportsESD: true; + judgementWindows: Array; +} + +interface GamePTConfigWithoutESD extends BaseGamePTConfig { + supportsESD: false; +} + +export type GamePTConfig = + | GamePTConfigWithESD + | GamePTConfigWithoutESD; + +/** + * A collection of CSS colours we want to work with. + */ +export const COLOUR_SET = { + gray: "rgba(105, 105, 105, 1)", + maroon: "rgba(85, 17, 17, 1)", + red: "rgba(170, 85, 85, 1)", + paleGreen: "rgba(142,174,79, 1)", + paleBlue: "rgba(92, 97, 153, 1)", + green: "rgba(50, 205, 50, 1)", + darkGreen: "rgba(30, 130, 76, 1)", + blue: "rgba(70, 130, 180, 1)", + gold: "rgba(255, 215, 0, 1)", + vibrantYellow: "rgba(245, 229, 27, 1)", + teal: "rgba(127, 255, 212, 1)", + white: "rgba(192, 192, 192, 1)", + purple: "rgba(153, 50, 204, 1)", + vibrantPurple: "rgba(161, 23, 230, 1)", + paleOrange: "rgba(235, 151, 78, 1)", + orange: "rgba(248, 148, 6, 1)", + vibrantOrange: "rgba(248, 175, 6, 1)", + vibrantBlue: "rgba(43, 149, 237, 1)", + vibrantGreen: "rgba(26, 232, 26, 1)", + vibrantRed: "rgba(243, 23, 45, 1)", + pink: "rgba(255, 182, 193, 1)", + vibrantPink: "rgba(255, 105, 180, 1)", +}; + +type GameConfigs = { + [G in Game]: GameConfig; +}; + +const GAME_CONFIGS: GameConfigs = { + iidx: { + defaultPlaytype: "SP", + name: "beatmania IIDX", + internalName: "iidx", + validPlaytypes: ["SP", "DP"], + }, + museca: { + defaultPlaytype: "Single", + name: "MÚSECA", + internalName: "museca", + validPlaytypes: ["Single"], + }, + chunithm: { + defaultPlaytype: "Single", + name: "CHUNITHM", + internalName: "chunithm", + validPlaytypes: ["Single"], + }, + ddr: { + defaultPlaytype: "SP", + name: "DDR", // used to be 'Dance Dance Revolution', is now DDR for space reasons. + internalName: "ddr", + validPlaytypes: ["SP", "DP"], + }, + bms: { + defaultPlaytype: "7K", + name: "BMS", + internalName: "bms", + validPlaytypes: ["7K", "14K"], + }, + gitadora: { + defaultPlaytype: "Dora", + name: "GITADORA", + internalName: "gitadora", + validPlaytypes: ["Gita", "Dora"], + }, + jubeat: { + defaultPlaytype: "Single", + name: "jubeat", + internalName: "jubeat", + validPlaytypes: ["Single"], + }, + maimai: { + defaultPlaytype: "Single", + name: "maimai", + internalName: "maimai", + validPlaytypes: ["Single"], + }, + popn: { + defaultPlaytype: "9B", + name: "pop'n music", + internalName: "popn", // note: KAI servers use pnm as a name. + validPlaytypes: ["9B"], + }, + sdvx: { + defaultPlaytype: "Single", + name: "SOUND VOLTEX", + internalName: "sdvx", + validPlaytypes: ["Single"], + }, + usc: { + defaultPlaytype: "Controller", + name: "USC", + internalName: "usc", + validPlaytypes: ["Controller", "Keyboard"], + }, + wacca: { + defaultPlaytype: "Single", + name: "WACCA", + internalName: "wacca", + validPlaytypes: ["Single"], + }, + pms: { + defaultPlaytype: "Controller", + internalName: "pms", + name: "PMS", + validPlaytypes: ["Controller", "Keyboard"], + }, + itg: { + defaultPlaytype: "Stamina", + internalName: "itg", + name: "ITG", + validPlaytypes: ["Stamina"], + }, +}; + +type GamePTConfigs = { + [I in IDStrings]: GamePTConfig; +}; + +const GAME_PT_CONFIGS: GamePTConfigs = { + "iidx:SP": { + idString: "iidx:SP", + + percentMax: 100, + + defaultScoreRatingAlg: "ktLampRating", + defaultSessionRatingAlg: "ktLampRating", + defaultProfileRatingAlg: "ktLampRating", + + scoreRatingAlgs: ["ktLampRating", "BPI"], + sessionRatingAlgs: ["ktLampRating", "BPI"], + profileRatingAlgs: ["ktLampRating", "BPI"], + + scoreRatingAlgDescriptions: { + ktLampRating: `A rating system that values your clear lamps on charts. Tierlist information is taken into account.`, + BPI: `A rating system for Kaiden level play. Only applies to 11s and 12s. A BPI of 0 states the score is equal to the Kaiden Average for that chart. A BPI of 100 is equal to the world record.`, + }, + profileRatingAlgDescriptions: { + ktLampRating: `An average of your best 20 ktLampRatings.`, + BPI: `An average of your best 20 BPIs.`, + }, + sessionRatingAlgDescriptions: { + ktLampRating: `An average of the best 10 ktLampRatings this session.`, + BPI: `An average of the best 10 BPIs this session.`, + }, + + scoreRatingAlgFormatters: {}, + profileRatingAlgFormatters: {}, + sessionRatingAlgFormatters: {}, + + difficulties: [ + "BEGINNER", + "NORMAL", + "HYPER", + "ANOTHER", + "LEGGENDARIA", + "All Scratch BEGINNER", + "All Scratch NORMAL", + "All Scratch HYPER", + "All Scratch ANOTHER", + "All Scratch LEGGENDARIA", + "Kichiku BEGINNER", + "Kichiku NORMAL", + "Kichiku HYPER", + "Kichiku ANOTHER", + "Kichiku LEGGENDARIA", + "Kiraku BEGINNER", + "Kiraku NORMAL", + "Kiraku HYPER", + "Kiraku ANOTHER", + "Kiraku LEGGENDARIA", + ], + shortDifficulties: { + BEGINNER: "B", + NORMAL: "N", + HYPER: "H", + ANOTHER: "A", + LEGGENDARIA: "L", + "All Scratch BEGINNER": "B (Scr.)", + "All Scratch NORMAL": "N (Scr.)", + "All Scratch HYPER": "H (Scr.)", + "All Scratch ANOTHER": "A (Scr.)", + "All Scratch LEGGENDARIA": "L (Scr.)", + "Kichiku BEGINNER": "B (Kc.)", + "Kichiku NORMAL": "N (Kc.)", + "Kichiku HYPER": "H (Kc.)", + "Kichiku ANOTHER": "A (Kc.)", + "Kichiku LEGGENDARIA": "L (Kc.)", + "Kiraku BEGINNER": "B (Kr.)", + "Kiraku NORMAL": "N (Kr.)", + "Kiraku HYPER": "H (Kr.)", + "Kiraku ANOTHER": "A (Kr.)", + "Kiraku LEGGENDARIA": "L (Kr.)", + }, + defaultDifficulty: "ANOTHER", + difficultyColours: { + BEGINNER: COLOUR_SET.paleGreen, + NORMAL: COLOUR_SET.blue, + HYPER: COLOUR_SET.orange, + ANOTHER: COLOUR_SET.red, + LEGGENDARIA: COLOUR_SET.purple, + "All Scratch BEGINNER": COLOUR_SET.paleGreen, + "All Scratch NORMAL": COLOUR_SET.blue, + "All Scratch HYPER": COLOUR_SET.orange, + "All Scratch ANOTHER": COLOUR_SET.red, + "All Scratch LEGGENDARIA": COLOUR_SET.purple, + "Kichiku BEGINNER": COLOUR_SET.paleGreen, + "Kichiku NORMAL": COLOUR_SET.blue, + "Kichiku HYPER": COLOUR_SET.orange, + "Kichiku ANOTHER": COLOUR_SET.red, + "Kichiku LEGGENDARIA": COLOUR_SET.purple, + "Kiraku BEGINNER": COLOUR_SET.paleGreen, + "Kiraku NORMAL": COLOUR_SET.blue, + "Kiraku HYPER": COLOUR_SET.orange, + "Kiraku ANOTHER": COLOUR_SET.red, + "Kiraku LEGGENDARIA": COLOUR_SET.purple, + }, + + grades: ["F", "E", "D", "C", "B", "A", "AA", "AAA", "MAX-", "MAX"], + gradeColours: { + F: COLOUR_SET.gray, + E: COLOUR_SET.red, + D: COLOUR_SET.maroon, + C: COLOUR_SET.purple, + B: COLOUR_SET.paleBlue, + A: COLOUR_SET.green, + AA: COLOUR_SET.blue, + AAA: COLOUR_SET.gold, + "MAX-": COLOUR_SET.teal, + MAX: COLOUR_SET.white, + }, + clearGrade: "A", + + // IIDX has some unhinged grade boundaries... + // You might think these two lines are identical, but they are not. The order of operations affects + // floating point math at this point. + // gradeBoundaries: [0, 2/9, 3/9, 4/9, 5/9, 6/9, 7/9, 8/9, 17/18, 9/9].map(e => e * 100), + gradeBoundaries: [ + 0, + (100 * 2) / 9, + (100 * 3) / 9, + (100 * 4) / 9, + (100 * 5) / 9, + (100 * 6) / 9, + (100 * 7) / 9, + (100 * 8) / 9, + (100 * 17) / 18, + (100 * 9) / 9, + ], + + lamps: [ + "NO PLAY", + "FAILED", + "ASSIST CLEAR", + "EASY CLEAR", + "CLEAR", + "HARD CLEAR", + "EX HARD CLEAR", + "FULL COMBO", + ], + lampColours: { + "NO PLAY": COLOUR_SET.gray, + FAILED: COLOUR_SET.red, + "ASSIST CLEAR": COLOUR_SET.purple, + "EASY CLEAR": COLOUR_SET.green, + CLEAR: COLOUR_SET.blue, + "HARD CLEAR": COLOUR_SET.orange, + "EX HARD CLEAR": COLOUR_SET.gold, + "FULL COMBO": COLOUR_SET.teal, + }, + clearLamp: "CLEAR", + + classHumanisedFormat: { + dan: IIDXDans, + }, + + supportsESD: true, + judgementWindows: [ + { name: "PGREAT", msBorder: 16.667, value: 2 }, + { name: "GREAT", msBorder: 33.333, value: 1 }, + { name: "GOOD", msBorder: 116.667, value: 0 }, + ], + judgements: ["pgreat", "great", "good", "bad", "poor"], + + scoreBucket: "lamp", + + supportedVersions: [ + "3-cs", + "4-cs", + "5-cs", + "6-cs", + "7-cs", + "8-cs", + "9-cs", + "10-cs", + "11-cs", + "12-cs", + "13-cs", + "14-cs", + "15-cs", + "16-cs", + "20", + "21", + "22", + "23", + "24", + "25", + "26", + "27", + "28", + "29", + "26-omni", + "27-omni", + "28-omni", + "27-2dxtra", + "28-2dxtra", + "bmus", + "inf", + ], + + tierlists: ["kt-NC", "kt-HC", "kt-EXHC"], + tierlistDescriptions: { + "kt-NC": + "The Normal Clear tiers for Kamaitachi. These are adapted from multiple sources.", + "kt-HC": + "The Hard Clear tiers for Kamaitachi. These are adapted from multiple sources.", + "kt-EXHC": + "The EX-HARD Clear tiers for Kamaitachi. These are adapted from multiple sources.", + }, + }, + "iidx:DP": { + idString: "iidx:DP", + + percentMax: 100, + + defaultScoreRatingAlg: "ktLampRating", + defaultSessionRatingAlg: "ktLampRating", + defaultProfileRatingAlg: "ktLampRating", + + scoreRatingAlgs: ["ktLampRating", "BPI"], + sessionRatingAlgs: ["ktLampRating", "BPI"], + profileRatingAlgs: ["ktLampRating", "BPI"], + + scoreRatingAlgDescriptions: { + ktLampRating: `A rating system that values your clear lamps on charts. Tierlist information is taken into account.`, + BPI: `A rating system for Kaiden level play. Only applies to 11s and 12s. A BPI of 0 states the score is equal to the Kaiden Average for that chart. A BPI of 100 is equal to the world record.`, + }, + profileRatingAlgDescriptions: { + ktLampRating: `An average of your best 20 ktLampRatings.`, + BPI: `An average of your best 20 BPIs.`, + }, + sessionRatingAlgDescriptions: { + ktLampRating: `An average of the best 10 ktLampRatings this session.`, + BPI: `An average of the best 10 BPIs this session.`, + }, + + scoreRatingAlgFormatters: {}, + profileRatingAlgFormatters: {}, + sessionRatingAlgFormatters: {}, + + difficulties: [ + "NORMAL", + "HYPER", + "ANOTHER", + "LEGGENDARIA", + "All Scratch NORMAL", + "All Scratch HYPER", + "All Scratch ANOTHER", + "All Scratch LEGGENDARIA", + "Kichiku NORMAL", + "Kichiku HYPER", + "Kichiku ANOTHER", + "Kichiku LEGGENDARIA", + "Kiraku NORMAL", + "Kiraku HYPER", + "Kiraku ANOTHER", + "Kiraku LEGGENDARIA", + ], + shortDifficulties: { + NORMAL: "N", + HYPER: "H", + ANOTHER: "A", + LEGGENDARIA: "L", + "All Scratch NORMAL": "N (Scr.)", + "All Scratch HYPER": "H (Scr.)", + "All Scratch ANOTHER": "A (Scr.)", + "All Scratch LEGGENDARIA": "L (Scr.)", + "Kichiku NORMAL": "N (Kc.)", + "Kichiku HYPER": "H (Kc.)", + "Kichiku ANOTHER": "A (Kc.)", + "Kichiku LEGGENDARIA": "L (Kc.)", + "Kiraku NORMAL": "N (Kr.)", + "Kiraku HYPER": "H (Kr.)", + "Kiraku ANOTHER": "A (Kr.)", + "Kiraku LEGGENDARIA": "L (Kr.)", + }, + defaultDifficulty: "ANOTHER", + difficultyColours: { + NORMAL: COLOUR_SET.blue, + HYPER: COLOUR_SET.orange, + ANOTHER: COLOUR_SET.red, + LEGGENDARIA: COLOUR_SET.purple, + "All Scratch NORMAL": COLOUR_SET.blue, + "All Scratch HYPER": COLOUR_SET.orange, + "All Scratch ANOTHER": COLOUR_SET.red, + "All Scratch LEGGENDARIA": COLOUR_SET.purple, + "Kichiku NORMAL": COLOUR_SET.blue, + "Kichiku HYPER": COLOUR_SET.orange, + "Kichiku ANOTHER": COLOUR_SET.red, + "Kichiku LEGGENDARIA": COLOUR_SET.purple, + "Kiraku NORMAL": COLOUR_SET.blue, + "Kiraku HYPER": COLOUR_SET.orange, + "Kiraku ANOTHER": COLOUR_SET.red, + "Kiraku LEGGENDARIA": COLOUR_SET.purple, + }, + + grades: ["F", "E", "D", "C", "B", "A", "AA", "AAA", "MAX-", "MAX"], + gradeColours: { + F: COLOUR_SET.gray, + E: COLOUR_SET.red, + D: COLOUR_SET.maroon, + C: COLOUR_SET.purple, + B: COLOUR_SET.paleBlue, + A: COLOUR_SET.green, + AA: COLOUR_SET.blue, + AAA: COLOUR_SET.gold, + "MAX-": COLOUR_SET.teal, + MAX: COLOUR_SET.white, + }, + clearGrade: "A", + + // see iidx:SP for an explaination of why this is wrote like this. + gradeBoundaries: [ + 0, + (100 * 2) / 9, + (100 * 3) / 9, + (100 * 4) / 9, + (100 * 5) / 9, + (100 * 6) / 9, + (100 * 7) / 9, + (100 * 8) / 9, + (100 * 17) / 18, + (100 * 9) / 9, + ], + + lamps: [ + "NO PLAY", + "FAILED", + "ASSIST CLEAR", + "EASY CLEAR", + "CLEAR", + "HARD CLEAR", + "EX HARD CLEAR", + "FULL COMBO", + ], + lampColours: { + "NO PLAY": COLOUR_SET.gray, + FAILED: COLOUR_SET.red, + "ASSIST CLEAR": COLOUR_SET.purple, + "EASY CLEAR": COLOUR_SET.green, + CLEAR: COLOUR_SET.blue, + "HARD CLEAR": COLOUR_SET.orange, + "EX HARD CLEAR": COLOUR_SET.gold, + "FULL COMBO": COLOUR_SET.teal, + }, + clearLamp: "CLEAR", + + classHumanisedFormat: { + dan: IIDXDans, + }, + + supportsESD: true, + judgementWindows: [ + { name: "PGREAT", msBorder: 16.667, value: 2 }, + { name: "GREAT", msBorder: 33.333, value: 1 }, + { name: "GOOD", msBorder: 116.667, value: 0 }, + ], + judgements: ["pgreat", "great", "good", "bad", "poor"], + + scoreBucket: "lamp", + + supportedVersions: [ + "3-cs", + "4-cs", + "5-cs", + "6-cs", + "7-cs", + "8-cs", + "9-cs", + "10-cs", + "11-cs", + "12-cs", + "13-cs", + "14-cs", + "15-cs", + "16-cs", + "20", + "21", + "22", + "23", + "24", + "25", + "26", + "27", + "28", + "29", + "26-omni", + "27-omni", + "28-omni", + "27-2dxtra", + "28-2dxtra", + "bmus", + "inf", + ], + + tierlists: ["dp-tier"], + tierlistDescriptions: { + "dp-tier": "The unofficial DP tiers, taken from https://zasa.sakura.ne.jp/dp/run.php.", + }, + }, + "chunithm:Single": { + idString: "chunithm:Single", + percentMax: 101, + + defaultScoreRatingAlg: "rating", + defaultSessionRatingAlg: "naiveRating", + defaultProfileRatingAlg: "naiveRating", + + scoreRatingAlgs: ["rating"], + sessionRatingAlgs: ["naiveRating"], + profileRatingAlgs: ["naiveRating"], + + scoreRatingAlgDescriptions: { + rating: "The rating value of this score. This is identical to the system used in game.", + }, + profileRatingAlgDescriptions: { + naiveRating: + "The average of your best 20 ratings. This is different to in-game, as it does not take into account your recent scores in any way.", + }, + sessionRatingAlgDescriptions: { + naiveRating: "The average of your best 10 ratings this session.", + }, + + scoreRatingAlgFormatters: {}, + profileRatingAlgFormatters: {}, + sessionRatingAlgFormatters: {}, + + difficulties: ["BASIC", "ADVANCED", "EXPERT", "MASTER"], + shortDifficulties: { + BASIC: "B", + ADVANCED: "A", + EXPERT: "E", + MASTER: "M", + }, + defaultDifficulty: "MASTER", + difficultyColours: { + BASIC: COLOUR_SET.blue, + ADVANCED: COLOUR_SET.orange, + EXPERT: COLOUR_SET.red, + MASTER: COLOUR_SET.purple, + }, + + grades: ["D", "C", "B", "BB", "BBB", "A", "AA", "AAA", "S", "SS", "SSS"], + gradeColours: { + D: COLOUR_SET.red, + C: COLOUR_SET.purple, + B: COLOUR_SET.paleBlue, + BB: COLOUR_SET.blue, + BBB: COLOUR_SET.vibrantBlue, + A: COLOUR_SET.paleGreen, + AA: COLOUR_SET.green, + AAA: COLOUR_SET.vibrantGreen, + S: COLOUR_SET.vibrantOrange, + SS: COLOUR_SET.vibrantYellow, + SSS: COLOUR_SET.teal, + }, + clearGrade: "A", + gradeBoundaries: [0, 50, 60, 70, 80, 90, 92.5, 95.0, 97.5, 100, 100.75], + + lamps: ["FAILED", "CLEAR", "FULL COMBO", "ALL JUSTICE", "ALL JUSTICE CRITICAL"], + lampColours: { + FAILED: COLOUR_SET.red, + CLEAR: COLOUR_SET.paleGreen, + "FULL COMBO": COLOUR_SET.paleBlue, + "ALL JUSTICE": COLOUR_SET.gold, + "ALL JUSTICE CRITICAL": COLOUR_SET.white, + }, + clearLamp: "CLEAR", + + classHumanisedFormat: { + colour: CHUNITHMColours, + }, + + supportsESD: false, + judgements: ["jcrit", "justice", "attack", "miss"], + + scoreBucket: "grade", + + supportedVersions: ["paradiselost"], + + tierlists: [], + tierlistDescriptions: {}, + }, + "sdvx:Single": { + idString: "sdvx:Single", + percentMax: 100, + + defaultScoreRatingAlg: "VF6", + defaultSessionRatingAlg: "ProfileVF6", + defaultProfileRatingAlg: "VF6", + + scoreRatingAlgs: ["VF6"], + sessionRatingAlgs: ["ProfileVF6", "VF6"], + profileRatingAlgs: ["VF6"], + + scoreRatingAlgDescriptions: { + VF6: "VOLFORCE as it is implemented in SDVX6.", + }, + profileRatingAlgDescriptions: { + VF6: "Your best 50 VF6 values added together.", + }, + sessionRatingAlgDescriptions: { + VF6: "The average of your best 10 VF6s this session.", + ProfileVF6: + "The average of your best 10 VF6s this session, multiplied to be on the same scale as profile VOLFORCE.", + }, + + scoreRatingAlgFormatters: { + VF6: (v) => v.toFixed(3), + }, + profileRatingAlgFormatters: { + VF6: (v) => v.toFixed(3), + }, + sessionRatingAlgFormatters: { + VF6: (v) => v.toFixed(3), + ProfileVF6: (v) => v.toFixed(3), + }, + + difficulties: ["NOV", "ADV", "EXH", "INF", "GRV", "HVN", "VVD", "MXM"], + shortDifficulties: {}, // they're all fine + defaultDifficulty: "EXH", + difficultyColours: { + NOV: COLOUR_SET.purple, // colour set dark purple + ADV: COLOUR_SET.vibrantYellow, + EXH: COLOUR_SET.red, + INF: COLOUR_SET.vibrantPink, + GRV: COLOUR_SET.orange, + HVN: COLOUR_SET.teal, + VVD: COLOUR_SET.pink, + MXM: COLOUR_SET.white, + }, + + grades: ["D", "C", "B", "A", "A+", "AA", "AA+", "AAA", "AAA+", "S", "PUC"], + gradeColours: { + D: COLOUR_SET.gray, + C: COLOUR_SET.red, + B: COLOUR_SET.maroon, + A: COLOUR_SET.paleBlue, + "A+": COLOUR_SET.blue, + AA: COLOUR_SET.paleGreen, + "AA+": COLOUR_SET.green, + AAA: COLOUR_SET.gold, + "AAA+": COLOUR_SET.vibrantOrange, + S: COLOUR_SET.teal, + PUC: COLOUR_SET.pink, + }, + clearGrade: "A", + gradeBoundaries: [0, 70, 80, 87, 90, 93, 95, 97, 98, 99, 100], + + lamps: ["FAILED", "CLEAR", "EXCESSIVE CLEAR", "ULTIMATE CHAIN", "PERFECT ULTIMATE CHAIN"], + lampColours: { + FAILED: COLOUR_SET.red, + CLEAR: COLOUR_SET.green, + "EXCESSIVE CLEAR": COLOUR_SET.purple, + "ULTIMATE CHAIN": COLOUR_SET.teal, + "PERFECT ULTIMATE CHAIN": COLOUR_SET.gold, + }, + clearLamp: "CLEAR", + + classHumanisedFormat: { + dan: SDVXDans, + vfClass: SDVXVFClasses, + }, + + supportsESD: false, + judgements: ["critical", "near", "miss"], + + scoreBucket: "grade", + + supportedVersions: ["booth", "inf", "gw", "heaven", "vivid", "exceed", "konaste"], + + tierlists: ["clear"], + tierlistDescriptions: { + clear: "Clearing values taken from the unofficial SDVX spreadsheet tierlists.", + }, + }, + "usc:Controller": { + idString: "usc:Controller", + percentMax: 100, + + defaultScoreRatingAlg: "VF6", + defaultSessionRatingAlg: "ProfileVF6", + defaultProfileRatingAlg: "VF6", + + scoreRatingAlgs: ["VF6"], + sessionRatingAlgs: ["ProfileVF6", "VF6"], + profileRatingAlgs: ["VF6"], + + scoreRatingAlgDescriptions: { + VF6: "VOLFORCE as it is implemented in SDVX6.", + }, + profileRatingAlgDescriptions: { + VF6: "Your best 50 VF6 values added together.", + }, + sessionRatingAlgDescriptions: { + VF6: "The average of your best 10 VF6s this session.", + ProfileVF6: + "The average of your best 10 VF6s this session, multiplied to be on the same scale as profile VOLFORCE.", + }, + + scoreRatingAlgFormatters: { + VF6: (v) => v.toFixed(3), + }, + profileRatingAlgFormatters: { + VF6: (v) => v.toFixed(3), + }, + sessionRatingAlgFormatters: { + VF6: (v) => v.toFixed(3), + ProfileVF6: (v) => v.toFixed(3), + }, + + difficulties: ["NOV", "ADV", "EXH", "INF"], + shortDifficulties: {}, // all fine + defaultDifficulty: "EXH", + difficultyColours: { + NOV: COLOUR_SET.purple, // colour set dark purple + ADV: COLOUR_SET.vibrantYellow, + EXH: COLOUR_SET.red, + INF: COLOUR_SET.vibrantPink, + }, + + grades: ["D", "C", "B", "A", "A+", "AA", "AA+", "AAA", "AAA+", "S", "PUC"], + gradeColours: { + D: COLOUR_SET.gray, + C: COLOUR_SET.red, + B: COLOUR_SET.maroon, + A: COLOUR_SET.paleBlue, + "A+": COLOUR_SET.blue, + AA: COLOUR_SET.paleGreen, + "AA+": COLOUR_SET.green, + AAA: COLOUR_SET.gold, + "AAA+": COLOUR_SET.vibrantOrange, + S: COLOUR_SET.teal, + PUC: COLOUR_SET.pink, + }, + clearGrade: "A", + gradeBoundaries: [0, 70, 80, 87, 90, 93, 95, 97, 98, 99, 100], + + lamps: ["FAILED", "CLEAR", "EXCESSIVE CLEAR", "ULTIMATE CHAIN", "PERFECT ULTIMATE CHAIN"], + lampColours: { + FAILED: COLOUR_SET.red, + CLEAR: COLOUR_SET.green, + "EXCESSIVE CLEAR": COLOUR_SET.orange, + "ULTIMATE CHAIN": COLOUR_SET.teal, + "PERFECT ULTIMATE CHAIN": COLOUR_SET.gold, + }, + clearLamp: "CLEAR", + + classHumanisedFormat: {}, + + supportsESD: false, + judgements: ["critical", "near", "miss"], + + scoreBucket: "grade", + + supportedVersions: [], + + tierlists: [], + tierlistDescriptions: {}, + }, + "usc:Keyboard": { + idString: "usc:Keyboard", + percentMax: 100, + + defaultScoreRatingAlg: "VF6", + defaultSessionRatingAlg: "ProfileVF6", + defaultProfileRatingAlg: "VF6", + + scoreRatingAlgs: ["VF6"], + sessionRatingAlgs: ["ProfileVF6", "VF6"], + profileRatingAlgs: ["VF6"], + + scoreRatingAlgDescriptions: { + VF6: "VOLFORCE as it is implemented in SDVX6.", + }, + profileRatingAlgDescriptions: { + VF6: "Your best 50 VF6 values added together.", + }, + sessionRatingAlgDescriptions: { + VF6: "The average of your best 10 VF6s this session.", + ProfileVF6: + "The average of your best 10 VF6s this session, multiplied to be on the same scale as profile VOLFORCE.", + }, + + scoreRatingAlgFormatters: { + VF6: (v) => v.toFixed(3), + }, + profileRatingAlgFormatters: { + VF6: (v) => v.toFixed(3), + }, + sessionRatingAlgFormatters: { + VF6: (v) => v.toFixed(3), + ProfileVF6: (v) => v.toFixed(3), + }, + + difficulties: ["NOV", "ADV", "EXH", "INF"], + shortDifficulties: {}, // all fine + defaultDifficulty: "EXH", + difficultyColours: { + NOV: COLOUR_SET.purple, // colour set dark purple + ADV: COLOUR_SET.vibrantYellow, + EXH: COLOUR_SET.red, + INF: COLOUR_SET.vibrantPink, + }, + + grades: ["D", "C", "B", "A", "A+", "AA", "AA+", "AAA", "AAA+", "S", "PUC"], + gradeColours: { + D: COLOUR_SET.gray, + C: COLOUR_SET.red, + B: COLOUR_SET.maroon, + A: COLOUR_SET.paleBlue, + "A+": COLOUR_SET.blue, + AA: COLOUR_SET.paleGreen, + "AA+": COLOUR_SET.green, + AAA: COLOUR_SET.gold, + "AAA+": COLOUR_SET.vibrantOrange, + S: COLOUR_SET.teal, + PUC: COLOUR_SET.pink, + }, + clearGrade: "A", + gradeBoundaries: [0, 70, 80, 87, 90, 93, 95, 97, 98, 99, 100], + + lamps: ["FAILED", "CLEAR", "EXCESSIVE CLEAR", "ULTIMATE CHAIN", "PERFECT ULTIMATE CHAIN"], + lampColours: { + FAILED: COLOUR_SET.red, + CLEAR: COLOUR_SET.green, + "EXCESSIVE CLEAR": COLOUR_SET.orange, + "ULTIMATE CHAIN": COLOUR_SET.teal, + "PERFECT ULTIMATE CHAIN": COLOUR_SET.gold, + }, + clearLamp: "CLEAR", + + classHumanisedFormat: {}, + + supportsESD: false, + judgements: ["critical", "near", "miss"], + + scoreBucket: "grade", + + supportedVersions: [], + + tierlists: [], + tierlistDescriptions: {}, + }, + "museca:Single": { + idString: "museca:Single", + percentMax: 100, + + defaultScoreRatingAlg: "ktRating", + defaultSessionRatingAlg: "ktRating", + defaultProfileRatingAlg: "ktRating", + + scoreRatingAlgs: ["ktRating"], + sessionRatingAlgs: ["ktRating"], + profileRatingAlgs: ["ktRating"], + + scoreRatingAlgDescriptions: { + ktRating: "A custom rating system that combines score and the chart level.", + }, + profileRatingAlgDescriptions: { + ktRating: "The average of your best 20 ktRatings.", + }, + sessionRatingAlgDescriptions: { + ktRating: "The average of your best 10 ktRatings this session.", + }, + + scoreRatingAlgFormatters: {}, + profileRatingAlgFormatters: {}, + sessionRatingAlgFormatters: {}, + + difficulties: ["Green", "Yellow", "Red"], + shortDifficulties: { Green: "G", Yellow: "Y", Red: "R" }, + defaultDifficulty: "Red", + difficultyColours: { + Green: COLOUR_SET.green, + Yellow: COLOUR_SET.vibrantYellow, + Red: COLOUR_SET.red, + }, + + grades: ["没", "拙", "凡", "佳", "良", "優", "秀", "傑", "傑G"], + gradeColours: { + 没: COLOUR_SET.gray, + 拙: COLOUR_SET.maroon, + 凡: COLOUR_SET.red, + 佳: COLOUR_SET.paleGreen, + 良: COLOUR_SET.paleBlue, + 優: COLOUR_SET.green, + 秀: COLOUR_SET.blue, + 傑: COLOUR_SET.teal, + 傑G: COLOUR_SET.gold, + }, + clearGrade: "良", + gradeBoundaries: [0, 60, 70, 80, 85, 90, 95, 97.5, 100], + + lamps: ["FAILED", "CLEAR", "CONNECT ALL", "PERFECT CONNECT ALL"], + lampColours: { + FAILED: COLOUR_SET.red, + CLEAR: COLOUR_SET.green, + "CONNECT ALL": COLOUR_SET.teal, + "PERFECT CONNECT ALL": COLOUR_SET.gold, + }, + clearLamp: "CLEAR", + + classHumanisedFormat: {}, + + supportsESD: true, + judgementWindows: [ + { name: "CRITICAL", msBorder: 33.333, value: 2 }, + { name: "NEAR", msBorder: 66.667, value: 1 }, + ], + judgements: ["critical", "near", "miss"], + + scoreBucket: "grade", + + supportedVersions: ["1.5", "1.5-b"], + + tierlists: [], + tierlistDescriptions: {}, + }, + "bms:7K": { + idString: "bms:7K", + percentMax: 100, + + defaultScoreRatingAlg: "sieglinde", + defaultSessionRatingAlg: "sieglinde", + defaultProfileRatingAlg: "sieglinde", + + scoreRatingAlgs: ["sieglinde"], + sessionRatingAlgs: ["sieglinde"], + profileRatingAlgs: ["sieglinde"], + + scoreRatingAlgDescriptions: { + sieglinde: + "A clearing algorithm that rewards you points based on how difficult an easy clear or hard clear was.", + }, + profileRatingAlgDescriptions: { + sieglinde: "The average of your best 20 sieglinde ratings.", + }, + sessionRatingAlgDescriptions: { + sieglinde: "The average of your best 10 sieglinde ratings this session.", + }, + + scoreRatingAlgFormatters: { + sieglinde: FormatSieglindeBMS, + }, + profileRatingAlgFormatters: { + sieglinde: FormatSieglindeBMS, + }, + sessionRatingAlgFormatters: { + sieglinde: FormatSieglindeBMS, + }, + + difficulties: ["CHART"], + shortDifficulties: {}, // not real + defaultDifficulty: "CHART", + difficultyColours: { + CHART: null, + }, + + grades: ["F", "E", "D", "C", "B", "A", "AA", "AAA", "MAX-", "MAX"], + gradeColours: { + F: COLOUR_SET.gray, + E: COLOUR_SET.red, + D: COLOUR_SET.maroon, + C: COLOUR_SET.purple, + B: COLOUR_SET.paleBlue, + A: COLOUR_SET.green, + AA: COLOUR_SET.blue, + AAA: COLOUR_SET.gold, + "MAX-": COLOUR_SET.teal, + MAX: COLOUR_SET.white, + }, + clearGrade: "A", + + // see iidx:SP for an explaination of why this is wrote like this. + gradeBoundaries: [ + 0, + (100 * 2) / 9, + (100 * 3) / 9, + (100 * 4) / 9, + (100 * 5) / 9, + (100 * 6) / 9, + (100 * 7) / 9, + (100 * 8) / 9, + (100 * 17) / 18, + (100 * 9) / 9, + ], + + lamps: [ + "NO PLAY", + "FAILED", + "ASSIST CLEAR", + "EASY CLEAR", + "CLEAR", + "HARD CLEAR", + "EX HARD CLEAR", + "FULL COMBO", + ], + lampColours: { + "NO PLAY": COLOUR_SET.gray, + FAILED: COLOUR_SET.red, + "ASSIST CLEAR": COLOUR_SET.purple, + "EASY CLEAR": COLOUR_SET.green, + CLEAR: COLOUR_SET.blue, + "HARD CLEAR": COLOUR_SET.orange, + "EX HARD CLEAR": COLOUR_SET.gold, + "FULL COMBO": COLOUR_SET.teal, + }, + clearLamp: "CLEAR", + + classHumanisedFormat: { + genocideDan: BMSGenocideDans, + stslDan: BMSStSlDans, + lnDan: BMSLNDans, + scratchDan: BMSScratchDans, + }, + + supportsESD: false, + judgements: ["pgreat", "great", "good", "bad", "poor"], + + scoreBucket: "lamp", + + supportedVersions: [], + + tierlists: ["sgl-EC", "sgl-HC"], + tierlistDescriptions: { + "sgl-EC": "Sieglinde Easy Clear ratings.", + "sgl-HC": "Sieglinde Hard Clear ratings.", + }, + }, + "bms:14K": { + idString: "bms:14K", + + percentMax: 100, + + defaultScoreRatingAlg: "sieglinde", + defaultSessionRatingAlg: "sieglinde", + defaultProfileRatingAlg: "sieglinde", + + scoreRatingAlgs: ["sieglinde"], + sessionRatingAlgs: ["sieglinde"], + profileRatingAlgs: ["sieglinde"], + + scoreRatingAlgDescriptions: { + sieglinde: + "A clearing algorithm that rewards you points based on how difficult an easy clear or hard clear was.", + }, + profileRatingAlgDescriptions: { + sieglinde: "The average of your best 20 sieglinde ratings.", + }, + sessionRatingAlgDescriptions: { + sieglinde: "The average of your best 10 sieglinde ratings this session.", + }, + + scoreRatingAlgFormatters: { + sieglinde: FormatSieglindeBMS, + }, + profileRatingAlgFormatters: { + sieglinde: FormatSieglindeBMS, + }, + sessionRatingAlgFormatters: { + sieglinde: FormatSieglindeBMS, + }, + + difficulties: ["CHART"], + shortDifficulties: {}, // not real + defaultDifficulty: "CHART", + difficultyColours: { + CHART: null, + }, + + grades: ["F", "E", "D", "C", "B", "A", "AA", "AAA", "MAX-", "MAX"], + gradeColours: { + F: COLOUR_SET.gray, + E: COLOUR_SET.red, + D: COLOUR_SET.maroon, + C: COLOUR_SET.purple, + B: COLOUR_SET.paleBlue, + A: COLOUR_SET.green, + AA: COLOUR_SET.blue, + AAA: COLOUR_SET.gold, + "MAX-": COLOUR_SET.teal, + MAX: COLOUR_SET.white, + }, + clearGrade: "A", + + // see iidx:SP for an explaination of why this is wrote like this. + gradeBoundaries: [ + 0, + (100 * 2) / 9, + (100 * 3) / 9, + (100 * 4) / 9, + (100 * 5) / 9, + (100 * 6) / 9, + (100 * 7) / 9, + (100 * 8) / 9, + (100 * 17) / 18, + (100 * 9) / 9, + ], + + lamps: [ + "NO PLAY", + "FAILED", + "ASSIST CLEAR", + "EASY CLEAR", + "CLEAR", + "HARD CLEAR", + "EX HARD CLEAR", + "FULL COMBO", + ], + lampColours: { + "NO PLAY": COLOUR_SET.gray, + FAILED: COLOUR_SET.red, + "ASSIST CLEAR": COLOUR_SET.purple, + "EASY CLEAR": COLOUR_SET.green, + CLEAR: COLOUR_SET.blue, + "HARD CLEAR": COLOUR_SET.orange, + "EX HARD CLEAR": COLOUR_SET.gold, + "FULL COMBO": COLOUR_SET.teal, + }, + clearLamp: "CLEAR", + + classHumanisedFormat: { + genocideDan: BMSGenocideDans, + }, + + supportsESD: false, + judgements: ["pgreat", "great", "good", "bad", "poor"], + + scoreBucket: "lamp", + + supportedVersions: [], + + tierlists: ["sgl-EC", "sgl-HC"], + tierlistDescriptions: { + "sgl-EC": "Sieglinde Easy Clear ratings.", + "sgl-HC": "Sieglinde Hard Clear ratings.", + }, + }, + "ddr:SP": { + idString: "ddr:SP", + + percentMax: 100, + + defaultScoreRatingAlg: "ktRating", + defaultSessionRatingAlg: "ktRating", + defaultProfileRatingAlg: "ktRating", + + scoreRatingAlgs: ["MFCP", "ktRating"], + sessionRatingAlgs: ["MFCP", "ktRating"], + profileRatingAlgs: ["MFCP", "ktRating"], + + scoreRatingAlgDescriptions: { + MFCP: "Marvelous Full Combo Points. An algorithm used in LIFE4 that rewards players for getting MFCs.", + ktRating: "TODO REMOVE ME", + }, + profileRatingAlgDescriptions: { + MFCP: "All of your MFC points added together.", + ktRating: "TODO REMOVE ME", + }, + sessionRatingAlgDescriptions: { + MFCP: "All of your MFC points achieved this session added together.", + ktRating: "TODO REMOVE ME", + }, + + scoreRatingAlgFormatters: { + MFCP: FormatInt, + }, + profileRatingAlgFormatters: { + MFCP: FormatInt, + }, + sessionRatingAlgFormatters: { + MFCP: FormatInt, + }, + + difficulties: ["BEGINNER", "BASIC", "DIFFICULT", "EXPERT", "CHALLENGE"], + shortDifficulties: { + BEGINNER: "b", + BASIC: "B", + DIFFICULT: "D", + EXPERT: "E", + CHALLENGE: "C", + }, + defaultDifficulty: "EXPERT", + difficultyColours: { + BEGINNER: COLOUR_SET.paleBlue, + BASIC: COLOUR_SET.orange, + DIFFICULT: COLOUR_SET.red, + EXPERT: COLOUR_SET.green, + CHALLENGE: COLOUR_SET.purple, + }, + + grades: [ + "D", + "D+", + "C-", + "C", + "C+", + "B-", + "B", + "B+", + "A-", + "A", + "A+", + "AA-", + "AA", + "AA+", + "AAA", + ], + gradeColours: { + D: COLOUR_SET.gray, + "D+": COLOUR_SET.maroon, + "C-": COLOUR_SET.red, + C: COLOUR_SET.purple, + "C+": COLOUR_SET.vibrantPurple, + "B-": COLOUR_SET.paleBlue, + B: COLOUR_SET.blue, + "B+": COLOUR_SET.vibrantBlue, + "A-": COLOUR_SET.paleGreen, + A: COLOUR_SET.green, + "A+": COLOUR_SET.vibrantGreen, + "AA-": COLOUR_SET.paleOrange, + AA: COLOUR_SET.orange, + "AA+": COLOUR_SET.vibrantOrange, + AAA: COLOUR_SET.gold, + }, + clearGrade: "A", + gradeBoundaries: [0, 55, 59, 60, 65, 69, 70, 75, 79, 80, 85, 89, 90, 95, 99], + + lamps: [ + "FAILED", + "CLEAR", + "LIFE4", + "FULL COMBO", + "GREAT FULL COMBO", + "PERFECT FULL COMBO", + "MARVELOUS FULL COMBO", + ], + lampColours: { + FAILED: COLOUR_SET.red, + CLEAR: COLOUR_SET.paleGreen, + LIFE4: COLOUR_SET.orange, + "FULL COMBO": COLOUR_SET.paleBlue, + "GREAT FULL COMBO": COLOUR_SET.green, + "PERFECT FULL COMBO": COLOUR_SET.gold, + "MARVELOUS FULL COMBO": COLOUR_SET.teal, + }, + clearLamp: "CLEAR", + + classHumanisedFormat: { + dan: DDRDans, + }, + + supportsESD: true, + judgementWindows: [ + { name: "MARVELOUS", msBorder: 15, value: 3 }, + { name: "PERFECT", msBorder: 30, value: 2 }, + { name: "GREAT", msBorder: 59, value: 1 }, + { name: "GOOD", msBorder: 89, value: 0 }, + { name: "BAD", msBorder: 119, value: 0 }, + ], + judgements: ["marvelous", "perfect", "great", "good", "boo", "miss", "ok", "ng"], + + scoreBucket: "lamp", + + supportedVersions: ["a20"], + + tierlists: [], + tierlistDescriptions: {}, + }, + "ddr:DP": { + idString: "ddr:DP", + + percentMax: 100, + + defaultScoreRatingAlg: "ktRating", + defaultSessionRatingAlg: "ktRating", + defaultProfileRatingAlg: "ktRating", + + scoreRatingAlgs: ["MFCP", "ktRating"], + sessionRatingAlgs: ["MFCP", "ktRating"], + profileRatingAlgs: ["MFCP", "ktRating"], + + scoreRatingAlgDescriptions: { + MFCP: "Marvelous Full Combo Points. An algorithm used in LIFE4 that rewards players for getting MFCs.", + ktRating: "TODO REMOVE ME", + }, + profileRatingAlgDescriptions: { + MFCP: "All of your MFC points added together.", + ktRating: "TODO REMOVE ME", + }, + sessionRatingAlgDescriptions: { + MFCP: "All of your MFC points achieved this session added together.", + ktRating: "TODO REMOVE ME", + }, + + scoreRatingAlgFormatters: { + MFCP: FormatInt, + }, + profileRatingAlgFormatters: { + MFCP: FormatInt, + }, + sessionRatingAlgFormatters: { + MFCP: FormatInt, + }, + + difficulties: ["BASIC", "DIFFICULT", "EXPERT", "CHALLENGE"], + shortDifficulties: { + BASIC: "B", + DIFFICULT: "D", + EXPERT: "E", + CHALLENGE: "C", + }, + defaultDifficulty: "EXPERT", + difficultyColours: { + BASIC: COLOUR_SET.orange, + DIFFICULT: COLOUR_SET.red, + EXPERT: COLOUR_SET.green, + CHALLENGE: COLOUR_SET.purple, + }, + + grades: [ + "D", + "D+", + "C-", + "C", + "C+", + "B-", + "B", + "B+", + "A-", + "A", + "A+", + "AA-", + "AA", + "AA+", + "AAA", + ], + gradeColours: { + D: COLOUR_SET.gray, + "D+": COLOUR_SET.maroon, + "C-": COLOUR_SET.red, + C: COLOUR_SET.purple, + "C+": COLOUR_SET.vibrantPurple, + "B-": COLOUR_SET.paleBlue, + B: COLOUR_SET.blue, + "B+": COLOUR_SET.vibrantBlue, + "A-": COLOUR_SET.paleGreen, + A: COLOUR_SET.green, + "A+": COLOUR_SET.vibrantGreen, + "AA-": COLOUR_SET.paleOrange, + AA: COLOUR_SET.orange, + "AA+": COLOUR_SET.vibrantOrange, + AAA: COLOUR_SET.gold, + }, + clearGrade: "A", + gradeBoundaries: [0, 55, 59, 60, 65, 69, 70, 75, 79, 80, 85, 89, 90, 95, 99], + + lamps: [ + "FAILED", + "CLEAR", + "LIFE4", + "FULL COMBO", + "GREAT FULL COMBO", + "PERFECT FULL COMBO", + "MARVELOUS FULL COMBO", + ], + lampColours: { + FAILED: COLOUR_SET.red, + CLEAR: COLOUR_SET.paleGreen, + LIFE4: COLOUR_SET.orange, + "FULL COMBO": COLOUR_SET.paleBlue, + "GREAT FULL COMBO": COLOUR_SET.green, + "PERFECT FULL COMBO": COLOUR_SET.gold, + "MARVELOUS FULL COMBO": COLOUR_SET.teal, + }, + clearLamp: "CLEAR", + + classHumanisedFormat: { + dan: DDRDans, + }, + + supportsESD: true, + judgementWindows: [ + { name: "MARVELOUS", msBorder: 15, value: 3 }, + { name: "PERFECT", msBorder: 30, value: 2 }, + { name: "GREAT", msBorder: 59, value: 1 }, + { name: "GOOD", msBorder: 89, value: 0 }, + { name: "BAD", msBorder: 119, value: 0 }, + ], + judgements: ["marvelous", "perfect", "great", "good", "boo", "miss", "ok", "ng"], + + scoreBucket: "lamp", + + supportedVersions: ["a20"], + + tierlists: [], + tierlistDescriptions: {}, + }, + "maimai:Single": { + idString: "maimai:Single", + + percentMax: Infinity, // doesn't apply to this game -- too difficult to make this field nullable though + + defaultScoreRatingAlg: "ktRating", + defaultSessionRatingAlg: "ktRating", + defaultProfileRatingAlg: "ktRating", + + scoreRatingAlgs: ["ktRating"], + sessionRatingAlgs: ["ktRating"], + profileRatingAlgs: ["ktRating"], + + scoreRatingAlgDescriptions: { + ktRating: "TODO REMOVE ME", + }, + profileRatingAlgDescriptions: { + ktRating: "TODO REMOVE ME", + }, + sessionRatingAlgDescriptions: { + ktRating: "TODO REMOVE ME", + }, + + scoreRatingAlgFormatters: {}, + profileRatingAlgFormatters: {}, + sessionRatingAlgFormatters: {}, + + difficulties: ["Easy", "Basic", "Advanced", "Expert", "Master", "Re:Master"], + shortDifficulties: { + Easy: "e", + Basic: "B", + Advanced: "A", + Expert: "E", + Master: "M", + "Re:Master": "Re", + }, + defaultDifficulty: "Master", + difficultyColours: { + Easy: COLOUR_SET.blue, + Basic: COLOUR_SET.green, + Advanced: COLOUR_SET.orange, + Expert: COLOUR_SET.red, + Master: COLOUR_SET.purple, + "Re:Master": COLOUR_SET.white, + }, + + grades: ["F", "E", "D", "C", "B", "A", "AA", "AAA", "S", "S+", "SS", "SS+", "SSS", "SSS+"], + gradeColours: { + F: COLOUR_SET.gray, + E: COLOUR_SET.red, + D: COLOUR_SET.maroon, + C: COLOUR_SET.purple, + B: COLOUR_SET.paleGreen, + A: COLOUR_SET.green, + AA: COLOUR_SET.paleBlue, + AAA: COLOUR_SET.blue, + S: COLOUR_SET.gold, + "S+": COLOUR_SET.vibrantYellow, + SS: COLOUR_SET.paleOrange, + "SS+": COLOUR_SET.orange, + SSS: COLOUR_SET.teal, + "SSS+": COLOUR_SET.white, + }, + clearGrade: "A", + + // @hack Maimai's top grade depends on the chart's maximum percent + // we just set it at percentMax, but it's not technically correct + gradeBoundaries: [0, 10, 20, 40, 60, 80, 90, 94, 97, 98, 99, 99.5, 100, Infinity], + + lamps: ["FAILED", "CLEAR", "FULL COMBO", "ALL PERFECT", "ALL PERFECT+"], + lampColours: { + FAILED: COLOUR_SET.red, + CLEAR: COLOUR_SET.green, + "FULL COMBO": COLOUR_SET.blue, + "ALL PERFECT": COLOUR_SET.gold, + "ALL PERFECT+": COLOUR_SET.teal, + }, + clearLamp: "CLEAR", + + classHumanisedFormat: {}, + + supportsESD: false, + judgements: ["perfect", "great", "good", "miss"], + + scoreBucket: "grade", + + supportedVersions: ["finale"], + + tierlists: [], + tierlistDescriptions: {}, + }, + "gitadora:Gita": { + idString: "gitadora:Gita", + + percentMax: 100, + + defaultScoreRatingAlg: "skill", + defaultSessionRatingAlg: "skill", + defaultProfileRatingAlg: "skill", + + scoreRatingAlgs: ["skill"], + sessionRatingAlgs: ["skill"], + profileRatingAlgs: ["skill"], + + scoreRatingAlgDescriptions: { + skill: "Skill Rating as it's implemented in game.", + }, + profileRatingAlgDescriptions: { + skill: "Your profile skill as it's implemented in game -- 25 HOT and 25 not HOT.", + naiveSkill: + "Your best 50 skill levels added together, regardless of whether the chart is HOT or not.", + }, + sessionRatingAlgDescriptions: { + skill: "The average of your best 10 skill ratings this session.", + }, + + scoreRatingAlgFormatters: { + skill: FormatInt, + }, + profileRatingAlgFormatters: { + skill: FormatInt, + naiveSkill: FormatInt, + }, + sessionRatingAlgFormatters: { + skill: FormatInt, + }, + + difficulties: [ + "BASIC", + "ADVANCED", + "EXTREME", + "MASTER", + "BASS BASIC", + "BASS ADVANCED", + "BASS EXTREME", + "BASS MASTER", + ], + shortDifficulties: { + BASIC: "G-BSC", + ADVANCED: "G-ADV", + EXTREME: "G-EXT", + MASTER: "G-MAS", + "BASS BASIC": "B-BSC", + "BASS ADVANCED": "B-ADV", + "BASS EXTREME": "B-EXT", + "BASS MASTER": "B-MAS", + }, + defaultDifficulty: "EXTREME", + difficultyColours: { + BASIC: COLOUR_SET.blue, + ADVANCED: COLOUR_SET.orange, + EXTREME: COLOUR_SET.red, + MASTER: COLOUR_SET.purple, + "BASS BASIC": COLOUR_SET.vibrantBlue, + "BASS ADVANCED": COLOUR_SET.vibrantOrange, + "BASS EXTREME": "todo", // colourset vibrant red + "BASS MASTER": COLOUR_SET.vibrantPurple, + }, + + grades: ["C", "B", "A", "S", "SS", "MAX"], + gradeColours: { + C: COLOUR_SET.purple, + B: COLOUR_SET.blue, + A: COLOUR_SET.green, + S: COLOUR_SET.orange, + SS: COLOUR_SET.gold, + MAX: COLOUR_SET.white, + }, + clearGrade: "A", + gradeBoundaries: [0, 63, 73, 80, 95, 100], + + lamps: ["FAILED", "CLEAR", "FULL COMBO", "EXCELLENT"], + lampColours: { + FAILED: COLOUR_SET.red, + CLEAR: COLOUR_SET.blue, + "FULL COMBO": COLOUR_SET.teal, + EXCELLENT: COLOUR_SET.gold, + }, + clearLamp: "CLEAR", + + classHumanisedFormat: { + colour: GitadoraColours, + }, + + supportsESD: false, + judgements: ["perfect", "great", "good", "ok", "miss"], + + scoreBucket: "grade", + + supportedVersions: ["nextage"], + + tierlists: [], + tierlistDescriptions: {}, + }, + "gitadora:Dora": { + idString: "gitadora:Dora", + + percentMax: 100, + + defaultScoreRatingAlg: "skill", + defaultSessionRatingAlg: "skill", + defaultProfileRatingAlg: "skill", + + scoreRatingAlgs: ["skill"], + sessionRatingAlgs: ["skill"], + profileRatingAlgs: ["skill"], + + scoreRatingAlgDescriptions: { + skill: "Skill Rating as it's implemented in game.", + }, + profileRatingAlgDescriptions: { + skill: "Your profile skill as it's implemented in game -- 25 HOT and 25 not HOT.", + naiveSkill: + "Your best 50 skill levels added together, regardless of whether the chart is HOT or not.", + }, + sessionRatingAlgDescriptions: { + skill: "The average of your best 10 skill ratings this session.", + }, + + scoreRatingAlgFormatters: { + skill: FormatInt, + }, + profileRatingAlgFormatters: { + skill: FormatInt, + naiveSkill: FormatInt, + }, + sessionRatingAlgFormatters: { + skill: FormatInt, + }, + + difficulties: ["BASIC", "ADVANCED", "EXTREME", "MASTER"], + shortDifficulties: { + BASIC: "D-BSC", + ADVANCED: "D-ADV", + EXTREME: "D-EXT", + MASTER: "D-MAS", + }, + defaultDifficulty: "EXTREME", + difficultyColours: { + BASIC: COLOUR_SET.blue, + ADVANCED: COLOUR_SET.orange, + EXTREME: COLOUR_SET.red, + MASTER: COLOUR_SET.purple, + }, + + grades: ["C", "B", "A", "S", "SS", "MAX"], + gradeColours: { + C: COLOUR_SET.purple, + B: COLOUR_SET.blue, + A: COLOUR_SET.green, + S: COLOUR_SET.orange, + SS: COLOUR_SET.gold, + MAX: COLOUR_SET.white, + }, + clearGrade: "A", + gradeBoundaries: [0, 63, 73, 80, 95, 100], + + lamps: ["FAILED", "CLEAR", "FULL COMBO", "EXCELLENT"], + lampColours: { + FAILED: COLOUR_SET.red, + CLEAR: COLOUR_SET.blue, + "FULL COMBO": COLOUR_SET.teal, + EXCELLENT: COLOUR_SET.gold, + }, + clearLamp: "CLEAR", + + classHumanisedFormat: { + colour: GitadoraColours, + }, + + supportsESD: false, + judgements: ["perfect", "great", "good", "ok", "miss"], + + scoreBucket: "grade", + + supportedVersions: ["nextage"], + + tierlists: [], + tierlistDescriptions: {}, + }, + "wacca:Single": { + idString: "wacca:Single", + + percentMax: 100, + + defaultScoreRatingAlg: "rate", + defaultSessionRatingAlg: "rate", + defaultProfileRatingAlg: "naiveRate", + + scoreRatingAlgs: ["rate"], + sessionRatingAlgs: ["rate"], + profileRatingAlgs: ["naiveRate", "rate"], + + scoreRatingAlgDescriptions: { + rate: "Rating as it's implemented in game.", + }, + profileRatingAlgDescriptions: { + naiveRate: "A naive rating algorithm that just averages your 50 best scores.", + rate: "Rating as it's implemented in game, taking 15 scores from the latest version and 35 from all old versions.", + }, + sessionRatingAlgDescriptions: { + rate: "The average of your best 10 ratings this session.", + }, + + scoreRatingAlgFormatters: {}, + profileRatingAlgFormatters: {}, + sessionRatingAlgFormatters: {}, + + difficulties: ["NORMAL", "HARD", "EXPERT", "INFERNO"], + shortDifficulties: { + NORMAL: "NRM", + HARD: "HRD", + EXPERT: "EXP", + INFERNO: "INF", + }, + defaultDifficulty: "EXPERT", + difficultyColours: { + NORMAL: COLOUR_SET.blue, + HARD: COLOUR_SET.gold, + EXPERT: COLOUR_SET.pink, + INFERNO: COLOUR_SET.purple, + }, + + grades: ["D", "C", "B", "A", "AA", "AAA", "S", "S+", "SS", "SS+", "SSS", "SSS+", "MASTER"], + gradeColours: { + D: COLOUR_SET.gray, + C: COLOUR_SET.maroon, + B: COLOUR_SET.red, + A: COLOUR_SET.paleGreen, + AA: COLOUR_SET.green, + AAA: COLOUR_SET.vibrantGreen, + S: COLOUR_SET.gold, + "S+": COLOUR_SET.vibrantYellow, + SS: COLOUR_SET.paleOrange, + "SS+": COLOUR_SET.orange, + SSS: COLOUR_SET.pink, + "SSS+": COLOUR_SET.vibrantPink, + MASTER: COLOUR_SET.white, + }, + clearGrade: "S", + + // i'm pretty sure the first grade is below the limit of reliable FPA accuracy + gradeBoundaries: [0, 0.0001, 3.0001, 70, 80, 85, 90, 93, 95, 97, 98, 99, 100], + + lamps: ["FAILED", "CLEAR", "MISSLESS", "FULL COMBO", "ALL MARVELOUS"], + lampColours: { + FAILED: COLOUR_SET.red, + CLEAR: COLOUR_SET.blue, + MISSLESS: COLOUR_SET.orange, + "FULL COMBO": COLOUR_SET.pink, + "ALL MARVELOUS": COLOUR_SET.gold, + }, + clearLamp: "CLEAR", + + classHumanisedFormat: { + stageUp: WaccaStageUps, + colour: WaccaColours, + }, + + supportsESD: false, + judgements: ["marvelous", "great", "good", "miss"], + + scoreBucket: "grade", + + supportedVersions: ["reverse"], + + tierlists: [], + tierlistDescriptions: {}, + }, + "popn:9B": { + idString: "popn:9B", + + percentMax: 100, + + defaultScoreRatingAlg: "classPoints", + defaultSessionRatingAlg: "classPoints", + defaultProfileRatingAlg: "naiveClassPoints", + + scoreRatingAlgs: ["classPoints"], + sessionRatingAlgs: ["classPoints"], + profileRatingAlgs: ["naiveClassPoints"], + + scoreRatingAlgDescriptions: { + classPoints: "Class Points as they're implemented in game.", + }, + profileRatingAlgDescriptions: { + naiveClassPoints: + "A naive average of your best 20 scores. This is different to in game class points, as that is affected by recent scores, and not just your best scores.", + }, + sessionRatingAlgDescriptions: { + classPoints: "The average of your best 10 class points this session.", + }, + + scoreRatingAlgFormatters: {}, + profileRatingAlgFormatters: {}, + sessionRatingAlgFormatters: {}, + + difficulties: ["Easy", "Normal", "Hyper", "EX"], + shortDifficulties: { + Easy: "E", + Normal: "N", + Hyper: "H", + EX: "EX", + }, + defaultDifficulty: "EX", + difficultyColours: { + Easy: COLOUR_SET.blue, + Normal: COLOUR_SET.green, + Hyper: COLOUR_SET.orange, + EX: COLOUR_SET.red, + }, + + grades: ["E", "D", "C", "B", "A", "AA", "AAA", "S"], + gradeColours: { + E: COLOUR_SET.gray, + D: COLOUR_SET.maroon, + C: COLOUR_SET.red, + B: COLOUR_SET.blue, + A: COLOUR_SET.green, + AA: COLOUR_SET.orange, + AAA: COLOUR_SET.gold, + S: COLOUR_SET.teal, + }, + clearGrade: "A", + + // TECHNICALLY THIS IS NOT CORRECT + // A Fail is capped at A rank (82%), no matter what! + gradeBoundaries: [0, 50, 62, 72, 82, 90, 95, 98], + + lamps: ["FAILED", "EASY CLEAR", "CLEAR", "FULL COMBO", "PERFECT"], + lampColours: { + FAILED: COLOUR_SET.red, + "EASY CLEAR": COLOUR_SET.green, + CLEAR: COLOUR_SET.blue, + "FULL COMBO": COLOUR_SET.teal, + PERFECT: COLOUR_SET.gold, + }, + clearLamp: "CLEAR", + + classHumanisedFormat: { + class: PopnClasses, + }, + + supportsESD: false, + judgements: ["cool", "great", "good", "bad"], + + scoreBucket: "lamp", + + supportedVersions: ["peace"], + + tierlists: [], + tierlistDescriptions: {}, + }, + "jubeat:Single": { + idString: "jubeat:Single", + + percentMax: 120, + + defaultScoreRatingAlg: "jubility", + defaultSessionRatingAlg: "jubility", + defaultProfileRatingAlg: "jubility", + + scoreRatingAlgs: ["jubility"], + sessionRatingAlgs: ["jubility"], + profileRatingAlgs: ["jubility", "naiveJubility"], + + scoreRatingAlgDescriptions: { + jubility: "Jubility as it's implemented in game.", + }, + profileRatingAlgDescriptions: { + jubility: + "Your profile jubility. This takes your best 30 scores on PICK UP songs, and your best 30 elsewhere.", + naiveJubility: + "A naive version of jubility which just adds together your best 60 scores.", + }, + sessionRatingAlgDescriptions: { + jubility: "The average of your best 10 jubilities this session.", + }, + + scoreRatingAlgFormatters: {}, + profileRatingAlgFormatters: {}, + sessionRatingAlgFormatters: {}, + + difficulties: ["BSC", "ADV", "EXT", "HARD BSC", "HARD ADV", "HARD EXT"], + shortDifficulties: { + BSC: "BSC", + ADV: "ADV", + EXT: "EXT", + "HARD BSC": "H. BSC", + "HARD ADV": "H. ADV", + "HARD EXT": "H. EXT", + }, + defaultDifficulty: "EXT", + difficultyColours: { + BSC: COLOUR_SET.green, + ADV: COLOUR_SET.gold, + EXT: COLOUR_SET.red, + "HARD BSC": COLOUR_SET.darkGreen, + "HARD ADV": COLOUR_SET.orange, + "HARD EXT": COLOUR_SET.vibrantRed, + }, + + grades: ["E", "D", "C", "B", "A", "S", "SS", "SSS", "EXC"], + gradeColours: { + E: COLOUR_SET.gray, + D: COLOUR_SET.maroon, + C: COLOUR_SET.red, + B: COLOUR_SET.blue, + A: COLOUR_SET.green, + S: COLOUR_SET.gold, + SS: COLOUR_SET.orange, + SSS: COLOUR_SET.teal, + EXC: COLOUR_SET.white, + }, + clearGrade: "A", + gradeBoundaries: null, + + lamps: ["FAILED", "CLEAR", "FULL COMBO", "EXCELLENT"], + lampColours: { + FAILED: COLOUR_SET.red, + CLEAR: COLOUR_SET.blue, + "FULL COMBO": COLOUR_SET.teal, + EXCELLENT: COLOUR_SET.white, + }, + clearLamp: "CLEAR", + + classHumanisedFormat: { + colour: JubeatColours, + }, + + supportsESD: false, + judgements: ["perfect", "great", "good", "poor", "miss"], + + scoreBucket: "grade", + + supportedVersions: ["clan", "festo"], + + tierlists: [], + tierlistDescriptions: {}, + }, + "pms:Controller": { + idString: "pms:Controller", + percentMax: 100, + + defaultScoreRatingAlg: "sieglinde", + defaultSessionRatingAlg: "sieglinde", + defaultProfileRatingAlg: "sieglinde", + + scoreRatingAlgs: ["sieglinde"], + sessionRatingAlgs: ["sieglinde"], + profileRatingAlgs: ["sieglinde"], + + scoreRatingAlgFormatters: { + sieglinde: FormatSieglindePMS, + }, + profileRatingAlgFormatters: { + sieglinde: FormatSieglindePMS, + }, + sessionRatingAlgFormatters: { + sieglinde: FormatSieglindePMS, + }, + + scoreRatingAlgDescriptions: { + sieglinde: + "A clearing algorithm that rewards you points based on how difficult an easy clear or hard clear was.", + }, + profileRatingAlgDescriptions: { + sieglinde: "The average of your best 20 sieglinde ratings.", + }, + sessionRatingAlgDescriptions: { + sieglinde: "The average of your best 10 sieglinde ratings this session.", + }, + + difficulties: ["CHART"], + shortDifficulties: {}, + defaultDifficulty: "CHART", + difficultyColours: { + CHART: null, + }, + + grades: ["F", "E", "D", "C", "B", "A", "AA", "AAA", "MAX-", "MAX"], + gradeColours: { + F: COLOUR_SET.gray, + E: COLOUR_SET.red, + D: COLOUR_SET.maroon, + C: COLOUR_SET.purple, + B: COLOUR_SET.paleBlue, + A: COLOUR_SET.green, + AA: COLOUR_SET.blue, + AAA: COLOUR_SET.gold, + "MAX-": COLOUR_SET.teal, + MAX: COLOUR_SET.white, + }, + clearGrade: "A", + + // see iidx:SP for an explaination of why this is wrote like this. + gradeBoundaries: [ + 0, + (100 * 2) / 9, + (100 * 3) / 9, + (100 * 4) / 9, + (100 * 5) / 9, + (100 * 6) / 9, + (100 * 7) / 9, + (100 * 8) / 9, + (100 * 17) / 18, + (100 * 9) / 9, + ], + + lamps: [ + "NO PLAY", + "FAILED", + "ASSIST CLEAR", + "EASY CLEAR", + "CLEAR", + "HARD CLEAR", + "EX HARD CLEAR", + "FULL COMBO", + ], + lampColours: { + "NO PLAY": COLOUR_SET.gray, + FAILED: COLOUR_SET.red, + "ASSIST CLEAR": COLOUR_SET.purple, + "EASY CLEAR": COLOUR_SET.green, + CLEAR: COLOUR_SET.blue, + "HARD CLEAR": COLOUR_SET.orange, + "EX HARD CLEAR": COLOUR_SET.gold, + "FULL COMBO": COLOUR_SET.teal, + }, + clearLamp: "CLEAR", + + classHumanisedFormat: { + dan: PMSDans, + }, + + supportsESD: false, + judgements: ["cool", "great", "good", "bad", "poor"], + + scoreBucket: "lamp", + + supportedVersions: [], + + tierlists: ["sgl-EC", "sgl-HC"], + tierlistDescriptions: { + "sgl-EC": "Sieglinde Easy Clear ratings.", + "sgl-HC": "Sieglinde Hard Clear ratings.", + }, + }, + "pms:Keyboard": { + idString: "pms:Keyboard", + percentMax: 100, + + defaultScoreRatingAlg: "sieglinde", + defaultSessionRatingAlg: "sieglinde", + defaultProfileRatingAlg: "sieglinde", + + scoreRatingAlgs: ["sieglinde"], + sessionRatingAlgs: ["sieglinde"], + profileRatingAlgs: ["sieglinde"], + + scoreRatingAlgFormatters: { + sieglinde: FormatSieglindePMS, + }, + profileRatingAlgFormatters: { + sieglinde: FormatSieglindePMS, + }, + sessionRatingAlgFormatters: { + sieglinde: FormatSieglindePMS, + }, + + scoreRatingAlgDescriptions: { + sieglinde: + "A clearing algorithm that rewards you points based on how difficult an easy clear or hard clear was.", + }, + profileRatingAlgDescriptions: { + sieglinde: "The average of your best 20 sieglinde ratings.", + }, + sessionRatingAlgDescriptions: { + sieglinde: "The average of your best 10 sieglinde ratings this session.", + }, + + difficulties: ["CHART"], + shortDifficulties: {}, + defaultDifficulty: "CHART", + difficultyColours: { + CHART: null, + }, + + grades: ["F", "E", "D", "C", "B", "A", "AA", "AAA", "MAX-", "MAX"], + gradeColours: { + F: COLOUR_SET.gray, + E: COLOUR_SET.red, + D: COLOUR_SET.maroon, + C: COLOUR_SET.purple, + B: COLOUR_SET.paleBlue, + A: COLOUR_SET.green, + AA: COLOUR_SET.blue, + AAA: COLOUR_SET.gold, + "MAX-": COLOUR_SET.teal, + MAX: COLOUR_SET.white, + }, + clearGrade: "A", + + // see iidx:SP for an explaination of why this is wrote like this. + gradeBoundaries: [ + 0, + (100 * 2) / 9, + (100 * 3) / 9, + (100 * 4) / 9, + (100 * 5) / 9, + (100 * 6) / 9, + (100 * 7) / 9, + (100 * 8) / 9, + (100 * 17) / 18, + (100 * 9) / 9, + ], + + lamps: [ + "NO PLAY", + "FAILED", + "ASSIST CLEAR", + "EASY CLEAR", + "CLEAR", + "HARD CLEAR", + "EX HARD CLEAR", + "FULL COMBO", + ], + lampColours: { + "NO PLAY": COLOUR_SET.gray, + FAILED: COLOUR_SET.red, + "ASSIST CLEAR": COLOUR_SET.purple, + "EASY CLEAR": COLOUR_SET.green, + CLEAR: COLOUR_SET.blue, + "HARD CLEAR": COLOUR_SET.orange, + "EX HARD CLEAR": COLOUR_SET.gold, + "FULL COMBO": COLOUR_SET.teal, + }, + clearLamp: "CLEAR", + + classHumanisedFormat: { + dan: PMSDans, + }, + + supportsESD: false, + judgements: ["cool", "great", "good", "bad", "poor"], + + scoreBucket: "lamp", + + supportedVersions: [], + + tierlists: ["sgl-EC", "sgl-HC"], + tierlistDescriptions: { + "sgl-EC": "Sieglinde Easy Clear ratings.", + "sgl-HC": "Sieglinde Hard Clear ratings.", + }, + }, + "itg:Stamina": { + idString: "itg:Stamina", + + percentMax: 100, + + defaultScoreRatingAlg: "blockRating", + defaultSessionRatingAlg: "blockRating", + defaultProfileRatingAlg: "highestBlock", + + scoreRatingAlgs: ["blockRating", "highest32", "highest256"], + sessionRatingAlgs: ["blockRating", "average32Speed"], + profileRatingAlgs: ["highest32", "highestBlock", "highest256"], + + scoreRatingAlgDescriptions: { + blockRating: "How much this clear is worth.", + highest32: "The highest BPM this score streamed 32 measures straight for.", + highest256: "The highest BPM this score streamed 256 measures straight for.", + }, + profileRatingAlgDescriptions: { + highest32: "The highest BPM this user has streamed 32 unbroken measures at.", + highest256: "The highest BPM this user has streamed 256 unbroken measures at.", + highestBlock: "The highest block level this player has cleared.", + }, + sessionRatingAlgDescriptions: { + blockRating: "An average of your best 5 blsock levels cleared this session.", + average32Speed: "An average of your fastest 5 32 measure runs this session.", + }, + + // always round these + scoreRatingAlgFormatters: { + highest256: (k) => k.toFixed(0), + highest32: (k) => k.toFixed(0), + blockRating: (k) => k.toFixed(0), + }, + profileRatingAlgFormatters: { + highest256: (k) => k.toFixed(0), + highest32: (k) => k.toFixed(0), + highestBlock: (k) => k.toFixed(0), + }, + sessionRatingAlgFormatters: {}, + + // This is quirky (lol) + // ITG difficulties can be any string they want to be. We just support some + // hardcoded defaults. + difficulties: ["Beginner", "Easy", "Medium", "Hard", "Challenge"], + shortDifficulties: { + Beginner: "B", + Easy: "E", + Medium: "M", + Hard: "H", + Challenge: "C", + }, + defaultDifficulty: "Challenge", + difficultyColours: { + Beginner: COLOUR_SET.paleBlue, + Easy: COLOUR_SET.green, + Medium: COLOUR_SET.vibrantYellow, + Hard: COLOUR_SET.red, + Challenge: COLOUR_SET.pink, + }, + + grades: ["D", "C", "B", "A", "S-", "S", "S+", "★", "★★", "★★★", "★★★★"], + gradeColours: { + D: COLOUR_SET.red, + C: COLOUR_SET.maroon, + B: COLOUR_SET.purple, + A: COLOUR_SET.green, + "S-": COLOUR_SET.paleOrange, + S: COLOUR_SET.orange, + "S+": COLOUR_SET.vibrantOrange, + "★": COLOUR_SET.pink, + "★★": COLOUR_SET.vibrantPink, + "★★★": COLOUR_SET.teal, + "★★★★": COLOUR_SET.white, + }, + clearGrade: "C", + gradeBoundaries: [0, 55, 68, 80, 89, 92, 94, 96, 98, 99, 100], + + lamps: ["FAILED", "CLEAR", "FULL COMBO", "FULL EXCELLENT COMBO", "QUAD"], + lampColours: { + FAILED: COLOUR_SET.red, + CLEAR: COLOUR_SET.blue, + "FULL COMBO": COLOUR_SET.pink, + "FULL EXCELLENT COMBO": COLOUR_SET.gold, + QUAD: COLOUR_SET.white, + }, + clearLamp: "CLEAR", + + classHumanisedFormat: {}, + + supportsESD: false, + judgements: ["fantastic", "excellent", "great", "decent", "wayoff", "miss"], + + scoreBucket: "lamp", + + supportedVersions: [], + + tierlists: [], + tierlistDescriptions: {}, + }, +}; + +/** + * Returns the configuration for this game. + */ +export function GetGameConfig(game: G): GameConfig { + return GAME_CONFIGS[game] as GameConfig; +} + +/** + * Returns the configuration for this Game + Playtype. + * Optionally, a generic parameter - IDStrings - can be passed + * to indicate what IDString this configuration is for. + */ +export function GetGamePTConfig( + game: Game, + playtype: Playtypes[Game] +): GamePTConfig { + const idString = `${game}:${playtype}` as IDStrings; + + return GAME_PT_CONFIGS[idString] as unknown as GamePTConfig; +} diff --git a/common/src/config/static-config.ts b/common/src/config/static-config.ts new file mode 100644 index 000000000..de54a4db6 --- /dev/null +++ b/common/src/config/static-config.ts @@ -0,0 +1,174 @@ +import type { + FileUploadImportTypes, + IRImportTypes, + APIImportTypes, + ImportTypes, + Game, + IDStrings, + GPTSupportedVersions, +} from ".."; + +export const fileImportTypes: Array = [ + "file/eamusement-iidx-csv", + "file/eamusement-sdvx-csv", + "file/batch-manual", + "file/solid-state-squad", + "file/mer-iidx", + "file/pli-iidx-csv", +]; + +export const irImportTypes: Array = [ + "ir/direct-manual", + "ir/barbatos", + "ir/fervidex", + "ir/fervidex-static", + "ir/beatoraja", + "ir/usc", + "ir/kshook-sv6c", +]; + +export const apiImportTypes: Array = [ + "api/arc-iidx", + "api/arc-sdvx", + "api/eag-iidx", + "api/eag-sdvx", + "api/flo-iidx", + "api/flo-sdvx", + "api/min-sdvx", +]; + +export const allImportTypes: Array = [ + ...fileImportTypes, + ...irImportTypes, + ...apiImportTypes, +]; + +export const allIDStrings: Array = [ + "iidx:SP", + "bms:14K", + "bms:7K", + "chunithm:Single", + "ddr:DP", + "ddr:SP", + "gitadora:Dora", + "gitadora:Gita", + "iidx:DP", + "maimai:Single", + "museca:Single", + "sdvx:Single", + "usc:Controller", + "usc:Keyboard", + "wacca:Single", + "pms:Controller", + "pms:Keyboard", + "jubeat:Single", + "itg:Stamina", +]; + +export const allSupportedGames: Array = [ + "iidx", + "museca", + "maimai", + "jubeat", + "popn", + "sdvx", + "ddr", + "bms", + "chunithm", + "gitadora", + "usc", + "wacca", + "pms", + "itg", +]; + +export type Versions = { + [I in IDStrings]: { + [K in GPTSupportedVersions[I]]: string; + }; +}; + +const prettyIIDXVersions: Versions["iidx:SP"] = { + 20: "tricoro", + 21: "SPADA", + 22: "PENDUAL", + 23: "copula", + 24: "SINOBUZ", + 25: "CANNON BALLERS", + 26: "ROOTAGE", + 27: "HEROIC VERSE", + 28: "BISTROVER", + 29: "CastHour", + "10-cs": "10th Style CS", + "11-cs": "IIDX RED CS", + "12-cs": "HAPPY SKY CS", + "13-cs": "DISTORTED CS", + "14-cs": "GOLD CS", + "15-cs": "DJ TROOPERS CS", + "16-cs": "EMPRESS CS", + "26-omni": "ROOTAGE Omnimix", + "3-cs": "3rd Style CS", + "4-cs": "4th Style CS", + "5-cs": "5th Style CS", + "6-cs": "6th Style CS", + "7-cs": "7th Style CS", + "8-cs": "8th Style CS", + "9-cs": "9th Style CS", + "27-omni": "HEROIC VERSE Omnimix", + "28-omni": "BISTROVER Omnimix", + "27-2dxtra": "HEROIC VERSE 2dxtra", + "28-2dxtra": "BISTROVER 2dxtra", + bmus: "BEATMANIA US", + inf: "INFINITAS", +}; + +const prettyDDRVersions: Versions["ddr:SP"] = { + a20: "A20", +}; + +const prettyGitadoraVersions: Versions["gitadora:Dora"] = { + nextage: "NEX+AGE", +}; + +export const PrettyVersions: Versions = { + "iidx:SP": prettyIIDXVersions, + "iidx:DP": prettyIIDXVersions, + "popn:9B": { peace: "peace" }, + "bms:7K": {}, + "bms:14K": {}, + "chunithm:Single": { + paradiselost: "Paradise Lost", + }, + "ddr:SP": prettyDDRVersions, + "ddr:DP": prettyDDRVersions, + "gitadora:Gita": prettyGitadoraVersions, + "gitadora:Dora": prettyGitadoraVersions, + "maimai:Single": { + finale: "FiNALE", + }, + "museca:Single": { + 1.5: "1 + 1/2", + "1.5-b": "1 + 1/2 Rev. B", + }, + "sdvx:Single": { + booth: "BOOTH", + inf: "Infinite Infection", + gw: "GRAVITY WARS", + heaven: "HEAVENLY HAVEN", + vivid: "VIVID WAVE", + exceed: "EXCEED GEAR", + konaste: "Konaste", + }, + "usc:Keyboard": {}, + "usc:Controller": {}, + "wacca:Single": { + reverse: "REVERSE", + }, + "jubeat:Single": { + festo: "festo", + clan: "clan", + }, + "pms:Controller": {}, + "pms:Keyboard": {}, + "itg:Stamina": {}, +}; diff --git a/common/src/constants/game.ts b/common/src/constants/game.ts new file mode 100644 index 000000000..9a058f42b --- /dev/null +++ b/common/src/constants/game.ts @@ -0,0 +1,172 @@ +export enum IIDX_LAMPS { + NO_PLAY = 0, + FAILED = 1, + ASSIST_CLEAR = 2, + EASY_CLEAR = 3, + CLEAR = 4, + HARD_CLEAR = 5, + EX_HARD_CLEAR = 6, + FULL_COMBO = 7, +} + +export enum CHUNITHM_LAMPS { + FAILED = 0, + CLEAR = 1, + FULL_COMBO = 2, + ALL_JUSTICE = 3, + ALL_JUSTICE_CRITICAL = 4, +} + +export enum SDVX_LAMPS { + FAILED = 0, + CLEAR = 1, + EXCESSIVE_CLEAR = 2, + ULTIMATE_CHAIN = 3, + PERFECT_ULTIMATE_CHAIN = 4, +} + +export enum MUSECA_LAMPS { + FAILED = 0, + CLEAR = 1, + CONNECT_ALL = 2, + PERFECT_CONNECT_ALL = 3, +} + +export enum DDR_LAMPS { + FAILED = 0, + CLEAR = 1, + LIFE4 = 2, + FULL_COMBO = 3, + GREAT_FULL_COMBO = 4, + PERFECT_FULL_COMBO = 5, + MARVELOUS_FULL_COMBO = 6, +} + +export enum MAIMAI_LAMPS { + FAILED = 0, + CLEAR = 1, + FULL_COMBO = 2, + ALL_PERFECT = 3, + ALL_PERFECT_PLUS = 4, +} + +export enum GITADORA_LAMPS { + FAILED = 0, + CLEAR = 1, + FULL_COMBO = 2, + EXCELLENT = 3, +} + +export enum IIDX_GRADES { + F = 0, + E = 1, + D = 2, + C = 3, + B = 4, + A = 5, + AA = 6, + AAA = 7, + MAX_MINUS = 8, + MAX = 9, +} + +export enum CHUNITHM_GRADES { + D = 0, + C = 1, + B = 2, + BB = 3, + BBB = 4, + A = 5, + AA = 6, + AAA = 7, + S = 8, + SS = 9, + SSS = 10, +} + +export enum SDVX_GRADES { + D = 0, + C = 1, + B = 2, + A = 3, + A_PLUS = 4, + AA = 5, + AA_PLUS = 6, + AAA = 7, + AAA_PLUS = 8, + S = 9, +} + +export enum MUSECA_GRADES { + 没 = 0, + 拙 = 1, + 凡 = 2, + 佳 = 3, + 良 = 4, + 優 = 5, + 秀 = 6, + 傑 = 7, + 傑G = 8, +} + +/** + * Kxxx refers to the starting point of that grade, + * since they dont translate to english well. + * K950 means the grade that starts at 950K and goes + * to 975K. + */ +export enum MUSECA_GRADES_ASCII { + ZERO = 0, + K600 = 1, + K700 = 2, + K800 = 3, + K850 = 4, + K900 = 5, + K950 = 6, + K975 = 7, + K1000 = 8, +} + +export enum DDR_GRADES { + D = 0, + D_PLUS = 1, + C_MINUS = 2, + C = 3, + C_PLUS = 4, + B_MINUS = 5, + B = 6, + B_PLUS = 7, + A_MINUS = 8, + A = 9, + A_PLUS = 10, + AA_MINUS = 11, + AA = 12, + AA_PLUS = 13, + AAA = 14, +} + +export enum MAIMAI_GRADES { + F = 0, + E = 1, + D = 2, + C = 3, + B = 4, + A = 5, + AA = 6, + AAA = 7, + S = 8, + S_PLUS = 9, + SS = 10, + SS_PLUS = 11, + SSS = 12, + SSS_PLUS = 13, +} + +export enum GITADORA_GRADES { + C = 0, + B = 1, + A = 2, + S = 3, + SS = 4, + MAX = 5, +} diff --git a/common/src/constants/permissions.ts b/common/src/constants/permissions.ts new file mode 100644 index 000000000..310419e71 --- /dev/null +++ b/common/src/constants/permissions.ts @@ -0,0 +1,14 @@ +import type { APIPermissions } from "../types"; + +// Some parts of tachi-server and some parts of the schemas refer to this +// in order to assert that permissions are correctly assigned. +export const ALL_PERMISSIONS: Record = { + customise_profile: true, + customise_score: true, + customise_session: true, + delete_score: true, + manage_rivals: true, + manage_targets: true, + submit_score: true, + manage_challenges: true, +}; diff --git a/common/src/game-classes.ts b/common/src/game-classes.ts new file mode 100644 index 000000000..e2b58c819 --- /dev/null +++ b/common/src/game-classes.ts @@ -0,0 +1,359 @@ +// Classes refer to things like dans. +// Not the JS construct. + +import type { IDStrings, integer } from "./types"; + +export interface ClassInfo { + display: string; + id: string; + mouseover?: string; + css?: { + backgroundColor: string; + color: string; + }; + variant?: "danger" | "info" | "primary" | "secondary" | "success" | "warning"; +} + +function mouseoverCSS(id: string, d: string, m: string, bg = "black", color = "white") { + return { id, display: d, mouseover: m, css: { backgroundColor: bg, color } }; +} + +function mouseoverVariant( + id: string, + d: string, + m: string, + variant: "danger" | "info" | "primary" | "secondary" | "success" | "warning" +) { + return { id, display: d, mouseover: m, variant }; +} + +function noMouseoverVariant( + id: string, + d: string, + variant: "danger" | "info" | "primary" | "secondary" | "success" | "warning" +) { + return { id, display: d, variant }; +} + +function noMouseoverCSS(id: string, d: string, bg = "#131313", color = "white") { + return { id, display: d, css: { backgroundColor: bg, color } }; +} + +export const IIDXDans: Array = [ + mouseoverCSS("KYU_7", "七級", "7th Kyu", "green"), + mouseoverCSS("KYU_6", "六級", "6th Kyu", "green"), + mouseoverCSS("KYU_5", "五級", "5th Kyu", "green"), + mouseoverCSS("KYU_4", "四級", "4th Kyu", "green"), + mouseoverCSS("KYU_3", "三級", "3rd Kyu", "green"), + mouseoverCSS("KYU_2", "二級", "2nd Kyu", "green"), + mouseoverCSS("KYU_1", "一級", "1st Kyu", "green"), + mouseoverVariant("DAN_1", "初段", "1st Dan", "info"), + mouseoverVariant("DAN_2", "二段", "2nd Dan", "info"), + mouseoverVariant("DAN_3", "三段", "3rd Dan", "info"), + mouseoverVariant("DAN_4", "四段", "4th Dan", "info"), + mouseoverVariant("DAN_5", "五段", "5th Dan", "info"), + mouseoverVariant("DAN_6", "六段", "6th Dan", "info"), + mouseoverVariant("DAN_7", "七段", "7th Dan", "info"), + mouseoverVariant("DAN_8", "八段", "8th Dan", "info"), + mouseoverVariant("DAN_9", "九段", "9th Dan", "danger"), + mouseoverVariant("DAN_10", "十段", "10th Dan", "danger"), + mouseoverCSS("CHUUDEN", "中伝", "Chuuden", "silver", "black"), + mouseoverVariant("KAIDEN", "皆伝", "Kaiden", "warning"), +]; + +export const GitadoraColours: Array = [ + mouseoverCSS("WHITE", "白", "White", "white", "black"), + mouseoverCSS("ORANGE", "橙", "Orange", "orange"), + mouseoverCSS("ORANGE_GRD", "橙グラ", "Orange Gradient", "orange"), + mouseoverVariant("YELLOW", "黄", "Yellow", "warning"), + mouseoverVariant("YELLOW_GRD", "黄グラ", "Yellow Gradient", "warning"), + mouseoverCSS("GREEN", "緑", "Green", "green"), + mouseoverCSS("GREEN_GRD", "緑グラ", "Green Gradient", "green"), + mouseoverVariant("BLUE", "青", "Blue", "info"), + mouseoverVariant("BLUE_GRD", "青グラ", "Blue Gradient", "info"), + mouseoverCSS("PURPLE", "紫", "Purple", "purple"), + mouseoverCSS("PURPLE_GRD", "紫グラ", "Purple Gradient", "purple"), + mouseoverVariant("RED", "赤", "Red", "danger"), + mouseoverVariant("RED_GRD", "赤グラ", "Red Gradient", "danger"), + mouseoverCSS("BRONZE", "銅", "Bronze", "bronze"), + mouseoverCSS("SILVER", "銀", "Silver", "silver", "black"), + mouseoverCSS("GOLD", "金", "Gold", "gold"), + + // @todo #11 Come up with CSS for Gitadora's rainbow icon. + mouseoverCSS("RAINBOW", "虹", "Rainbow", "todo"), +]; + +export const BMSGenocideDans: Array = [ + mouseoverCSS("NORMAL_1", "☆1", "Normal 1st Dan", "lightblue"), + mouseoverCSS("NORMAL_2", "☆2", "Normal 2nd Dan", "lightblue"), + mouseoverCSS("NORMAL_3", "☆3", "Normal 3rd Dan", "lightblue"), + mouseoverCSS("NORMAL_4", "☆4", "Normal 4th Dan", "lightblue"), + mouseoverCSS("NORMAL_5", "☆5", "Normal 5th Dan", "lightblue"), + mouseoverCSS("NORMAL_6", "☆6", "Normal 6th Dan", "lightblue"), + mouseoverCSS("NORMAL_7", "☆7", "Normal 7th Dan", "lightblue"), + mouseoverCSS("NORMAL_8", "☆8", "Normal 8th Dan", "lightblue"), + mouseoverCSS("NORMAL_9", "☆9", "Normal 9th Dan", "lightred"), + mouseoverCSS("NORMAL_10", "☆10", "Normal 10th Dan", "lightred"), + mouseoverVariant("INSANE_1", "★1", "Insane 1st Dan", "info"), + mouseoverVariant("INSANE_2", "★2", "Insane 2nd Dan", "info"), + mouseoverVariant("INSANE_3", "★3", "Insane 3rd Dan", "info"), + mouseoverVariant("INSANE_4", "★4", "Insane 4th Dan", "info"), + mouseoverVariant("INSANE_5", "★5", "Insane 5th Dan", "info"), + mouseoverVariant("INSANE_6", "★6", "Insane 6th Dan", "info"), + mouseoverVariant("INSANE_7", "★7", "Insane 7th Dan", "info"), + mouseoverVariant("INSANE_8", "★8", "Insane 8th Dan", "info"), + mouseoverCSS("INSANE_9", "★9", "Insane 9th Dan", "red"), + mouseoverCSS("INSANE_10", "★10", "Insane 10th Dan", "red"), + mouseoverCSS("INSANE_KAIDEN", "★★", "Insane Kaiden", "teal"), + mouseoverCSS("OVERJOY", "(^^)", "Overjoy", "purple"), +]; + +export const BMSStSlDans: Array = [ + mouseoverCSS("SL0", "sl0", "Satellite 0"), + mouseoverCSS("SL1", "sl1", "Satellite 1"), + mouseoverCSS("SL2", "sl2", "Satellite 2"), + mouseoverCSS("SL3", "sl3", "Satellite 3"), + mouseoverCSS("SL4", "sl4", "Satellite 4"), + mouseoverCSS("SL5", "sl5", "Satellite 5"), + mouseoverCSS("SL6", "sl6", "Satellite 6"), + mouseoverCSS("SL7", "sl7", "Satellite 7"), + mouseoverCSS("SL8", "sl8", "Satellite 8"), + mouseoverCSS("SL9", "sl9", "Satellite 9"), + mouseoverCSS("SL10", "sl10", "Satellite 10"), + mouseoverCSS("SL11", "sl11", "Satellite 11"), + mouseoverCSS("SL12", "sl12", "Satellite 12"), + mouseoverCSS("ST0", "st0", "Stella 0"), + mouseoverCSS("ST1", "st1", "Stella 1"), + mouseoverCSS("ST2", "st2", "Stella 2"), + mouseoverCSS("ST3", "st3", "Stella 3"), + mouseoverCSS("ST4", "st4", "Stella 4"), + mouseoverCSS("ST5", "st5", "Stella 5"), + mouseoverCSS("ST6", "st6", "Stella 6"), + mouseoverCSS("ST7", "st7", "Stella 7"), + mouseoverCSS("ST8", "st8", "Stella 8"), + mouseoverCSS("ST9", "st9", "Stella 9"), + mouseoverCSS("ST10", "st10", "Stella 10"), + mouseoverCSS("ST11", "st11", "Stella 11"), +]; + +export const BMSLNDans: Array = [ + mouseoverCSS("DAN_1", "◆1", "LN 1st Dan", "lightblue"), + mouseoverCSS("DAN_2", "◆2", "LN 2nd Dan", "lightblue"), + mouseoverCSS("DAN_3", "◆3", "LN 3rd Dan", "lightblue"), + mouseoverCSS("DAN_4", "◆4", "LN 4th Dan", "lightblue"), + mouseoverCSS("DAN_5", "◆5", "LN 5th Dan", "lightblue"), + mouseoverCSS("DAN_6", "◆6", "LN 6th Dan", "lightblue"), + mouseoverCSS("DAN_7", "◆7", "LN 7th Dan", "lightblue"), + mouseoverCSS("DAN_8", "◆8", "LN 8th Dan", "lightblue"), + mouseoverCSS("DAN_9", "◆9", "LN 9th Dan", "lightred"), + mouseoverCSS("DAN_10", "◆10", "LN 10th Dan", "lightred"), + mouseoverCSS("KAIDEN", "◆◆", "LN Kaiden", "teal"), + mouseoverCSS("OVERJOY", "◆(^^)", "LN Overjoy", "purple"), + mouseoverCSS("UDON", "◆うどん", "LN Udon", "gold"), +]; + +export const BMSScratchDans: Array = [ + mouseoverCSS("KYU_7", "七級", "Scratch 7th Kyu", "green"), + mouseoverCSS("KYU_6", "六級", "Scratch 6th Kyu", "green"), + mouseoverCSS("KYU_5", "五級", "Scratch 5th Kyu", "green"), + mouseoverCSS("KYU_4", "四級", "Scratch 4th Kyu", "green"), + mouseoverCSS("KYU_3", "三級", "Scratch 3rd Kyu", "green"), + mouseoverCSS("KYU_2", "二級", "Scratch 2nd Kyu", "green"), + mouseoverCSS("KYU_1", "一級", "Scratch 1st Kyu", "green"), + mouseoverVariant("DAN_1", "初段", "Scratch 1st Dan", "info"), + mouseoverVariant("DAN_2", "二段", "Scratch 2nd Dan", "info"), + mouseoverVariant("DAN_3", "三段", "Scratch 3rd Dan", "info"), + mouseoverVariant("DAN_4", "四段", "Scratch 4th Dan", "info"), + mouseoverVariant("DAN_5", "五段", "Scratch 5th Dan", "info"), + mouseoverVariant("DAN_6", "六段", "Scratch 6th Dan", "info"), + mouseoverVariant("DAN_7", "七段", "Scratch 7th Dan", "info"), + mouseoverVariant("DAN_8", "八段", "Scratch 8th Dan", "info"), + mouseoverVariant("DAN_9", "九段", "Scratch 9th Dan", "danger"), + mouseoverVariant("DAN_10", "十段", "Scratch 10th Dan", "danger"), + mouseoverVariant("KAIDEN", "皆伝", "Scratch Kaiden", "warning"), +]; + +export const SDVXDans: Array = [ + mouseoverCSS("DAN_1", "LV.01", "1st Dan"), + mouseoverCSS("DAN_2", "LV.02", "2nd Dan"), + mouseoverCSS("DAN_3", "LV.03", "3rd Dan"), + mouseoverCSS("DAN_4", "LV.04", "4th Dan"), + mouseoverCSS("DAN_5", "LV.05", "5th Dan"), + mouseoverCSS("DAN_6", "LV.06", "6th Dan"), + mouseoverCSS("DAN_7", "LV.07", "7th Dan"), + mouseoverCSS("DAN_8", "LV.08", "8th Dan"), + mouseoverCSS("DAN_9", "LV.09", "9th Dan"), + mouseoverVariant("DAN_10", "LV.10", "10th Dan", "warning"), + mouseoverVariant("DAN_11", "LV.11", "11th Dan", "danger"), + mouseoverCSS("INF", "LV.INF", "Inf. Dan", "purple", "gold"), +]; + +export const SDVXVFClasses: Array = [ + noMouseoverCSS("SIENNA_I", "Sienna I"), + noMouseoverCSS("SIENNA_II", "Sienna II"), + noMouseoverCSS("SIENNA_III", "Sienna III"), + noMouseoverCSS("SIENNA_IV", "Sienna IV"), + noMouseoverCSS("COBALT_I", "Cobalt I"), + noMouseoverCSS("COBALT_II", "Cobalt II"), + noMouseoverCSS("COBALT_III", "Cobalt III"), + noMouseoverCSS("COBALT_IV", "Cobalt IV"), + noMouseoverCSS("DANDELION_I", "Dandelion I"), + noMouseoverCSS("DANDELION_II", "Dandelion II"), + noMouseoverCSS("DANDELION_III", "Dandelion III"), + noMouseoverCSS("DANDELION_IV", "Dandelion IV"), + noMouseoverCSS("CYAN_I", "Cyan I"), + noMouseoverCSS("CYAN_II", "Cyan II"), + noMouseoverCSS("CYAN_III", "Cyan III"), + noMouseoverCSS("CYAN_IV", "Cyan IV"), + noMouseoverCSS("SCARLET_I", "Scarlet I"), + noMouseoverCSS("SCARLET_II", "Scarlet II"), + noMouseoverCSS("SCARLET_III", "Scarlet III"), + noMouseoverCSS("SCARLET_IV", "Scarlet IV"), + noMouseoverCSS("CORAL_I", "Coral I"), + noMouseoverCSS("CORAL_II", "Coral II"), + noMouseoverCSS("CORAL_III", "Coral III"), + noMouseoverCSS("CORAL_IV", "Coral IV"), + noMouseoverCSS("ARGENTO_I", "Argento I"), + noMouseoverCSS("ARGENTO_II", "Argento II"), + noMouseoverCSS("ARGENTO_III", "Argento III"), + noMouseoverCSS("ARGENTO_IV", "Argento IV"), + noMouseoverCSS("ELDORA_I", "Eldora I"), + noMouseoverCSS("ELDORA_II", "Eldora II"), + noMouseoverCSS("ELDORA_III", "Eldora III"), + noMouseoverCSS("ELDORA_IV", "Eldora IV"), + noMouseoverCSS("CRIMSON_I", "Crimson I"), + noMouseoverCSS("CRIMSON_II", "Crimson II"), + noMouseoverCSS("CRIMSON_III", "Crimson III"), + noMouseoverCSS("CRIMSON_IV", "Crimson IV"), + noMouseoverCSS("IMPERIAL_I", "Imperial I"), + noMouseoverCSS("IMPERIAL_II", "Imperial II"), + noMouseoverCSS("IMPERIAL_III", "Imperial III"), + noMouseoverCSS("IMPERIAL_IV", "Imperial IV"), +]; + +export const DDRDans = [ + mouseoverVariant("DAN_1", "初段", "1st Dan", "info"), + mouseoverVariant("DAN_2", "二段", "2nd Dan", "info"), + mouseoverVariant("DAN_3", "三段", "3rd Dan", "info"), + mouseoverVariant("DAN_4", "四段", "4th Dan", "info"), + mouseoverVariant("DAN_5", "五段", "5th Dan", "info"), + mouseoverVariant("DAN_6", "六段", "6th Dan", "info"), + mouseoverVariant("DAN_7", "七段", "7th Dan", "info"), + mouseoverVariant("DAN_8", "八段", "8th Dan", "info"), + mouseoverVariant("DAN_9", "九段", "9th Dan", "danger"), + mouseoverVariant("DAN_10", "十段", "10th Dan", "danger"), + mouseoverVariant("KAIDEN", "皆伝", "Kaiden", "warning"), +]; + +export const CHUNITHMColours = [ + mouseoverVariant("BLUE", "青", "Blue", "info"), + mouseoverCSS("GREEN", "緑", "Green", "green"), + mouseoverCSS("ORANGE", "橙", "Orange", "orange"), + mouseoverVariant("RED", "赤", "Red", "danger"), + mouseoverCSS("PURPLE", "紫", "Purple", "purple"), + mouseoverCSS("COPPER", "銅", "Copper", "bronze"), + mouseoverVariant("SILVER", "銀", "Silver", "secondary"), + mouseoverVariant("GOLD", "金", "Gold", "warning"), + mouseoverCSS("PLATINUM", "鉑", "Platinum", "silver", "black"), + mouseoverVariant("RAINBOW", "虹", "Rainbow", "success"), +]; + +// todo: #6 Add colours to the wacca stage ups. +export const WaccaStageUps = [ + noMouseoverCSS("I", "I"), + noMouseoverCSS("II", "II"), + noMouseoverCSS("III", "III"), + noMouseoverCSS("IV", "IV"), + noMouseoverCSS("V", "V"), + noMouseoverCSS("VI", "VI"), + noMouseoverCSS("VII", "VII"), + noMouseoverCSS("VIII", "VIII"), + noMouseoverCSS("IX", "IX"), + noMouseoverCSS("X", "X"), + noMouseoverCSS("XI", "XI"), + noMouseoverCSS("XII", "XII"), + noMouseoverCSS("XIII", "XIII"), + noMouseoverCSS("XIV", "XIV"), +]; + +export const WaccaColours = [ + noMouseoverVariant("ASH", "Ash", "secondary"), + noMouseoverCSS("NAVY", "Navy", "darkblue"), + noMouseoverCSS("YELLOW", "Yellow", "orange"), + noMouseoverVariant("RED", "Red", "danger"), + noMouseoverCSS("PURPLE", "Purple", "purple"), + noMouseoverVariant("BLUE", "Blue", "info"), + noMouseoverCSS("SILVER", "Silver", "silver", "black"), + noMouseoverVariant("GOLD", "Gold", "warning"), + noMouseoverVariant("RAINBOW", "Rainbow", "success"), +]; + +// what the hell are these names? +export const PopnClasses = [ + mouseoverCSS("KITTY", "にゃんこ", "Kitty", "brown"), + mouseoverCSS("STUDENT", "小学生", "Grade School Student", "green"), + mouseoverCSS("DELINQUENT", "番長", "Delinquent", "lime", "black"), + mouseoverCSS("DETECTIVE", "刑事", "Detective", "purple"), + mouseoverVariant("IDOL", "アイドル", "Idol", "danger"), + mouseoverCSS("GENERAL", "将軍", "General", "darkgoldenrod"), + mouseoverVariant("HERMIT", "仙人", "Hermit", "success"), + mouseoverVariant("GOD", "神", "God", "warning"), +]; + +export const PMSDans = [ + mouseoverVariant("INSANE_1", "●1", "Insane 1st Dan", "info"), + mouseoverVariant("INSANE_2", "●2", "Insane 2nd Dan", "info"), + mouseoverVariant("INSANE_3", "●3", "Insane 3rd Dan", "info"), + mouseoverVariant("INSANE_4", "●4", "Insane 4th Dan", "info"), + mouseoverVariant("INSANE_5", "●5", "Insane 5th Dan", "info"), + mouseoverVariant("INSANE_6", "●6", "Insane 6th Dan", "info"), + mouseoverVariant("INSANE_7", "●7", "Insane 7th Dan", "info"), + mouseoverVariant("INSANE_8", "●8", "Insane 8th Dan", "info"), + mouseoverCSS("INSANE_9", "●9", "Insane 9th Dan", "red"), + mouseoverCSS("INSANE_10", "●10", "Insane 10th Dan", "red"), + mouseoverCSS("INSANE_KAIDEN", "●●", "Insane Kaiden", "teal"), + noMouseoverCSS("OVERJOY", "●OJ", "Overjoy", "purple"), + mouseoverVariant("UNDEFINED", "●UNDF", "Undefined (Post-Overjoy)", "warning"), +]; + +export const JubeatColours = [ + noMouseoverVariant("BLACK", "Black", "secondary"), + noMouseoverCSS("YELLOW_GREEN", "Yellow-Green", "yellowgreen", "black"), + noMouseoverCSS("GREEN", "Green", "green"), + noMouseoverCSS("LIGHT_BLUE", "Light Blue", "cyan", "black"), + noMouseoverVariant("BLUE", "Blue", "info"), + noMouseoverCSS("VIOLET", "Violet", "violet"), + noMouseoverCSS("PURPLE", "Purple", "purple"), + noMouseoverCSS("PINK", "Pink", "pink", "black"), + noMouseoverCSS("ORANGE", "Orange", "orange"), + noMouseoverVariant("GOLD", "Gold", "warning"), +]; + +export interface GameClassSets { + "iidx:SP": "dan"; + "iidx:DP": "dan"; + "popn:9B": "class"; + "sdvx:Single": "dan" | "vfClass"; + "usc:Keyboard": never; + "usc:Controller": never; + "ddr:SP": "dan"; + "ddr:DP": "dan"; + "maimai:Single": never; + "jubeat:Single": "colour"; + "museca:Single": never; + "bms:7K": "genocideDan" | "lnDan" | "scratchDan" | "stslDan"; + "bms:14K": "genocideDan"; + "chunithm:Single": "colour"; + "gitadora:Gita": "colour"; + "gitadora:Dora": "colour"; + "wacca:Single": "colour" | "stageUp"; + "pms:Controller": "dan"; + "pms:Keyboard": "dan"; + "itg:Stamina": never; +} + +export type AllClassSets = GameClassSets[IDStrings]; + +export type GameClasses = { + [K in GameClassSets[I]]: integer; +}; diff --git a/common/src/index.test.ts b/common/src/index.test.ts new file mode 100644 index 000000000..ed41de5f8 --- /dev/null +++ b/common/src/index.test.ts @@ -0,0 +1,12 @@ +import * as tachiCommon from "./index"; +import t from "tap"; + +// we just check that we're exporting stuff properly +t.test("Property Checks", (t) => { + t.type(tachiCommon.GetGameConfig, "function"); + t.type(tachiCommon.GetGamePTConfig, "function"); + t.type(tachiCommon.gameClasses, "object"); + t.type(tachiCommon.COLOUR_SET, "object"); + + t.end(); +}); diff --git a/common/src/index.ts b/common/src/index.ts new file mode 100644 index 000000000..4262f22fa --- /dev/null +++ b/common/src/index.ts @@ -0,0 +1,11 @@ +export * from "./types"; + +export * from "./config/config"; +export * as gameClasses from "./game-classes"; +export * as ESDCore from "./lib/esd"; +export * as StaticConfig from "./config/static-config"; +export * from "./utils/util"; +export * from "./webhooks"; +export * from "./constants/game"; +export * from "./constants/permissions"; +export * as Schemas from "./lib/schemas"; diff --git a/common/src/lib/esd.test.ts b/common/src/lib/esd.test.ts new file mode 100644 index 000000000..39c227028 --- /dev/null +++ b/common/src/lib/esd.test.ts @@ -0,0 +1,46 @@ +import { CalculateESD, ESDCompare, PercentCompare } from "./esd"; +import { GetGamePTConfig } from "../config/config"; +import t from "tap"; + +function approx(t: Tap.Test, given: number, recieved: number, acc = 0.01) { + if (Math.abs(given - recieved) < acc) { + return t.pass(); + } + + return t.fail(`Expected ${given} to be within ${acc} of ${recieved}.`); +} + +const gptConfig = GetGamePTConfig("iidx", "SP"); + +if (!gptConfig.supportsESD) { + throw new Error(`IIDX:SP no longer supports esd, these tests won't work.`); +} + +const judgementWindows = gptConfig.judgementWindows; + +t.test("#CalculateESD", (t) => { + const jdg = judgementWindows; + + approx(t, CalculateESD(jdg, 0.8), 17.58); + approx(t, CalculateESD(jdg, 0), 200); + t.throws(() => CalculateESD(jdg, 0, true)); + + t.end(); +}); + +t.test("#ESDCompare", (t) => { + approx(t, ESDCompare(16, 14), 6.42); + approx(t, ESDCompare(16, 14, 1), ESDCompare(16, 14)); + approx(t, ESDCompare(0, 200, 1), -68.27); + + t.end(); +}); + +t.test("#PercentCompare", (t) => { + const jdg = judgementWindows; + + approx(t, PercentCompare(jdg, 0.99, 0.98), -5.53); + approx(t, PercentCompare(jdg, 0.99, 0.98), PercentCompare(jdg, 0.99, 0.98, 1)); + + t.end(); +}); diff --git a/common/src/lib/esd.ts b/common/src/lib/esd.ts new file mode 100644 index 000000000..70ce50fb9 --- /dev/null +++ b/common/src/lib/esd.ts @@ -0,0 +1,188 @@ +/** + * Cumulative Distribution Function + * https://en.wikipedia.org/wiki/Cumulative_distribution_function + */ +function cdf(x: number, mean: number, variance: number) { + return 0.5 * (1 + erf((x - mean) / Math.sqrt(2 * variance))); +} + +/** + * Error Function + * https://en.wikipedia.org/wiki/Error_function + */ +function erf(x: number) { + // save the sign of x + const sign = x >= 0 ? 1 : -1; + + const absX = Math.abs(x); + + // constants + const a1 = 0.254829592; + const a2 = -0.284496736; + const a3 = 1.421413741; + const a4 = -1.453152027; + const a5 = 1.061405429; + const p = 0.3275911; + + // A&S formula 7.1.26 + const t = 1.0 / (1.0 + p * absX); + const y = 1.0 - ((((a5 * t + a4) * t + a3) * t + a2) * t + a1) * t * Math.exp(-absX * absX); + + return sign * y; // erf(-x) = -erf(x); +} + +function CDFBetween(lowBound: number, highBound: number, mean: number, variance: number) { + // Since the normal distribution is symmetrical, we want to double this, as the cut will only get one tail. + return 2 * (cdf(highBound, mean, variance) - cdf(lowBound, mean, variance)); +} + +// This is a direct port of ESD-JS to typescript. Maybe this should be released as its own module some day. + +const MEAN = 0; + +/** + * Gets the percent this score would roughly be, given a standard deviation. + * @param judgements - The judgements this game uses. + * @param stddev - The standard deviation to estimate the percent of. + * @param largestValue - The largest "value" of a judgement in this game. This lets us normalise results. + * @returns The percent this std. deviation would produce. + */ +function StdDeviationToPercent( + judgements: Array, + stddev: number, + largestValue: number +) { + let lastJudgeMSBorder = 0; + let prbSum = 0; + + for (const judge of judgements) { + const nVal = CDFBetween(lastJudgeMSBorder, judge.msBorder, MEAN, stddev ** 2) * judge.value; + + lastJudgeMSBorder = judge.msBorder; + prbSum = prbSum + nVal; + } + + prbSum = prbSum / largestValue; + + return prbSum; +} + +const ACCEPTABLE_ERROR = 0.001; +const MAX_ITERATIONS = 50; + +export interface ESDJudgementFormat { + name: string; + msBorder: number; + value: number; +} + +/** + * Given judgements and a percent, estimate the standard deviation needed to get a score + * with that percent. + * @param judgements - The judgements for this game. + * @param percent - The percent to estimate SD needed to get. + * @param errOnInaccuracy - Whether or whether not to throw if the estimate is not accurate enough. + * @returns + */ +export function CalculateESD( + judgements: Array, + percent: number, + errOnInaccuracy = false +): number { + if (percent > 1 || percent < 0 || Number.isNaN(percent)) { + throw new Error( + "(ESD) Invalid percent. Percent must be between 0 and 1, and also a number." + ); + } + + const largestValue = judgements.slice(0).sort((a, b) => b.value - a.value)[0].value; + + // massive optimisation possible here by using better initial estimates with precalc'd table of values. + // until then, it's just kinda slow. + let minSD = 0; + let maxSD = 200; + let estSD = (minSD + maxSD) / 2; + + // So, fundamentally the function that takes SD and returns estimated percent is NOT invertible. + // as in, it is very literally not invertible. + // if you figure it out, let me know. + // until then; we make MAX_ITERATIONS attempts at finding a value within the acceptable range of error. + // the defaults for these are 100 for iterations, and 0.001 for error. + // for most of what i've tested, this has been fine. + for (let i = 0; i < MAX_ITERATIONS; i++) { + const estimatedPercent = StdDeviationToPercent(judgements, estSD, largestValue); + + if (Math.abs(estimatedPercent - percent) < ACCEPTABLE_ERROR) { + return estSD; + } + + if (estimatedPercent < percent) { + maxSD = estSD; + } else { + minSD = estSD; + } + + if (estSD === (minSD + maxSD) / 2) { + // if it isn't moving, just terminate + break; + } + + estSD = (minSD + maxSD) / 2; + } + + if (errOnInaccuracy) { + throw new Error(`(ESD-JS) Did not reach value within MAX_ITERATIONS (${MAX_ITERATIONS})`); + } + + return estSD; +} + +/** + * Compares two ESD values such that 1->2 produces a larger value than 101->102. + * @param baseESD - The first ESD to compare. + * @param compareESD - The second ESD to compare. + * @param cdeg - The degrees of confidence to use. This should be 1. + * @returns A number between -100 and 100. + */ +export function ESDCompare(baseESD: number, compareESD: number, cdeg = 1): number { + const CONFIDENCE_DEGREE = cdeg; + const BASE_CASE = CDFBetween(-1 * CONFIDENCE_DEGREE, CONFIDENCE_DEGREE, 0, 1) / 2; + + let inv = false; + let variance; + let bound; + + if (compareESD > baseESD) { + inv = true; + variance = compareESD ** 2; + bound = CONFIDENCE_DEGREE * baseESD; + } else { + variance = baseESD ** 2; + bound = CONFIDENCE_DEGREE * compareESD; + } + + const esdc = CDFBetween(-1 * bound, bound, 0, variance) / 2; + + let besdc = BASE_CASE - esdc; + + if (inv) { + besdc = besdc * -1; + } + + return besdc * 100; +} + +/** + * Converts two percents to ESD, then runs ESDCompare. + */ +export function PercentCompare( + judgements: Array, + baseP: number, + compareP: number, + cdeg = 1 +): number { + const e1 = CalculateESD(judgements, baseP); + const e2 = CalculateESD(judgements, compareP); + + return ESDCompare(e1, e2, cdeg); +} diff --git a/common/src/lib/schemas.ts b/common/src/lib/schemas.ts new file mode 100644 index 000000000..7afb98e40 --- /dev/null +++ b/common/src/lib/schemas.ts @@ -0,0 +1,1161 @@ +// Schemas for some objects in tachi. These are exported in a record that maps +// their name in the database to the object they schemaify. +// The schemas themselves are wrapped in functions that throw on error. + +import { allIDStrings, allImportTypes, allSupportedGames } from "../config/static-config"; +import { ALL_PERMISSIONS } from "../constants/permissions"; +import { GetGameConfig, GetGamePTConfig } from "../index"; +import { UserAuthLevels } from "../types"; +import p from "prudence"; +import type { GamePTConfig } from "../index"; +import type { Game, IDStrings, NotificationBody, Playtypes } from "../types"; +import type { + PrudenceSchema, + ValidationFunctionParentOptionsKeychain, + ValidSchemaValue, +} from "prudence"; + +export const optNull = (v: ValidSchemaValue): ValidationFunctionParentOptionsKeychain => + p.optional(p.nullable(v)); + +export const optNullFluffStrField = optNull(p.isBoundedString(3, 140)); + +/** + * Wrap a prudence schema in a callable function that takes an unknown item and attempts + * to validate it. + */ +function prSchemaFnWrap(schema: PrudenceSchema) { + return (s: unknown): true => { + const err = p(s, schema); + + if (err) { + throw err; + } + + return true; + }; +} + +const PR_GAME_STATS = (game: Game, playtype: Playtypes[Game], gptConfig: GamePTConfig) => ({ + userID: p.isPositiveNonZeroInteger, + game: p.is(game), + playtype: p.is(playtype), + ratings: Object.fromEntries(gptConfig.scoreRatingAlgs.map((s) => [s, "*number"])), + classes: Object.fromEntries( + Object.keys(gptConfig.classHumanisedFormat).map((e) => [e, p.optional(p.isInteger)]) + ), +}); + +const PR_GOAL_INFO = { + progress: "?number", + progressHuman: "string", + outOf: "number", + outOfHuman: "string", + achieved: "boolean", +}; + +const GetHitMeta = (game: Game): PrudenceSchema => { + if (game === "iidx") { + return { + bp: optNull(p.isPositiveInteger), + gauge: optNull(p.isBetween(0, 100)), + gaugeHistory: optNull([p.nullable(p.isBetween(0, 100))]), + comboBreak: optNull(p.isPositiveInteger), + gsm: optNull({ + EASY: [p.nullable(p.isBetween(0, 100))], + NORMAL: [p.nullable(p.isBetween(0, 100))], + HARD: [p.nullable(p.isBetween(0, 100))], + EX_HARD: [p.nullable(p.isBetween(0, 100))], + }), + }; + } else if (game === "bms") { + return { + epg: optNull(p.isPositiveInteger), + egr: optNull(p.isPositiveInteger), + egd: optNull(p.isPositiveInteger), + epr: optNull(p.isPositiveInteger), + ebd: optNull(p.isPositiveInteger), + lpg: optNull(p.isPositiveInteger), + lgr: optNull(p.isPositiveInteger), + lgd: optNull(p.isPositiveInteger), + lpr: optNull(p.isPositiveInteger), + lbd: optNull(p.isPositiveInteger), + bp: optNull(p.isPositiveInteger), + gauge: optNull(p.isBetween(0, 100)), + }; + } else if (game === "sdvx" || game === "usc") { + return { gauge: optNull(p.isBetween(0, 100)) }; + } + + return {}; +}; + +const extractGPTIDString = (self: unknown) => { + if (typeof self !== "object" || !self) { + throw new Error("Expected an object."); + } + + const s = self as Record; + + if (typeof s.idString !== "string") { + throw new Error(`Expected a string where self.idString is. Got ${s.idString}`); + } + + // if there's no ":" in the string, this returns only one element. + const [game, playtype] = s.idString.split(":") as [string, string | undefined]; + + if (!IsValidGame(game)) { + throw new Error(`Expected valid game -- got ${game} from idString ${s.idString}.`); + } + + // Playtype might be undefined in the case where the string contains no colon. + if (playtype === undefined || !IsValidPlaytype(game, playtype)) { + throw new Error(`Expected valid playtype -- got ${playtype} from idString ${s.idString}.`); + } + + return { game, playtype }; +}; + +const extractGPT = (self: unknown) => { + if (typeof self !== "object" || !self) { + throw new Error("Expected an object."); + } + + const s = self as Record; + + if (typeof s.game !== "string") { + throw new Error(`Expected a string where self.game is. Got ${s.game}`); + } + + if (typeof s.playtype !== "string") { + throw new Error(`Expected a string where self.playtype is. Got ${s.playtype}`); + } + + if (!IsValidGame(s.game)) { + throw new Error(`Expected valid game -- got ${s.game}.`); + } + + if (!IsValidPlaytype(s.game, s.playtype)) { + throw new Error(`Expected valid playtype -- got ${s.playtype}.`); + } + + return { game: s.game, playtype: s.playtype }; +}; + +const PR_RANDOM = p.isIn("NONRAN", "RANDOM", "R-RANDOM", "S-RANDOM", "MIRROR"); + +const GetScoreMeta = (game: Game, playtype: Playtypes[Game]): PrudenceSchema => { + if (game === "iidx" && playtype === "SP") { + return { + random: optNull(PR_RANDOM), + assist: optNull(p.isIn("NO ASSIST", "AUTO SCRATCH", "LEGACY NOTE", "FULL ASSIST")), + range: optNull(p.isIn("NONE", "SUDDEN+", "HIDDEN+", "SUD+ HID+", "LIFT", "LIFT SUD+")), + gauge: optNull(p.isIn("ASSISTED EASY", "EASY", "NORMAL", "HARD", "EX-HARD")), + }; + } else if (game === "iidx" && playtype === "DP") { + return { + random: optNull({ + left: PR_RANDOM, + right: PR_RANDOM, + }), + assist: optNull(p.isIn("NO ASSIST", "AUTO SCRATCH", "LEGACY NOTE", "FULL ASSIST")), + range: optNull(p.isIn("NONE", "SUDDEN+", "HIDDEN+", "SUD+ HID+", "LIFT", "LIFT SUD+")), + gauge: optNull(p.isIn("ASSISTED EASY", "EASY", "NORMAL", "HARD", "EX-HARD")), + }; + } else if (game === "sdvx") { + return { inSkillAnalyser: "*?boolean" }; + } else if (game === "usc") { + return { + noteMod: optNull(p.isIn("NORMAL", "MIRROR", "RANDOM", "MIR-RAN")), + gaugeMod: optNull(p.isIn("NORMAL", "HARD", "PERMISSIVE")), + }; + } else if (game === "bms" && playtype === "7K") { + return { + random: optNull(PR_RANDOM), + inputDevice: optNull(p.isIn("KEYBOARD", "BM_CONTROLLER")), + client: optNull(p.isIn("LR2", "lr2oraja")), + }; + } else if (game === "bms" && playtype === "14K") { + return { + random: optNull({ + left: PR_RANDOM, + right: PR_RANDOM, + }), + inputDevice: optNull(p.isIn("KEYBOARD", "BM_CONTROLLER")), + client: optNull(p.isIn("LR2", "lr2oraja")), + }; + } + + return {}; +}; + +function IsValidPlaytype(game: Game, str: string): str is Playtypes[Game] { + return GetGameConfig(game).validPlaytypes.includes(str as Playtypes[Game]); +} + +function IsValidGame(str: string): str is Game { + return allSupportedGames.includes(str as Game); +} + +const getPlaytype = (game: Game, self: unknown): Playtypes[Game] => { + if (self === null || typeof self !== "object") { + throw new Error("Expected an object."); + } + + const gameConfig = GetGameConfig(game); + + const s = self as Record; + + const playtype = s.playtype as string; + + if (!IsValidPlaytype(game, playtype)) { + throw new Error(`Expected any of ${gameConfig.validPlaytypes.join(", ")}`); + } + + return playtype; +}; + +const games = allSupportedGames; + +const isValidPlaytype = (self: unknown, parent: Record) => { + if (typeof parent.game !== "string" || !IsValidGame(parent.game)) { + throw new Error(`Invalid Schema, need game to base IsValidPlaytype off of.`); + } + + if (typeof self !== "string") { + return "Expected a string."; + } + + if (!IsValidPlaytype(parent.game, self)) { + return `Expected a valid playtype for ${parent.game}`; + } + + return true; +}; + +function GetSongDataForGame(game: Game): PrudenceSchema { + switch (game) { + case "bms": + return { + genre: "?string", + subtitle: "?string", + subartist: "?string", + tableString: "?string", + }; + case "chunithm": + return { + genre: "string", + displayVersion: "string", + }; + case "sdvx": + return { + displayVersion: "string", + }; + case "maimai": + return { + titleJP: "string", + artistJP: "string", + displayVersion: "string", + }; + case "museca": + return { + titleJP: "string", + artistJP: "string", + displayVersion: "string", + }; + case "iidx": + return { + genre: "string", + displayVersion: "string", + }; + case "wacca": + return { + genre: "string", + displayVersion: "?string", + titleJP: "string", + artistJP: "string", + }; + case "pms": + return { + genre: "?string", + subtitle: "?string", + subartist: "?string", + tableString: "?string", + }; + case "popn": + return { + genre: "string", + genreEN: "?string", + displayVersion: "?string", + }; + case "jubeat": + return { + displayVersion: "string", + }; + case "itg": + return { + subtitle: "string", + banner: "?string", + }; + case "usc": + case "gitadora": + case "ddr": + return {}; + } +} + +function GetChartDataForGPT(idString: IDStrings): PrudenceSchema { + switch (idString) { + case "bms:7K": + case "bms:14K": + return { + notecount: p.isPositiveNonZeroInteger, + hashSHA256: "?string", + hashMD5: "?string", + aiLevel: "?number", + tableFolders: [ + { + table: "string", + level: "string", + }, + ], + }; + case "jubeat:Single": + return { + inGameID: p.isPositiveInteger, + isHardMode: "boolean", + }; + case "chunithm:Single": + return { + inGameID: p.isPositiveInteger, + }; + case "iidx:SP": + case "iidx:DP": + return { + notecount: p.isPositiveNonZeroInteger, + inGameID: p.or(p.isPositiveNonZeroInteger, [p.isPositiveNonZeroInteger]), + arcChartID: "?string", + hashSHA256: "?string", + "2dxtraSet": "?string", + kaidenAverage: "?number", + worldRecord: "?number", + bpiCoefficient: "?number", + }; + case "maimai:Single": + return { + maxPercent: p.gt(0), + inGameID: p.isPositiveInteger, + inGameStrID: "string", + }; + case "museca:Single": + return { + inGameID: p.isPositiveInteger, + }; + case "sdvx:Single": + return { + inGameID: p.isPositiveInteger, + arcChartID: "?string", + }; + case "usc:Controller": + case "usc:Keyboard": + return { + hashSHA1: p.or("string", ["string"]), + isOfficial: "boolean", + effector: "string", + tableFolders: [ + { + table: "string", + level: "string", + }, + ], + }; + case "wacca:Single": + return { + isHot: "boolean", + }; + case "pms:Controller": + case "pms:Keyboard": + return { + notecount: p.isPositiveNonZeroInteger, + hashSHA256: "?string", + hashMD5: "?string", + tableFolders: [ + { + table: "string", + level: "string", + }, + ], + }; + case "popn:9B": + return { + hashSHA256: "?string", + inGameID: p.isPositiveInteger, + }; + case "itg:Stamina": + return { + chartHash: "string", + breakdown: { + detailed: "string", + partiallySimplified: "string", + simplified: "string", + total: "string", + npsPerMeasure: [p.isPositive], + notesPerMeasure: [p.isPositive], + }, + tech: { + crossovers: p.isPositiveInteger, + jacks: p.isPositiveInteger, + brackets: p.isPositiveInteger, + footswitches: p.isPositiveInteger, + sideswitches: p.isPositiveInteger, + }, + length: p.isPositive, + charter: "string", + displayBPM: p.isPositive, + }; + case "gitadora:Dora": + case "gitadora:Gita": + case "ddr:SP": + case "ddr:DP": + return {}; + } +} + +const PR_SONG_DOCUMENT = (game: Game): PrudenceSchema => { + const data = GetSongDataForGame(game); + + return { + id: p.isPositiveInteger, + title: "string", + artist: "string", + searchTerms: ["string"], + altTitles: ["string"], + data, + }; +}; + +const PR_CHART_DOCUMENT = (game: Game) => (self: unknown) => { + const playtype = getPlaytype(game, self); + + const gptConfig = GetGamePTConfig(game, playtype); + + const data = GetChartDataForGPT(`${game}:${playtype}` as IDStrings); + + return prSchemaFnWrap({ + songID: p.isPositiveInteger, + chartID: "string", + rgcID: "?string", + level: "string", + levelNum: "number", + isPrimary: "boolean", + difficulty: p.isIn(gptConfig.difficulties), + playtype: p.is(playtype), + data, + tierlistInfo: Object.fromEntries( + gptConfig.tierlists.map((t) => [ + t, + p.optional({ text: "string", value: "number", individualDifference: "*boolean" }), + ]) + ), + versions: [p.isIn(gptConfig.supportedVersions)], + })(self); +}; + +const PR_CHALLENGE = { + chartID: "string", + authorID: p.isPositiveNonZeroInteger, + type: p.isIn("lamp", "score"), + + game: p.isIn(games), + playtype: isValidPlaytype, +}; + +// Returns true on success, throws on failure. +export type SchemaValidatorFunction = (self: unknown) => true; + +type GameSchemas = Record<`${"charts" | "songs"}-${Game}`, SchemaValidatorFunction>; + +function CreateGameSchemas() { + const obj: Partial = {}; + + for (const game of games) { + obj[`songs-${game}`] = prSchemaFnWrap(PR_SONG_DOCUMENT(game)); + obj[`charts-${game}`] = PR_CHART_DOCUMENT(game); + } + + // guaranteed to no longer be partial + return obj as GameSchemas; +} + +const GAME_SCHEMAS = CreateGameSchemas(); + +// OK, here's an insane hack. +// We want schemas to be a a record of key -> SchemaValidatorFunction, but it can't +// be naively done with Record, because that results +// in you losing the actual hardcoded keys in the map. + +// The alternative? Declare it const and redeclare it with those keys on export! +// This is typesafe to including garbage in PRE_SCHEMAS, although with a slightly more +// obtuse error message. +const PRE_SCHEMAS = { + "bms-course-lookup": prSchemaFnWrap({ + title: "string", + + // Must be comprised of 4 md5 hashes, which are 32 chars long. + md5sums: (self) => { + if (typeof self !== "string") { + return "Expected a string"; + } + + if (self.length !== 32 * 4) { + return "Expected 32 * 4 characters (4 md5 hashes long)."; + } + + if (!/^[a-z0-9]*$/u.exec(self)) { + return "Expected all chars to be in the range of a-z0-9."; + } + + return true; + }, + set: p.isIn("genocideDan", "stslDan"), + playtype: p.isIn("7K", "14K"), + value: p.isInteger, + }), + folders: prSchemaFnWrap({ + title: "string", + game: p.isIn(games), + playtype: isValidPlaytype, + folderID: "string", + inactive: "boolean", + searchTerms: ["string"], + type: p.isIn("songs", "charts", "static"), + data: (self, parent) => { + if (parent.type === "songs") { + return true; // temp. should be a song. + } else if (parent.type === "charts") { + return true; // temp. should be a chart. + } + + return ( + (Array.isArray(self) && self.every((r) => typeof r === "string")) || + "Expected an array of only strings." + ); + }, + }), + tables: prSchemaFnWrap({ + tableID: "string", + game: p.isIn(games), + playtype: isValidPlaytype, + title: "string", + default: "boolean", + description: "string", + folders: ["string"], + inactive: "boolean", + }), + ...GAME_SCHEMAS, + "milestone-sets": prSchemaFnWrap({ + setID: "string", + game: p.isIn(games), + playtype: isValidPlaytype, + milestones: ["string"], + }), + milestones: prSchemaFnWrap({ + game: p.isIn(games), + playtype: isValidPlaytype, + name: "string", + desc: "string", + milestoneID: "string", + group: "?string", + groupINdex: p.nullable(p.isInteger), + milestoneData: [ + { + title: "string", + desc: "string", + goals: [{ goalID: "string", note: "*string" }], + }, + ], + criteria: p.or( + { + type: "all", + }, + { + type: p.isIn("absolute", "proportion"), + value: p.isPositive, + } + ), + }), + "goal-subs": prSchemaFnWrap({ + goalID: "string", + userID: p.isPositiveNonZeroInteger, + game: p.isIn(games), + playtype: isValidPlaytype, + achieved: "boolean", + timeSet: p.isPositive, + timeAchieved: p.nullable(p.isPositive), + lastInteraction: p.nullable(p.isPositive), + progress: "?number", + progressHuman: "string", + outOf: "number", + outOfHuman: "string", + }), + "milestone-subs": prSchemaFnWrap({ + milestoneID: "string", + userID: p.isPositiveNonZeroInteger, + game: p.isIn(games), + playtype: isValidPlaytype, + timeSet: p.isPositive, + achieved: "boolean", + timeAchieved: p.nullable(p.isPositive), + progress: p.isInteger, + }), + "recent-folder-views": prSchemaFnWrap({ + userID: p.isPositiveNonZeroInteger, + game: p.isIn(games), + playtype: isValidPlaytype, + folderID: "string", + lastViewed: "number", + }), + goals: prSchemaFnWrap({ + game: p.isIn(games), + playtype: isValidPlaytype, + timeAdded: p.isPositive, + title: "string", + goalID: "string", + criteria: p.or( + { + mode: p.is("single"), + key: p.isIn( + "scoreData.percent", + "scoreData.lampIndex", + "scoreData.gradeIndex", + "scoreData.score" + ), + value: "number", + }, + { + mode: p.isIn("absolute", "proportion"), + countNum: p.isPositive, + key: p.isIn( + "scoreData.percent", + "scoreData.lampIndex", + "scoreData.gradeIndex", + "scoreData.score" + ), + value: "number", + } + ), + charts: p.or( + { + type: p.is("any"), + }, + { + type: p.is("folder"), + data: "string", + }, + { + type: p.is("multi"), + data: ["string"], + }, + { + type: p.is("single"), + data: "string", + } + ), + }), + scores: (self: unknown) => { + const { game, playtype } = extractGPT(self); + + const gptConfig = GetGamePTConfig(game, playtype); + + const hitMeta = { + fast: optNull(p.isPositiveInteger), + slow: optNull(p.isPositiveInteger), + maxCombo: optNull(p.isPositiveInteger), + ...GetHitMeta(game), + }; + + const scoreMeta = GetScoreMeta(game, playtype); + + return prSchemaFnWrap({ + service: "string", + game: p.is(game), + playtype: p.is(playtype), + userID: p.isPositiveNonZeroInteger, + songID: p.isPositiveInteger, + chartID: "string", + isPrimary: "boolean", + highlight: "boolean", + comment: "?string", + timeAdded: p.isInteger, + scoreID: "string", + importType: p.nullable(p.isIn(allImportTypes)), + timeAchieved: p.nullable(p.isPositive), + scoreData: { + score: "number", + lamp: p.isIn(gptConfig.lamps), + lampIndex: p.isIn(Object.keys(gptConfig.lamps).map(Number)), + grade: p.isIn(gptConfig.grades), + gradeIndex: p.isIn(Object.keys(gptConfig.grades).map(Number)), + percent: p.isBetween(0, gptConfig.percentMax), + esd: "?number", + judgements: Object.fromEntries( + gptConfig.judgements.map((j) => [j, optNull(p.isInteger)]) + ), + hitMeta, + }, + scoreMeta, + calculatedData: Object.fromEntries( + gptConfig.scoreRatingAlgs.map((a) => [a, "*?number"]) + ), + })(self); + }, + "personal-bests": (self: unknown) => { + const { game, playtype } = extractGPT(self); + + const gptConfig = GetGamePTConfig(game, playtype); + + const hitMeta = { + fast: optNull(p.isPositiveInteger), + slow: optNull(p.isPositiveInteger), + maxCombo: optNull(p.isPositiveInteger), + ...GetHitMeta(game), + }; + + return prSchemaFnWrap({ + composedFrom: { + scorePB: "string", + lampPB: "string", + other: p.optional([{ name: "string", scoreID: "string" }]), + }, + rankingData: { + rank: p.isPositiveNonZeroInteger, + outOf: p.isPositiveNonZeroInteger, + }, + userID: p.isPositiveNonZeroInteger, + chartID: "string", + game: p.is(game), + playtype: p.is(playtype), + songID: p.isPositiveNonZeroInteger, + highlight: "boolean", + isPrimary: "boolean", + timeAchieved: p.nullable(p.isPositive), + scoreData: { + score: "number", + lamp: p.isIn(gptConfig.lamps), + lampIndex: p.isIn(Object.keys(gptConfig.lamps).map(Number)), + grade: p.isIn(gptConfig.grades), + gradeIndex: p.isIn(Object.keys(gptConfig.grades).map(Number)), + percent: p.isBetween(0, gptConfig.percentMax), + esd: "?number", + judgements: Object.fromEntries( + gptConfig.judgements.map((j) => [j, optNull(p.isInteger)]) + ), + hitMeta, + }, + calculatedData: Object.fromEntries( + gptConfig.scoreRatingAlgs.map((a) => [a, "*?number"]) + ), + })(self); + }, + "user-settings": prSchemaFnWrap({ + userID: p.isPositiveNonZeroInteger, + preferences: { + invisible: "boolean", + developerMode: "boolean", + advancedMode: "boolean", + contentiousContent: "boolean", + deletableScores: "boolean", + }, + }), + sessions: (self: unknown) => { + const { game, playtype } = extractGPT(self); + + const gptConfig = GetGamePTConfig(game, playtype); + + return prSchemaFnWrap({ + userID: p.isPositiveNonZeroInteger, + sessionID: "string", + name: "string", + desc: "?string", + game: p.is(game), + playtype: isValidPlaytype, + importType: p.nullable(p.isIn(allImportTypes)), + timeInserted: p.isPositiveInteger, + timeEnded: p.isPositiveInteger, + timeStarted: p.isPositiveInteger, + highlight: "boolean", + views: p.isInteger, + calculatedData: Object.fromEntries( + gptConfig.sessionRatingAlgs.map((k) => [k, "*?number"]) + ), + scoreInfo: [ + p.or( + { + scoreID: "string", + isNewScore: p.is(true), + }, + { + scoreID: "string", + isNewScore: p.is(false), + scoreDelta: "number", + gradeDelta: p.isInteger, + lampDelta: p.isInteger, + percentDelta: "number", + } + ), + ], + })(self); + }, + imports: prSchemaFnWrap({ + userID: p.isPositiveNonZeroInteger, + timeStarted: p.isPositive, + timeFinished: p.isPositive, + idStrings: [p.isIn(allIDStrings)], + importID: "string", + scoreIDs: ["string"], + game: p.isIn(games), + + // @ts-expect-error We've asserted this is definitely a game. + playtypes: [(self) => p.isIn(GetGameConfig(self.game).validPlaytypes)(self)], + errors: [ + { + type: "string", + message: "?string", + }, + ], + createdSessions: [ + { + sessionID: "string", + type: p.isIn("Created", "Appended"), + }, + ], + importType: p.nullable(p.isIn(allImportTypes)), + classDeltas: [ + { + game: p.isIn(games), + playtype: isValidPlaytype, + set: "string", // Technically false, but the validation is a pain. + old: p.nullable(p.isInteger), + new: p.isInteger, + }, + ], + goalInfo: [ + { + goalID: "string", + old: PR_GOAL_INFO, + new: PR_GOAL_INFO, + }, + ], + milestoneInfo: [ + { + milestoneID: "string", + old: { progress: p.isInteger, achieved: "boolean" }, + new: { progress: p.isInteger, achieved: "boolean" }, + }, + ], + userIntent: "boolean", + }), + counters: prSchemaFnWrap({ + counterName: "string", + value: p.isPositiveInteger, + }), + "score-blacklist": prSchemaFnWrap({ + scoreID: "string", + userID: p.isPositiveNonZeroInteger, + score: p.any, + }), + "game-stats": (self: unknown) => { + const { game, playtype } = extractGPT(self); + + const gptConfig = GetGamePTConfig(game, playtype); + + return prSchemaFnWrap(PR_GAME_STATS(game, playtype, gptConfig))(self); + }, + "game-stats-snapshots": (self: unknown) => { + const { game, playtype } = extractGPT(self); + + const gptConfig = GetGamePTConfig(game, playtype); + + return prSchemaFnWrap( + Object.assign(PR_GAME_STATS(game, playtype, gptConfig), { + rankings: Object.fromEntries( + gptConfig.profileRatingAlgs.map((e) => [ + e, + { outOf: p.isPositiveNonZeroInteger, ranking: p.isPositiveNonZeroInteger }, + ]) + ), + playcount: p.isPositiveInteger, + timestamp: p.isPositive, + }) + )(self); + }, + "game-settings": (s: unknown) => { + const { game, playtype } = extractGPT(s); + + const gptConfig = GetGamePTConfig(game, playtype); + + return prSchemaFnWrap({ + userID: p.isPositiveNonZeroInteger, + game: p.is(game), + playtype: p.is(playtype), + preferences: { + preferredScoreAlg: p.nullable(p.isIn(gptConfig.scoreRatingAlgs)), + preferredSessionAlg: p.nullable(p.isIn(gptConfig.sessionRatingAlgs)), + preferredProfileAlg: p.nullable(p.isIn(gptConfig.profileRatingAlgs)), + + // ouch + stats: p.and( + [ + p.or( + { + mode: p.is("chart"), + chartID: "string", + property: p.isIn("score", "percent", "grade", "lamp", "playcount"), + }, + { + mode: p.is("folder"), + folderID: p.or("string", ["string"]), + property: p.isIn("score", "percent", "grade", "lamp"), + gte: p.isPositive, + } + ), + ], + (self) => Array.isArray(self) && self.length <= 6 + ), + scoreBucket: p.isIn(null, "grade", "lamp"), + gameSpecific: + game === "iidx" + ? { + display2DXTra: "boolean", + } + : {}, + }, + })(s); + }, + "folder-chart-lookup": prSchemaFnWrap({ + chartID: "string", + folderID: "string", + }), + "class-achievements": prSchemaFnWrap({ + game: p.isIn(games), + playtype: isValidPlaytype, + classSet: (self, parent) => { + const gptConfig = GetGamePTConfig( + parent.game as Game, + parent.playtype as Playtypes[Game] + ); + + const keys = Object.keys(gptConfig.classHumanisedFormat); + + if (typeof self !== "string") { + return "Expected a string."; + } + + if (!keys.includes(self)) { + return `Expected any of ${keys.join(", ")}`; + } + + return true; + }, + classOldValue: p.nullable(p.isInteger), + classValue: p.isInteger, + timeAchieved: p.isPositive, + userID: p.isPositiveNonZeroInteger, + }), + "fer-settings": prSchemaFnWrap({ + userID: p.isPositiveNonZeroInteger, + cards: p.nullable( + (self) => + Array.isArray(self) && self.length <= 6 && self.every((t) => typeof t === "string") + ), + forceStaticImport: "boolean", + }), + users: prSchemaFnWrap({ + username: p.regex(/^[a-zA-Z_-][a-zA-Z0-9_-]{2,20}$/u), + usernameLowercase: (self, parent) => self === (parent.username as string).toLowerCase(), + id: p.isPositiveInteger, + socialMedia: { + discord: "*?string", + twitter: "*?string", + github: "*?string", + steam: "*?string", + youtube: "*?string", + twitch: "*?string", + }, + joinDate: p.isPositiveInteger, + about: p.isBoundedString(0, 4000), + status: p.nullable(p.isBoundedString(3, 140)), + customPfpLocation: "?string", + customBannerLocation: "?string", + clan: p.nullable(p.isBoundedString(2, 4)), + lastSeen: p.isPositiveInteger, + badges: [p.isIn("beta", "alpha", "devTeam")], + authLevel: p.isBoundedInteger(UserAuthLevels.BANNED, UserAuthLevels.ADMIN), + }), + "api-tokens": prSchemaFnWrap({ + userID: p.nullable(p.isPositiveNonZeroInteger), + token: "?string", + identifier: "string", + permissions: (self) => { + if (typeof self !== "object" || !self) { + return `Invalid permissions - expected an object.`; + } + + // Typescript moment... + const s = self as Record; + + for (const key of Object.keys(ALL_PERMISSIONS)) { + if (s[key] !== undefined && typeof s[key] !== "boolean") { + return `Invalid permission value of ${s[key]} at ${key}.`; + } + } + + return true; + }, + fromOAuth2Client: "*string", + }), + "session-view-cache": prSchemaFnWrap({ + sessionID: "string", + ip: "string", + timestamp: p.isPositive, + }), + "user-private-information": prSchemaFnWrap({ + userID: p.isPositiveNonZeroInteger, + password: "string", + email: "string", + }), + "orphan-chart-queue": (self: unknown) => { + const { game } = extractGPTIDString(self); + + return prSchemaFnWrap({ + idString: p.isIn(allIDStrings), + chartDoc: PR_CHART_DOCUMENT(game), + songDoc: PR_SONG_DOCUMENT(game), + userIDs: [p.isPositiveNonZeroInteger], + })(self); + }, + "api-clients": prSchemaFnWrap({ + clientID: "string", + clientSecret: "string", + name: "string", + author: p.isPositiveNonZeroInteger, + requestedPermissions: [p.isIn(Object.keys(ALL_PERMISSIONS))], + redirectUri: "?string", + webhookUri: "?string", + apiKeyTemplate: p.nullable((self) => { + if (typeof self !== "string") { + return "Expected a string."; + } + + if (!self.includes("%%TACHI_KEY%%")) { + return "Template must include %%TACHI_KEY%%."; + } + + return true; + }), + apiKeyFilename: "?string", + }), + "import-timings": prSchemaFnWrap({ + importID: "string", + timestamp: p.isPositive, + total: p.isPositive, + rel: Object.fromEntries( + ["import", "importParse", "session", "pb"].map((k) => [k, p.isPositive]) + ), + abs: Object.fromEntries( + ["parse", "import", "importParse", "session", "pb", "ugs", "goal", "milestone"].map( + (k) => [k, p.isPositive] + ) + ), + }), + "arc-saved-profiles": prSchemaFnWrap({ + userID: p.isPositiveNonZeroInteger, + accountID: "string", + forImportType: p.isIn("api/arc-iidx", "api/arc-sdvx"), + }), + invites: prSchemaFnWrap({ + createdBy: p.isPositiveInteger, + code: "string", + createdAt: p.isPositive, + consumed: "boolean", + consumedBy: (self, parent) => { + if (parent.consumed === true) { + return self === null; + } + + return p.isPositiveInteger(self); + }, + consumedAt: (self, parent) => { + if (parent.consumed === true) { + return self === null; + } + + return p.isPositive(self); + }, + }), + + "challenge-wall": prSchemaFnWrap(PR_CHALLENGE), + "challenge-subs": prSchemaFnWrap({ + ...PR_CHALLENGE, + userID: p.isPositiveNonZeroInteger, + achieved: "boolean", + achievedAt: "?number", + }), + + notifications: prSchemaFnWrap({ + title: "string", + notifID: "string", + sentTo: p.isPositiveNonZeroInteger, + sentAt: p.isPositive, + read: "boolean", + body: { + type: p.isIn("RIVALED_BY", "MILESTONE_CHANGED"), + content: (self, parent) => { + const type = parent.type as NotificationBody["type"]; + + let subSchema = {}; + + switch (type) { + case "RIVALED_BY": { + subSchema = { + userID: p.isPositiveNonZeroInteger, + game: p.isIn(games), + playtype: isValidPlaytype, + }; + break; + } + + case "MILESTONE_CHANGED": { + subSchema = { + milestoneID: "string", + }; + break; + } + + case "CHALLENGE_BEAT": { + subSchema = { + userID: p.isPositiveNonZeroInteger, + challenge: PR_CHALLENGE, + }; + break; + } + + case "CHALLENGE_RECEIVED": + subSchema = { + challenge: PR_CHALLENGE, + }; + } + + const err = p(self, subSchema); + + if (err) { + return "Invalid notification.body"; + } + + return true; + }, + }, + }), +} as const; + +export const SCHEMAS: Record = PRE_SCHEMAS; diff --git a/common/src/types.ts b/common/src/types.ts new file mode 100644 index 000000000..a1ced83dd --- /dev/null +++ b/common/src/types.ts @@ -0,0 +1,1635 @@ +// Monk type-stub to avoid pulling the whole dep in +import type { AllClassSets, GameClasses, GameClassSets } from "./game-classes"; +import type { FilterQuery } from "mongodb"; + +export interface IObjectID { + readonly toHexString: () => string; + readonly toString: () => string; +} + +export interface CounterDocument { + counterName: string; + value: integer; +} + +export type Playtype = Playtypes[Game]; + +/** + * IDStrings are an internal (ish) identifier used to identify + * Game + Playtype combos. These are used because TypeScript cannot accurately + * apply two levels of indexing to types, which makes writing generic interfaces + * like the ScoreDocument (Especially the ScoreDocument) *very* hard! + */ +export type IDStrings = + | "bms:7K" + | "bms:14K" + | "chunithm:Single" + | "ddr:DP" + | "ddr:SP" + | "gitadora:Dora" + | "gitadora:Gita" + | "iidx:DP" + | "iidx:SP" + | "itg:Stamina" + | "jubeat:Single" + | "maimai:Single" + | "museca:Single" + | "pms:Controller" + | "pms:Keyboard" + | "popn:9B" + | "sdvx:Single" + | "usc:Controller" + | "usc:Keyboard" + | "wacca:Single"; + +export interface IDStringToPlaytype { + "iidx:SP": "SP"; + "iidx:DP": "DP"; + "popn:9B": "9B"; + "sdvx:Single": "Single"; + "usc:Keyboard": "Keyboard"; + "usc:Controller": "Controller"; + "ddr:SP": "SP"; + "ddr:DP": "DP"; + "maimai:Single": "Single"; + "jubeat:Single": "Single"; + "museca:Single": "Single"; + "bms:7K": "7K"; + "bms:14K": "14K"; + "chunithm:Single": "Single"; + "gitadora:Gita": "Gita"; + "gitadora:Dora": "Dora"; + "wacca:Single": "Single"; + "pms:Keyboard": "Keyboard"; + "pms:Controller": "Controller"; + "itg:Stamina": "Stamina"; +} + +export interface IDStringToGame { + "iidx:SP": "iidx"; + "iidx:DP": "iidx"; + "popn:9B": "popn"; + "sdvx:Single": "sdvx"; + "usc:Keyboard": "usc"; + "usc:Controller": "usc"; + "ddr:SP": "ddr"; + "ddr:DP": "ddr"; + "maimai:Single": "maimai"; + "jubeat:Single": "jubeat"; + "museca:Single": "museca"; + "bms:7K": "bms"; + "bms:14K": "bms"; + "chunithm:Single": "chunithm"; + "gitadora:Gita": "gitadora"; + "gitadora:Dora": "gitadora"; + "wacca:Single": "wacca"; + "pms:Keyboard": "pms"; + "pms:Controller": "pms"; + "itg:Stamina": "itg"; +} + +export interface GameToIDStrings { + iidx: "iidx:DP" | "iidx:SP"; + sdvx: "sdvx:Single"; + usc: "usc:Controller" | "usc:Keyboard"; + ddr: "ddr:DP" | "ddr:SP"; + maimai: "maimai:Single"; + jubeat: "jubeat:Single"; + museca: "museca:Single"; + bms: "bms:7K" | "bms:14K"; + chunithm: "chunithm:Single"; + gitadora: "gitadora:Dora" | "gitadora:Gita"; + popn: "popn:9B"; + wacca: "wacca:Single"; + pms: "pms:Controller" | "pms:Keyboard"; + itg: "itg:Stamina"; +} + +/** + * A utility type for creating an ID string given a game and playtype. + * It should be noted that typescript refuses to assert that + * IDString is a member of IDStrings. You're free to attempt to + * rewrite IDStrings to try and get this to work, I promise you it doesn't. + */ +// export type IDString = `${G}:${P}`; + +/** + * All MongoDB Documents require this field, or atleast they all have them in ktchi's DB. + */ +export interface MongoDBDocument { + _id?: IObjectID; +} + +/** + * All supported games by Tachi. + */ +export type Game = + | "bms" + | "chunithm" + | "ddr" + | "gitadora" + | "iidx" + | "itg" + | "jubeat" + | "maimai" + | "museca" + | "pms" + | "popn" + | "sdvx" + | "usc" + | "wacca"; + +/** + * This is the generic response from the Kamaitachi API in event of a failure. + */ +export interface UnsuccessfulAPIResponse { + success: false; + description: string; +} + +/** + * In the event of a successful API request, body is attached onto the request, which contains + * endpoint-defined information about the response, such as database data. + */ +export interface SuccessfulAPIResponse { + success: true; + description: string; + + // This isn't ideal, but we need to restrict + // this to only objects - Record + // mandates indexability of the type, which makes + // it unusable for known objects. + body: T; +} + +export interface ChartFolderLookupDocument extends MongoDBDocument { + chartID: string; + folderID: string; +} + +export interface Playtypes { + iidx: "DP" | "SP"; + popn: "9B"; + sdvx: "Single"; + usc: "Controller" | "Keyboard"; + ddr: "DP" | "SP"; + maimai: "Single"; + jubeat: "Single"; + museca: "Single"; + chunithm: "Single"; + bms: "7K" | "14K"; + gitadora: "Dora" | "Gita"; + wacca: "Single"; + pms: "Controller" | "Keyboard"; + itg: "Stamina"; +} + +type IIDXGrades = "A" | "AA" | "AAA" | "B" | "C" | "D" | "E" | "F" | "MAX-" | "MAX"; +type SDVXGrades = "A" | "A+" | "AA" | "AA+" | "AAA" | "AAA+" | "B" | "C" | "D" | "PUC" | "S"; +type DDRGrades = + | "A-" + | "A" + | "A+" + | "AA-" + | "AA" + | "AA+" + | "AAA" + | "B-" + | "B" + | "B+" + | "C-" + | "C" + | "C+" + | "D" + | "D+"; + +type GitadoraGrades = "A" | "B" | "C" | "MAX" | "S" | "SS"; + +export interface Grades { + "iidx:SP": IIDXGrades; + "iidx:DP": IIDXGrades; + "popn:9B": "A" | "AA" | "AAA" | "B" | "C" | "D" | "E" | "S"; + "sdvx:Single": SDVXGrades; + "usc:Keyboard": SDVXGrades; + "usc:Controller": SDVXGrades; + "ddr:SP": DDRGrades; + "ddr:DP": DDRGrades; + "maimai:Single": + | "A" + | "AA" + | "AAA" + | "B" + | "C" + | "D" + | "E" + | "F" + | "S" + | "S+" + | "SS" + | "SS+" + | "SSS" + | "SSS+"; + "jubeat:Single": "A" | "B" | "C" | "D" | "E" | "EXC" | "S" | "SS" | "SSS"; + "museca:Single": "佳" | "傑" | "傑G" | "優" | "凡" | "拙" | "没" | "秀" | "良"; + "bms:7K": IIDXGrades; + "bms:14K": IIDXGrades; + "chunithm:Single": "A" | "AA" | "AAA" | "B" | "BB" | "BBB" | "C" | "D" | "S" | "SS" | "SSS"; + "gitadora:Gita": GitadoraGrades; + "gitadora:Dora": GitadoraGrades; + "wacca:Single": + | "A" + | "AA" + | "AAA" + | "B" + | "C" + | "D" + | "MASTER" + | "S" + | "S+" + | "SS" + | "SS+" + | "SSS" + | "SSS+"; + "pms:Controller": IIDXGrades; + "pms:Keyboard": IIDXGrades; + "itg:Stamina": "★" | "★★" | "★★★" | "★★★★" | "A" | "B" | "C" | "D" | "S-" | "S" | "S+"; +} + +type IIDXLamps = + | "ASSIST CLEAR" + | "CLEAR" + | "EASY CLEAR" + | "EX HARD CLEAR" + | "FAILED" + | "FULL COMBO" + | "HARD CLEAR" + | "NO PLAY"; + +type GitadoraLamps = "CLEAR" | "EXCELLENT" | "FAILED" | "FULL COMBO"; + +type SDVXLamps = + | "CLEAR" + | "EXCESSIVE CLEAR" + | "FAILED" + | "PERFECT ULTIMATE CHAIN" + | "ULTIMATE CHAIN"; + +type DDRLamps = + | "CLEAR" + | "FAILED" + | "FULL COMBO" + | "GREAT FULL COMBO" + | "LIFE4" + | "MARVELOUS FULL COMBO" + | "PERFECT FULL COMBO"; + +export interface Lamps { + "iidx:SP": IIDXLamps; + "iidx:DP": IIDXLamps; + "popn:9B": "CLEAR" | "EASY CLEAR" | "FAILED" | "FULL COMBO" | "PERFECT"; + "sdvx:Single": SDVXLamps; + "usc:Keyboard": SDVXLamps; + "usc:Controller": SDVXLamps; + "ddr:SP": DDRLamps; + "ddr:DP": DDRLamps; + "maimai:Single": "ALL PERFECT" | "ALL PERFECT+" | "CLEAR" | "FAILED" | "FULL COMBO"; + "jubeat:Single": "CLEAR" | "EXCELLENT" | "FAILED" | "FULL COMBO"; + "museca:Single": "CLEAR" | "CONNECT ALL" | "FAILED" | "PERFECT CONNECT ALL"; + "bms:7K": IIDXLamps; + "bms:14K": IIDXLamps; + "chunithm:Single": "ALL JUSTICE CRITICAL" | "ALL JUSTICE" | "CLEAR" | "FAILED" | "FULL COMBO"; + "gitadora:Gita": GitadoraLamps; + "gitadora:Dora": GitadoraLamps; + "wacca:Single": "ALL MARVELOUS" | "CLEAR" | "FAILED" | "FULL COMBO" | "MISSLESS"; + "pms:Controller": IIDXLamps; + "pms:Keyboard": IIDXLamps; + "itg:Stamina": "CLEAR" | "FAILED" | "FULL COMBO" | "FULL EXCELLENT COMBO" | "QUAD"; +} + +export type IIDX2DXTraSets = "All Scratch" | "Kichiku" | "Kiraku"; +export type IIDXSPDifficulties = "ANOTHER" | "BEGINNER" | "HYPER" | "LEGGENDARIA" | "NORMAL"; +export type IIDXDPDifficulties = "ANOTHER" | "HYPER" | "LEGGENDARIA" | "NORMAL"; + +export interface Difficulties { + "iidx:SP": `${"" | `${IIDX2DXTraSets} `}${IIDXSPDifficulties}`; + "iidx:DP": `${"" | `${IIDX2DXTraSets} `}${IIDXDPDifficulties}`; + "popn:9B": "Easy" | "EX" | "Hyper" | "Normal"; + "sdvx:Single": "ADV" | "EXH" | "GRV" | "HVN" | "INF" | "MXM" | "NOV" | "VVD"; + "usc:Controller": "ADV" | "EXH" | "INF" | "NOV"; + "usc:Keyboard": "ADV" | "EXH" | "INF" | "NOV"; + "ddr:SP": "BASIC" | "BEGINNER" | "CHALLENGE" | "DIFFICULT" | "EXPERT"; + "ddr:DP": "BASIC" | "CHALLENGE" | "DIFFICULT" | "EXPERT"; + "maimai:Single": "Advanced" | "Basic" | "Easy" | "Expert" | "Master" | "Re:Master"; + "jubeat:Single": "ADV" | "BSC" | "EXT" | "HARD ADV" | "HARD BSC" | "HARD EXT"; + "museca:Single": "Green" | "Red" | "Yellow"; + "bms:7K": "CHART"; + "bms:14K": "CHART"; + "chunithm:Single": "ADVANCED" | "BASIC" | "EXPERT" | "MASTER"; + "gitadora:Gita": + | "ADVANCED" + | "BASIC" + | "BASS ADVANCED" + | "BASS BASIC" + | "BASS EXTREME" + | "BASS MASTER" + | "EXTREME" + | "MASTER"; + "gitadora:Dora": "ADVANCED" | "BASIC" | "EXTREME" | "MASTER"; + "wacca:Single": "EXPERT" | "HARD" | "INFERNO" | "NORMAL"; + "pms:Controller": "CHART"; + "pms:Keyboard": "CHART"; + "itg:Stamina": string; +} + +/** + * An alias for number, that makes part of the code self-documenting. + * Note that if it were possible to enforce integer-y ness, then I would absolutely do so here + * but i can not. + */ +export type integer = number; + +export type Ratings = Record>; + +export interface BaseGoalDocument extends MongoDBDocument { + game: Game; + playtype: Playtype; + timeAdded: integer; + name: string; + goalID: string; + criteria: GoalCountCriteria | GoalSingleCriteria; +} + +interface GoalCriteria { + key: "scoreData.gradeIndex" | "scoreData.lampIndex" | "scoreData.percent" | "scoreData.score"; + value: number; +} + +export interface GoalSingleCriteria extends GoalCriteria { + mode: "single"; +} + +/** + * Criteria for a score to match this criteria - this is a "count" mode, which means that + * atleast N scores have to match this criteria. This is for things like folders. + */ +export interface GoalCountCriteria extends GoalCriteria { + mode: "absolute" | "proportion"; + countNum: number; +} + +/** + * Goal Document - Single. A goal document that is only for one specific chart. + */ +export interface GoalDocumentSingle extends BaseGoalDocument { + charts: { + type: "single"; + data: string; + }; +} + +/** + * Goal Document - Multi. A goal document whos set of charts is the array of + * chartIDs inside "charts". + */ +export interface GoalDocumentMulti extends BaseGoalDocument { + charts: { + type: "multi"; + data: Array; + }; +} + +/** + * Goal Document - Folder. A goal document whos set of charts is derived from + * the folderID inside "charts". + */ +export interface GoalDocumentFolder extends BaseGoalDocument { + charts: { + type: "folder"; + data: string; + }; +} + +/** + * Goal Document - Any. A goal document whos set of charts is not bound. + */ +export interface GoalDocumentAny extends BaseGoalDocument { + charts: { + type: "any"; + }; +} + +export type GoalDocument = + | GoalDocumentAny + | GoalDocumentFolder + | GoalDocumentMulti + | GoalDocumentSingle; + +interface BaseInviteCodeDocument extends MongoDBDocument { + createdBy: integer; + code: string; + createdAt: number; +} + +export type InviteCodeDocument = BaseInviteCodeDocument & + ( + | { consumed: false; consumedBy: null; consumedAt: null } + | { consumed: true; consumedBy: integer; consumedAt: number } + ); + +export interface SessionInfoReturn { + sessionID: string; + type: "Appended" | "Created"; +} + +interface SessionScorePBInfo { + scoreID: string; + isNewScore: false; + scoreDelta: number; + gradeDelta: integer; + lampDelta: integer; + percentDelta: number; +} + +interface SessionScoreNewInfo { + scoreID: string; + isNewScore: true; +} + +export type SessionScoreInfo = SessionScoreNewInfo | SessionScorePBInfo; + +export interface SessionCalculatedDataLookup { + "iidx:SP": "BPI" | "ktLampRating"; + "iidx:DP": "BPI" | "ktLampRating"; + "popn:9B": "classPoints"; + "sdvx:Single": "ProfileVF6" | "VF6"; + "usc:Keyboard": "ProfileVF6" | "VF6"; + "usc:Controller": "ProfileVF6" | "VF6"; + "ddr:SP": "ktRating" | "MFCP"; + "ddr:DP": "ktRating" | "MFCP"; + "maimai:Single": "ktRating"; + "jubeat:Single": "jubility"; + "museca:Single": "ktRating"; + "bms:7K": "sieglinde"; + "bms:14K": "sieglinde"; + "chunithm:Single": "naiveRating"; + "gitadora:Gita": "skill"; + "gitadora:Dora": "skill"; + "wacca:Single": "rate"; + "pms:Controller": "sieglinde"; + "pms:Keyboard": "sieglinde"; + "itg:Stamina": "average32Speed" | "blockRating"; +} + +export interface SessionDocument extends MongoDBDocument { + userID: integer; + sessionID: string; + scoreInfo: Array; + name: string; + desc: string | null; + game: Game; + playtype: Playtype; + + // This field is allowed to be null for compatibility with kamaitachi1 sessions, where import types didn't exist. + importType: ImportTypes | null; + timeInserted: integer; + timeEnded: integer; + timeStarted: integer; + calculatedData: Partial>; + highlight: boolean; + views: integer; +} + +export interface SessionViewDocument extends MongoDBDocument { + sessionID: string; + ip: string; + timestamp: number; +} + +interface ImportErrContent { + type: string; + message: string; +} + +export interface ClassDelta { + game: Game; + set: AllClassSets; + playtype: Playtype; + old: integer | null; + new: integer; +} + +export interface ImportDocument extends MongoDBDocument { + userID: integer; + timeStarted: number; + timeFinished: number; + + // Contains an array of IDStrings, which dictates what (game:playtype)s were involved in this import. + idStrings: Array; + importID: string; + scoreIDs: Array; + game: Game; + playtypes: Array; + errors: Array; + + // For performance reasons, imports only show what sessions they created, rather than what sessions they didn't. + // This is just an array of sessionIDs, to keep things normalised. May be empty. + createdSessions: Array; + importType: ImportTypes; + classDeltas: Array; + goalInfo: Array; + milestoneInfo: Array; + + /** + * Whether the user deliberately imported this through an action (i.e. uploaded a file personally) [true] + * or was imported on their behalf through a service (i.e. fervidex) + */ + userIntent: boolean; +} + +export interface ImportTimingsDocument { + importID: string; + timestamp: number; + total: number; + + /** + * Relative times - these are the times for each section + * divided by how much data they had to process. + */ + rel: Omit; + + /** + * Absolute times - these are the times for each section. + */ + abs: ImportTimingSections; +} + +interface ImportTimingSections { + parse: number; + import: number; + importParse: number; + session: number; + pb: number; + ugs: number; + goal: number; + milestone: number; +} + +export type GoalImportStat = Pick< + GoalSubscriptionDocument, + "achieved" | "outOf" | "outOfHuman" | "progress" | "progressHuman" +>; + +export interface GoalImportInfo { + goalID: string; + old: GoalImportStat; + new: GoalImportStat; +} + +export type MilestoneImportStat = Pick; + +export interface MilestoneImportInfo { + milestoneID: string; + old: MilestoneImportStat; + new: MilestoneImportStat; +} + +export type GoalSubscriptionDocument = MongoDBDocument & { + goalID: string; + userID: integer; + game: Game; + playtype: Playtype; + timeSet: integer; + lastInteraction: integer | null; + progress: number | null; + progressHuman: string; + outOf: number; + outOfHuman: string; + wasInstantlyAchieved: boolean; +} & ( + | { + achieved: false; + timeAchieved: null; + } + | { + achieved: true; + timeAchieved: integer; + } + ); + +interface MilestoneGoalReference { + goalID: string; + note?: string; +} + +interface MilestoneSection { + title: string; + desc: string; + goals: Array; +} + +export interface MilestoneDocument extends MongoDBDocument { + game: Game; + playtype: Playtype; + + /** + * all: All goals must be achieved in order for the milestone to be complete + * abs: Goals achieved must be greater than or equal to criteria.value. + * proportion: Goals achieved must be greater than or equal to criteria.value * total_goals. + */ + criteria: MilestoneAbsPropCriteria | MilestoneAllCriteria; + name: string; + desc: string; + milestoneData: Array; + milestoneID: string; +} + +interface MilestoneAllCriteria { + type: "all"; +} + +interface MilestoneAbsPropCriteria { + type: "total"; + value: number; +} + +export interface MilestoneSetDocument extends MongoDBDocument { + setID: string; + name: string; + desc: string; + game: Game; + playtype: Playtype; + milestones: Array; +} + +export type UserBadges = "alpha" | "beta" | "contributor" | "dev-team" | "significant-contributor"; + +export enum UserAuthLevels { + BANNED = 0, + USER = 1, + MOD = 2, + ADMIN = 3, +} + +export interface PublicUserDocument extends MongoDBDocument { + username: string; + usernameLowercase: string; + id: integer; + socialMedia: { + discord?: string | null; + twitter?: string | null; + github?: string | null; + steam?: string | null; + youtube?: string | null; + twitch?: string | null; + }; + joinDate: integer; + lastSeen: integer; + about: string; + status: string | null; + customPfpLocation: string | null; + customBannerLocation: string | null; + clan: string | null; // todo + badges: Array; + authLevel: UserAuthLevels; +} + +export interface UGSRatingsLookup { + "iidx:SP": "BPI" | "ktLampRating"; + "iidx:DP": "BPI" | "ktLampRating"; + "popn:9B": "naiveClassPoints"; + "sdvx:Single": "VF6"; + "usc:Keyboard": "VF6"; + "usc:Controller": "VF6"; + "ddr:SP": "ktRating" | "MFCP"; + "ddr:DP": "ktRating" | "MFCP"; + "maimai:Single": "ktRating"; + "jubeat:Single": "jubility" | "naiveJubility"; + "museca:Single": "ktRating"; + "bms:7K": "sieglinde"; + "bms:14K": "sieglinde"; + "chunithm:Single": "naiveRating"; + "gitadora:Gita": "naiveSkill" | "skill"; + "gitadora:Dora": "naiveSkill" | "skill"; + "wacca:Single": "naiveRate" | "rate"; + "pms:Controller": "sieglinde"; + "pms:Keyboard": "sieglinde"; + "itg:Stamina": "highest32" | "highest256" | "highestBlock"; +} + +export interface UserGameStats extends MongoDBDocument { + userID: integer; + game: IDStringToGame[I]; + playtype: IDStringToPlaytype[I]; + ratings: Partial>; + classes: Partial>; +} + +type SupportedIIDXVersions = + | "3-cs" + | "4-cs" + | "5-cs" + | "6-cs" + | "7-cs" + | "8-cs" + | "9-cs" + | "10-cs" + | "11-cs" + | "12-cs" + | "13-cs" + | "14-cs" + | "15-cs" + | "16-cs" + | "20" + | "21" + | "22" + | "23" + | "24" + | "25" + | "26-omni" + | "26" + | "27-2dxtra" + | "27-omni" + | "27" + | "28-2dxtra" + | "28-omni" + | "28-omni" + | "28" + | "29" + | "bmus" + | "inf"; + +export interface GPTSupportedVersions { + "iidx:SP": SupportedIIDXVersions; + "iidx:DP": SupportedIIDXVersions; + "popn:9B": "peace"; + "sdvx:Single": "booth" | "exceed" | "gw" | "heaven" | "inf" | "konaste" | "vivid"; + "usc:Controller": never; + "usc:Keyboard": never; + "ddr:SP": "a20"; + "ddr:DP": "a20"; + "maimai:Single": "finale"; + "jubeat:Single": "clan" | "festo"; + "museca:Single": "1.5-b" | "1.5"; + "bms:7K": never; + "bms:14K": never; + "chunithm:Single": "paradiselost"; + "gitadora:Gita": "nextage"; + "gitadora:Dora": "nextage"; + "wacca:Single": "reverse"; + "pms:Controller": never; + "pms:Keyboard": never; + "itg:Stamina": never; +} + +interface CDDataIIDXSP { + notecount: integer; + inGameID: Array | integer; + arcChartID: string | null; + hashSHA256: string | null; + "2dxtraSet": string | null; + kaidenAverage: integer | null; + worldRecord: integer | null; + bpiCoefficient: number | null; +} + +interface CDDataDDRSP { + songHash: string; + inGameID: string; +} + +interface CDDataPMS { + notecount: integer; + hashMD5: string; + hashSHA256: string; + tableFolders: Array<{ table: string; level: string }>; +} + +type CDDataBMS = CDDataPMS & { + aiRating: string | null; +}; + +interface CDDataUSC { + hashSHA1: Array | string; + isOfficial: boolean; + effector: string; + tableFolders: Array<{ table: string; level: string }>; +} + +interface ChartDocumentData { + "iidx:SP": CDDataIIDXSP; + "iidx:DP": CDDataIIDXSP; + "popn:9B": { hashSHA256: string | null; inGameID: integer }; + "sdvx:Single": { inGameID: integer; arcChartID: string | null }; + "usc:Keyboard": CDDataUSC; + "usc:Controller": CDDataUSC; + "ddr:SP": CDDataDDRSP; + "ddr:DP": CDDataDDRSP; + "maimai:Single": { + maxPercent: number; + inGameID: number; + inGameStrID: string; + }; + "jubeat:Single": { inGameID: integer; isHardMode: boolean }; + "museca:Single": { inGameID: integer }; + "bms:7K": CDDataBMS; + "bms:14K": CDDataBMS; + "chunithm:Single": { inGameID: integer }; + "gitadora:Gita": { inGameID: integer }; + "gitadora:Dora": { inGameID: integer }; + "wacca:Single": { isHot: boolean }; + "pms:Controller": CDDataPMS; + "pms:Keyboard": CDDataPMS; + "itg:Stamina": { + chartHash: string; + difficultyTag: "Beginner" | "Challenge" | "Easy" | "Hard" | "Medium"; + breakdown: { + detailed: string; + partiallySimplified: string; + simplified: string; + total: string; + npsPerMeasure: Array; + notesPerMeasure: Array; + }; + tech: { + crossovers: integer; + jacks: integer; + brackets: integer; + footswitches: integer; + sideswitches: integer; + }; + length: number; + charter: string; + displayBPM: number; + }; +} + +export interface GPTTierlists { + "iidx:SP": "kt-EXHC" | "kt-HC" | "kt-NC"; + "iidx:DP": "dp-tier"; + "bms:7K": "sgl-EC" | "sgl-HC"; + "bms:14K": "sgl-EC" | "sgl-HC"; + "popn:9B": never; + "sdvx:Single": "clear"; + "usc:Keyboard": never; + "usc:Controller": never; + "ddr:SP": never; + "ddr:DP": never; + "maimai:Single": never; + "jubeat:Single": never; + "museca:Single": never; + "chunithm:Single": never; + "gitadora:Gita": never; + "gitadora:Dora": never; + "wacca:Single": never; + "pms:Controller": "sgl-EC" | "sgl-HC"; + "pms:Keyboard": "sgl-EC" | "sgl-HC"; + "itg:Stamina": never; +} + +export interface ChartTierlistInfo { + text: string; + value: number; + individualDifference?: boolean; +} + +export interface ChartDocument extends MongoDBDocument { + chartID: string; + rgcID: string | null; // ID to perform backbeat lookup in future. + songID: integer; + level: string; + levelNum: number; + isPrimary: boolean; + difficulty: Difficulties[I]; + playtype: IDStringToPlaytype[I]; + data: ChartDocumentData[I]; + tierlistInfo: Partial>; + versions: Array; +} + +interface SongDocumentData { + iidx: { genre: string; displayVersion: string | null }; + museca: { titleJP: string; artistJP: string; displayVersion: string }; + maimai: { titleJP: string; artistJP: string; displayVersion: string }; + jubeat: { displayVersion: string }; + popn: { + displayVersion: string | null; + genre: string; + genreEN: string | null; + }; + sdvx: { displayVersion: string }; + usc: Record; + ddr: { displayVersion: string }; + bms: { + genre: string | null; + subtitle: string | null; + subartist: string | null; + tableString: string | null; + }; + chunithm: { genre: string; displayVersion: string }; + gitadora: { isHot: boolean; displayVersion: string }; + wacca: { + titleJP: string; + artistJP: string; + genre: string; + displayVersion: string | null; + }; + pms: { + genre: string | null; + subtitle: string | null; + subartist: string | null; + tableString: string | null; + }; + itg: { + subtitle: string; + banner: string | null; + }; +} + +export interface SongDocument extends MongoDBDocument { + id: integer; + title: string; + artist: string; + searchTerms: Array; + altTitles: Array; + data: SongDocumentData[G]; +} + +export interface TableDocument extends MongoDBDocument { + tableID: string; + game: Game; + playtype: Playtype; + title: string; + description: string; + folders: Array; + inactive: boolean; + default: boolean; +} + +export interface BaseFolderDocument extends MongoDBDocument { + title: string; + game: Game; + playtype: Playtype; + folderID: string; + + /** + * This folder has been superceded by another folder, + * such as one on a more modern version of the game. + */ + inactive: boolean; + searchTerms: Array; +} + +export interface FolderSongsDocument extends BaseFolderDocument { + type: "songs"; + data: FilterQuery; +} + +export interface FolderChartsDocument extends BaseFolderDocument { + type: "charts"; + data: FilterQuery; +} + +export interface FolderStaticDocument extends BaseFolderDocument { + type: "static"; + data: Array; +} + +export type FolderDocument = FolderChartsDocument | FolderSongsDocument | FolderStaticDocument; + +export interface FolderChartLookup extends MongoDBDocument { + chartID: string; + folderID: string; +} + +export type MilestoneSubscriptionDocument = MongoDBDocument & { + milestoneID: string; + userID: integer; + game: Game; + playtype: Playtype; + timeSet: integer; + progress: integer; + lastInteraction: integer | null; + wasInstantlyAchieved: boolean; +} & ( + | { + achieved: false; + timeAchieved: null; + } + | { + achieved: true; + timeAchieved: integer; + } + ); + +type RanOptions = "MIRROR" | "NONRAN" | "R-RANDOM" | "RANDOM" | "S-RANDOM"; + +interface IIDXSPScoreMeta { + random: RanOptions | null; + assist: "AUTO SCRATCH" | "FULL ASSIST" | "LEGACY NOTE" | "NO ASSIST" | null; + range: "HIDDEN+" | "LIFT SUD+" | "LIFT" | "NONE" | "SUD+ HID+" | "SUDDEN+" | null; + gauge: "ASSISTED EASY" | "EASY" | "EX-HARD" | "HARD" | "NORMAL" | null; +} + +interface BMS7KScoreMeta { + random: RanOptions | null; + inputDevice: "BM_CONTROLLER" | "KEYBOARD" | null; + client: "LR2" | "lr2oraja" | null; + gauge: "EASY" | "EX-HARD" | "HARD" | "NORMAL" | null; +} + +interface PMSScoreMeta { + random: RanOptions | null; + client: "beatoraja" | null; + gauge: "EASY" | "EX-HARD" | "HARD" | "NORMAL" | null; +} + +interface USCScoreMeta { + noteMod: "MIR-RAN" | "MIRROR" | "NORMAL" | "RANDOM" | null; + gaugeMod: "HARD" | "NORMAL" | "PERMISSIVE" | null; +} + +interface ScoreMetaLookup { + "iidx:SP": IIDXSPScoreMeta; + "iidx:DP": IIDXSPScoreMeta & { + random: { left: RanOptions; right: RanOptions } | null; + }; + "popn:9B": { + hiSpeed: number | null; + hidden: integer | null; + sudden: integer | null; + random: "MIRROR" | "NONRAN" | "RANDOM" | "S-RANDOM" | null; + gauge: "DANGER" | "EASY" | "HARD" | "NORMAL" | null; + }; + "sdvx:Single": { inSkillAnalyser: boolean | null }; + "usc:Controller": USCScoreMeta; + "usc:Keyboard": USCScoreMeta; + "ddr:SP": Record; + "ddr:DP": Record; + "maimai:Single": Record; + "jubeat:Single": Record; + "museca:Single": Record; + "bms:7K": BMS7KScoreMeta; + "bms:14K": BMS7KScoreMeta & { + random: { left: RanOptions; right: RanOptions } | null; + }; + "chunithm:Single": Record; + "gitadora:Gita": Record; + "gitadora:Dora": Record; + "wacca:Single": { mirror: boolean | null }; + "pms:Controller": PMSScoreMeta; + "pms:Keyboard": PMSScoreMeta; + "itg:Stamina": Record; +} + +interface BASE_VALID_HIT_META { + fast: integer | null; + slow: integer | null; + maxCombo: integer | null; +} + +type IIDXHitMeta = BASE_VALID_HIT_META & { + bp: integer | null; + gauge: number | null; + gaugeHistory: Array | null; + scoreHistory: Array | null; + comboBreak: integer | null; + gsm: { + EASY: Array; + NORMAL: Array; + HARD: Array; + EX_HARD: Array; + } | null; +}; + +type BMSJudgePermutations = `${"e" | "l"}${"bd" | "gd" | "gr" | "pg" | "pr"}`; + +type BMSHitMeta = BASE_VALID_HIT_META & { + [K in BMSJudgePermutations]: integer; +} & { + bp: integer | null; + gauge: number | null; + gaugeHistory: Array | null; +}; + +export type USCHitMeta = BASE_VALID_HIT_META & { + gauge: number | null; +}; + +export interface HitMetaLookup { + "iidx:SP": IIDXHitMeta; + "iidx:DP": IIDXHitMeta; + "popn:9B": BASE_VALID_HIT_META & { + gauge: number | null; + specificClearType: + | "clearCircle" + | "clearDiamond" + | "clearStar" + | "easyClear" + | "failedCircle" + | "failedDiamond" + | "failedStar" + | "fullComboCircle" + | "fullComboDiamond" + | "fullComboStar" + | "perfect" + | null; + }; + "sdvx:Single": BASE_VALID_HIT_META & { + gauge: number | null; + }; + "usc:Controller": USCHitMeta; + "usc:Keyboard": USCHitMeta; + "ddr:SP": BASE_VALID_HIT_META; + "ddr:DP": BASE_VALID_HIT_META; + "maimai:Single": BASE_VALID_HIT_META; + "jubeat:Single": BASE_VALID_HIT_META; + "museca:Single": BASE_VALID_HIT_META; + "bms:7K": BMSHitMeta; + "bms:14K": BMSHitMeta; + "chunithm:Single": BASE_VALID_HIT_META; + "gitadora:Gita": BASE_VALID_HIT_META; + "gitadora:Dora": BASE_VALID_HIT_META; + "wacca:Single": BASE_VALID_HIT_META; + "pms:Controller": BMSHitMeta; + "pms:Keyboard": BMSHitMeta; + "itg:Stamina": BASE_VALID_HIT_META & { + gaugeHistory: Array | null; + diedAt: integer | null; + }; +} + +type IIDXJudges = "bad" | "good" | "great" | "pgreat" | "poor"; +type DDRJudges = "boo" | "good" | "great" | "marvelous" | "miss" | "ng" | "ok" | "perfect"; +type GitadoraJudges = "good" | "great" | "miss" | "ok" | "perfect"; + +// judges might need to be changed here... +// @bug Known that sdvx calls misses errors. I, however, don't care. +type SDVXJudges = "critical" | "miss" | "near"; + +export interface JudgementLookup { + "iidx:SP": IIDXJudges; + "iidx:DP": IIDXJudges; + "popn:9B": "bad" | "cool" | "good" | "great"; + "sdvx:Single": SDVXJudges; + "usc:Controller": SDVXJudges; + "usc:Keyboard": SDVXJudges; + "ddr:SP": DDRJudges; + "ddr:DP": DDRJudges; + "maimai:Single": "good" | "great" | "miss" | "perfect"; + "jubeat:Single": "good" | "great" | "miss" | "perfect" | "poor"; + "museca:Single": "critical" | "miss" | "near"; + "bms:7K": IIDXJudges; + "bms:14K": IIDXJudges; + "chunithm:Single": "attack" | "jcrit" | "justice" | "miss"; + "gitadora:Gita": GitadoraJudges; + "gitadora:Dora": GitadoraJudges; + "wacca:Single": "good" | "great" | "marvelous" | "miss"; + "pms:Controller": "bad" | "cool" | "good" | "great" | "poor"; + "pms:Keyboard": "bad" | "cool" | "good" | "great" | "poor"; + "itg:Stamina": "decent" | "excellent" | "fantastic" | "great" | "miss" | "wayoff"; +} + +export interface ScoreCalculatedDataLookup { + "iidx:SP": "BPI" | "ktLampRating"; + "iidx:DP": "BPI" | "ktLampRating"; + "popn:9B": "classPoints"; + "sdvx:Single": "VF6"; + "usc:Keyboard": "VF6"; + "usc:Controller": "VF6"; + "ddr:SP": "ktRating" | "MFCP"; + "ddr:DP": "ktRating" | "MFCP"; + "maimai:Single": "ktRating"; + "jubeat:Single": "jubility"; + "museca:Single": "ktRating"; + "bms:7K": "sieglinde"; + "bms:14K": "sieglinde"; + "chunithm:Single": "rating"; + "gitadora:Gita": "skill"; + "gitadora:Dora": "skill"; + "wacca:Single": "rate"; + "pms:Controller": "sieglinde"; + "pms:Keyboard": "sieglinde"; + "itg:Stamina": "blockRating" | "highest32" | "highest256"; +} + +export interface ScoreDocument extends MongoDBDocument { + service: string; + game: IDStringToGame[I]; + playtype: IDStringToPlaytype[I]; + userID: integer; + scoreData: { + score: number; + lamp: Lamps[I]; + percent: number; + grade: Grades[I]; + lampIndex: integer; + gradeIndex: integer; + esd: number | null; + judgements: Partial>; + hitMeta: Partial; + }; + scoreMeta: Partial; + calculatedData: Partial>; + timeAchieved: integer | null; + songID: integer; + chartID: string; + isPrimary: boolean; + highlight: boolean; + comment: string | null; + timeAdded: integer; + scoreID: string; + importType: ImportTypes | null; +} + +export interface PBScoreComposedReference { + name: string; + scoreID: string; +} + +export interface PBScoreDocument extends MongoDBDocument { + composedFrom: { + scorePB: string; + lampPB: string; + other?: Array; + }; + rankingData: { + rank: integer; + outOf: integer; + }; + userID: integer; + chartID: string; + game: Game; + playtype: Playtype; + songID: integer; + highlight: boolean; + isPrimary: boolean; + timeAchieved: number | null; + scoreData: { + score: number; + lamp: Lamps[I]; + percent: number; + grade: Grades[I]; + lampIndex: integer; + gradeIndex: integer; + esd: number | null; + judgements: Partial>; + hitMeta: Partial; + }; + calculatedData: Partial>; +} + +export type FileUploadImportTypes = + | "file/batch-manual" + | "file/eamusement-iidx-csv" + | "file/eamusement-sdvx-csv" + | "file/mer-iidx" + | "file/pli-iidx-csv" + | "file/solid-state-squad"; + +export type APIImportTypes = + | "api/arc-iidx" + | "api/arc-sdvx" + | "api/eag-iidx" + | "api/eag-sdvx" + | "api/flo-iidx" + | "api/flo-sdvx" + | "api/min-sdvx"; + +export type IRImportTypes = + | "ir/barbatos" + | "ir/beatoraja" + | "ir/direct-manual" + | "ir/fervidex-static" + | "ir/fervidex" + | "ir/kshook-sv6c" + | "ir/lr2hook" + | "ir/usc"; + +export type ImportTypes = APIImportTypes | FileUploadImportTypes | IRImportTypes; + +export interface ImportProcessInfoKTDataNotFound { + success: false; + type: "KTDataNotFound"; + message: string; + content: { + data: unknown; + context: unknown; // @TODO Type these properly. + orphanID: string; + }; +} + +export interface ImportProcessInfoOrphanExists { + success: false; + type: "OrphanExists"; + message: string; + content: { + orphanID: string; + }; +} + +export interface ImportProcessInfoInvalidDatapoint { + success: false; + type: "InvalidDatapoint"; + message: string; + content: Record; +} + +export interface ImportProcessInfoScoreImported { + success: true; + type: "ScoreImported"; + message: string; + content: { + score: ScoreDocument; + }; +} + +export interface ImportProcessInfoInternalError { + success: false; + type: "InternalError"; + message: string; + content: Record; +} + +export type ImportProcessingInfo = + | ImportProcessInfoInternalError + | ImportProcessInfoInvalidDatapoint + | ImportProcessInfoKTDataNotFound + | ImportProcessInfoOrphanExists + | ImportProcessInfoScoreImported; + +export interface ImportStatistics { + scoreCount: integer; + msPerScore: number; + sessionCount: integer; + msPerSession: number; + ratingTime: number; + importID: string; +} + +export interface KaiAuthDocument { + userID: integer; + token: string; + refreshToken: string; + service: "EAG" | "FLO" | "MIN"; +} + +/** + * Used to resolve beatoraja IR courses. + */ +export interface BMSCourseDocument { + title: string; + md5sums: string; + set: "genocideDan" | "lnDan" | "scratchDan" | "stslDan"; + playtype: "7K" | "14K"; + value: integer; +} + +/** + * All the permissions a token may have. + */ +export type APIPermissions = + | "customise_profile" + | "customise_score" + | "customise_session" + | "delete_score" + | "manage_challenges" + | "manage_rivals" + | "manage_targets" + | "submit_score"; + +/** + * Information about the API Token used to make this request. + */ +export interface APITokenDocument extends MongoDBDocument { + userID: integer | null; + token: string | null; + identifier: string; + permissions: Partial>; + + // API Tokens may be created as a result of a Tachi Client flow. This prop optionally + // stores that. + fromAPIClient: string | null; +} + +export interface ImportLockDocument extends MongoDBDocument { + userID: integer; +} + +export type ShowcaseStatDetails = ShowcaseStatChart | ShowcaseStatFolder; + +export interface ShowcaseStatFolder { + mode: "folder"; + folderID: string; + property: "grade" | "lamp" | "percent" | "score"; + gte: number; +} + +export interface ShowcaseStatChart { + mode: "chart"; + chartID: string; + property: "grade" | "lamp" | "percent" | "playcount" | "score"; +} + +export interface UGPTSpecificPreferences { + "iidx:SP": { display2DXTra: boolean; bpiTarget: number }; + "iidx:DP": { display2DXTra: boolean; bpiTarget: number }; + "popn:9B": Record; + "sdvx:Single": { vf6Target: number }; + "usc:Controller": { vf6Target: number }; + "usc:Keyboard": { vf6Target: number }; + "ddr:SP": Record; + "ddr:DP": Record; + "maimai:Single": Record; + "jubeat:Single": { jubilityTarget: number }; + "museca:Single": Record; + "bms:7K": Record; + "bms:14K": Record; + "chunithm:Single": Record; + "gitadora:Gita": Record; + "gitadora:Dora": Record; + "wacca:Single": Record; + "pms:Controller": Record; + "pms:Keyboard": Record; + "itg:Stamina": Record; +} + +export interface UGPTSettings extends MongoDBDocument { + userID: integer; + game: IDStringToGame[I]; + playtype: IDStringToPlaytype[I]; + preferences: { + preferredScoreAlg: ScoreCalculatedDataLookup[I] | null; + preferredSessionAlg: SessionCalculatedDataLookup[I] | null; + preferredProfileAlg: UGSRatingsLookup[I] | null; + stats: Array; + scoreBucket: "grade" | "lamp" | null; + defaultTable: string | null; + gameSpecific: UGPTSpecificPreferences[I]; + }; + rivals: Array; +} + +export interface UserGameStatsSnapshot + extends MongoDBDocument, + UserGameStats { + rankings: Record; + playcount: integer; + timestamp: integer; +} + +export interface ARCSavedProfileDocument extends MongoDBDocument { + userID: integer; + accountID: string; + forImportType: "api/arc-iidx" | "api/arc-sdvx"; +} + +export type BatchManualScore = { + score: number; + lamp: Lamps[I]; + percent?: number; + identifier: string; + comment?: string | null; + judgements?: Record; + timeAchieved?: number | null; + hitMeta?: Partial; + scoreMeta?: Partial; +} & ( + | { + matchType: "bmsChartHash" | "itgChartHash" | "popnChartHash" | "uscChartHash"; + difficulty?: undefined; // hack to stop ts from screaming when this is accessed sometimes + } + | { + matchType: "ddrSongHash" | "inGameID" | "songTitle" | "tachiSongID"; + difficulty: Difficulties[I]; + } +); + +export interface BatchManual { + meta: { + game: IDStringToGame[I]; + playtype: IDStringToPlaytype[I]; + service: string; + version?: GPTSupportedVersions[I]; + }; + scores: Array>; + classes?: Record | null; +} + +export interface UserSettings { + userID: integer; + preferences: { + invisible: boolean; + developerMode: boolean; + advancedMode: boolean; + contentiousContent: boolean; + deletableScores: boolean; + }; +} + +export interface TachiAPIClientDocument { + clientID: string; + clientSecret: string; + name: string; + author: integer; + requestedPermissions: Array; + redirectUri: string | null; + webhookUri: string | null; + apiKeyTemplate: string | null; + apiKeyFilename: string | null; +} + +export interface FervidexSettingsDocument { + userID: integer; + cards: Array | null; + forceStaticImport: boolean; +} + +export interface OrphanChart { + idString: I; + chartDoc: ChartDocument; + songDoc: SongDocument; + userIDs: Array; +} + +export interface ClassAchievementDocument extends MongoDBDocument { + game: IDStringToGame[I]; + playtype: IDStringToPlaytype[I]; + classSet: AllClassSets; + classOldValue: integer | null; + classValue: integer; + timeAchieved: number; + userID: integer; +} + +export interface RecentlyViewedFolderDocument { + userID: integer; + game: Game; + playtype: Playtypes[Game]; + folderID: string; + lastViewed: number; +} + +interface BaseNotification { + title: string; + notifID: string; + + // The user this notification was sent to. + sentTo: integer; + sentAt: integer; + read: boolean; +} + +export type NotificationBody = + | { + type: "CHALLENGE_BEAT"; + content: { + userID: integer; + challenge: ChallengeWallDocument; + }; + } + | { + type: "CHALLENGE_RECEIVED"; + content: { + challenge: ChallengeWallDocument; + }; + } + | { + type: "MILESTONE_CHANGED"; // Emitted when a milestone the user is subscribed to changed. + content: { + milestoneID: string; + }; + } + | { + type: "RIVALED_BY"; // Emitted when the user is rivalled by someone. + content: { + userID: integer; + game: Game; + playtype: Playtype; + }; + }; + +export type NotificationDocument = BaseNotification & { + body: NotificationBody; +}; + +export interface ChallengeSubscriptionDocument { + chartID: string; + authorID: integer; + type: "lamp" | "score"; + + game: Game; + playtype: Playtype; + + userID: integer; + achieved: boolean; + achievedAt: number | null; +} + +export interface ChallengeWallDocument { + chartID: string; + authorID: integer; + type: "lamp" | "score"; + + game: Game; + playtype: Playtype; +} diff --git a/common/src/utils/util.ts b/common/src/utils/util.ts new file mode 100644 index 000000000..ecc1642cc --- /dev/null +++ b/common/src/utils/util.ts @@ -0,0 +1,245 @@ +import { GetGameConfig, GetGamePTConfig } from "../config/config"; +import type { ChartDocument, SongDocument, Game, IDStrings, Playtypes } from ".."; +import type { Grades, integer } from "../types"; + +export function FormatInt(v: number): string { + return v.toString(); +} + +export function FormatDifficulty(chart: ChartDocument, game: Game): string { + if (game === "bms" || game === "pms") { + const bmsChart = chart as ChartDocument<"bms:7K" | "bms:14K">; + + return ( + bmsChart.data.tableFolders.map((e) => `${e.table}${e.level}`).join(", ") || "Unrated" + ); + } + + if (game === "itg") { + const itgChart = chart as ChartDocument<"itg:Stamina">; + + return `${itgChart.data.difficultyTag} ${itgChart.level}`; + } + + const gameConfig = GetGameConfig(game); + + if (gameConfig.validPlaytypes.length > 1) { + return `${chart.playtype} ${chart.difficulty} ${chart.level}`; + } + + return `${chart.difficulty} ${chart.level}`; +} + +/** + * Formats a chart's difficulty into a shorter variant. This handles a lot of + * game-specific strange edge cases. + */ +export function FormatDifficultyShort(chart: ChartDocument, game: Game): string { + const gameConfig = GetGameConfig(game); + const gptConfig = GetGamePTConfig(game, chart.playtype); + + if (game === "itg") { + const itgChart = chart as ChartDocument<"itg:Stamina">; + + return `S${itgChart.data.difficultyTag} ${chart.level}`; + } + + const shortDiff = gptConfig.shortDifficulties[chart.difficulty] ?? chart.difficulty; + + if (game === "ddr") { + return `${shortDiff}${chart.playtype}`; + } + + if (gameConfig.validPlaytypes.length === 1 || game === "gitadora") { + return `${shortDiff} ${chart.level}`; + } + + if (game === "usc") { + return `${chart.playtype === "Controller" ? "CON" : "KB"} ${shortDiff} ${chart.level}`; + } + + return `${chart.playtype}${shortDiff} ${chart.level}`; +} + +export function FormatGame(game: Game, playtype: Playtypes[Game]): string { + const gameConfig = GetGameConfig(game); + + if (gameConfig.validPlaytypes.length === 1) { + return gameConfig.name; + } + + return `${gameConfig.name} (${playtype})`; +} + +export function FormatChart(game: Game, song: SongDocument, chart: ChartDocument): string { + if (game === "bms") { + const tables = (chart as ChartDocument<"bms:7K" | "bms:14K">).data.tableFolders; + + const bmsSong = song as SongDocument<"bms">; + + let realTitle = bmsSong.title; + + if (bmsSong.data.subtitle) { + realTitle = `${realTitle} - ${bmsSong.data.subtitle}`; + } + + if (bmsSong.data.genre) { + realTitle = `${realTitle} [${bmsSong.data.genre}]`; + } + + if (tables.length === 0) { + return realTitle; + } + + return `${realTitle} (${tables.map((e) => `${e.table}${e.level}`).join(", ")})`; + } else if (game === "usc") { + const uscChart = chart as ChartDocument<"usc:Controller" | "usc:Keyboard">; + + // If this chart isn't an official, render it differently + if (!uscChart.data.isOfficial) { + // Same as BMS. turn this into SongTitle (Keyboard MXM normal1, insane2) + return `${song.title} (${chart.playtype} ${ + chart.difficulty + } ${uscChart.data.tableFolders.map((e) => `${e.table}${e.level}`).join(", ")})`; + } else if (uscChart.data.tableFolders.length !== 0) { + // if this chart is an official **AND** is on tables (unlikely), render + // it as so: + + // SongTitle (Keyboard MXM 17, normal1, insane2) + return `${song.title} (${chart.playtype} ${chart.difficulty} ${ + chart.level + }, ${uscChart.data.tableFolders.map((e) => `${e.table}${e.level}`).join(", ")})`; + } + + // otherwise, it's just an official and should be rendered like any other game. + } else if (game === "itg") { + const itgChart = chart as ChartDocument<"itg:Stamina">; + const itgSong = song as SongDocument<"itg">; + + return `${itgSong.title}${itgSong.data.subtitle ? ` ${itgSong.data.subtitle}` : ""} ${ + itgChart.data.difficultyTag + } ${chart.level}`; + } + + const gameConfig = GetGameConfig(game); + + let playtypeStr = `${chart.playtype} `; + + if (gameConfig.validPlaytypes.length === 1) { + playtypeStr = ""; + } + + // return the most recent version this chart appeared in if it + // is not primary. + if (!chart.isPrimary) { + return `${song.title} (${playtypeStr}${chart.difficulty} ${chart.level} ${chart.versions[0]})`; + } + + return `${song.title} (${playtypeStr}${chart.difficulty} ${chart.level})`; +} + +export function AbsoluteGradeDelta( + game: Game, + playtype: Playtypes[Game], + score: number, + percent: number, + gradeOrIndex: Grades[I] | integer +): number { + const gptConfig = GetGamePTConfig(game, playtype); + + if (!gptConfig.gradeBoundaries) { + return 0; + } + + const maxScore = Math.round(score * (100 / percent)); + + const gradeIndex = + typeof gradeOrIndex === "number" ? gradeOrIndex : gptConfig.grades.indexOf(gradeOrIndex); + + const gradeScore = Math.ceil((gptConfig.gradeBoundaries[gradeIndex] / 100) * maxScore); + + return score - gradeScore; +} + +export function RelativeGradeDelta( + game: Game, + playtype: Playtypes[Game], + score: number, + percent: number, + grade: Grades[I], + relativeIndex: integer +): { grade: string; delta: number } | null { + const gptConfig = GetGamePTConfig(game, playtype); + + const nextGradeIndex = gptConfig.grades.indexOf(grade) + relativeIndex; + + if (nextGradeIndex < 0 || nextGradeIndex >= gptConfig.grades.length) { + return null; + } + + return { + grade: gptConfig.grades[nextGradeIndex], + delta: AbsoluteGradeDelta(game, playtype, score, percent, nextGradeIndex), + }; +} + +function WrapGrade(grade: string) { + if (grade.endsWith("-") || grade.endsWith("+")) { + return `(${grade})`; + } + + return grade; +} + +export function GenericFormatGradeDelta( + game: Game, + playtype: Playtypes[Game], + score: number, + percent: number, + grade: Grades[I], + formatNumFn: (n: number) => number = (s) => s +): { + lower: string; + upper?: string; + closer: "lower" | "upper"; +} { + const upper = RelativeGradeDelta(game, playtype, score, percent, grade, 1); + const lower = AbsoluteGradeDelta(game, playtype, score, percent, grade); + + const formatLower = `${WrapGrade(grade)}+${formatNumFn(lower)}`; + + if (!upper) { + return { + lower: formatLower, + closer: "lower", + }; + } + + const formatUpper = `${WrapGrade(upper.grade)}${formatNumFn(upper.delta)}`; + + return { + lower: formatLower, + upper: formatUpper, + closer: upper.delta + lower < 0 ? "lower" : "upper", + }; +} + +export function FormatSieglindeBMS(sgl: number): string { + const fixedSgl = sgl.toFixed(2); + + if (sgl < 13) { + return `${fixedSgl} (☆${fixedSgl})`; + } + + return `${fixedSgl} (🟊${(sgl - 12).toFixed(2)})`; +} + +export function FormatSieglindePMS(sgl: number): string { + const fixedSgl = sgl.toFixed(2); + + if (sgl < 13) { + return `${fixedSgl} (○${fixedSgl})`; + } + + return `${fixedSgl} (●${(sgl - 12).toFixed(2)})`; +} diff --git a/common/src/webhooks.ts b/common/src/webhooks.ts new file mode 100644 index 000000000..0ec6b58a0 --- /dev/null +++ b/common/src/webhooks.ts @@ -0,0 +1,68 @@ +import type { Game, Playtypes } from "."; +import type { GameClassSets } from "./game-classes"; +import type { GoalImportStat, IDStrings, integer, MilestoneImportStat } from "./types"; + +/** + * An event fired when a users class improves. + */ +export interface WebhookEventClassUpdateV1 { + type: "class-update/v1"; + content: { + userID: integer; + set: GameClassSets[IDStrings]; + old: integer | null; + new: integer; + game: Game; + playtype: Playtypes[Game]; + }; +} + +/** + * An event fired when a goal is achieved. + */ +export interface WebhookEventGoalAchievedV1 { + type: "goals-achieved/v1"; + content: { + userID: integer; + game: Game; + goals: Array<{ + goalID: string; + old: GoalImportStat; + new: GoalImportStat; + playtype: Playtypes[Game]; + }>; + }; +} + +/** + * An event fired when a milestone is achieved. + */ +export interface WebhookEventMilestoneAchievedV1 { + type: "milestone-achieved/v1"; + content: { + userID: integer; + milestoneID: string; + old: MilestoneImportStat; + new: MilestoneImportStat; + game: Game; + playtype: Playtypes[Game]; + }; +} + +/** + * An event used for debugging. Contains information about the + * registered client and the server. + */ +export interface WebhookEventStatusV1 { + type: "status/v1"; + content: { + clientName: string; + clientID: string; + serverVersion: string; + }; +} + +export type WebhookEvents = + | WebhookEventClassUpdateV1 + | WebhookEventGoalAchievedV1 + | WebhookEventMilestoneAchievedV1; diff --git a/common/tsconfig.json b/common/tsconfig.json new file mode 100644 index 000000000..1a691c5ea --- /dev/null +++ b/common/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "ES6", + "module": "commonjs", + "lib": [ + "ES2019" + ], + "declaration": true, + "outDir": "./js", + "strict": true, + "allowJs": false, + "esModuleInterop": true, + "typeRoots": [ + "@types" + ], + "sourceMap": true + }, + "include": [ + "src/**/*.ts", + "src/**/*.test.ts" + ], + "exclude": [ + "src/legacy/**/*.ts" + ] +} \ No newline at end of file