From a75e18ebbb4988ef58ebec3f572a436e6c525e02 Mon Sep 17 00:00:00 2001 From: Saya Date: Wed, 21 Aug 2024 17:43:14 +0800 Subject: [PATCH] fix: updater --- .eslintrc.yml | 42 -- eslint.config.js | 31 ++ index.js | 4 +- package-lock.json | 674 +++++++++++++++++++----- package.json | 10 +- src/Config.js | 20 +- src/Formidable.js | 375 +++++++------ src/FormidableWorker.js | 98 ++-- src/endpoints/Update.js | 42 +- src/endpoints/Version.js | 8 +- src/endpoints/barrages/Search.js | 20 +- src/endpoints/chapters/Search.js | 16 +- src/endpoints/equipments/Category.js | 20 +- src/endpoints/equipments/Nationality.js | 20 +- src/endpoints/equipments/Search.js | 20 +- src/endpoints/ships/Class.js | 20 +- src/endpoints/ships/Hull.js | 20 +- src/endpoints/ships/Id.js | 14 +- src/endpoints/ships/Nationality.js | 20 +- src/endpoints/ships/Random.js | 6 +- src/endpoints/ships/Rarity.js | 20 +- src/endpoints/ships/Search.js | 20 +- src/endpoints/voicelines/Ship.js | 16 +- src/struct/Cache.js | 64 +-- src/struct/Endpoint.js | 38 +- src/struct/Limiter.js | 98 ++-- src/struct/Ratelimit.js | 10 +- src/struct/Required.js | 16 +- src/struct/Shared.js | 36 +- src/struct/Updater.js | 128 +++-- src/struct/Utils.js | 21 +- 31 files changed, 1196 insertions(+), 751 deletions(-) delete mode 100644 .eslintrc.yml create mode 100644 eslint.config.js diff --git a/.eslintrc.yml b/.eslintrc.yml deleted file mode 100644 index b6387d5..0000000 --- a/.eslintrc.yml +++ /dev/null @@ -1,42 +0,0 @@ -env: - commonjs: true - es6: true - es2021: true - node: true -extends: 'eslint:recommended' -globals: - Atomics: readonly - SharedArrayBuffer: readonly - load: readonly - BigInt: readonly -parserOptions: - ecmaVersion: 2021 -rules: - indent: - - error - - 4 - - SwitchCase: 1 - linebreak-style: - - error - - unix - quotes: - - error - - single - semi: - - error - no-extra-parens: - - warn - - all - array-callback-return: - - warn - prefer-spread: - - warn - no-useless-constructor: - - warn - no-console: - - off - arrow-parens: - - error - - as-needed - no-multi-spaces: - - error diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..0a0d560 --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,31 @@ +const globals = require('globals'); +const pluginJs = require('@eslint/js'); +const stylisticJs = require('@stylistic/eslint-plugin-js'); + +module.exports = [ + { + plugins: { + '@stylistic/js': stylisticJs + }, + languageOptions: { globals: globals.node }, + rules: { + '@stylistic/js/semi': [ 'error' ], + '@stylistic/js/indent': [ 'error', 'tab', { 'SwitchCase': 1 }], + '@stylistic/js/space-infix-ops': [ 'error' ], + '@stylistic/js/key-spacing': [ 'error', { 'mode': 'strict' }], + '@stylistic/js/keyword-spacing': [ 'error' ], + '@stylistic/js/quotes': [ 'error', 'single' ], + '@stylistic/js/comma-dangle': [ 'error', 'never' ], + '@stylistic/js/brace-style': [ 'error', '1tbs' ], + '@stylistic/js/object-curly-spacing': [ 'error', 'always', { 'objectsInObjects': false, 'arraysInObjects': false }], + '@stylistic/js/array-bracket-spacing': [ 'error', 'always', { 'objectsInArrays': false, 'arraysInArrays': false }], + '@stylistic/js/block-spacing': [ 'error', 'always' ], + '@stylistic/js/arrow-spacing': 'error', + '@stylistic/js/switch-colon-spacing': [ 'error', { 'after': true, 'before': false }], + '@stylistic/js/no-multiple-empty-lines': [ 'error', { 'max': 1 }], + '@stylistic/js/eol-last': [ 'warn', 'always' ], + '@stylistic/js/no-trailing-spaces': [ 'warn', { 'ignoreComments': true }] + } + }, + pluginJs.configs.recommended +]; diff --git a/index.js b/index.js index 2d3f734..c6a8c16 100644 --- a/index.js +++ b/index.js @@ -1,5 +1,5 @@ const Formidable = require('./src/Formidable.js'); new Formidable() - .load() - .listen(); \ No newline at end of file + .load() + .listen(); diff --git a/package-lock.json b/package-lock.json index 9b47f38..8953528 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,15 +12,20 @@ "@fastify/compress": "^7.0.3", "@fastify/cors": "^9.0.1", "@fastify/static": "^7.0.4", + "axios": "^1.7.4", "fastify": "^4.28.1", "fs-extra": "^11.2.0", "fuse.js": "^7.0.0", + "node-fetch": "^3.3.2", "pino": "^9.3.2", "piscina": "^4.6.1", "rate-limiter-flexible": "^5.0.3" }, "devDependencies": { - "eslint": "^9.0.0", + "@eslint/js": "^9.8.0", + "@stylistic/eslint-plugin-js": "^2.6.2", + "eslint": "^9.9.0", + "globals": "^15.9.0", "pino-pretty": "^11.0.0" }, "engines": { @@ -64,19 +69,36 @@ } }, "node_modules/@eslint-community/regexpp": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz", - "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==", + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.0.tgz", + "integrity": "sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==", "dev": true, + "license": "MIT", "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, + "node_modules/@eslint/config-array": { + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.17.1.tgz", + "integrity": "sha512-BlYOpej8AQ8Ev9xVqroV7a02JK3SkBAaN9GfMMH9W6Ch8FlQlkjGw4Ir7+FgYwfirivAf4t+GtzuAxqfukmISA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.4", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, "node_modules/@eslint/eslintrc": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.0.2.tgz", - "integrity": "sha512-wV19ZEGEMAC1eHgrS7UQPqsdEiCIbTKTasEfcXAigzoXICcqZSjBZEHlZwNVvKg6UBCjSlos84XiLqsRJnIcIg==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.1.0.tgz", + "integrity": "sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==", "dev": true, + "license": "MIT", "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", @@ -95,11 +117,35 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@eslint/js": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.0.0.tgz", - "integrity": "sha512-RThY/MnKrhubF6+s1JflwUjPEsnCEmYCWwqa/aRISKWNXGZ9epUwft4bUMM35SdKF9xvBrLydAM1RDHd1Z//ZQ==", + "version": "9.9.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.9.0.tgz", + "integrity": "sha512-hhetes6ZHP3BlXLxmd8K2SNgkhNSi+UcecbnwWKwpP7kyi/uC75DJ1lOOBO3xrC4jyojtGE3YxKZPHfk4yrgug==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.4.tgz", + "integrity": "sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==", "dev": true, + "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } @@ -213,20 +259,6 @@ "glob": "^10.3.4" } }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.12.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.12.3.tgz", - "integrity": "sha512-jsNnTBlMWuTpDkeE3on7+dWJi0D6fdDfeANj/w7MpS8ztROCoLvIO2nG0CcFj+E4k8j4QrSTh4Oryi3i2G669g==", - "dev": true, - "dependencies": { - "@humanwhocodes/object-schema": "^2.0.3", - "debug": "^4.3.1", - "minimatch": "^3.0.5" - }, - "engines": { - "node": ">=10.10.0" - } - }, "node_modules/@humanwhocodes/module-importer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", @@ -240,11 +272,19 @@ "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", - "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", - "dev": true + "node_modules/@humanwhocodes/retry": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.0.tgz", + "integrity": "sha512-d2CGZR2o7fS6sWB7DG/3a95bGKQyHMACZ5aW8qGkkqQpUoZV6C0X7Pc7l4ZNMZkfNBf4VWNe9E1jRsf0G146Ew==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } }, "node_modules/@isaacs/cliui": { "version": "8.0.2", @@ -339,6 +379,50 @@ "node": ">=14" } }, + "node_modules/@stylistic/eslint-plugin-js": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin-js/-/eslint-plugin-js-2.6.4.tgz", + "integrity": "sha512-kx1hS3xTvzxZLdr/DCU/dLBE++vcP97sHeEFX2QXhk1Ipa4K1rzPOLw1HCbf4mU3s+7kHP5eYpDe+QteEOFLug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/eslint": "^9.6.0", + "acorn": "^8.12.1", + "eslint-visitor-keys": "^4.0.0", + "espree": "^10.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "peerDependencies": { + "eslint": ">=8.40.0" + } + }, + "node_modules/@types/eslint": { + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.0.tgz", + "integrity": "sha512-gi6WQJ7cHRgZxtkQEoyHMppPjq9Kxo5Tjn2prSKDSmZrCz8TZ3jSRCeTJm+WoM+oB0WG37bRqLzaaU3q7JypGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, "node_modules/abort-controller": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", @@ -356,10 +440,11 @@ "integrity": "sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==" }, "node_modules/acorn": { - "version": "8.11.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", - "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "version": "8.12.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", + "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", "dev": true, + "license": "MIT", "bin": { "acorn": "bin/acorn" }, @@ -372,6 +457,7 @@ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "dev": true, + "license": "MIT", "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } @@ -381,6 +467,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, + "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -459,7 +546,14 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true + "dev": true, + "license": "Python-2.0" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" }, "node_modules/atomic-sleep": { "version": "1.0.0", @@ -480,6 +574,17 @@ "fastq": "^1.17.1" } }, + "node_modules/axios": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.4.tgz", + "integrity": "sha512-DukmaFRnY6AzAALSH4J2M3k6PkaC+MfaAGdEERRWcC9q3/TWQwLpHR8ZRLKTdQ3aBDL64EdluRDjJqKw+BPZEw==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -509,6 +614,7 @@ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -547,6 +653,7 @@ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -589,11 +696,24 @@ "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", "dev": true }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/content-disposition": { "version": "0.5.4", @@ -632,6 +752,15 @@ "node": ">= 8" } }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, "node_modules/dateformat": { "version": "4.6.3", "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz", @@ -663,6 +792,15 @@ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -737,33 +875,34 @@ } }, "node_modules/eslint": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.0.0.tgz", - "integrity": "sha512-IMryZ5SudxzQvuod6rUdIUz29qFItWx281VhtFVc2Psy/ZhlCeD/5DT6lBIJ4H3G+iamGJoTln1v+QSuPw0p7Q==", + "version": "9.9.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.9.0.tgz", + "integrity": "sha512-JfiKJrbx0506OEerjK2Y1QlldtBxkAlLxT5OEcRF8uaQ86noDe2k31Vw9rnSWv+MXZHj7OOUV/dA0AhdLFcyvA==", "dev": true, + "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^3.0.2", - "@eslint/js": "9.0.0", - "@humanwhocodes/config-array": "^0.12.3", + "@eslint-community/regexpp": "^4.11.0", + "@eslint/config-array": "^0.17.1", + "@eslint/eslintrc": "^3.1.0", + "@eslint/js": "9.9.0", "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.3.0", "@nodelib/fs.walk": "^1.2.8", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.0.1", + "eslint-scope": "^8.0.2", "eslint-visitor-keys": "^4.0.0", - "espree": "^10.0.1", - "esquery": "^1.4.2", + "espree": "^10.1.0", + "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", - "graphemer": "^1.4.0", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", @@ -784,14 +923,23 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } } }, "node_modules/eslint-scope": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.0.1.tgz", - "integrity": "sha512-pL8XjgP4ZOmmwfFE8mEhSxA7ZY4C+LWyqjQ3o4yWkkmD0qcMT9kkW3zWHOczhWcjTSgqycYAgwSlXvZltv65og==", + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.0.2.tgz", + "integrity": "sha512-6E4xmrTw5wtxnLA5wYL3WDfhZ/1bUBGOXV0zQvVRDOtrR8D0p6W7fs3JweNYhwRYeGvd/1CKX2se0/2s7Q/nJA==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" @@ -816,12 +964,13 @@ } }, "node_modules/espree": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.0.1.tgz", - "integrity": "sha512-MWkrWZbJsL2UwnjxTX3gG8FneachS/Mwg7tdGXce011sJd5b0JG54vat5KHnfSBODZ3Wvzd2WnjxyzsRoVv+ww==", + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.1.0.tgz", + "integrity": "sha512-M1M6CpiE6ffoigIOWYO9UDP8TMUw9kqb21tf+08IgDYjCsOvCuDt4jQcZmoYxx+w7zlKw9/N0KXfto+I8/FrXA==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "acorn": "^8.11.3", + "acorn": "^8.12.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^4.0.0" }, @@ -849,6 +998,7 @@ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "estraverse": "^5.2.0" }, @@ -915,7 +1065,8 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/fast-json-stringify": { "version": "5.14.1", @@ -1048,6 +1199,29 @@ "reusify": "^1.0.4" } }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, "node_modules/file-entry-cache": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", @@ -1108,6 +1282,26 @@ "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", "dev": true }, + "node_modules/follow-redirects": { + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, "node_modules/foreground-child": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", @@ -1123,6 +1317,32 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "license": "MIT", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -1208,10 +1428,11 @@ } }, "node_modules/globals": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", - "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "version": "15.9.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.9.0.tgz", + "integrity": "sha512-SmSKyLLKFbSr6rptvP8izbyxJL4ILwqO9Jg23UA0sDlGlu58V59D1//I3vlc0KJphVdUR7vMjHIplYnzBxorQA==", "dev": true, + "license": "MIT", "engines": { "node": ">=18" }, @@ -1224,12 +1445,6 @@ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" }, - "node_modules/graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true - }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -1280,10 +1495,11 @@ ] }, "node_modules/ignore": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", - "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true, + "license": "MIT", "engines": { "node": ">= 4" } @@ -1293,6 +1509,7 @@ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", "dev": true, + "license": "MIT", "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" @@ -1405,6 +1622,7 @@ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "dev": true, + "license": "MIT", "dependencies": { "argparse": "^2.0.1" }, @@ -1430,7 +1648,8 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", @@ -1529,11 +1748,24 @@ "node": ">= 0.6" } }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, + "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -1597,6 +1829,43 @@ "integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==", "optional": true }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "license": "MIT", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, "node_modules/node-gyp-build": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.6.0.tgz", @@ -1681,6 +1950,7 @@ "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "dev": true, + "license": "MIT", "dependencies": { "callsites": "^3.0.0" }, @@ -1856,6 +2126,12 @@ "node": ">= 0.10" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, "node_modules/pump": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", @@ -1984,6 +2260,7 @@ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true, + "license": "MIT", "engines": { "node": ">=4" } @@ -2381,6 +2658,15 @@ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -2538,15 +2824,26 @@ } }, "@eslint-community/regexpp": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz", - "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==", + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.0.tgz", + "integrity": "sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==", "dev": true }, + "@eslint/config-array": { + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.17.1.tgz", + "integrity": "sha512-BlYOpej8AQ8Ev9xVqroV7a02JK3SkBAaN9GfMMH9W6Ch8FlQlkjGw4Ir7+FgYwfirivAf4t+GtzuAxqfukmISA==", + "dev": true, + "requires": { + "@eslint/object-schema": "^2.1.4", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + } + }, "@eslint/eslintrc": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.0.2.tgz", - "integrity": "sha512-wV19ZEGEMAC1eHgrS7UQPqsdEiCIbTKTasEfcXAigzoXICcqZSjBZEHlZwNVvKg6UBCjSlos84XiLqsRJnIcIg==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.1.0.tgz", + "integrity": "sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==", "dev": true, "requires": { "ajv": "^6.12.4", @@ -2558,12 +2855,26 @@ "js-yaml": "^4.1.0", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" + }, + "dependencies": { + "globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true + } } }, "@eslint/js": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.0.0.tgz", - "integrity": "sha512-RThY/MnKrhubF6+s1JflwUjPEsnCEmYCWwqa/aRISKWNXGZ9epUwft4bUMM35SdKF9xvBrLydAM1RDHd1Z//ZQ==", + "version": "9.9.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.9.0.tgz", + "integrity": "sha512-hhetes6ZHP3BlXLxmd8K2SNgkhNSi+UcecbnwWKwpP7kyi/uC75DJ1lOOBO3xrC4jyojtGE3YxKZPHfk4yrgug==", + "dev": true + }, + "@eslint/object-schema": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.4.tgz", + "integrity": "sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==", "dev": true }, "@fastify/accept-negotiator": { @@ -2669,27 +2980,16 @@ "glob": "^10.3.4" } }, - "@humanwhocodes/config-array": { - "version": "0.12.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.12.3.tgz", - "integrity": "sha512-jsNnTBlMWuTpDkeE3on7+dWJi0D6fdDfeANj/w7MpS8ztROCoLvIO2nG0CcFj+E4k8j4QrSTh4Oryi3i2G669g==", - "dev": true, - "requires": { - "@humanwhocodes/object-schema": "^2.0.3", - "debug": "^4.3.1", - "minimatch": "^3.0.5" - } - }, "@humanwhocodes/module-importer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", "dev": true }, - "@humanwhocodes/object-schema": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", - "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "@humanwhocodes/retry": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.0.tgz", + "integrity": "sha512-d2CGZR2o7fS6sWB7DG/3a95bGKQyHMACZ5aW8qGkkqQpUoZV6C0X7Pc7l4ZNMZkfNBf4VWNe9E1jRsf0G146Ew==", "dev": true }, "@isaacs/cliui": { @@ -2757,6 +3057,40 @@ "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", "optional": true }, + "@stylistic/eslint-plugin-js": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin-js/-/eslint-plugin-js-2.6.4.tgz", + "integrity": "sha512-kx1hS3xTvzxZLdr/DCU/dLBE++vcP97sHeEFX2QXhk1Ipa4K1rzPOLw1HCbf4mU3s+7kHP5eYpDe+QteEOFLug==", + "dev": true, + "requires": { + "@types/eslint": "^9.6.0", + "acorn": "^8.12.1", + "eslint-visitor-keys": "^4.0.0", + "espree": "^10.1.0" + } + }, + "@types/eslint": { + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.0.tgz", + "integrity": "sha512-gi6WQJ7cHRgZxtkQEoyHMppPjq9Kxo5Tjn2prSKDSmZrCz8TZ3jSRCeTJm+WoM+oB0WG37bRqLzaaU3q7JypGg==", + "dev": true, + "requires": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "dev": true + }, + "@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true + }, "abort-controller": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", @@ -2771,9 +3105,9 @@ "integrity": "sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==" }, "acorn": { - "version": "8.11.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", - "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "version": "8.12.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", + "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", "dev": true }, "acorn-jsx": { @@ -2845,6 +3179,11 @@ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, "atomic-sleep": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", @@ -2861,6 +3200,16 @@ "fastq": "^1.17.1" } }, + "axios": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.4.tgz", + "integrity": "sha512-DukmaFRnY6AzAALSH4J2M3k6PkaC+MfaAGdEERRWcC9q3/TWQwLpHR8ZRLKTdQ3aBDL64EdluRDjJqKw+BPZEw==", + "requires": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, "balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -2930,6 +3279,14 @@ "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", "dev": true }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "requires": { + "delayed-stream": "~1.0.0" + } + }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -2964,6 +3321,11 @@ "which": "^2.0.1" } }, + "data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==" + }, "dateformat": { "version": "4.6.3", "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz", @@ -2984,6 +3346,11 @@ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" + }, "depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -3051,33 +3418,33 @@ "dev": true }, "eslint": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.0.0.tgz", - "integrity": "sha512-IMryZ5SudxzQvuod6rUdIUz29qFItWx281VhtFVc2Psy/ZhlCeD/5DT6lBIJ4H3G+iamGJoTln1v+QSuPw0p7Q==", + "version": "9.9.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.9.0.tgz", + "integrity": "sha512-JfiKJrbx0506OEerjK2Y1QlldtBxkAlLxT5OEcRF8uaQ86noDe2k31Vw9rnSWv+MXZHj7OOUV/dA0AhdLFcyvA==", "dev": true, "requires": { "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^3.0.2", - "@eslint/js": "9.0.0", - "@humanwhocodes/config-array": "^0.12.3", + "@eslint-community/regexpp": "^4.11.0", + "@eslint/config-array": "^0.17.1", + "@eslint/eslintrc": "^3.1.0", + "@eslint/js": "9.9.0", "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.3.0", "@nodelib/fs.walk": "^1.2.8", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.0.1", + "eslint-scope": "^8.0.2", "eslint-visitor-keys": "^4.0.0", - "espree": "^10.0.1", - "esquery": "^1.4.2", + "espree": "^10.1.0", + "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", - "graphemer": "^1.4.0", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", @@ -3093,9 +3460,9 @@ } }, "eslint-scope": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.0.1.tgz", - "integrity": "sha512-pL8XjgP4ZOmmwfFE8mEhSxA7ZY4C+LWyqjQ3o4yWkkmD0qcMT9kkW3zWHOczhWcjTSgqycYAgwSlXvZltv65og==", + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.0.2.tgz", + "integrity": "sha512-6E4xmrTw5wtxnLA5wYL3WDfhZ/1bUBGOXV0zQvVRDOtrR8D0p6W7fs3JweNYhwRYeGvd/1CKX2se0/2s7Q/nJA==", "dev": true, "requires": { "esrecurse": "^4.3.0", @@ -3109,12 +3476,12 @@ "dev": true }, "espree": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.0.1.tgz", - "integrity": "sha512-MWkrWZbJsL2UwnjxTX3gG8FneachS/Mwg7tdGXce011sJd5b0JG54vat5KHnfSBODZ3Wvzd2WnjxyzsRoVv+ww==", + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.1.0.tgz", + "integrity": "sha512-M1M6CpiE6ffoigIOWYO9UDP8TMUw9kqb21tf+08IgDYjCsOvCuDt4jQcZmoYxx+w7zlKw9/N0KXfto+I8/FrXA==", "dev": true, "requires": { - "acorn": "^8.11.3", + "acorn": "^8.12.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^4.0.0" } @@ -3292,6 +3659,15 @@ "reusify": "^1.0.4" } }, + "fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "requires": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + } + }, "file-entry-cache": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", @@ -3337,6 +3713,11 @@ "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", "dev": true }, + "follow-redirects": { + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==" + }, "foreground-child": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", @@ -3346,6 +3727,24 @@ "signal-exit": "^4.0.1" } }, + "form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + }, + "formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "requires": { + "fetch-blob": "^3.1.2" + } + }, "forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -3406,9 +3805,9 @@ } }, "globals": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", - "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "version": "15.9.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.9.0.tgz", + "integrity": "sha512-SmSKyLLKFbSr6rptvP8izbyxJL4ILwqO9Jg23UA0sDlGlu58V59D1//I3vlc0KJphVdUR7vMjHIplYnzBxorQA==", "dev": true }, "graceful-fs": { @@ -3416,12 +3815,6 @@ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" }, - "graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true - }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -3452,9 +3845,9 @@ "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" }, "ignore": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", - "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true }, "import-fresh": { @@ -3637,6 +4030,14 @@ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" }, + "mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "requires": { + "mime-db": "1.52.0" + } + }, "minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -3692,6 +4093,21 @@ "integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==", "optional": true }, + "node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==" + }, + "node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "requires": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + } + }, "node-gyp-build": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.6.0.tgz", @@ -3894,6 +4310,11 @@ "ipaddr.js": "1.9.1" } }, + "proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, "pump": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", @@ -4281,6 +4702,11 @@ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, + "web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==" + }, "which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/package.json b/package.json index f2f3ebf..4aaf29d 100644 --- a/package.json +++ b/package.json @@ -13,9 +13,10 @@ "node": ">=14.0.0" }, "dependencies": { - "@fastify/static": "^7.0.4", - "@fastify/cors": "^9.0.1", "@fastify/compress": "^7.0.3", + "@fastify/cors": "^9.0.1", + "@fastify/static": "^7.0.4", + "axios": "^1.7.4", "fastify": "^4.28.1", "fs-extra": "^11.2.0", "fuse.js": "^7.0.0", @@ -24,7 +25,10 @@ "rate-limiter-flexible": "^5.0.3" }, "devDependencies": { - "eslint": "^9.0.0", + "@eslint/js": "^9.8.0", + "@stylistic/eslint-plugin-js": "^2.6.2", + "eslint": "^9.9.0", + "globals": "^15.9.0", "pino-pretty": "^11.0.0" } } diff --git a/src/Config.js b/src/Config.js index 4db2cbd..a1bd3a4 100644 --- a/src/Config.js +++ b/src/Config.js @@ -6,13 +6,13 @@ const maxResults = !isNaN(Number(process.env.MAX_RESULTS)) ? Number(process.env. const autoUpdateInterval = !isNaN(Number(process.env.AUTO_UPDATE_INTERVAL)) ? Number(process.env.AUTO_UPDATE_INTERVAL) : 10; module.exports = { - level: process.env.LEVEL ?? 'info', - host: process.env.HOST ?? '0.0.0.0', - auth: process.env.AUTH, - port, - distance, - threads, - maxQueue, - maxResults, - autoUpdateInterval -}; \ No newline at end of file + level: process.env.LEVEL ?? 'info', + host: process.env.HOST ?? '0.0.0.0', + auth: process.env.AUTH, + port, + distance, + threads, + maxQueue, + maxResults, + autoUpdateInterval +}; diff --git a/src/Formidable.js b/src/Formidable.js index 06d0f91..fb8368c 100644 --- a/src/Formidable.js +++ b/src/Formidable.js @@ -12,186 +12,211 @@ const Piscina = require('piscina'); const Path = require('path'); const Limiter = require('./struct/Limiter.js'); +const Updater = require('./struct/Updater.js'); const Config = require('./Config.js'); const { abortTimeout } = require('./struct/Utils.js'); class Formidable { - constructor() { - this.logger = new Pino( - { name: `Formidable [${process.pid}]`, level: Config.level }, - Pino.destination({ sync: false }) - ); - this.pool = new Piscina({ - filename: `${__dirname}/FormidableWorker.js`, - idleTimeout: 30000, - minThreads: 0, - maxThreads: Config.threads === 'auto' ? Os.cpus().length : Config.threads, - maxQueue: Config.maxQueue - }); - this.endpoints = []; - this.pool.on('error', error => this.logger.error(error)); - this.logger.info(`[Server] Using ${this.pool.maxThreads} workers`); - this.server = Server(); - this.server.addContentTypeParser('*', (_, body, done) => done(null, body)); - this.server - .register(Static, { root: Path.join(__dirname, '..', 'site') }) - .register(Cors) - .register(Compress); - } - - async handle(command, endpoint, request, reply) { - try { - let body = request.query; - if (!Config.auth && command.locked) { - this.logger.warn(`[Server] ${endpoint} requires an auth, but user didn't set "auth" on config or AUTH in process enviroment variables`); - } - if (Config.auth && command.locked && Config.auth !== request.headers?.authorization) { - reply.code(401); - return 'Unauthorized'; - } - const queueSize = this.pool.queueSize; - if (queueSize >= this.pool.options.maxQueue) { - reply.code(503); - return { message: `Worker queue is full. Has ${queueSize} pending request(s). Please try again later` }; - } - if (command.method === 'POST') { - const contentType = request.headers['content-type']; - if (!contentType?.includes('application/json')) { - reply.code(415); - reply.type('application/json'); - return { message: `This method only accepts 'application/json', received: ${contentType}` }; - } - body = request.body; - } - const controller = new AbortController(); - const timeout = setTimeout(() => controller.abort(), abortTimeout).unref(); - const result = await this.pool - .run({ endpoint, body }, { signal: controller.signal }) - .finally(() => clearTimeout(timeout)); - if (!result.ok) { - reply.code(400); - if (command.method === 'POST') - return { message: 'Required body not found. Body must be application/json, and must contain (refer to required):', required: result.required || null }; - else - return { message: 'Required query not found. Querystring must contain (refer to required):', required: result.required || null }; - } - this.logger.debug(`[Endpoints] Endpoint Executed: ${endpoint} | Took ${result.time}ms`); - reply.type(command.type); - return Buffer.from(result.data); - } catch (error) { - reply.type('application/json'); - this.logger.error(error); - reply.code(500); - return { error: error.message }; - } - } - - sendStats(_, reply) { - const utilization = this.pool.utilization; - const status = { - completed: this.pool.completed, - utilization: utilization > 100 ? 100 : Math.round(this.pool.utilization), - memoryRss: process.memoryUsage().rss, - runTime: this.pool.runTime.average, - waitime: this.pool.waitTime.average, - queueSize: this.pool.queueSize, - uptime: Math.floor(process.uptime()) - }; - reply.type('application/json'); - reply.send(status); - } - - sendEndpoints(_, reply) { - reply.type('application/json'); - reply.send(this.endpoints); - } - - load() { - // Load global ratelimit - const global = new Limiter(this).global(); - this.logger.info(`[Ratelimits] Global: ${global.options.points} reqs / ${global.options.duration}s`); - // Load 404 ratelimit - const notFound = new Limiter(this, { points: 5, duration: 5 }).notFound(); - this.logger.info(`[Ratelimits] 404s: ${notFound.options.points} reqs / ${notFound.options.duration}s`); - // Load endpoints and their specific ratelimits - const routes = readdirSync('./src/endpoints', { withFileTypes: true }); - for (const path of routes) { - // 1st level - if (path.isFile() && path.name.endsWith('.js')) { - if (path.name === 'Landing.js') continue; - const endpoint = `/${path.name.split('.').shift().toLowerCase()}`; - const command = new (require(`./endpoints/${path.name}`))(); - const ratelimit = new Limiter(this, command.ratelimit); - ratelimit.route({ - method: command.method, - url: endpoint, - handler: (...args) => this.handle(command, endpoint, ...args) - }); - this.endpoints.push(endpoint); - this.logger.info(`[Endpoints] Endpoint Loaded: ${endpoint} | [Ratelimits] ${ratelimit.options.points} reqs / ${ratelimit.options.duration}s`); - } - else if (path.isDirectory()) { - // 2nd level - const endpoints = readdirSync(`./src/endpoints/${path.name}`, { withFileTypes: true }); - for (const point of endpoints) { - if (point.isDirectory()) { - // 3rd level - const _endpoints = readdirSync(`./src/endpoints/${path.name}/${point.name}`, { withFileTypes: true }); - for (const _point of _endpoints) { - if (!_point.isFile() || !_point.name.endsWith('.js')) continue; - const endpoint = `/${path.name}/${point.name}/${_point.name.split('.').shift().toLowerCase()}`; - const command = new (require(`./endpoints/${path.name}/${point.name}/${_point.name}`))(); - const ratelimit = new Limiter(this, command.ratelimit); - ratelimit.route({ - method: command.method, - url: endpoint, - handler: (...args) => this.handle(command, endpoint, ...args) - }); - this.endpoints.push(endpoint); - this.logger.info(`[Endpoints] Endpoint Loaded: ${endpoint} | [Ratelimits] ${ratelimit.options.points} reqs / ${ratelimit.options.duration}s`); - } - } - else if (point.isFile() && point.name.endsWith('.js')){ - const endpoint = `/${path.name}/${point.name.split('.').shift().toLowerCase()}`; - const command = new (require(`./endpoints/${path.name}/${point.name}`))(); - const ratelimit = new Limiter(this, command.ratelimit); - ratelimit.route({ - method: command.method, - url: endpoint, - handler: (...args) => this.handle(command, endpoint, ...args) - }); - this.endpoints.push(endpoint); - this.logger.info(`[Endpoints] Endpoint Loaded: ${endpoint} | [Ratelimits] ${ratelimit.options.points} reqs / ${ratelimit.options.duration}s`); - } - } - } - } - // custom endpoints that dont need workers - const stats = new Limiter(this); - stats.route({ - method: 'GET', - url: '/stats', - handler: (...args) => this.sendStats(...args) - }); - this.logger.info(`[Endpoints] Endpoint Loaded: /stats | [Ratelimits] ${stats.options.points} reqs / ${stats.options.duration}s`); - const endpoints = new Limiter(this); - endpoints.route({ - method: 'GET', - url: '/endpoints', - handler: (...args) => this.sendEndpoints(...args) - }); - this.logger.info(`[Endpoints] Endpoint Loaded: /endpoints | [Ratelimits] ${stats.options.points} reqs / ${stats.options.duration}s`); - this.logger.info('[Endpoints] Done loading endpoints!'); - return this; - } - - async listen() { - if (!Config.port) this.logger.warn('[Server] User didn\'t set a port, will use a random open port'); - if (!Config.auth) this.logger.warn('[Server] User didn\'t set an auth, locked endpoints will execute without authorization'); - const address = await this.server.listen({ host: Config.host, port: Config.port }); - this.logger.info(`[Server] Server Loaded, Listening at ${address}`); - } + constructor() { + this.logger = new Pino( + { name: `Formidable [${process.pid}]`, level: Config.level }, + Pino.destination({ sync: false }) + ); + this.pool = new Piscina({ + filename: `${__dirname}/FormidableWorker.js`, + idleTimeout: 30000, + minThreads: 0, + maxThreads: Config.threads === 'auto' ? Os.cpus().length : Config.threads, + maxQueue: Config.maxQueue + }); + this.endpoints = []; + this.pool.on('error', error => this.logger.error(error)); + this.logger.info(`[Server] Using ${this.pool.maxThreads} workers`); + this.server = Server(); + this.server.addContentTypeParser('*', (_, body, done) => done(null, body)); + this.server + .register(Static, { root: Path.join(__dirname, '..', 'site') }) + .register(Cors) + .register(Compress); + } + + async update() { + await Updater.Create(); + const outdated = await Updater.Check(); + if (!outdated.length) return; + await Updater.Update(outdated); + } + + async handle(command, endpoint, request, reply) { + console.log(endpoint); + try { + let body = request.query; + if (!Config.auth && command.locked) { + this.logger.warn(`[Server] ${endpoint} requires an auth, but user didn't set "auth" on config or AUTH in process enviroment variables`); + } + if (Config.auth && command.locked && Config.auth !== request.headers?.authorization) { + reply.code(401); + return 'Unauthorized'; + } + const queueSize = this.pool.queueSize; + if (queueSize >= this.pool.options.maxQueue) { + reply.code(503); + return { message: `Worker queue is full. Has ${queueSize} pending request(s). Please try again later` }; + } + if (command.method === 'POST') { + const contentType = request.headers['content-type']; + if (!contentType?.includes('application/json')) { + reply.code(415); + reply.type('application/json'); + return { message: `This method only accepts 'application/json', received: ${contentType}` }; + } + body = request.body; + } + const controller = new AbortController(); + const timeout = setTimeout(() => controller.abort(), abortTimeout).unref(); + const result = await this.pool + .run({ endpoint, body }, { signal: controller.signal }) + .finally(() => clearTimeout(timeout)); + if (!result.ok) { + reply.code(400); + if (command.method === 'POST') + return { message: 'Required body not found. Body must be application/json, and must contain (refer to required):', required: result.required || null }; + else + return { message: 'Required query not found. Querystring must contain (refer to required):', required: result.required || null }; + } + this.logger.debug(`[Endpoints] Endpoint Executed: ${endpoint} | Took ${result.time}ms`); + reply.type(command.type); + return Buffer.from(result.data); + } catch (error) { + reply.type('application/json'); + this.logger.error(error); + reply.code(500); + return { error: error.message }; + } + } + + sendStats(_, reply) { + const utilization = this.pool.utilization; + const status = { + completed: this.pool.completed, + utilization: utilization > 100 ? 100 : Math.round(this.pool.utilization), + memoryRss: process.memoryUsage().rss, + runTime: this.pool.runTime.average, + waitime: this.pool.waitTime.average, + queueSize: this.pool.queueSize, + uptime: Math.floor(process.uptime()) + }; + reply.type('application/json'); + reply.send(status); + } + + sendEndpoints(_, reply) { + reply.type('application/json'); + reply.send(this.endpoints); + } + + load() { + // Load global ratelimit + const global = new Limiter(this).global(); + this.logger.info(`[Ratelimits] Global: ${global.options.points} reqs / ${global.options.duration}s`); + // Load 404 ratelimit + const notFound = new Limiter(this, { points: 5, duration: 5 }).notFound(); + this.logger.info(`[Ratelimits] 404s: ${notFound.options.points} reqs / ${notFound.options.duration}s`); + // Load endpoints and their specific ratelimits + const routes = readdirSync('./src/endpoints', { withFileTypes: true }); + for (const path of routes) { + // 1st level + if (path.isFile() && path.name.endsWith('.js')) { + if (path.name === 'Landing.js') continue; + const endpoint = `/${path.name.split('.').shift().toLowerCase()}`; + const command = new (require(`./endpoints/${path.name}`))(); + const ratelimit = new Limiter(this, command.ratelimit); + ratelimit.route({ + method: command.method, + url: endpoint, + handler: (...args) => this.handle(command, endpoint, ...args) + }); + this.endpoints.push(endpoint); + this.logger.info(`[Endpoints] Endpoint Loaded: ${endpoint} | [Ratelimits] ${ratelimit.options.points} reqs / ${ratelimit.options.duration}s`); + } else if (path.isDirectory()) { + // 2nd level + const endpoints = readdirSync(`./src/endpoints/${path.name}`, { withFileTypes: true }); + for (const point of endpoints) { + if (point.isDirectory()) { + // 3rd level + const _endpoints = readdirSync(`./src/endpoints/${path.name}/${point.name}`, { withFileTypes: true }); + for (const _point of _endpoints) { + if (!_point.isFile() || !_point.name.endsWith('.js')) continue; + const endpoint = `/${path.name}/${point.name}/${_point.name.split('.').shift().toLowerCase()}`; + const command = new (require(`./endpoints/${path.name}/${point.name}/${_point.name}`))(); + const ratelimit = new Limiter(this, command.ratelimit); + ratelimit.route({ + method: command.method, + url: endpoint, + handler: (...args) => this.handle(command, endpoint, ...args) + }); + this.endpoints.push(endpoint); + this.logger.info(`[Endpoints] Endpoint Loaded: ${endpoint} | [Ratelimits] ${ratelimit.options.points} reqs / ${ratelimit.options.duration}s`); + } + } else if (point.isFile() && point.name.endsWith('.js')){ + const endpoint = `/${path.name}/${point.name.split('.').shift().toLowerCase()}`; + const command = new (require(`./endpoints/${path.name}/${point.name}`))(); + const ratelimit = new Limiter(this, command.ratelimit); + ratelimit.route({ + method: command.method, + url: endpoint, + handler: (...args) => this.handle(command, endpoint, ...args) + }); + this.endpoints.push(endpoint); + this.logger.info(`[Endpoints] Endpoint Loaded: ${endpoint} | [Ratelimits] ${ratelimit.options.points} reqs / ${ratelimit.options.duration}s`); + } + } + } + } + // custom endpoints that dont need workers + const stats = new Limiter(this); + stats.route({ + method: 'GET', + url: '/stats', + handler: (...args) => this.sendStats(...args) + }); + this.logger.info(`[Endpoints] Endpoint Loaded: /stats | [Ratelimits] ${stats.options.points} reqs / ${stats.options.duration}s`); + const endpoints = new Limiter(this); + endpoints.route({ + method: 'GET', + url: '/endpoints', + handler: (...args) => this.sendEndpoints(...args) + }); + this.logger.info(`[Endpoints] Endpoint Loaded: /endpoints | [Ratelimits] ${stats.options.points} reqs / ${stats.options.duration}s`); + this.logger.info('[Endpoints] Done loading endpoints!'); + return this; + } + + async listen() { + if (!Config.port) this.logger.warn('[Server] User didn\'t set a port, will use a random open port'); + if (!Config.auth) this.logger.warn('[Server] User didn\'t set an auth, locked endpoints will execute without authorization'); + + this.logger.info('[Server] Checking for latest updates...'); + try { + await this.update(); + this.logger.info('[Server] Update done'); + } catch (error) { + this.logger.error(error); + } + + setInterval(() => { + try { + this.update(); + } catch (error) { + this.logger.error(error); + } + }, Config.autoUpdateInterval * 60000); + this.logger.info(`[Server] Checking for updates every ${Config.autoUpdateInterval} min(s)`); + + const address = await this.server.listen({ host: Config.host, port: Config.port }); + this.logger.info(`[Server] Server Loaded, Listening at ${address}`); + } } module.exports = Formidable; diff --git a/src/FormidableWorker.js b/src/FormidableWorker.js index 9568f6a..281bcac 100644 --- a/src/FormidableWorker.js +++ b/src/FormidableWorker.js @@ -4,9 +4,9 @@ const Shared = require('./struct/Shared.js'); const Cache = require('./struct/Cache.js'); const isIncomplete = (required, input) => { - const array = Array.from(required.entries()); - /* eslint no-extra-parens: */ - return array.some(([key, req]) => req.type !== (input[key])?.constructor.name.toLowerCase() || req.type === 'array' && req.count !== input[key]?.length); + const array = Array.from(required.entries()); + /* eslint no-extra-parens: */ + return array.some(([ key, req ]) => req.type !== (input[key])?.constructor.name.toLowerCase() || req.type === 'array' && req.count !== input[key]?.length); }; // load global cache const cache = new Cache(); @@ -14,59 +14,57 @@ const cache = new Cache(); const handlers = new Map(); const routes = readdirSync('./src/endpoints', { withFileTypes: true }); for (const path of routes) { - if (path.isFile() && path.name.endsWith('.js')) { - if (path.name === 'Landing.js') continue; - const endpoint = `/${path.name.split('.').shift().toLowerCase()}`; - const command = new (require(`./endpoints/${path.name}`))(cache); - handlers.set(endpoint, command); - } - else if (path.isDirectory()) { - const endpoints = readdirSync(`./src/endpoints/${path.name}`, { withFileTypes: true }); - for (const point of endpoints) { - if (point.isDirectory()) { - const _endpoints = readdirSync(`./src/endpoints/${path.name}/${point.name}`, { withFileTypes: true }); - for (const _point of _endpoints) { - if (!_point.isFile() || !_point.name.endsWith('.js')) continue; - const endpoint = `/${path.name}/${point.name}/${_point.name.split('.').shift().toLowerCase()}`; - const command = new (require(`./endpoints/${path.name}/${point.name}/${_point.name}`))(cache); - handlers.set(endpoint, command); - } - } - else if (point.isFile() && point.name.endsWith('.js')) { - const endpoint = `/${path.name}/${point.name.split('.').shift().toLowerCase()}`; - const command = new (require(`./endpoints/${path.name}/${point.name}`))(cache); - handlers.set(endpoint, command); - } - } - } + if (path.isFile() && path.name.endsWith('.js')) { + if (path.name === 'Landing.js') continue; + const endpoint = `/${path.name.split('.').shift().toLowerCase()}`; + const command = new (require(`./endpoints/${path.name}`))(cache); + handlers.set(endpoint, command); + } else if (path.isDirectory()) { + const endpoints = readdirSync(`./src/endpoints/${path.name}`, { withFileTypes: true }); + for (const point of endpoints) { + if (point.isDirectory()) { + const _endpoints = readdirSync(`./src/endpoints/${path.name}/${point.name}`, { withFileTypes: true }); + for (const _point of _endpoints) { + if (!_point.isFile() || !_point.name.endsWith('.js')) continue; + const endpoint = `/${path.name}/${point.name}/${_point.name.split('.').shift().toLowerCase()}`; + const command = new (require(`./endpoints/${path.name}/${point.name}/${_point.name}`))(cache); + handlers.set(endpoint, command); + } + } else if (point.isFile() && point.name.endsWith('.js')) { + const endpoint = `/${path.name}/${point.name.split('.').shift().toLowerCase()}`; + const command = new (require(`./endpoints/${path.name}/${point.name}`))(cache); + handlers.set(endpoint, command); + } + } + } } // Handler const handle = async ({ endpoint, body }) => { - const handler = handlers.get(endpoint); - if (!handler) - throw new Error('Endpoint don\'t exist'); - const start = performance.now(); - if (handler.required.size && isIncomplete(handler.required, body)) - return { ok: false, required: handler.toString() }; - const result = await handler.run(body); - let data; - switch (handler.type) { - case 'application/json': - data = JSON.stringify(result, null, 4); - break; - case 'text/plain': - data = result; - break; - default: - throw new Error('Unknown type'); - } - const end = performance.now(); - return new Shared({ ok: true, time: Math.round(end - start), data }).notMovable(); + const handler = handlers.get(endpoint); + if (!handler) + throw new Error('Endpoint don\'t exist'); + const start = performance.now(); + if (handler.required.size && isIncomplete(handler.required, body)) + return { ok: false, required: handler.toString() }; + const result = await handler.run(body); + let data; + switch (handler.type) { + case 'application/json': + data = JSON.stringify(result, null, 4); + break; + case 'text/plain': + data = result; + break; + default: + throw new Error('Unknown type'); + } + const end = performance.now(); + return new Shared({ ok: true, time: Math.round(end - start), data }).notMovable(); }; // Load worker module const start = async () => { - await Promise.all([...handlers.values()].map(handler => handler.load())); - return handle; + await Promise.all([ ...handlers.values() ].map(handler => handler.load())); + return handle; }; module.exports = start(); diff --git a/src/endpoints/Update.js b/src/endpoints/Update.js index 8280e91..4ac1ed5 100644 --- a/src/endpoints/Update.js +++ b/src/endpoints/Update.js @@ -1,29 +1,29 @@ -const { Create, Check, Update: UpdateData } = require('../struct/Updater.js'); +const Updater = require('../struct/Updater.js'); const Endpoint = require('../struct/Endpoint.js'); const Ratelimit = require('../struct/Ratelimit.js'); class Update extends Endpoint { - constructor(...args) { - super(...args); - this.ratelimit = new Ratelimit(1, 120); - this.method = 'POST'; - this.locked = true; - this.mimeType = 'text/plain'; - } + constructor(...args) { + super(...args); + this.ratelimit = new Ratelimit(1, 120); + this.method = 'POST'; + this.locked = true; + this.mimeType = 'text/plain'; + } - async run() { - Create(); - const outdated = await Check(); - if (!outdated.length) { - const msg = 'Data is up to date!'; - this.info(msg); - return msg; - } - await UpdateData(outdated); - const msg = 'Not up to date, data updated!'; - this.info(msg); - return msg; - } + async run() { + await Updater.Create(); + const outdated = await Updater.Check(); + if (!outdated.length) { + const msg = 'Data is up to date!'; + this.info(msg); + return msg; + } + await Updater.Update(outdated); + const msg = 'Not up to date, data updated!'; + this.info(msg); + return msg; + } } module.exports = Update; diff --git a/src/endpoints/Version.js b/src/endpoints/Version.js index cc5f99c..8a86f47 100644 --- a/src/endpoints/Version.js +++ b/src/endpoints/Version.js @@ -1,9 +1,9 @@ const Endpoint = require('../struct/Endpoint.js'); class Update extends Endpoint { - run() { - return this.cache.data.version; - } + run() { + return this.cache.data.version; + } } -module.exports = Update; \ No newline at end of file +module.exports = Update; diff --git a/src/endpoints/barrages/Search.js b/src/endpoints/barrages/Search.js index 67e47fa..5fd150a 100644 --- a/src/endpoints/barrages/Search.js +++ b/src/endpoints/barrages/Search.js @@ -3,17 +3,17 @@ const Ratelimit = require('../../struct/Ratelimit.js'); const Required = require('../../struct/Required.js'); class Search extends Endpoint { - constructor(...args) { - super(...args); - this.ratelimit = new Ratelimit(25, 5); - this.required.set('name', new Required('string')); - } + constructor(...args) { + super(...args); + this.ratelimit = new Ratelimit(25, 5); + this.required.set('name', new Required('string')); + } - run(query) { - const results = this.cache.fuse.barrages.search(query.name); - if (results.length > this.maxResults) results.length = this.maxResults; - return results.map(res => res.item); - } + run(query) { + const results = this.cache.fuse.barrages.search(query.name); + if (results.length > this.maxResults) results.length = this.maxResults; + return results.map(res => res.item); + } } module.exports = Search; diff --git a/src/endpoints/chapters/Search.js b/src/endpoints/chapters/Search.js index b3b4b77..81a4428 100644 --- a/src/endpoints/chapters/Search.js +++ b/src/endpoints/chapters/Search.js @@ -3,15 +3,15 @@ const Ratelimit = require('../../struct/Ratelimit.js'); const Required = require('../../struct/Required.js'); class Search extends Endpoint { - constructor(...args) { - super(...args); - this.ratelimit = new Ratelimit(25, 5); - this.required.set('name', new Required('string')); - } + constructor(...args) { + super(...args); + this.ratelimit = new Ratelimit(25, 5); + this.required.set('name', new Required('string')); + } - run(query) { - return this.cache.fuse.chapters.search(query.name).shift()?.item; - } + run(query) { + return this.cache.fuse.chapters.search(query.name).shift()?.item; + } } module.exports = Search; diff --git a/src/endpoints/equipments/Category.js b/src/endpoints/equipments/Category.js index 674aa85..48bf0b0 100644 --- a/src/endpoints/equipments/Category.js +++ b/src/endpoints/equipments/Category.js @@ -2,16 +2,16 @@ const Endpoint = require('../../struct/Endpoint.js'); const Required = require('../../struct/Required.js'); class Category extends Endpoint { - constructor(...args) { - super(...args); - this.required.set('name', new Required('string')); - } + constructor(...args) { + super(...args); + this.required.set('name', new Required('string')); + } - run(query) { - return this.cache.data.equipments.filter( - equip => equip.category?.toLowerCase() === query.name.toLowerCase() - ); - } + run(query) { + return this.cache.data.equipments.filter( + equip => equip.category?.toLowerCase() === query.name.toLowerCase() + ); + } } -module.exports = Category; \ No newline at end of file +module.exports = Category; diff --git a/src/endpoints/equipments/Nationality.js b/src/endpoints/equipments/Nationality.js index 6c81d8f..46e12eb 100644 --- a/src/endpoints/equipments/Nationality.js +++ b/src/endpoints/equipments/Nationality.js @@ -2,16 +2,16 @@ const Endpoint = require('../../struct/Endpoint.js'); const Required = require('../../struct/Required.js'); class Nationality extends Endpoint { - constructor(...args) { - super(...args); - this.required.set('name', new Required('string')); - } + constructor(...args) { + super(...args); + this.required.set('name', new Required('string')); + } - run(query) { - return this.cache.data.equipments.filter( - equip => equip.nationality?.toLowerCase() === query.name.toLowerCase() - ); - } + run(query) { + return this.cache.data.equipments.filter( + equip => equip.nationality?.toLowerCase() === query.name.toLowerCase() + ); + } } -module.exports = Nationality; \ No newline at end of file +module.exports = Nationality; diff --git a/src/endpoints/equipments/Search.js b/src/endpoints/equipments/Search.js index 2d21cd3..a4157f7 100644 --- a/src/endpoints/equipments/Search.js +++ b/src/endpoints/equipments/Search.js @@ -3,17 +3,17 @@ const Ratelimit = require('../../struct/Ratelimit.js'); const Required = require('../../struct/Required.js'); class Search extends Endpoint { - constructor(...args) { - super(...args); - this.ratelimit = new Ratelimit(25, 5); - this.required.set('name', new Required('string')); - } + constructor(...args) { + super(...args); + this.ratelimit = new Ratelimit(25, 5); + this.required.set('name', new Required('string')); + } - run(query) { - const results = this.cache.fuse.equipments.search(query.name); - if (results.length > this.maxResults) results.length = this.maxResults; - return results.map(res => res.item); - } + run(query) { + const results = this.cache.fuse.equipments.search(query.name); + if (results.length > this.maxResults) results.length = this.maxResults; + return results.map(res => res.item); + } } module.exports = Search; diff --git a/src/endpoints/ships/Class.js b/src/endpoints/ships/Class.js index 00ec6ed..69d4655 100644 --- a/src/endpoints/ships/Class.js +++ b/src/endpoints/ships/Class.js @@ -2,16 +2,16 @@ const Endpoint = require('../../struct/Endpoint.js'); const Required = require('../../struct/Required.js'); class Class extends Endpoint { - constructor(...args) { - super(...args); - this.required.set('name', new Required('string')); - } + constructor(...args) { + super(...args); + this.required.set('name', new Required('string')); + } - run(query) { - return this.cache.data.ships.filter( - ship => ship.class?.toLowerCase() === query.name.toLowerCase() - ); - } + run(query) { + return this.cache.data.ships.filter( + ship => ship.class?.toLowerCase() === query.name.toLowerCase() + ); + } } -module.exports = Class; \ No newline at end of file +module.exports = Class; diff --git a/src/endpoints/ships/Hull.js b/src/endpoints/ships/Hull.js index 1f9adbf..d10a106 100644 --- a/src/endpoints/ships/Hull.js +++ b/src/endpoints/ships/Hull.js @@ -2,16 +2,16 @@ const Endpoint = require('../../struct/Endpoint.js'); const Required = require('../../struct/Required.js'); class Class extends Endpoint { - constructor(...args) { - super(...args); - this.required.set('name', new Required('string')); - } + constructor(...args) { + super(...args); + this.required.set('name', new Required('string')); + } - run(query) { - return this.cache.data.ships.filter( - ship => ship.hullType?.toLowerCase() === query.name.toLowerCase() - ); - } + run(query) { + return this.cache.data.ships.filter( + ship => ship.hullType?.toLowerCase() === query.name.toLowerCase() + ); + } } -module.exports = Class; \ No newline at end of file +module.exports = Class; diff --git a/src/endpoints/ships/Id.js b/src/endpoints/ships/Id.js index 7f9fa1e..73f8e7a 100644 --- a/src/endpoints/ships/Id.js +++ b/src/endpoints/ships/Id.js @@ -2,14 +2,14 @@ const Endpoint = require('../../struct/Endpoint.js'); const Required = require('../../struct/Required.js'); class Id extends Endpoint { - constructor(...args) { - super(...args); - this.required.set('code', new Required('string')); - } + constructor(...args) { + super(...args); + this.required.set('code', new Required('string')); + } - run(query) { - return this.cache.data.ships.find(ship => ship.id === query.code); - } + run(query) { + return this.cache.data.ships.find(ship => ship.id === query.code); + } } module.exports = Id; diff --git a/src/endpoints/ships/Nationality.js b/src/endpoints/ships/Nationality.js index 7039d0a..4c475d0 100644 --- a/src/endpoints/ships/Nationality.js +++ b/src/endpoints/ships/Nationality.js @@ -2,16 +2,16 @@ const Endpoint = require('../../struct/Endpoint.js'); const Required = require('../../struct/Required.js'); class Nationality extends Endpoint { - constructor(...args) { - super(...args); - this.required.set('name', new Required('string')); - } + constructor(...args) { + super(...args); + this.required.set('name', new Required('string')); + } - run(query) { - return this.cache.data.ships.filter( - ship => ship.nationality?.toLowerCase() === query.name.toLowerCase() - ); - } + run(query) { + return this.cache.data.ships.filter( + ship => ship.nationality?.toLowerCase() === query.name.toLowerCase() + ); + } } -module.exports = Nationality; \ No newline at end of file +module.exports = Nationality; diff --git a/src/endpoints/ships/Random.js b/src/endpoints/ships/Random.js index e9fd62f..dbc2bdb 100644 --- a/src/endpoints/ships/Random.js +++ b/src/endpoints/ships/Random.js @@ -1,9 +1,9 @@ const Endpoint = require('../../struct/Endpoint.js'); class Random extends Endpoint { - run() { - return this.cache.data.ships[Math.random() * this.cache.data.ships.length | 0]; - } + run() { + return this.cache.data.ships[Math.random() * this.cache.data.ships.length | 0]; + } } module.exports = Random; diff --git a/src/endpoints/ships/Rarity.js b/src/endpoints/ships/Rarity.js index a4f35ae..53eabb2 100644 --- a/src/endpoints/ships/Rarity.js +++ b/src/endpoints/ships/Rarity.js @@ -2,16 +2,16 @@ const Endpoint = require('../../struct/Endpoint.js'); const Required = require('../../struct/Required.js'); class Rarity extends Endpoint { - constructor(...args) { - super(...args); - this.required.set('name', new Required('string')); - } + constructor(...args) { + super(...args); + this.required.set('name', new Required('string')); + } - run(query) { - return this.cache.data.ships.filter( - ship => ship.rarity?.toLowerCase() === query.name.toLowerCase() - ); - } + run(query) { + return this.cache.data.ships.filter( + ship => ship.rarity?.toLowerCase() === query.name.toLowerCase() + ); + } } -module.exports = Rarity; \ No newline at end of file +module.exports = Rarity; diff --git a/src/endpoints/ships/Search.js b/src/endpoints/ships/Search.js index 7b06f4d..0a26eeb 100644 --- a/src/endpoints/ships/Search.js +++ b/src/endpoints/ships/Search.js @@ -3,17 +3,17 @@ const Ratelimit = require('../../struct/Ratelimit.js'); const Required = require('../../struct/Required.js'); class Search extends Endpoint { - constructor(...args) { - super(...args); - this.ratelimit = new Ratelimit(25, 5); - this.required.set('name', new Required('string')); - } + constructor(...args) { + super(...args); + this.ratelimit = new Ratelimit(25, 5); + this.required.set('name', new Required('string')); + } - run(query) { - const results = this.cache.fuse.ships.search(query.name); - if (results.length > this.maxResults) results.length = this.maxResults; - return results.map(res => res.item); - } + run(query) { + const results = this.cache.fuse.ships.search(query.name); + if (results.length > this.maxResults) results.length = this.maxResults; + return results.map(res => res.item); + } } module.exports = Search; diff --git a/src/endpoints/voicelines/Ship.js b/src/endpoints/voicelines/Ship.js index e957938..e6032f7 100644 --- a/src/endpoints/voicelines/Ship.js +++ b/src/endpoints/voicelines/Ship.js @@ -2,14 +2,14 @@ const Endpoint = require('../../struct/Endpoint.js'); const Required = require('../../struct/Required.js'); class Ship extends Endpoint { - constructor(...args) { - super(...args); - this.required.set('id', new Required('string')); - } + constructor(...args) { + super(...args); + this.required.set('id', new Required('string')); + } - run(query) { - return this.cache.data.voicelines[query.id] || null; - } + run(query) { + return this.cache.data.voicelines[query.id] || null; + } } -module.exports = Ship; \ No newline at end of file +module.exports = Ship; diff --git a/src/struct/Cache.js b/src/struct/Cache.js index 8c48736..ce0d2a2 100644 --- a/src/struct/Cache.js +++ b/src/struct/Cache.js @@ -7,38 +7,40 @@ const Config = require('../Config.js'); const Fuse = require('fuse.js'); class Cache { - constructor() { - this.data = {}; - this.fuse = {}; - this.listening = false; - for (const [ key, file ] of Object.entries(FILES)) this.update(key, `${DIRECTORY}/${file}`); - this.monitor(); - } - - get version() { - return this.data[FILES.VERSION.toLowerCase()]; - } + constructor() { + this.data = {}; + this.fuse = {}; + this.listening = false; + for (const [ key, file ] of Object.entries(FILES)) this.update(key, `${DIRECTORY}/${file}`); + this.monitor(); + } - update(key, directory) { - // makes sure data exists - Create(); - // read data - const data = readJSONSync(directory); - // do not update internal data if local files is empty - if (!Object.keys(data || {}).length) return; - this.data[key.toLowerCase()] = data; - const keys = SEARCH[key]; - if (!keys) return; - const dist = Config.distance; - this.fuse[key.toLowerCase()] = new Fuse(this.data[key.toLowerCase()], { keys, distance: dist }); - } + get version() { + return this.data[FILES.VERSION.toLowerCase()]; + } - monitor() { - if (this.listening) return; - for (const [ key, file ] of Object.entries(FILES)) - watchFile(`${DIRECTORY}/${file}`, () => this.update(key, `${DIRECTORY}/${file}`)); - this.listening = true; - } + update(key, directory) { + Create() + .then(() => { + // read data + const data = readJSONSync(directory); + // do not update internal data if local files is empty + if (!Object.keys(data || {}).length) return; + this.data[key.toLowerCase()] = data; + const keys = SEARCH[key]; + if (!keys) return; + const dist = Config.distance; + this.fuse[key.toLowerCase()] = new Fuse(this.data[key.toLowerCase()], { keys, distance: dist }); + }) + .catch(() => null); + } + + monitor() { + if (this.listening) return; + for (const [ key, file ] of Object.entries(FILES)) + watchFile(`${DIRECTORY}/${file}`, () => this.update(key, `${DIRECTORY}/${file}`)); + this.listening = true; + } } -module.exports = Cache; \ No newline at end of file +module.exports = Cache; diff --git a/src/struct/Endpoint.js b/src/struct/Endpoint.js index fb88d38..2e79496 100644 --- a/src/struct/Endpoint.js +++ b/src/struct/Endpoint.js @@ -1,28 +1,28 @@ const Ratelimit = require('./Ratelimit.js'); class Endpoint { - constructor(cache) { - this.cache = cache; - this.method = 'GET'; - this.type = 'application/json'; - this.locked = false; - this.ratelimit = new Ratelimit(); - this.required = new Map(); - } + constructor(cache) { + this.cache = cache; + this.method = 'GET'; + this.type = 'application/json'; + this.locked = false; + this.ratelimit = new Ratelimit(); + this.required = new Map(); + } - load() { - return Promise.resolve(); - } + load() { + return Promise.resolve(); + } - run() { - throw new Error('Must be implemented'); - } + run() { + throw new Error('Must be implemented'); + } - toString() { - const object = {}; - for (const [key, value] of this.required.entries()) object[key] = value.toString(); - return object; - } + toString() { + const object = {}; + for (const [ key, value ] of this.required.entries()) object[key] = value.toString(); + return object; + } } module.exports = Endpoint; diff --git a/src/struct/Limiter.js b/src/struct/Limiter.js index e83a24d..204985b 100644 --- a/src/struct/Limiter.js +++ b/src/struct/Limiter.js @@ -1,54 +1,54 @@ const { RateLimiterMemory } = require('rate-limiter-flexible'); class Limiter { - // Default global ratelimit, 50 req / 5s - constructor(nagato, options = { points: 50, duration: 5 }) { - this.nagato = nagato; - this.options = options; - this.manager = new RateLimiterMemory(options); - } - - get server() { - return this.nagato.server; - } - - createHeaders(RateLimiterResponse) { - return { - 'Retry-After': RateLimiterResponse.msBeforeNext / 1000, - 'X-RateLimit-Limit': this.options.points, - 'X-RateLimit-Remaining': RateLimiterResponse.remainingPoints, - 'X-RateLimit-Reset': (Date.now() + RateLimiterResponse.msBeforeNext) / 1000 - }; - } - - async execute(request, reply) { - try { - const RateLimiterResponse = await this.manager.consume(request.ip); - reply.headers(this.createHeaders(RateLimiterResponse)); - } catch (RateLimiterResponse) { - reply.headers(this.createHeaders(RateLimiterResponse)); - reply.type('application/json'); - reply.code(429); - throw { message: 'You are being ratelimited!' }; - } - } - - global() { - this.server.addHook('onRequest', (...args) => this.execute(...args)); - return this; - } - - route(options) { - if (!options) throw new Error('Options not supplied'); - options.onRequest = (...args) => this.execute(...args); - this.server.route(options); - return this; - } - - notFound() { - this.server.setNotFoundHandler({ onRequest: (...args) => this.execute(...args) }); - return this; - } + // Default global ratelimit, 50 req / 5s + constructor(nagato, options = { points: 50, duration: 5 }) { + this.nagato = nagato; + this.options = options; + this.manager = new RateLimiterMemory(options); + } + + get server() { + return this.nagato.server; + } + + createHeaders(RateLimiterResponse) { + return { + 'Retry-After': RateLimiterResponse.msBeforeNext / 1000, + 'X-RateLimit-Limit': this.options.points, + 'X-RateLimit-Remaining': RateLimiterResponse.remainingPoints, + 'X-RateLimit-Reset': (Date.now() + RateLimiterResponse.msBeforeNext) / 1000 + }; + } + + async execute(request, reply) { + try { + const RateLimiterResponse = await this.manager.consume(request.ip); + reply.headers(this.createHeaders(RateLimiterResponse)); + } catch (RateLimiterResponse) { + reply.headers(this.createHeaders(RateLimiterResponse)); + reply.type('application/json'); + reply.code(429); + throw { message: 'You are being ratelimited!' }; + } + } + + global() { + this.server.addHook('onRequest', (...args) => this.execute(...args)); + return this; + } + + route(options) { + if (!options) throw new Error('Options not supplied'); + options.onRequest = (...args) => this.execute(...args); + this.server.route(options); + return this; + } + + notFound() { + this.server.setNotFoundHandler({ onRequest: (...args) => this.execute(...args) }); + return this; + } } -module.exports = Limiter; \ No newline at end of file +module.exports = Limiter; diff --git a/src/struct/Ratelimit.js b/src/struct/Ratelimit.js index 48dad5b..2c32234 100644 --- a/src/struct/Ratelimit.js +++ b/src/struct/Ratelimit.js @@ -1,8 +1,8 @@ class Ratelimit { - constructor(points, duration) { - this.points = points || 50; - this.duration = duration || 5; - } + constructor(points, duration) { + this.points = points || 50; + this.duration = duration || 5; + } } -module.exports = Ratelimit; \ No newline at end of file +module.exports = Ratelimit; diff --git a/src/struct/Required.js b/src/struct/Required.js index bd5495e..c0f68cd 100644 --- a/src/struct/Required.js +++ b/src/struct/Required.js @@ -1,12 +1,12 @@ class Required { - constructor(type, count = 1) { - this.type = type || 'string'; - this.count = type === 'array' ? count : null; - } + constructor(type, count = 1) { + this.type = type || 'string'; + this.count = type === 'array' ? count : null; + } - toString() { - return this.type === 'array' ? `Array(${this.count})`: this.type[0].toUpperCase() + this.type.substring(1); - } + toString() { + return this.type === 'array' ? `Array(${this.count})` : this.type[0].toUpperCase() + this.type.substring(1); + } } -module.exports = Required; \ No newline at end of file +module.exports = Required; diff --git a/src/struct/Shared.js b/src/struct/Shared.js index 26b9df8..2b4e3d2 100644 --- a/src/struct/Shared.js +++ b/src/struct/Shared.js @@ -1,27 +1,27 @@ const Piscina = require('piscina'); class Shared { - constructor({ ok, time, data } = {}) { - this.ok = ok; - this.time = time; - this.data = data; - } + constructor({ ok, time, data } = {}) { + this.ok = ok; + this.time = time; + this.data = data; + } - get [Piscina.transferableSymbol]() { - return [this.data]; - } + get [Piscina.transferableSymbol]() { + return [ this.data ]; + } - get [Piscina.valueSymbol]() { - return { ok: this.ok, time: this.time, data: this.data }; - } + get [Piscina.valueSymbol]() { + return { ok: this.ok, time: this.time, data: this.data }; + } - movable() { - return Piscina.move(this); - } + movable() { + return Piscina.move(this); + } - notMovable() { - return { ok: this.ok, time: this.time, data: this.data }; - } + notMovable() { + return { ok: this.ok, time: this.time, data: this.data }; + } } -module.exports = Shared; \ No newline at end of file +module.exports = Shared; diff --git a/src/struct/Updater.js b/src/struct/Updater.js index e1fa851..969948c 100644 --- a/src/struct/Updater.js +++ b/src/struct/Updater.js @@ -1,74 +1,94 @@ -const { writeJSONSync, readJSONSync, existsSync, mkdirSync } = require('fs-extra'); -const { fetch } = require('../struct/Utils.js'); +const { writeJSON, readJSON, exists, mkdir } = require('fs-extra'); +const Axios = require('axios'); // constants const DIRECTORY = './.data'; + const FILES = { - SHIPS: 'Ships.json', - EQUIPMENTS: 'Equips.json', - CHAPTERS: 'Chapters.json', - BARRAGES: 'Barrages.json', - VOICELINES: 'Voice.json', - VERSION: 'Version.json' + SHIPS: 'Ships.json', + EQUIPMENTS: 'Equips.json', + CHAPTERS: 'Chapters.json', + BARRAGES: 'Barrages.json', + VOICELINES: 'Voice.json', + VERSION: 'Version.json' }; + const SEARCH = { - SHIPS: ['names.en', 'names.jp', 'names.cn', 'names.kr', 'id'], - EQUIPMENTS: ['names.en', 'names.jp', 'names.cn', 'names.kr', 'id'], - CHAPTERS: ['names.en', 'names.jp', 'names.cn', 'normal.code'], - BARRAGES: ['ships', 'name'] + SHIPS: [ 'names.en', 'names.jp', 'names.cn', 'names.kr', 'id' ], + EQUIPMENTS: [ 'names.en', 'names.jp', 'names.cn', 'names.kr', 'id' ], + CHAPTERS: [ 'names.en', 'names.jp', 'names.cn', 'normal.code' ], + BARRAGES: [ 'ships', 'name' ] }; + // idk what to use here tbh, kumo pls help const URLS = { - SHIPS: 'https://raw.githubusercontent.com/AzurAPI/azurapi-js-setup/master/dist/ships.json', - EQUIPMENTS: 'https://raw.githubusercontent.com/AzurAPI/azurapi-js-setup/master/dist/equipments.json', - CHAPTERS: 'https://raw.githubusercontent.com/AzurAPI/azurapi-js-setup/master/dist/chapters.json', - BARRAGES: 'https://raw.githubusercontent.com/AzurAPI/azurapi-js-setup/master/dist/barrage.json', - VOICELINES: 'https://raw.githubusercontent.com/AzurAPI/azurapi-js-setup/master/voice_lines.json', - VERSION: 'https://raw.githubusercontent.com/AzurAPI/azurapi-js-setup/master/dist/version.json' + SHIPS: 'https://raw.githubusercontent.com/AzurAPI/azurapi-js-setup/master/dist/ships.json', + EQUIPMENTS: 'https://raw.githubusercontent.com/AzurAPI/azurapi-js-setup/master/dist/equipments.json', + CHAPTERS: 'https://raw.githubusercontent.com/AzurAPI/azurapi-js-setup/master/dist/chapters.json', + BARRAGES: 'https://raw.githubusercontent.com/AzurAPI/azurapi-js-setup/master/dist/barrage.json', + VOICELINES: 'https://raw.githubusercontent.com/AzurAPI/azurapi-js-setup/master/voice_lines.json', + VERSION: 'https://raw.githubusercontent.com/AzurAPI/azurapi-js-setup/master/dist/version.json' }; -// non exported functions const Download = async key => { - const data = await fetch(URLS[key]); - const json = JSON.parse(data); - return { - key, - json - }; + const response = await Axios.get(URLS[key]); + return { + key, + json: response.data + }; }; -const ForceUpdate = () => Object.values(FILES).some(file => !Object.keys(readJSONSync(`${DIRECTORY}/${file}`)).length); -// exported functions -const Create = () => { - if (!existsSync(DIRECTORY)) mkdirSync(DIRECTORY); - for (const file of Object.values(FILES)) { - if (existsSync(`${DIRECTORY}/${file}`)) continue; - writeJSONSync(`${DIRECTORY}/${file}`, {}); - } + +const ForceUpdate = async () => { + const results = await Promise.all(Object.values(FILES).map(async (file) => { + const keys = await readJSON(`${DIRECTORY}/${file}`); + return Object.keys(keys).length === 0; + })); + return results.some(Boolean); +}; + +const Create = async () => { + const directoryExists = await exists(DIRECTORY); + if (!directoryExists) { + await mkdir(DIRECTORY); + } + await Promise.all(Object.values(FILES).map(async (file) => { + const fileExists = await exists(`${DIRECTORY}/${file}`); + if (fileExists) return; + await writeJSON(`${DIRECTORY}/${file}`, {}); + })); }; + const Check = async () => { - if (ForceUpdate()) return Object.keys(FILES); - const remote = JSON.parse(await fetch(URLS.VERSION)); - const local = readJSONSync(`${DIRECTORY}/${FILES.VERSION}`); - const outdated = []; - for (const [ key, value ] of Object.entries(remote)) { - if (local[key]['version-number'] === value['version-number']) continue; - outdated.push(key.toUpperCase()); - } - if (outdated.length) outdated.push('VERSION'); - return outdated; + const shouldForceUpdate = await ForceUpdate(); + if (shouldForceUpdate) return Object.keys(FILES); + + /** @type string */ + const raw = await fetch(URLS.VERSION); + const remote = JSON.parse(raw); + const local = await readJSON(`${DIRECTORY}/${FILES.VERSION}`); + + const outdated = []; + for (const [ key, value ] of Object.entries(remote)) { + if (local[key]['version-number'] === value['version-number']) continue; + outdated.push(key.toUpperCase()); + } + if (outdated.length) outdated.push('VERSION'); + + return outdated; }; + const Update = async outdated => { - const data = await Promise.all(outdated.map(Download)); - for (const { key, json } of data) { - if (key === 'CHAPTERS') { - const modified = []; - for (const value of json.map(data => Object.values(data))) - for (const data of value) modified.push(data); - writeJSONSync(`${DIRECTORY}/${FILES[key]}`, modified, { spaces: 2 }); - continue; - } - writeJSONSync(`${DIRECTORY}/${FILES[key]}`, json, { spaces: 2 }); - } + const downloaded = await Promise.all(outdated.map(Download)); + await Promise.all(downloaded.map(async ({ key, json }) => { + let data = json; + if (key === 'CHAPTERS') { + data = []; + for (const values of json.map(data => Object.values(data))) { + data.push(...values); + } + } + await writeJSON(`${DIRECTORY}/${FILES[key]}`, data, { spaces: 2 }); + })); }; // export diff --git a/src/struct/Utils.js b/src/struct/Utils.js index 7b592e3..d966f6f 100644 --- a/src/struct/Utils.js +++ b/src/struct/Utils.js @@ -1,22 +1,3 @@ -const Https = require('https'); - module.exports = { - abortTimeout: 10000, - fetch: (url, options) => new Promise((resolve, reject) => { - const request = Https.request(url, options, response => { - const chunks = []; - response.on('data', chunk => chunks.push(chunk)); - response.on('error', reject); - response.on('end', () => { - const code = response.statusCode ?? 500; - const body = chunks.join(''); - if (code >= 200 && code <= 299) - resolve(body); - else - reject(new Error(`Response received is not ok, Status Code: ${response.statusCode}, body: ${body}`)); - }); - }); - request.on('error', reject); - request.end(); - }) + abortTimeout: 10000 };