From 536b227cdbf158fea94776e93bb894fdd7a8de98 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Tue, 28 Feb 2023 20:23:51 -0500 Subject: [PATCH 001/219] Update .gitignore --- .gitignore | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index ce5d611..bd3afc3 100644 --- a/.gitignore +++ b/.gitignore @@ -58,10 +58,7 @@ typings/ .env # custom -sign.js -t.js -p.js config.json certs -/cdn -dump.rdb \ No newline at end of file +cdn +dist \ No newline at end of file From b655fec99a0ccc60e239287d2a73e07aa1a24b0e Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Tue, 28 Feb 2023 20:25:27 -0500 Subject: [PATCH 002/219] Move logger to src folder --- src/config-manager.js | 2 +- src/database.js | 2 +- logger.js => src/logger.js | 0 src/server.js | 2 +- src/services/api/index.js | 2 +- src/services/api/routes/v1/register.js | 2 +- src/services/assets/index.js | 2 +- src/services/conntest/index.js | 2 +- src/services/datastore/index.js | 2 +- src/services/local-cdn/index.js | 2 +- src/services/nasc/index.js | 2 +- src/services/nnid/index.js | 2 +- src/services/nnid/routes/people.js | 2 +- 13 files changed, 12 insertions(+), 12 deletions(-) rename logger.js => src/logger.js (100%) diff --git a/src/config-manager.js b/src/config-manager.js index 6376355..954f2a4 100644 --- a/src/config-manager.js +++ b/src/config-manager.js @@ -1,7 +1,7 @@ const fs = require('fs-extra'); const get = require('lodash.get'); const set = require('lodash.set'); -const logger = require('../logger'); +const logger = require('./logger'); require('dotenv').config(); diff --git a/src/database.js b/src/database.js index bfce6ef..209b5ef 100644 --- a/src/database.js +++ b/src/database.js @@ -4,7 +4,7 @@ const joi = require('joi'); const util = require('./util'); const { PNID } = require('./models/pnid'); const { Server } = require('./models/server'); -const logger = require('../logger'); +const logger = require('./logger'); const { config } = require('./config-manager'); const { connection_string, options } = config.mongoose; diff --git a/logger.js b/src/logger.js similarity index 100% rename from logger.js rename to src/logger.js diff --git a/src/server.js b/src/server.js index 9372a3a..a0b86cd 100644 --- a/src/server.js +++ b/src/server.js @@ -10,7 +10,7 @@ const xmlparser = require('./middleware/xml-parser'); const cache = require('./cache'); const database = require('./database'); const util = require('./util'); -const logger = require('../logger'); +const logger = require('./logger'); const { config } = configManager; diff --git a/src/services/api/index.js b/src/services/api/index.js index 8db69f5..5aee4a6 100644 --- a/src/services/api/index.js +++ b/src/services/api/index.js @@ -4,7 +4,7 @@ const express = require('express'); const subdomain = require('express-subdomain'); const cors = require('cors'); const APIMiddleware = require('../../middleware/api'); -const logger = require('../../../logger'); +const logger = require('../../logger'); const routes = require('./routes'); // Router to handle the subdomain restriction diff --git a/src/services/api/routes/v1/register.js b/src/services/api/routes/v1/register.js index 729bc41..860fad4 100644 --- a/src/services/api/routes/v1/register.js +++ b/src/services/api/routes/v1/register.js @@ -11,7 +11,7 @@ const { NEXAccount } = require('../../../../models/nex-account'); const database = require('../../../../database'); const cache = require('../../../../cache'); const util = require('../../../../util'); -const logger = require('../../../../../logger'); +const logger = require('../../../../logger'); const { config, disabledFeatures } = require('../../../../config-manager'); const PNID_VALID_CHARACTERS_REGEX = /^[\w\-\.]*$/gm; diff --git a/src/services/assets/index.js b/src/services/assets/index.js index 2059739..5061bf3 100644 --- a/src/services/assets/index.js +++ b/src/services/assets/index.js @@ -2,7 +2,7 @@ const express = require('express'); const subdomain = require('express-subdomain'); -const logger = require('../../../logger'); +const logger = require('../../logger'); const path = require('path'); // Router to handle the subdomain restriction diff --git a/src/services/conntest/index.js b/src/services/conntest/index.js index ebe2515..bd4b823 100644 --- a/src/services/conntest/index.js +++ b/src/services/conntest/index.js @@ -2,7 +2,7 @@ const express = require('express'); const subdomain = require('express-subdomain'); -const logger = require('../../../logger'); +const logger = require('../../logger'); // Router to handle the subdomain restriction const conntest = express.Router(); diff --git a/src/services/datastore/index.js b/src/services/datastore/index.js index ab263d2..a5803d1 100644 --- a/src/services/datastore/index.js +++ b/src/services/datastore/index.js @@ -1,6 +1,6 @@ const express = require('express'); const subdomain = require('express-subdomain'); -const logger = require('../../../logger'); +const logger = require('../../logger'); const routes = require('./routes'); // Router to handle the subdomain diff --git a/src/services/local-cdn/index.js b/src/services/local-cdn/index.js index b0e3985..ca8b894 100644 --- a/src/services/local-cdn/index.js +++ b/src/services/local-cdn/index.js @@ -1,6 +1,6 @@ const express = require('express'); const subdomain = require('express-subdomain'); -const logger = require('../../../logger'); +const logger = require('../../logger'); const { config, disabledFeatures } = require('../../config-manager'); if (!disabledFeatures.s3) { diff --git a/src/services/nasc/index.js b/src/services/nasc/index.js index 9f4f9f3..9266a81 100644 --- a/src/services/nasc/index.js +++ b/src/services/nasc/index.js @@ -3,7 +3,7 @@ const express = require('express'); const subdomain = require('express-subdomain'); const NASCMiddleware = require('../../middleware/nasc'); -const logger = require('../../../logger'); +const logger = require('../../logger'); const routes = require('./routes'); // Router to handle the subdomain restriction diff --git a/src/services/nnid/index.js b/src/services/nnid/index.js index 84f1d71..39334b6 100644 --- a/src/services/nnid/index.js +++ b/src/services/nnid/index.js @@ -5,7 +5,7 @@ const subdomain = require('express-subdomain'); const clientHeaderCheck = require('../../middleware/client-header'); const cemuMiddleware = require('../../middleware/cemu'); const pnidMiddleware = require('../../middleware/pnid'); -const logger = require('../../../logger'); +const logger = require('../../logger'); const routes = require('./routes'); // Router to handle the subdomain restriction diff --git a/src/services/nnid/routes/people.js b/src/services/nnid/routes/people.js index 7d1287a..6460877 100644 --- a/src/services/nnid/routes/people.js +++ b/src/services/nnid/routes/people.js @@ -9,7 +9,7 @@ const deviceCertificateMiddleware = require('../../../middleware/device-certific const ratelimit = require('../../../middleware/ratelimit'); const database = require('../../../database'); const util = require('../../../util'); -const logger = require('../../../../logger'); +const logger = require('../../../logger'); require('moment-timezone'); /** From e1649503908a2e16092e1d3eae3f02b869632d0a Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Tue, 28 Feb 2023 20:27:59 -0500 Subject: [PATCH 003/219] Make logger path configurable --- src/config-manager.js | 1 + src/logger.js | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/config-manager.js b/src/config-manager.js index 954f2a4..83e9b45 100644 --- a/src/config-manager.js +++ b/src/config-manager.js @@ -72,6 +72,7 @@ function configure() { logger.info('Loading config from environment variable'); config = { + logger_path: process.env.PN_ACT_LOGGER_PATH, http: { port: Number(process.env.PN_ACT_CONFIG_HTTP_PORT) }, diff --git a/src/logger.js b/src/logger.js index 07215de..3f61fb6 100644 --- a/src/logger.js +++ b/src/logger.js @@ -1,7 +1,9 @@ const fs = require('fs-extra'); require('colors'); -const root = __dirname; +const { config } = require('./config-manager'); + +const root = config.logger_path ? config.logger_path : `${__dirname}/..`; fs.ensureDirSync(`${root}/logs`); const streams = { From 6787361ee414188337495d89459a0512511d6327 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Tue, 28 Feb 2023 20:44:01 -0500 Subject: [PATCH 004/219] Remove unused modules --- package-lock.json | 448 +--------------------------------------------- package.json | 3 - 2 files changed, 2 insertions(+), 449 deletions(-) diff --git a/package-lock.json b/package-lock.json index 79df13e..63f9f0b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "account", - "version": "1.0.0", + "version": "2.0.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "account", - "version": "1.0.0", + "version": "2.0.0", "license": "ISC", "dependencies": { "aws-sdk": "^2.978.0", @@ -17,10 +17,8 @@ "dotenv": "^16.0.3", "email-validator": "^2.0.4", "express": "^4.17.1", - "express-form-data": "^2.0.17", "express-rate-limit": "^5.3.0", "express-subdomain": "^1.0.5", - "formidable": "^1.2.2", "fs-extra": "^8.1.0", "got": "^11.8.2", "hcaptcha": "^0.1.0", @@ -35,7 +33,6 @@ "mongoose": "^5.8.3", "mongoose-unique-validator": "^2.0.3", "morgan": "^1.9.1", - "multer": "^1.4.3", "nodemailer": "^6.4.2", "redis": "^4.3.1", "tga": "^1.0.4", @@ -454,11 +451,6 @@ "node": ">=8" } }, - "node_modules/append-field": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", - "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==" - }, "node_modules/aproba": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", @@ -547,14 +539,6 @@ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, - "node_modules/at-least-node": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", - "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", - "engines": { - "node": ">= 4.0.0" - } - }, "node_modules/atob-lite": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/atob-lite/-/atob-lite-2.0.0.tgz", @@ -803,18 +787,6 @@ "resolved": "https://registry.npmjs.org/buffer-to-uint8array/-/buffer-to-uint8array-1.1.0.tgz", "integrity": "sha512-JVTSbtA6YuOGdu5NL0ffizsBwuwbTXfV7OC91FhazMz9UKP/KlDS+Z7wuiSRClbnTQz52fJgVXI9YDXQRVl2sQ==" }, - "node_modules/busboy": { - "version": "0.2.14", - "resolved": "https://registry.npmjs.org/busboy/-/busboy-0.2.14.tgz", - "integrity": "sha512-InWFDomvlkEj+xWLBfU3AvnbVYqeTWmQopiW0tWWEy5yehYm2YkGEc59sUmw/4ty5Zj/b0WHGs1LgecuBSBGrg==", - "dependencies": { - "dicer": "0.2.5", - "readable-stream": "1.1.x" - }, - "engines": { - "node": ">=0.8.0" - } - }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -970,84 +942,6 @@ "safe-buffer": "~5.1.0" } }, - "node_modules/connect-multiparty": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/connect-multiparty/-/connect-multiparty-2.2.0.tgz", - "integrity": "sha512-zKcpA7cuXGEhuw9Pz7JmVCFmp85jzGLGm/iiagXTwyEAJp4ypLPtRS/V4IGuGb9KjjrgHBs6P/gDCpZHnFzksA==", - "dependencies": { - "http-errors": "~1.7.0", - "multiparty": "~4.2.1", - "on-finished": "~2.3.0", - "qs": "~6.5.2", - "type-is": "~1.6.16" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/connect-multiparty/node_modules/depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/connect-multiparty/node_modules/http-errors": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz", - "integrity": "sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==", - "dependencies": { - "depd": "~1.1.2", - "inherits": "2.0.4", - "setprototypeof": "1.1.1", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/connect-multiparty/node_modules/on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/connect-multiparty/node_modules/qs": { - "version": "6.5.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", - "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/connect-multiparty/node_modules/setprototypeof": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", - "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" - }, - "node_modules/connect-multiparty/node_modules/statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/connect-multiparty/node_modules/toidentifier": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", - "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", - "engines": { - "node": ">=0.6" - } - }, "node_modules/console-control-strings": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", @@ -1542,52 +1436,6 @@ "node": ">= 0.10.0" } }, - "node_modules/express-form-data": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/express-form-data/-/express-form-data-2.0.19.tgz", - "integrity": "sha512-QWulRIYvZV/ZOuO0+SNpCs9Gl0st9SssYtm5Icfm17p2MU4HASLpd4K5w8fwnXB/lPPskecVSeqjVWi2SwIb1A==", - "dependencies": { - "connect-multiparty": "^2.2.0", - "fs-extra": "^9.1.0", - "signal-exit": "^3.0.7" - }, - "engines": { - "node": ">=5.0.0" - } - }, - "node_modules/express-form-data/node_modules/fs-extra": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", - "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", - "dependencies": { - "at-least-node": "^1.0.0", - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/express-form-data/node_modules/jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/express-form-data/node_modules/universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", - "engines": { - "node": ">= 10.0.0" - } - }, "node_modules/express-rate-limit": { "version": "5.5.1", "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-5.5.1.tgz", @@ -1715,15 +1563,6 @@ "node": ">= 0.12" } }, - "node_modules/formidable": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.6.tgz", - "integrity": "sha512-KcpbcpuLNOwrEjnbpMC0gS+X8ciDoZE1kkqzat4a8vrprf+s9pKNQ/QIwWfbfs4ltgmFl3MD177SNTkve3BwGQ==", - "deprecated": "Please upgrade to latest, formidable@v2 or formidable@v3! Check these notes: https://bit.ly/2ZEqIau", - "funding": { - "url": "https://ko-fi.com/tunnckoCore/commissions" - } - }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -2603,17 +2442,6 @@ "node": ">=8" } }, - "node_modules/mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "dependencies": { - "minimist": "^1.2.6" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, "node_modules/moment": { "version": "2.29.4", "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", @@ -2802,69 +2630,6 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, - "node_modules/multer": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.4.tgz", - "integrity": "sha512-2wY2+xD4udX612aMqMcB8Ws2Voq6NIUPEtD1be6m411T4uDH/VtL9i//xvcyFlTVfRdaBsk7hV5tgrGQqhuBiw==", - "deprecated": "Multer 1.x is affected by CVE-2022-24434. This is fixed in v1.4.4-lts.1 which drops support for versions of Node.js before 6. Please upgrade to at least Node.js 6 and version 1.4.4-lts.1 of Multer. If you need support for older versions of Node.js, we are open to accepting patches that would fix the CVE on the main 1.x release line, whilst maintaining compatibility with Node.js 0.10.", - "dependencies": { - "append-field": "^1.0.0", - "busboy": "^0.2.11", - "concat-stream": "^1.5.2", - "mkdirp": "^0.5.4", - "object-assign": "^4.1.1", - "on-finished": "^2.3.0", - "type-is": "^1.6.4", - "xtend": "^4.0.0" - }, - "engines": { - "node": ">= 0.10.0" - } - }, - "node_modules/multiparty": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/multiparty/-/multiparty-4.2.3.tgz", - "integrity": "sha512-Ak6EUJZuhGS8hJ3c2fY6UW5MbkGUPMBEGd13djUzoY/BHqV/gTuFWtC6IuVA7A2+v3yjBS6c4or50xhzTQZImQ==", - "dependencies": { - "http-errors": "~1.8.1", - "safe-buffer": "5.2.1", - "uid-safe": "2.1.5" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/multiparty/node_modules/depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/multiparty/node_modules/http-errors": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", - "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", - "dependencies": { - "depd": "~1.1.2", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.1" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/multiparty/node_modules/statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/mute-stream": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", @@ -3216,14 +2981,6 @@ "quote-stream": "bin/cmd.js" } }, - "node_modules/random-bytes": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", - "integrity": "sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -3904,17 +3661,6 @@ "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==" }, - "node_modules/uid-safe": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", - "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==", - "dependencies": { - "random-bytes": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/universalify": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", @@ -4491,11 +4237,6 @@ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" }, - "append-field": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", - "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==" - }, "aproba": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", @@ -4574,11 +4315,6 @@ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, - "at-least-node": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", - "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==" - }, "atob-lite": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/atob-lite/-/atob-lite-2.0.0.tgz", @@ -4785,15 +4521,6 @@ "resolved": "https://registry.npmjs.org/buffer-to-uint8array/-/buffer-to-uint8array-1.1.0.tgz", "integrity": "sha512-JVTSbtA6YuOGdu5NL0ffizsBwuwbTXfV7OC91FhazMz9UKP/KlDS+Z7wuiSRClbnTQz52fJgVXI9YDXQRVl2sQ==" }, - "busboy": { - "version": "0.2.14", - "resolved": "https://registry.npmjs.org/busboy/-/busboy-0.2.14.tgz", - "integrity": "sha512-InWFDomvlkEj+xWLBfU3AvnbVYqeTWmQopiW0tWWEy5yehYm2YkGEc59sUmw/4ty5Zj/b0WHGs1LgecuBSBGrg==", - "requires": { - "dicer": "0.2.5", - "readable-stream": "1.1.x" - } - }, "bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -4918,65 +4645,6 @@ } } }, - "connect-multiparty": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/connect-multiparty/-/connect-multiparty-2.2.0.tgz", - "integrity": "sha512-zKcpA7cuXGEhuw9Pz7JmVCFmp85jzGLGm/iiagXTwyEAJp4ypLPtRS/V4IGuGb9KjjrgHBs6P/gDCpZHnFzksA==", - "requires": { - "http-errors": "~1.7.0", - "multiparty": "~4.2.1", - "on-finished": "~2.3.0", - "qs": "~6.5.2", - "type-is": "~1.6.16" - }, - "dependencies": { - "depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==" - }, - "http-errors": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz", - "integrity": "sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==", - "requires": { - "depd": "~1.1.2", - "inherits": "2.0.4", - "setprototypeof": "1.1.1", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.0" - } - }, - "on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", - "requires": { - "ee-first": "1.1.1" - } - }, - "qs": { - "version": "6.5.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", - "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==" - }, - "setprototypeof": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", - "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" - }, - "statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==" - }, - "toidentifier": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", - "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" - } - } - }, "console-control-strings": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", @@ -5375,43 +5043,6 @@ "vary": "~1.1.2" } }, - "express-form-data": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/express-form-data/-/express-form-data-2.0.19.tgz", - "integrity": "sha512-QWulRIYvZV/ZOuO0+SNpCs9Gl0st9SssYtm5Icfm17p2MU4HASLpd4K5w8fwnXB/lPPskecVSeqjVWi2SwIb1A==", - "requires": { - "connect-multiparty": "^2.2.0", - "fs-extra": "^9.1.0", - "signal-exit": "^3.0.7" - }, - "dependencies": { - "fs-extra": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", - "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", - "requires": { - "at-least-node": "^1.0.0", - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - } - }, - "jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "requires": { - "graceful-fs": "^4.1.6", - "universalify": "^2.0.0" - } - }, - "universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==" - } - } - }, "express-rate-limit": { "version": "5.5.1", "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-5.5.1.tgz", @@ -5523,11 +5154,6 @@ "mime-types": "^2.1.12" } }, - "formidable": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.6.tgz", - "integrity": "sha512-KcpbcpuLNOwrEjnbpMC0gS+X8ciDoZE1kkqzat4a8vrprf+s9pKNQ/QIwWfbfs4ltgmFl3MD177SNTkve3BwGQ==" - }, "forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -6200,14 +5826,6 @@ } } }, - "mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "requires": { - "minimist": "^1.2.6" - } - }, "moment": { "version": "2.29.4", "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", @@ -6346,55 +5964,6 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, - "multer": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.4.tgz", - "integrity": "sha512-2wY2+xD4udX612aMqMcB8Ws2Voq6NIUPEtD1be6m411T4uDH/VtL9i//xvcyFlTVfRdaBsk7hV5tgrGQqhuBiw==", - "requires": { - "append-field": "^1.0.0", - "busboy": "^0.2.11", - "concat-stream": "^1.5.2", - "mkdirp": "^0.5.4", - "object-assign": "^4.1.1", - "on-finished": "^2.3.0", - "type-is": "^1.6.4", - "xtend": "^4.0.0" - } - }, - "multiparty": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/multiparty/-/multiparty-4.2.3.tgz", - "integrity": "sha512-Ak6EUJZuhGS8hJ3c2fY6UW5MbkGUPMBEGd13djUzoY/BHqV/gTuFWtC6IuVA7A2+v3yjBS6c4or50xhzTQZImQ==", - "requires": { - "http-errors": "~1.8.1", - "safe-buffer": "5.2.1", - "uid-safe": "2.1.5" - }, - "dependencies": { - "depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==" - }, - "http-errors": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", - "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", - "requires": { - "depd": "~1.1.2", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.1" - } - }, - "statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==" - } - } - }, "mute-stream": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", @@ -6653,11 +6222,6 @@ "through2": "^2.0.0" } }, - "random-bytes": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", - "integrity": "sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==" - }, "range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -7219,14 +6783,6 @@ "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==" }, - "uid-safe": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", - "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==", - "requires": { - "random-bytes": "~1.0.0" - } - }, "universalify": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", diff --git a/package.json b/package.json index bed4955..e6b5372 100644 --- a/package.json +++ b/package.json @@ -28,10 +28,8 @@ "dotenv": "^16.0.3", "email-validator": "^2.0.4", "express": "^4.17.1", - "express-form-data": "^2.0.17", "express-rate-limit": "^5.3.0", "express-subdomain": "^1.0.5", - "formidable": "^1.2.2", "fs-extra": "^8.1.0", "got": "^11.8.2", "hcaptcha": "^0.1.0", @@ -46,7 +44,6 @@ "mongoose": "^5.8.3", "mongoose-unique-validator": "^2.0.3", "morgan": "^1.9.1", - "multer": "^1.4.3", "nodemailer": "^6.4.2", "redis": "^4.3.1", "tga": "^1.0.4", From 868a8ed9bbfbb49a6b1f67915a08506ccd8e06a5 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Tue, 28 Feb 2023 20:58:51 -0500 Subject: [PATCH 005/219] Pull logger path from env directly --- src/config-manager.js | 1 - src/logger.js | 4 +--- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/config-manager.js b/src/config-manager.js index 83e9b45..954f2a4 100644 --- a/src/config-manager.js +++ b/src/config-manager.js @@ -72,7 +72,6 @@ function configure() { logger.info('Loading config from environment variable'); config = { - logger_path: process.env.PN_ACT_LOGGER_PATH, http: { port: Number(process.env.PN_ACT_CONFIG_HTTP_PORT) }, diff --git a/src/logger.js b/src/logger.js index 3f61fb6..3a925e2 100644 --- a/src/logger.js +++ b/src/logger.js @@ -1,9 +1,7 @@ const fs = require('fs-extra'); require('colors'); -const { config } = require('./config-manager'); - -const root = config.logger_path ? config.logger_path : `${__dirname}/..`; +const root = process.env.PN_ACT_LOGGER_PATH ? process.env.PN_ACT_LOGGER_PATH : `${__dirname}/..`; fs.ensureDirSync(`${root}/logs`); const streams = { From ff69ac3ceac4211e5c7c75b1baea5544fbf47a9f Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Tue, 28 Feb 2023 21:08:52 -0500 Subject: [PATCH 006/219] Added tsconfig and start of TypeScript migration --- package.json | 7 ++++++- tsconfig.json | 11 +++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 tsconfig.json diff --git a/package.json b/package.json index e6b5372..0385b45 100644 --- a/package.json +++ b/package.json @@ -2,9 +2,14 @@ "name": "account", "version": "1.0.0", "description": "", - "main": "./src/server.js", + "main": "./dist/server.js", "scripts": { "lint": "./node_modules/.bin/eslint .", + "build": "npm run clean && tsc && npm run copy-static", + "clean": "rm -rf ./dist", + "copy-static": "npm run copy-assets && npm run copy-timezones", + "copy-assets": "cp -r ./src/assets ./dist/assets", + "copy-timezones": "cp ./src/services/nnid/timezones.json ./dist/services/nnid/timezones.json", "start": "node .", "start:dev": "NODE_ENV=development node ." }, diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..af34411 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "outDir": "./dist", + "allowJs": true, + "target": "es6", + "noEmitOnError": true + }, + "include": [ + "./src/**/*" + ] +} \ No newline at end of file From 0543bd45c843931bbd5c8173376d1efc3bffed96 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Wed, 1 Mar 2023 16:34:17 -0500 Subject: [PATCH 007/219] Enabled esModuleInterop in tsconfig --- tsconfig.json | 1 + 1 file changed, 1 insertion(+) diff --git a/tsconfig.json b/tsconfig.json index af34411..805bffc 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,5 +1,6 @@ { "compilerOptions": { + "esModuleInterop": true, "outDir": "./dist", "allowJs": true, "target": "es6", From b4980e942d9f944af60a987e4f690b9dbdd77b7b Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Wed, 1 Mar 2023 16:36:21 -0500 Subject: [PATCH 008/219] Set moduleResolution to node in tsconfig --- tsconfig.json | 1 + 1 file changed, 1 insertion(+) diff --git a/tsconfig.json b/tsconfig.json index 805bffc..bdc34e9 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,6 +1,7 @@ { "compilerOptions": { "esModuleInterop": true, + "moduleResolution": "node", "outDir": "./dist", "allowJs": true, "target": "es6", From bcbf403abaf4d6e758e14dfc2edb10535f7a04bb Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Wed, 1 Mar 2023 17:53:14 -0500 Subject: [PATCH 009/219] Set module to commonjs in tsconfig --- tsconfig.json | 1 + 1 file changed, 1 insertion(+) diff --git a/tsconfig.json b/tsconfig.json index bdc34e9..9fe2c15 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,5 +1,6 @@ { "compilerOptions": { + "module": "commonjs", "esModuleInterop": true, "moduleResolution": "node", "outDir": "./dist", From 68a0fa819319a25f3c2348c51b591fbcfa474b00 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Thu, 2 Mar 2023 19:29:09 -0500 Subject: [PATCH 010/219] Added es2021 lib to compilerOptions (needed for replaceAll) --- tsconfig.json | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tsconfig.json b/tsconfig.json index 9fe2c15..a15797f 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,8 +6,14 @@ "outDir": "./dist", "allowJs": true, "target": "es6", - "noEmitOnError": true + "noEmitOnError": true, + "lib": [ + "es2021" + ] }, + "lib": [ + "es6" + ], "include": [ "./src/**/*" ] From bf3e357bc1284a84df33aeef88be806c46876f4c Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Thu, 2 Mar 2023 19:37:17 -0500 Subject: [PATCH 011/219] Set tsconfig target to es2022 --- tsconfig.json | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/tsconfig.json b/tsconfig.json index a15797f..ce05381 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,15 +5,9 @@ "moduleResolution": "node", "outDir": "./dist", "allowJs": true, - "target": "es6", - "noEmitOnError": true, - "lib": [ - "es2021" - ] + "target": "es2022", + "noEmitOnError": true }, - "lib": [ - "es6" - ], "include": [ "./src/**/*" ] From 8bb3d600c48ebe307ddf382ea3f3728ddfad3ce8 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Fri, 3 Mar 2023 00:28:08 -0500 Subject: [PATCH 012/219] Add types folder to ts includes --- tsconfig.json | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tsconfig.json b/tsconfig.json index ce05381..7e9987e 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -8,7 +8,5 @@ "target": "es2022", "noEmitOnError": true }, - "include": [ - "./src/**/*" - ] + "include": ["src", "types"] } \ No newline at end of file From 84bf498d48f665656e8de85de5e11c3f67cb6029 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Fri, 3 Mar 2023 00:29:12 -0500 Subject: [PATCH 013/219] Enable "resolveJsonModule": true in tsconfig --- tsconfig.json | 1 + 1 file changed, 1 insertion(+) diff --git a/tsconfig.json b/tsconfig.json index 7e9987e..9d94299 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,5 +1,6 @@ { "compilerOptions": { + "resolveJsonModule": true, "module": "commonjs", "esModuleInterop": true, "moduleResolution": "node", From 3ef40c64cd53a8fceefe6ba93b07ce7b331bf531 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Fri, 3 Mar 2023 12:37:50 -0500 Subject: [PATCH 014/219] Updated packages --- package-lock.json | 490 ++++++++++++++++++++++++++++++++++------------ package.json | 11 +- 2 files changed, 367 insertions(+), 134 deletions(-) diff --git a/package-lock.json b/package-lock.json index 63f9f0b..1473949 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,18 +1,19 @@ { "name": "account", - "version": "2.0.0", + "version": "1.0.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "account", - "version": "2.0.0", + "version": "1.0.0", "license": "ISC", "dependencies": { "aws-sdk": "^2.978.0", "bcrypt": "^5.0.0", "colors": "^1.4.0", "cors": "^2.8.5", + "countries-and-timezones": "^3.4.1", "dicer": "^0.2.5", "dotenv": "^16.0.3", "email-validator": "^2.0.4", @@ -23,16 +24,16 @@ "got": "^11.8.2", "hcaptcha": "^0.1.0", "image-pixels": "^1.1.1", - "joi": "^17.6.1", + "joi": "^17.8.3", "kaitai-struct": "^0.9.0", "lodash.get": "^4.4.2", "lodash.set": "^4.3.2", "mii-js": "github:PretendoNetwork/mii-js", - "moment": "^2.24.0", - "moment-timezone": "^0.5.27", + "moment": "^2.29.4", "mongoose": "^5.8.3", "mongoose-unique-validator": "^2.0.3", "morgan": "^1.9.1", + "neat-config": "^1.0.0", "nodemailer": "^6.4.2", "redis": "^4.3.1", "tga": "^1.0.4", @@ -41,6 +42,11 @@ "xmlbuilder2": "0.0.4" }, "devDependencies": { + "@types/colors": "^1.2.1", + "@types/express": "^4.17.17", + "@types/fs-extra": "^11.0.1", + "@types/morgan": "^1.9.4", + "@types/node": "^18.14.4", "node-rsa": "^1.0.7", "prompt": "^1.0.0", "yesno": "^0.4.0" @@ -212,9 +218,9 @@ } }, "node_modules/@redis/client": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@redis/client/-/client-1.5.5.tgz", - "integrity": "sha512-fuMnpDYSjT5JXR9rrCW1YWA4L8N/9/uS4ImT3ZEC/hcaQRI1D/9FvwjriRj1UvepIgzZXthFVKMNRzP/LNL7BQ==", + "version": "1.5.6", + "resolved": "https://registry.npmjs.org/@redis/client/-/client-1.5.6.tgz", + "integrity": "sha512-dFD1S6je+A47Lj22jN/upVU2fj4huR7S9APd7/ziUXsIXDL+11GPYti4Suv5y8FuXaN+0ZG4JF+y1houEJ7ToA==", "dependencies": { "cluster-key-slot": "1.1.2", "generic-pool": "3.9.0", @@ -241,9 +247,9 @@ } }, "node_modules/@redis/search": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@redis/search/-/search-1.1.1.tgz", - "integrity": "sha512-pqCXTc5e7wJJgUuJiC3hBgfoFRoPxYzwn0BEfKgejTM7M/9zP3IpUcqcjgfp8hF+LoV8rHZzcNTz7V+pEIY7LQ==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@redis/search/-/search-1.1.2.tgz", + "integrity": "sha512-/cMfstG/fOh/SsE+4/BQGeuH/JJloeWuH+qJzM8dbxuWvdWibWAOAHHCZTMPhV3xIlH4/cUEIA8OV5QnYpaVoA==", "peerDependencies": { "@redis/client": "^1.0.0" } @@ -296,6 +302,16 @@ "node": ">=10" } }, + "node_modules/@types/body-parser": { + "version": "1.19.2", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", + "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==", + "dev": true, + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, "node_modules/@types/bson": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/@types/bson/-/bson-4.0.5.tgz", @@ -315,11 +331,72 @@ "@types/responselike": "^1.0.0" } }, + "node_modules/@types/colors": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@types/colors/-/colors-1.2.1.tgz", + "integrity": "sha512-7jNkpfN2lVO07nJ1RWzyMnNhH/I5N9iWuMPx9pedptxJ4MODf8rRV0lbJi6RakQ4sKQk231Fw4e2W9n3D7gZ3w==", + "deprecated": "This is a stub types definition. colors provides its own type definitions, so you don't need this installed.", + "dev": true, + "dependencies": { + "colors": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.35", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", + "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/express": { + "version": "4.17.17", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.17.tgz", + "integrity": "sha512-Q4FmmuLGBG58btUnfS1c1r/NQdlp3DMfGDGig8WhfpA2YRUtEkxAjkZb0yvplJGYdF1fsQ81iMDcH24sSCNC/Q==", + "dev": true, + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.17.33", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.33.tgz", + "integrity": "sha512-TPBqmR/HRYI3eC2E5hmiivIzv+bidAfXofM+sbonAGvyDhySGw9/PQZFt2BLOrjUUR++4eJVpx6KnLQK1Fk9tA==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*" + } + }, + "node_modules/@types/fs-extra": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-11.0.1.tgz", + "integrity": "sha512-MxObHvNl4A69ofaTRU8DFqvgzzv8s9yRtaPPm5gud9HDNvpB3GPQFvNuTWAI59B9huVGV5jXYJwbCsmBsOGYWA==", + "dev": true, + "dependencies": { + "@types/jsonfile": "*", + "@types/node": "*" + } + }, "node_modules/@types/http-cache-semantics": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz", "integrity": "sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==" }, + "node_modules/@types/jsonfile": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/@types/jsonfile/-/jsonfile-6.1.1.tgz", + "integrity": "sha512-GSgiRCVeapDN+3pqA35IkQwasaCh/0YFH5dEF6S88iDvEn901DjOeH3/QPY+XYP1DFzDZPvIvfeEgk+7br5png==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/keyv": { "version": "3.1.4", "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz", @@ -328,6 +405,12 @@ "@types/node": "*" } }, + "node_modules/@types/mime": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-3.0.1.tgz", + "integrity": "sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==", + "dev": true + }, "node_modules/@types/mongodb": { "version": "3.6.20", "resolved": "https://registry.npmjs.org/@types/mongodb/-/mongodb-3.6.20.tgz", @@ -337,10 +420,31 @@ "@types/node": "*" } }, + "node_modules/@types/morgan": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@types/morgan/-/morgan-1.9.4.tgz", + "integrity": "sha512-cXoc4k+6+YAllH3ZHmx4hf7La1dzUk6keTR4bF4b4Sc0mZxU/zK4wO7l+ZzezXm/jkYj/qC+uYGZrarZdIVvyQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/node": { - "version": "18.13.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.13.0.tgz", - "integrity": "sha512-gC3TazRzGoOnoKAhUx+Q0t8S9Tzs74z7m0ipwGpSqQrleP14hKxP4/JUeEQcD3W1/aIpnWl8pHowI7WokuZpXg==" + "version": "18.14.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.14.4.tgz", + "integrity": "sha512-VhCw7I7qO2X49+jaKcAUwi3rR+hbxT5VcYF493+Z5kMLI0DL568b7JI4IDJaxWFH0D/xwmGJNoXisyX+w7GH/g==" + }, + "node_modules/@types/qs": { + "version": "6.9.7", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", + "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==", + "dev": true + }, + "node_modules/@types/range-parser": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", + "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==", + "dev": true }, "node_modules/@types/responselike": { "version": "1.0.0", @@ -350,6 +454,16 @@ "@types/node": "*" } }, + "node_modules/@types/serve-static": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.1.tgz", + "integrity": "sha512-NUo5XNiAdULrJENtJXZZ3fHtfMolzZwczzBbnAeBbqBwG+LaG6YaJtuwzwGSQZ2wsCrxjEhNNjAkKigy3n8teQ==", + "dev": true, + "dependencies": { + "@types/mime": "*", + "@types/node": "*" + } + }, "node_modules/abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", @@ -469,9 +583,9 @@ } }, "node_modules/are-we-there-yet/node_modules/readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.1.tgz", + "integrity": "sha512-+rQmrWMYGA90yenhTYsLWAsLsqVC8osOw6PKE1HDYiO0gdPeKe/xDHNzIAIn4C91YQ6oenEhfYqqc1883qHbjQ==", "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -556,9 +670,9 @@ } }, "node_modules/aws-sdk": { - "version": "2.1313.0", - "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1313.0.tgz", - "integrity": "sha512-8GMdtV2Uch3HL2c6+P3lNZFTcg/fqq9L3EWYRLb6ljCZvWKTssjdkjSJFDyTReNgeiKV224YRPYQbKpOEz4flQ==", + "version": "2.1325.0", + "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1325.0.tgz", + "integrity": "sha512-ztg9HG5aoUHTprY+/eqjqb25E4joCgz+8ToxsP4OSKFQCtaBcF6my03j4e/J2j3fmpPifJnZSPMu4kV7DBj8WA==", "dependencies": { "buffer": "4.9.2", "events": "1.1.1", @@ -664,9 +778,9 @@ } }, "node_modules/bl/node_modules/readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -916,9 +1030,9 @@ } }, "node_modules/concat-stream/node_modules/readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -1001,6 +1115,15 @@ "node": ">= 0.10" } }, + "node_modules/countries-and-timezones": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/countries-and-timezones/-/countries-and-timezones-3.4.1.tgz", + "integrity": "sha512-INeHGCony4XUUR8iGL/lmt9s1Oi+n+gFHeJAMfbV5hJfYeDOB8JG1oxz5xFQu5oBZoRCJe/87k1Vzue9DoIauA==", + "engines": { + "node": ">=8.x", + "npm": ">=5.x" + } + }, "node_modules/cycle": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/cycle/-/cycle-1.0.3.tgz", @@ -1164,9 +1287,9 @@ } }, "node_modules/duplexer2/node_modules/readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -2140,9 +2263,9 @@ } }, "node_modules/joi": { - "version": "17.7.1", - "resolved": "https://registry.npmjs.org/joi/-/joi-17.7.1.tgz", - "integrity": "sha512-teoLhIvWE298R6AeJywcjR4sX2hHjB3/xJX4qPjg+gTg+c0mzUDsziYlqPmLomq9gVsfaMcgPaGc7VxtD/9StA==", + "version": "17.8.3", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.8.3.tgz", + "integrity": "sha512-q5Fn6Tj/jR8PfrLrx4fpGH4v9qM6o+vDUfD4/3vxxyg34OmKcNqYZ1qn2mpLza96S8tL0p0rIw2gOZX+/cTg9w==", "dependencies": { "@hapi/hoek": "^9.0.0", "@hapi/topo": "^5.0.0", @@ -2412,9 +2535,9 @@ } }, "node_modules/minipass": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.0.3.tgz", - "integrity": "sha512-OW2r4sQ0sI+z5ckEt5c1Tri4xTgZwYDxpE54eqWlQloQRoWtXjqt9udJ5Z4dSv7wK+nfFI7FRXyCpBSft+gpFw==", + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.4.tgz", + "integrity": "sha512-lwycX3cBMTvcejsHITUgYj6Gy6A7Nh4Q6h9NP4sTHY1ccJlC7yKzDmiShEHsJ16Jf1nKGDEaiHxiltsJEvk0nQ==", "engines": { "node": ">=8" } @@ -2442,6 +2565,17 @@ "node": ">=8" } }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/moment": { "version": "2.29.4", "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", @@ -2450,17 +2584,6 @@ "node": "*" } }, - "node_modules/moment-timezone": { - "version": "0.5.40", - "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.40.tgz", - "integrity": "sha512-tWfmNkRYmBkPJz5mr9GVDn9vRlVZOTe6yqY92rFxiOdWXbjaR0+9LwQnZGGuNR63X456NqmEkbskte8tWL5ePg==", - "dependencies": { - "moment": ">= 2.9.0" - }, - "engines": { - "node": "*" - } - }, "node_modules/mongodb": { "version": "3.7.3", "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.7.3.tgz", @@ -2511,9 +2634,9 @@ } }, "node_modules/mongoose": { - "version": "5.13.15", - "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-5.13.15.tgz", - "integrity": "sha512-cxp1Gbb8yUWkaEbajdhspSaKzAvsIvOtRlYD87GN/P2QEUhpd6bIvebi36T6M0tIVAMauNaK9SPA055N3PwF8Q==", + "version": "5.13.16", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-5.13.16.tgz", + "integrity": "sha512-kBNB+BfaQjn3Jjh1SfdZZub70pde9dI0sA8VN6AnnCOeK4TzbLDyB0lBmPBOajppm6U9orde5YfTRyyVa1U45w==", "dependencies": { "@types/bson": "1.x || 4.0.x", "@types/mongodb": "^3.5.27", @@ -2636,6 +2759,14 @@ "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", "dev": true }, + "node_modules/neat-config": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/neat-config/-/neat-config-1.0.0.tgz", + "integrity": "sha512-EKvHxsZ9yOUHWhx6MxxzbTDhNK0wiSbfaU42s2OnQuVgR+FwpswaiSm3xX7ST4TcN+rJiEw+GuhfK015vHKwrA==", + "dependencies": { + "joi": "^17.6.0" + } + }, "node_modules/negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", @@ -3032,15 +3163,15 @@ "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==" }, "node_modules/redis": { - "version": "4.6.4", - "resolved": "https://registry.npmjs.org/redis/-/redis-4.6.4.tgz", - "integrity": "sha512-wi2tgDdQ+Q8q+PR5FLRx4QvDiWaA+PoJbrzsyFqlClN5R4LplHqN3scs/aGjE//mbz++W19SgxiEnQ27jnCRaA==", + "version": "4.6.5", + "resolved": "https://registry.npmjs.org/redis/-/redis-4.6.5.tgz", + "integrity": "sha512-O0OWA36gDQbswOdUuAhRL6mTZpHFN525HlgZgDaVNgCJIAZR3ya06NTESb0R+TUZ+BFaDpz6NnnVvoMx9meUFg==", "dependencies": { "@redis/bloom": "1.2.0", - "@redis/client": "1.5.5", + "@redis/client": "1.5.6", "@redis/graph": "1.1.0", "@redis/json": "1.0.4", - "@redis/search": "1.1.1", + "@redis/search": "1.1.2", "@redis/time-series": "1.0.4" } }, @@ -3406,9 +3537,9 @@ } }, "node_modules/static-module/node_modules/readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -3518,17 +3649,6 @@ "node": ">=10" } }, - "node_modules/tar/node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/tga": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/tga/-/tga-1.0.7.tgz", @@ -3551,9 +3671,9 @@ } }, "node_modules/through2/node_modules/readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -4046,9 +4166,9 @@ "requires": {} }, "@redis/client": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@redis/client/-/client-1.5.5.tgz", - "integrity": "sha512-fuMnpDYSjT5JXR9rrCW1YWA4L8N/9/uS4ImT3ZEC/hcaQRI1D/9FvwjriRj1UvepIgzZXthFVKMNRzP/LNL7BQ==", + "version": "1.5.6", + "resolved": "https://registry.npmjs.org/@redis/client/-/client-1.5.6.tgz", + "integrity": "sha512-dFD1S6je+A47Lj22jN/upVU2fj4huR7S9APd7/ziUXsIXDL+11GPYti4Suv5y8FuXaN+0ZG4JF+y1houEJ7ToA==", "requires": { "cluster-key-slot": "1.1.2", "generic-pool": "3.9.0", @@ -4068,9 +4188,9 @@ "requires": {} }, "@redis/search": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@redis/search/-/search-1.1.1.tgz", - "integrity": "sha512-pqCXTc5e7wJJgUuJiC3hBgfoFRoPxYzwn0BEfKgejTM7M/9zP3IpUcqcjgfp8hF+LoV8rHZzcNTz7V+pEIY7LQ==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@redis/search/-/search-1.1.2.tgz", + "integrity": "sha512-/cMfstG/fOh/SsE+4/BQGeuH/JJloeWuH+qJzM8dbxuWvdWibWAOAHHCZTMPhV3xIlH4/cUEIA8OV5QnYpaVoA==", "requires": {} }, "@redis/time-series": { @@ -4110,6 +4230,16 @@ "defer-to-connect": "^2.0.0" } }, + "@types/body-parser": { + "version": "1.19.2", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", + "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==", + "dev": true, + "requires": { + "@types/connect": "*", + "@types/node": "*" + } + }, "@types/bson": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/@types/bson/-/bson-4.0.5.tgz", @@ -4129,11 +4259,71 @@ "@types/responselike": "^1.0.0" } }, + "@types/colors": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@types/colors/-/colors-1.2.1.tgz", + "integrity": "sha512-7jNkpfN2lVO07nJ1RWzyMnNhH/I5N9iWuMPx9pedptxJ4MODf8rRV0lbJi6RakQ4sKQk231Fw4e2W9n3D7gZ3w==", + "dev": true, + "requires": { + "colors": "*" + } + }, + "@types/connect": { + "version": "3.4.35", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", + "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/express": { + "version": "4.17.17", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.17.tgz", + "integrity": "sha512-Q4FmmuLGBG58btUnfS1c1r/NQdlp3DMfGDGig8WhfpA2YRUtEkxAjkZb0yvplJGYdF1fsQ81iMDcH24sSCNC/Q==", + "dev": true, + "requires": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "@types/express-serve-static-core": { + "version": "4.17.33", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.33.tgz", + "integrity": "sha512-TPBqmR/HRYI3eC2E5hmiivIzv+bidAfXofM+sbonAGvyDhySGw9/PQZFt2BLOrjUUR++4eJVpx6KnLQK1Fk9tA==", + "dev": true, + "requires": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*" + } + }, + "@types/fs-extra": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-11.0.1.tgz", + "integrity": "sha512-MxObHvNl4A69ofaTRU8DFqvgzzv8s9yRtaPPm5gud9HDNvpB3GPQFvNuTWAI59B9huVGV5jXYJwbCsmBsOGYWA==", + "dev": true, + "requires": { + "@types/jsonfile": "*", + "@types/node": "*" + } + }, "@types/http-cache-semantics": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz", "integrity": "sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==" }, + "@types/jsonfile": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/@types/jsonfile/-/jsonfile-6.1.1.tgz", + "integrity": "sha512-GSgiRCVeapDN+3pqA35IkQwasaCh/0YFH5dEF6S88iDvEn901DjOeH3/QPY+XYP1DFzDZPvIvfeEgk+7br5png==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/keyv": { "version": "3.1.4", "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz", @@ -4142,6 +4332,12 @@ "@types/node": "*" } }, + "@types/mime": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-3.0.1.tgz", + "integrity": "sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==", + "dev": true + }, "@types/mongodb": { "version": "3.6.20", "resolved": "https://registry.npmjs.org/@types/mongodb/-/mongodb-3.6.20.tgz", @@ -4151,10 +4347,31 @@ "@types/node": "*" } }, + "@types/morgan": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@types/morgan/-/morgan-1.9.4.tgz", + "integrity": "sha512-cXoc4k+6+YAllH3ZHmx4hf7La1dzUk6keTR4bF4b4Sc0mZxU/zK4wO7l+ZzezXm/jkYj/qC+uYGZrarZdIVvyQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/node": { - "version": "18.13.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.13.0.tgz", - "integrity": "sha512-gC3TazRzGoOnoKAhUx+Q0t8S9Tzs74z7m0ipwGpSqQrleP14hKxP4/JUeEQcD3W1/aIpnWl8pHowI7WokuZpXg==" + "version": "18.14.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.14.4.tgz", + "integrity": "sha512-VhCw7I7qO2X49+jaKcAUwi3rR+hbxT5VcYF493+Z5kMLI0DL568b7JI4IDJaxWFH0D/xwmGJNoXisyX+w7GH/g==" + }, + "@types/qs": { + "version": "6.9.7", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", + "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==", + "dev": true + }, + "@types/range-parser": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", + "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==", + "dev": true }, "@types/responselike": { "version": "1.0.0", @@ -4164,6 +4381,16 @@ "@types/node": "*" } }, + "@types/serve-static": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.1.tgz", + "integrity": "sha512-NUo5XNiAdULrJENtJXZZ3fHtfMolzZwczzBbnAeBbqBwG+LaG6YaJtuwzwGSQZ2wsCrxjEhNNjAkKigy3n8teQ==", + "dev": true, + "requires": { + "@types/mime": "*", + "@types/node": "*" + } + }, "abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", @@ -4252,9 +4479,9 @@ }, "dependencies": { "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.1.tgz", + "integrity": "sha512-+rQmrWMYGA90yenhTYsLWAsLsqVC8osOw6PKE1HDYiO0gdPeKe/xDHNzIAIn4C91YQ6oenEhfYqqc1883qHbjQ==", "requires": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -4326,9 +4553,9 @@ "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==" }, "aws-sdk": { - "version": "2.1313.0", - "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1313.0.tgz", - "integrity": "sha512-8GMdtV2Uch3HL2c6+P3lNZFTcg/fqq9L3EWYRLb6ljCZvWKTssjdkjSJFDyTReNgeiKV224YRPYQbKpOEz4flQ==", + "version": "2.1325.0", + "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1325.0.tgz", + "integrity": "sha512-ztg9HG5aoUHTprY+/eqjqb25E4joCgz+8ToxsP4OSKFQCtaBcF6my03j4e/J2j3fmpPifJnZSPMu4kV7DBj8WA==", "requires": { "buffer": "4.9.2", "events": "1.1.1", @@ -4409,9 +4636,9 @@ }, "dependencies": { "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -4617,9 +4844,9 @@ }, "dependencies": { "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -4692,6 +4919,11 @@ "vary": "^1" } }, + "countries-and-timezones": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/countries-and-timezones/-/countries-and-timezones-3.4.1.tgz", + "integrity": "sha512-INeHGCony4XUUR8iGL/lmt9s1Oi+n+gFHeJAMfbV5hJfYeDOB8JG1oxz5xFQu5oBZoRCJe/87k1Vzue9DoIauA==" + }, "cycle": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/cycle/-/cycle-1.0.3.tgz", @@ -4811,9 +5043,9 @@ }, "dependencies": { "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -5579,9 +5811,9 @@ "integrity": "sha512-9FzQjJ7MATs1tSpnco1K6ayiYE3figslrXA72G2HQ/n76RzvYlofyi5QM+iX4YRs/pu3yzxlVQSST23+dMDknw==" }, "joi": { - "version": "17.7.1", - "resolved": "https://registry.npmjs.org/joi/-/joi-17.7.1.tgz", - "integrity": "sha512-teoLhIvWE298R6AeJywcjR4sX2hHjB3/xJX4qPjg+gTg+c0mzUDsziYlqPmLomq9gVsfaMcgPaGc7VxtD/9StA==", + "version": "17.8.3", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.8.3.tgz", + "integrity": "sha512-q5Fn6Tj/jR8PfrLrx4fpGH4v9qM6o+vDUfD4/3vxxyg34OmKcNqYZ1qn2mpLza96S8tL0p0rIw2gOZX+/cTg9w==", "requires": { "@hapi/hoek": "^9.0.0", "@hapi/topo": "^5.0.0", @@ -5803,9 +6035,9 @@ "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==" }, "minipass": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.0.3.tgz", - "integrity": "sha512-OW2r4sQ0sI+z5ckEt5c1Tri4xTgZwYDxpE54eqWlQloQRoWtXjqt9udJ5Z4dSv7wK+nfFI7FRXyCpBSft+gpFw==" + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.4.tgz", + "integrity": "sha512-lwycX3cBMTvcejsHITUgYj6Gy6A7Nh4Q6h9NP4sTHY1ccJlC7yKzDmiShEHsJ16Jf1nKGDEaiHxiltsJEvk0nQ==" }, "minizlib": { "version": "2.1.2", @@ -5826,19 +6058,16 @@ } } }, + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" + }, "moment": { "version": "2.29.4", "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==" }, - "moment-timezone": { - "version": "0.5.40", - "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.40.tgz", - "integrity": "sha512-tWfmNkRYmBkPJz5mr9GVDn9vRlVZOTe6yqY92rFxiOdWXbjaR0+9LwQnZGGuNR63X456NqmEkbskte8tWL5ePg==", - "requires": { - "moment": ">= 2.9.0" - } - }, "mongodb": { "version": "3.7.3", "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.7.3.tgz", @@ -5863,9 +6092,9 @@ } }, "mongoose": { - "version": "5.13.15", - "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-5.13.15.tgz", - "integrity": "sha512-cxp1Gbb8yUWkaEbajdhspSaKzAvsIvOtRlYD87GN/P2QEUhpd6bIvebi36T6M0tIVAMauNaK9SPA055N3PwF8Q==", + "version": "5.13.16", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-5.13.16.tgz", + "integrity": "sha512-kBNB+BfaQjn3Jjh1SfdZZub70pde9dI0sA8VN6AnnCOeK4TzbLDyB0lBmPBOajppm6U9orde5YfTRyyVa1U45w==", "requires": { "@types/bson": "1.x || 4.0.x", "@types/mongodb": "^3.5.27", @@ -5970,6 +6199,14 @@ "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", "dev": true }, + "neat-config": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/neat-config/-/neat-config-1.0.0.tgz", + "integrity": "sha512-EKvHxsZ9yOUHWhx6MxxzbTDhNK0wiSbfaU42s2OnQuVgR+FwpswaiSm3xX7ST4TcN+rJiEw+GuhfK015vHKwrA==", + "requires": { + "joi": "^17.6.0" + } + }, "negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", @@ -6266,15 +6503,15 @@ } }, "redis": { - "version": "4.6.4", - "resolved": "https://registry.npmjs.org/redis/-/redis-4.6.4.tgz", - "integrity": "sha512-wi2tgDdQ+Q8q+PR5FLRx4QvDiWaA+PoJbrzsyFqlClN5R4LplHqN3scs/aGjE//mbz++W19SgxiEnQ27jnCRaA==", + "version": "4.6.5", + "resolved": "https://registry.npmjs.org/redis/-/redis-4.6.5.tgz", + "integrity": "sha512-O0OWA36gDQbswOdUuAhRL6mTZpHFN525HlgZgDaVNgCJIAZR3ya06NTESb0R+TUZ+BFaDpz6NnnVvoMx9meUFg==", "requires": { "@redis/bloom": "1.2.0", - "@redis/client": "1.5.5", + "@redis/client": "1.5.6", "@redis/graph": "1.1.0", "@redis/json": "1.0.4", - "@redis/search": "1.1.1", + "@redis/search": "1.1.2", "@redis/time-series": "1.0.4" } }, @@ -6565,9 +6802,9 @@ }, "dependencies": { "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -6658,13 +6895,6 @@ "minizlib": "^2.1.1", "mkdirp": "^1.0.3", "yallist": "^4.0.0" - }, - "dependencies": { - "mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" - } } }, "tga": { @@ -6686,9 +6916,9 @@ }, "dependencies": { "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", diff --git a/package.json b/package.json index 0385b45..c4acc78 100644 --- a/package.json +++ b/package.json @@ -39,13 +39,11 @@ "got": "^11.8.2", "hcaptcha": "^0.1.0", "image-pixels": "^1.1.1", - "joi": "^17.6.1", - "kaitai-struct": "^0.9.0", + "joi": "^17.8.3", "lodash.get": "^4.4.2", "lodash.set": "^4.3.2", "mii-js": "github:PretendoNetwork/mii-js", - "moment": "^2.24.0", - "moment-timezone": "^0.5.27", + "moment": "^2.29.4", "mongoose": "^5.8.3", "mongoose-unique-validator": "^2.0.3", "morgan": "^1.9.1", @@ -57,6 +55,11 @@ "xmlbuilder2": "0.0.4" }, "devDependencies": { + "@types/colors": "^1.2.1", + "@types/express": "^4.17.17", + "@types/fs-extra": "^11.0.1", + "@types/morgan": "^1.9.4", + "@types/node": "^18.14.4", "node-rsa": "^1.0.7", "prompt": "^1.0.0", "yesno": "^0.4.0" From a75769a326948aaf3e14074d4d137439e8c8c866 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Fri, 3 Mar 2023 12:38:36 -0500 Subject: [PATCH 015/219] First steps to TypeScript --- src/{cache.js => cache.ts} | 57 ++--- src/{config-manager.js => config-manager.ts} | 76 ++----- src/{database.js => database.ts} | 67 +++--- src/{logger.js => logger.ts} | 8 +- src/{mailer.js => mailer.ts} | 37 +-- src/middleware/{api.js => api.ts} | 8 +- src/middleware/{cemu.js => cemu.ts} | 4 +- .../{client-header.js => client-header.ts} | 14 +- ...e-certificate.js => device-certificate.ts} | 6 +- src/middleware/{nasc.js => nasc.ts} | 17 +- src/middleware/{pnid.js => pnid.ts} | 8 +- src/middleware/{ratelimit.js => ratelimit.ts} | 6 +- .../{xml-parser.js => xml-parser.ts} | 8 +- src/mii.js | 214 ------------------ src/models/{device.js => device.ts} | 16 +- src/models/{nex-account.js => nex-account.ts} | 22 +- src/models/{pnid.js => pnid.ts} | 65 ++---- src/models/{server.js => server.ts} | 10 +- ...certificate.js => nintendo-certificate.ts} | 30 ++- src/{server.js => server.ts} | 34 +-- src/services/api/{index.js => index.ts} | 14 +- src/services/api/routes/index.js | 11 - src/services/api/routes/index.ts | 19 ++ .../v1/{connections.js => connections.ts} | 8 +- .../api/routes/v1/{email.js => email.ts} | 20 +- .../{forgotPassword.js => forgotPassword.ts} | 12 +- .../api/routes/v1/{login.js => login.ts} | 16 +- .../routes/v1/{register.js => register.ts} | 35 +-- .../v1/{resetPassword.js => resetPassword.ts} | 12 +- .../api/routes/v1/{user.js => user.ts} | 12 +- src/services/assets/{index.js => index.ts} | 10 +- src/services/conntest/{index.js => index.ts} | 8 +- src/services/datastore/{index.js => index.ts} | 10 +- src/services/datastore/routes/index.js | 3 - src/services/datastore/routes/index.ts | 5 + .../datastore/routes/{upload.js => upload.ts} | 16 +- src/services/local-cdn/index.js | 30 --- src/services/local-cdn/index.ts | 26 +++ .../local-cdn/routes/{get.js => get.ts} | 8 +- src/services/local-cdn/routes/index.js | 3 - src/services/local-cdn/routes/index.ts | 5 + src/services/nasc/{index.js => index.ts} | 12 +- src/services/nasc/routes/{ac.js => ac.ts} | 12 +- src/services/nasc/routes/index.js | 3 - src/services/nasc/routes/index.ts | 5 + src/services/nnid/{index.js => index.ts} | 16 +- src/services/nnid/routes/admin.js | 59 ----- src/services/nnid/routes/admin.ts | 88 +++++++ .../nnid/routes/{content.js => content.ts} | 14 +- .../nnid/routes/{devices.js => devices.ts} | 8 +- src/services/nnid/routes/index.js | 10 - src/services/nnid/routes/index.ts | 19 ++ src/services/nnid/routes/{miis.js => miis.ts} | 12 +- .../nnid/routes/{oauth.js => oauth.ts} | 16 +- .../nnid/routes/{people.js => people.ts} | 68 +++--- .../nnid/routes/{provider.js => provider.ts} | 22 +- .../nnid/routes/{support.js => support.ts} | 16 +- src/{util.js => util.ts} | 2 +- types/express.d.ts | 12 + types/mongoose/device-attribute.d.ts | 13 ++ types/mongoose/device.d.ts | 32 +++ types/mongoose/nex-account.d.ts | 23 ++ types/mongoose/pnid.d.ts | 81 +++++++ types/mongoose/server.d.ts | 19 ++ 64 files changed, 775 insertions(+), 777 deletions(-) rename src/{cache.js => cache.ts} (75%) rename src/{config-manager.js => config-manager.ts} (77%) rename src/{database.js => database.ts} (73%) rename src/{logger.js => logger.ts} (90%) rename src/{mailer.js => mailer.ts} (54%) rename src/middleware/{api.js => api.ts} (62%) rename src/middleware/{cemu.js => cemu.ts} (56%) rename src/middleware/{client-header.js => client-header.ts} (72%) rename src/middleware/{device-certificate.js => device-certificate.ts} (56%) rename src/middleware/{nasc.js => nasc.ts} (93%) rename src/middleware/{pnid.js => pnid.ts} (83%) rename src/middleware/{ratelimit.js => ratelimit.ts} (61%) rename src/middleware/{xml-parser.js => xml-parser.ts} (79%) delete mode 100644 src/mii.js rename src/models/{device.js => device.ts} (62%) rename src/models/{nex-account.js => nex-account.ts} (82%) rename src/models/{pnid.js => pnid.ts} (77%) rename src/models/{server.js => server.ts} (66%) rename src/{nintendo-certificate.js => nintendo-certificate.ts} (92%) rename src/{server.js => server.ts} (67%) rename src/services/api/{index.js => index.ts} (74%) delete mode 100644 src/services/api/routes/index.js create mode 100644 src/services/api/routes/index.ts rename src/services/api/routes/v1/{connections.js => connections.ts} (92%) rename src/services/api/routes/v1/{email.js => email.ts} (67%) rename src/services/api/routes/v1/{forgotPassword.js => forgotPassword.ts} (72%) rename src/services/api/routes/v1/{login.js => login.ts} (91%) rename src/services/api/routes/v1/{register.js => register.ts} (92%) rename src/services/api/routes/v1/{resetPassword.js => resetPassword.ts} (93%) rename src/services/api/routes/v1/{user.js => user.ts} (92%) rename src/services/assets/{index.js => index.ts} (68%) rename src/services/conntest/{index.js => index.ts} (80%) rename src/services/datastore/{index.js => index.ts} (66%) delete mode 100644 src/services/datastore/routes/index.js create mode 100644 src/services/datastore/routes/index.ts rename src/services/datastore/routes/{upload.js => upload.ts} (89%) delete mode 100644 src/services/local-cdn/index.js create mode 100644 src/services/local-cdn/index.ts rename src/services/local-cdn/routes/{get.js => get.ts} (65%) delete mode 100644 src/services/local-cdn/routes/index.js create mode 100644 src/services/local-cdn/routes/index.ts rename src/services/nasc/{index.js => index.ts} (62%) rename src/services/nasc/routes/{ac.js => ac.ts} (92%) delete mode 100644 src/services/nasc/routes/index.js create mode 100644 src/services/nasc/routes/index.ts rename src/services/nnid/{index.js => index.ts} (69%) delete mode 100644 src/services/nnid/routes/admin.js create mode 100644 src/services/nnid/routes/admin.ts rename src/services/nnid/routes/{content.js => content.ts} (92%) rename src/services/nnid/routes/{devices.js => devices.ts} (65%) delete mode 100644 src/services/nnid/routes/index.js create mode 100644 src/services/nnid/routes/index.ts rename src/services/nnid/routes/{miis.js => miis.ts} (87%) rename src/services/nnid/routes/{oauth.js => oauth.ts} (87%) rename src/services/nnid/routes/{people.js => people.ts} (82%) rename src/services/nnid/routes/{provider.js => provider.ts} (86%) rename src/services/nnid/routes/{support.js => support.ts} (88%) rename src/{util.js => util.ts} (96%) create mode 100644 types/express.d.ts create mode 100644 types/mongoose/device-attribute.d.ts create mode 100644 types/mongoose/device.d.ts create mode 100644 types/mongoose/nex-account.d.ts create mode 100644 types/mongoose/pnid.d.ts create mode 100644 types/mongoose/server.d.ts diff --git a/src/cache.js b/src/cache.ts similarity index 75% rename from src/cache.js rename to src/cache.ts index 6cd89e2..946209e 100644 --- a/src/cache.js +++ b/src/cache.ts @@ -1,6 +1,7 @@ -const fs = require('fs-extra'); -const redis = require('redis'); -const { config, disabledFeatures } = require('./config-manager'); +import fs from 'fs-extra'; +import redis from 'redis'; +import { config, disabledFeatures } from './config-manager'; + let client; const memoryCache = {}; @@ -9,7 +10,7 @@ const SERVICE_CERTS_BASE = `${__dirname}/../certs/service`; const NEX_CERTS_BASE = `${__dirname}/../certs/nex`; const LOCAL_CDN_BASE = `${__dirname}/../cdn`; -async function connect() { +export async function connect() { if (!disabledFeatures.redis) { client = redis.createClient(config.redis.client); client.on('error', (err) => console.log('Redis Client Error', err)); @@ -18,7 +19,7 @@ async function connect() { } } -async function setCachedFile(fileName, value) { +export async function setCachedFile(fileName, value) { if (disabledFeatures.redis) { memoryCache[fileName] = value; } else { @@ -26,7 +27,7 @@ async function setCachedFile(fileName, value) { } } -async function getCachedFile(fileName, encoding) { +export async function getCachedFile(fileName, encoding?) { let cachedFile; if (disabledFeatures.redis) { @@ -42,9 +43,9 @@ async function getCachedFile(fileName, encoding) { return cachedFile; } -// NEX server cache functions +// * NEX server cache functions -async function getNEXPublicKey(name, encoding) { +export async function getNEXPublicKey(name, encoding?) { let publicKey = await getCachedFile(`nex:${name}:public_key`, encoding); if (publicKey === null) { @@ -55,7 +56,7 @@ async function getNEXPublicKey(name, encoding) { return publicKey; } -async function getNEXPrivateKey(name, encoding) { +export async function getNEXPrivateKey(name, encoding?) { let privateKey = await getCachedFile(`nex:${name}:private_key`, encoding); if (privateKey === null) { @@ -66,7 +67,7 @@ async function getNEXPrivateKey(name, encoding) { return privateKey; } -async function getNEXSecretKey(name, encoding) { +export async function getNEXSecretKey(name, encoding?) { let secretKey = await getCachedFile(`nex:${name}:secret_key`, encoding); if (secretKey === null) { @@ -78,7 +79,7 @@ async function getNEXSecretKey(name, encoding) { return secretKey; } -async function getNEXAESKey(name, encoding) { +export async function getNEXAESKey(name, encoding?) { let aesKey = await getCachedFile(`nex:${name}:aes_key`, encoding); if (aesKey === null) { @@ -90,25 +91,25 @@ async function getNEXAESKey(name, encoding) { return aesKey; } -async function setNEXPublicKey(name, value) { +export async function setNEXPublicKey(name, value) { await setCachedFile(`nex:${name}:public_key`, value); } -async function setNEXPrivateKey(name, value) { +export async function setNEXPrivateKey(name, value) { await setCachedFile(`nex:${name}:private_key`, value); } -async function setNEXSecretKey(name, value) { +export async function setNEXSecretKey(name, value) { await setCachedFile(`nex:${name}:secret_key`, value); } -async function setNEXAESKey(name, value) { +export async function setNEXAESKey(name, value) { await setCachedFile(`nex:${name}:aes_key`, value); } -// 3rd party service cache functions +// * 3rd party service cache functions -async function getServicePublicKey(name, encoding) { +export async function getServicePublicKey(name, encoding?) { let publicKey = await getCachedFile(`service:${name}:public_key`, encoding); if (publicKey === null) { @@ -119,7 +120,7 @@ async function getServicePublicKey(name, encoding) { return publicKey; } -async function getServicePrivateKey(name, encoding) { +export async function getServicePrivateKey(name, encoding?) { let privateKey = await getCachedFile(`service:${name}:private_key`, encoding); if (privateKey === null) { @@ -130,7 +131,7 @@ async function getServicePrivateKey(name, encoding) { return privateKey; } -async function getServiceSecretKey(name, encoding) { +export async function getServiceSecretKey(name, encoding?) { let secretKey = await getCachedFile(`service:${name}:secret_key`, encoding); if (secretKey === null) { @@ -142,7 +143,7 @@ async function getServiceSecretKey(name, encoding) { return secretKey; } -async function getServiceAESKey(name, encoding) { +export async function getServiceAESKey(name, encoding?) { let aesKey = await getCachedFile(`service:${name}:aes_key`, encoding); if (aesKey === null) { @@ -154,25 +155,25 @@ async function getServiceAESKey(name, encoding) { return aesKey; } -async function setServicePublicKey(name, value) { +export async function setServicePublicKey(name, value) { await setCachedFile(`service:${name}:public_key`, value); } -async function setServicePrivateKey(name, value) { +export async function setServicePrivateKey(name, value) { await setCachedFile(`service:${name}:private_key`, value); } -async function setServiceSecretKey(name, value) { +export async function setServiceSecretKey(name, value) { await setCachedFile(`service:${name}:secret_key`, value); } -async function setServiceAESKey(name, value) { +export async function setServiceAESKey(name, value) { await setCachedFile(`service:${name}:aes_key`, value); } -// Local CDN cache functions +// * Local CDN cache functions -async function getLocalCDNFile(name, encoding) { +export async function getLocalCDNFile(name, encoding?) { let file = await getCachedFile(`local_cdn:${name}`, encoding); if (file === null) { @@ -185,11 +186,11 @@ async function getLocalCDNFile(name, encoding) { return file; } -async function setLocalCDNFile(name, value) { +export async function setLocalCDNFile(name, value) { await setCachedFile(`local_cdn:${name}`, value); } -module.exports = { +export default { connect, getNEXPublicKey, getNEXPrivateKey, diff --git a/src/config-manager.js b/src/config-manager.ts similarity index 77% rename from src/config-manager.js rename to src/config-manager.ts index 954f2a4..15a79d5 100644 --- a/src/config-manager.js +++ b/src/config-manager.ts @@ -1,58 +1,14 @@ -const fs = require('fs-extra'); -const get = require('lodash.get'); -const set = require('lodash.set'); -const logger = require('./logger'); +import fs from 'fs-extra'; +import get from 'lodash.get'; +import set from 'lodash.set'; +import dotenv from 'dotenv'; +import logger from './logger'; -require('dotenv').config(); +dotenv.config(); -/** - * @typedef {Object} Config - * @property {object} http HTTP server settings - * @property {number} http.port HTTP port the server will listen on - * @property {object} mongoose Mongose connection settings - * @property {string} mongoose.connection_string MongoDB connection string - * @property {object} mongoose.options MongoDB connection options - * @property {object} [redis] redis settings - * @property {string} [redis.client] redis client settings - * @property {string} [redis.client.url] redis server URL - * @property {object} [email] node-mailer client settings - * @property {string} [email.host] SMTP server address - * @property {number} [email.port] SMTP server port - * @property {boolean} [email.secure] Secure SMTP - * @property {string} [email.from] Email 'from' name/address - * @property {object} [email.auth] Email authentication settings - * @property {string} [email.auth.user] Email username - * @property {string} [email.auth.pass] Email password - * @property {object} [s3] s3 client settings - * @property {object} [s3.endpoint] s3 endpoint URL - * @property {string} [s3.key] s3 access key - * @property {string} [s3.secret] s3 access secret - * @property {object} [hcaptcha] hCaptcha settings - * @property {string} [hcaptcha.secret] hCaptcha secret - * @property {object} cdn CDN config settings - * @property {object} [cdn.subdomain] Subdomain used for serving CDN contents when s3 is disabled - * @property {string} [cdn.disk_path] Fully qualified file system path for storing and reading local CDN contents - * @property {string} cdn.base_url Base URL for CDN server - * @property {string} website_base Base URL for service website (used with emails) - */ +export let config: Record = {}; -/** - * @type {Config} - */ -let config = {}; - -/** - * @typedef {Object} DisabledFeatures - * @property {boolean} redis true if redis is disabled - * @property {boolean} email true if email sending is disabled - * @property {boolean} captcha true if captcha verification is disabled - * @property {boolean} s3 true if s3 services is disabled - */ - -/** - * @type {DisabledFeatures} - */ -const disabledFeatures = { +export const disabledFeatures = { redis: false, email: false, captcha: false, @@ -65,7 +21,7 @@ const requiredFields = [ ['cdn.base_url', 'PN_ACT_CONFIG_CDN_BASE_URL'] ]; -function configure() { +export function configure() { const usingEnv = process.env.PN_ACT_PREFER_ENV_CONFIG === 'true'; if (usingEnv) { @@ -129,22 +85,22 @@ function configure() { // * Check for required settings for (const requiredField of requiredFields) { - const [keyPath, env, convertType] = requiredField; + const [keyPath, envVarName, convertType] = requiredField; const configValue = get(config, keyPath); - const envValue = get(process.env, keyPath); + const envValue = get(process.env, envVarName); if (!configValue || (typeof configValue === 'string' && configValue.trim() === '')) { if (!envValue || envValue.trim() === '') { - logger.error(`Failed to locate required field ${keyPath}. Set ${keyPath} in config.json or the ${env} environment variable`); + logger.error(`Failed to locate required field ${keyPath}. Set ${keyPath} in config.json or the ${envVarName} environment variable`); process.exit(0); } else { - logger.info(`${keyPath} not found in config, using environment variable ${env}`); + logger.info(`${keyPath} not found in config, using environment variable ${envVarName}`); const newValue = envValue; - set(config, keyPath, convertType ? convertType(newValue) : newValue); + set(config, keyPath, convertType ? (convertType as Function)(newValue) : newValue); } } } @@ -324,11 +280,9 @@ function configure() { logger.warn(`s3 file storage disabled. Using disk-based file storage. Please ensure cdn.base_url config or PN_ACT_CONFIG_CDN_BASE env variable is set to point to this server with the subdomain being ${config.cdn.subdomain}`); } - - module.exports.config = config; } -module.exports = { +export default { configure, config, disabledFeatures diff --git a/src/database.js b/src/database.ts similarity index 73% rename from src/database.js rename to src/database.ts index 209b5ef..93bffff 100644 --- a/src/database.js +++ b/src/database.ts @@ -1,11 +1,12 @@ -const mongoose = require('mongoose'); -const bcrypt = require('bcrypt'); -const joi = require('joi'); -const util = require('./util'); -const { PNID } = require('./models/pnid'); -const { Server } = require('./models/server'); -const logger = require('./logger'); -const { config } = require('./config-manager'); +import mongoose from 'mongoose'; +import bcrypt from 'bcrypt'; +import joi from 'joi'; +import util from './util'; +import { PNID } from './models/pnid'; +import { Server } from './models/server'; +import logger from './logger'; +import { config } from './config-manager'; + const { connection_string, options } = config.mongoose; // TODO: Extend this later with more settings @@ -15,13 +16,11 @@ const discordConnectionSchema = joi.object({ let connection; -async function connect() { +export async function connect() { await mongoose.connect(connection_string, options); connection = mongoose.connection; connection.on('error', console.error.bind(console, 'connection error:')); - - module.exports.connection = connection; } function verifyConnected() { @@ -30,7 +29,7 @@ function verifyConnected() { } } -async function getUserByUsername(username) { +export async function getUserByUsername(username) { verifyConnected(); if (typeof username !== 'string') { @@ -44,7 +43,7 @@ async function getUserByUsername(username) { return user; } -async function getUserByPID(pid) { +export async function getUserByPID(pid) { verifyConnected(); const user = await PNID.findOne({ @@ -54,7 +53,7 @@ async function getUserByPID(pid) { return user; } -async function getUserByEmailAddress(email) { +export async function getUserByEmailAddress(email) { verifyConnected(); const user = await PNID.findOne({ @@ -64,17 +63,17 @@ async function getUserByEmailAddress(email) { return user; } -async function doesUserExist(username) { +export async function doesUserExist(username) { verifyConnected(); return !!await getUserByUsername(username); } -async function getUserBasic(token) { +export async function getUserBasic(token) { verifyConnected(); - // Wii U sends Basic auth as `username password`, where the password may not have spaces - // This is not to spec, but that is the consoles fault not ours + // * Wii U sends Basic auth as `username password`, where the password may not have spaces + // * This is not to spec, but that is the consoles fault not ours const [username, password] = Buffer.from(token, 'base64').toString().split(' '); const user = await getUserByUsername(username); @@ -91,7 +90,7 @@ async function getUserBasic(token) { return user; } -async function getUserBearer(token) { +export async function getUserBearer(token) { verifyConnected(); try { @@ -116,18 +115,19 @@ async function getUserBearer(token) { } } -async function getUserProfileJSONByPID(pid) { +export async function getUserProfileJSONByPID(pid) { verifyConnected(); const user = await getUserByPID(pid); - const device = user.get('devices')[0]; // Just grab the first device + const device = user.get('devices')[0]; // * Just grab the first device let device_attributes; if (device) { device_attributes = device.get('device_attributes').map(({name, value, created_date}) => { const deviceAttributeDocument = { name, - value + value, + created_date: '' }; if (created_date) { @@ -160,8 +160,9 @@ async function getUserProfileJSONByPID(pid) { primary: user.get('email.primary') ? 'Y' : 'N', reachable: user.get('email.reachable') ? 'Y' : 'N', type: 'DEFAULT', - updated_by: 'USER', // Can also be INTERNAL WS, don't know the difference - validated: user.get('email.validated') ? 'Y' : 'N' + updated_by: 'USER', // * Can also be INTERNAL WS, don't know the difference + validated: user.get('email.validated') ? 'Y' : 'N', + validated_date: '' }, mii: { status: 'COMPLETED', @@ -170,8 +171,8 @@ async function getUserProfileJSONByPID(pid) { mii_hash: user.get('mii.hash'), mii_images: { mii_image: { - // Images MUST be loaded over HTTPS or console ignores them - // Bunny CDN is the only CDN which seems to support TLS 1.0/1.1 (required) + // * Images MUST be loaded over HTTPS or console ignores them + // * Bunny CDN is the only CDN which seems to support TLS 1.0/1.1 (required) cached_url: `${config.cdn.base_url}/mii/${user.pid}/standard.tga`, id: user.get('mii.image_id'), url: `${config.cdn.base_url}/mii/${user.pid}/standard.tga`, @@ -208,13 +209,13 @@ function getServerByTitleId(titleId, accessMode) { }); } -async function addUserConnection(pnid, data, type) { +export async function addUserConnection(pnid, data, type) { if (type === 'discord') { return await addUserConnectionDiscord(pnid, data); } } -async function addUserConnectionDiscord(pnid, data) { +export async function addUserConnectionDiscord(pnid, data) { const valid = discordConnectionSchema.validate(data); if (valid.error) { @@ -237,14 +238,14 @@ async function addUserConnectionDiscord(pnid, data) { }; } -async function removeUserConnection(pnid, type) { - // Add more connections later? +export async function removeUserConnection(pnid, type) { + // * Add more connections later? if (type === 'discord') { return await removeUserConnectionDiscord(pnid); } } -async function removeUserConnectionDiscord(pnid) { +export async function removeUserConnectionDiscord(pnid) { await PNID.updateOne({ pid: pnid.get('pid') }, { $set: { 'connections.discord.id': '' @@ -257,7 +258,7 @@ async function removeUserConnectionDiscord(pnid) { }; } -module.exports = { +export default { connect, connection, getUserByUsername, @@ -270,5 +271,5 @@ module.exports = { getServer, getServerByTitleId, addUserConnection, - removeUserConnection, + removeUserConnection }; \ No newline at end of file diff --git a/src/logger.js b/src/logger.ts similarity index 90% rename from src/logger.js rename to src/logger.ts index 3a925e2..6d9cacf 100644 --- a/src/logger.js +++ b/src/logger.ts @@ -1,5 +1,7 @@ -const fs = require('fs-extra'); -require('colors'); +import fs from 'fs-extra'; +import colors from 'colors'; + +colors.enable(); const root = process.env.PN_ACT_LOGGER_PATH ? process.env.PN_ACT_LOGGER_PATH : `${__dirname}/..`; fs.ensureDirSync(`${root}/logs`); @@ -44,7 +46,7 @@ function info(input) { console.log(`${input}`.cyan.bold); } -module.exports = { +export default { success, error, warn, diff --git a/src/mailer.js b/src/mailer.ts similarity index 54% rename from src/mailer.js rename to src/mailer.ts index 7292f6e..6d2a878 100644 --- a/src/mailer.js +++ b/src/mailer.ts @@ -1,7 +1,8 @@ -const nodemailer = require('nodemailer'); -const { config, disabledFeatures } = require('./config-manager'); -const path = require('path'); -const fs = require("fs"); +import path from 'node:path'; +import fs from 'node:fs'; +import nodemailer from 'nodemailer'; +import { config, disabledFeatures } from './config-manager'; + const genericEmailTemplate = fs.readFileSync(path.join(__dirname, './assets/emails/genericTemplate.html'), 'utf8'); const confirmationEmailTemplate = fs.readFileSync(path.join(__dirname, './assets/emails/confirmationTemplate.html'), 'utf8'); @@ -11,30 +12,10 @@ if (!disabledFeatures.email) { transporter = nodemailer.createTransport(config.email); } -/** - @param {Object} options - @param {String} options.to The address of the recipient - @param {String} options.subject The subject of the email - - @param {String} options.username The username of the user (shown in the greeting) - @param {String} options.preview The preview text of the email (shown in the inbox by the email client) - @param {String} options.text The text version of the email - - @param {String} options.paragraph The main content of the email - - @param {Object} options.confirmation Whether or not the email is a confirmation email - @param {String} options.confirmation.href The link to the confirmation page - @param {String} options.confirmation.code The confirmation code - - @param {Object} options.link An object containing the link to be shown in the email - @param {String} options.link.href The URL of the link - @param {String} options.link.text The text of the link - -*/ -async function sendMail(options) { +export async function sendMail(options) { if (!disabledFeatures.email) { const { to, subject, username, paragraph, preview, text, link, confirmation } = options; - + let html = confirmation ? confirmationEmailTemplate : genericEmailTemplate; html = html.replace(/{{username}}/g, username); @@ -51,7 +32,7 @@ async function sendMail(options) { } await transporter.sendMail({ - from: config.email.from, + from: config.email.from, to, subject, text, @@ -60,6 +41,6 @@ async function sendMail(options) { } } -module.exports = { +export default { sendMail }; diff --git a/src/middleware/api.js b/src/middleware/api.ts similarity index 62% rename from src/middleware/api.js rename to src/middleware/api.ts index d6489d1..b8a3b3e 100644 --- a/src/middleware/api.js +++ b/src/middleware/api.ts @@ -1,7 +1,7 @@ -const xmlbuilder = require('xmlbuilder'); -const database = require('../database'); +import xmlbuilder from 'xmlbuilder'; +import database from '../database'; -async function APIMiddleware(request, response, next) { +export async function APIMiddleware(request, _response, next) { const { headers } = request; if (!headers.authorization || !(headers.authorization.startsWith('Bearer'))) { @@ -16,4 +16,4 @@ async function APIMiddleware(request, response, next) { return next(); } -module.exports = APIMiddleware; \ No newline at end of file +export default APIMiddleware; \ No newline at end of file diff --git a/src/middleware/cemu.js b/src/middleware/cemu.ts similarity index 56% rename from src/middleware/cemu.js rename to src/middleware/cemu.ts index c6e8441..22f3e73 100644 --- a/src/middleware/cemu.js +++ b/src/middleware/cemu.ts @@ -1,4 +1,4 @@ -async function CemuMiddleware(request, response, next) { +export async function CemuMiddleware(request, _response, next) { const subdomain = request.subdomains.reverse().join('.'); request.isCemu = subdomain === 'c.account'; @@ -6,4 +6,4 @@ async function CemuMiddleware(request, response, next) { return next(); } -module.exports = CemuMiddleware; \ No newline at end of file +export default CemuMiddleware; \ No newline at end of file diff --git a/src/middleware/client-header.js b/src/middleware/client-header.ts similarity index 72% rename from src/middleware/client-header.js rename to src/middleware/client-header.ts index 1999223..7f7a581 100644 --- a/src/middleware/client-header.js +++ b/src/middleware/client-header.ts @@ -1,14 +1,14 @@ -const xmlbuilder = require('xmlbuilder'); +import xmlbuilder from 'xmlbuilder'; const VALID_CLIENT_ID_SECRET_PAIRS = { - // 'Key' is the client ID, 'Value' is the client secret - 'a2efa818a34fa16b8afbc8a74eba3eda': 'c91cdb5658bd4954ade78533a339cf9a', // Possibly WiiU exclusive? - 'daf6227853bcbdce3d75baee8332b': '3eff548eac636e2bf45bb7b375e7b6b0', // Possibly 3DS exclusive? - 'ea25c66c26b403376b4c5ed94ab9cdea': 'd137be62cb6a2b831cad8c013b92fb55', // Possibly 3DS exclusive? + // * 'Key' is the client ID, 'Value' is the client secret + 'a2efa818a34fa16b8afbc8a74eba3eda': 'c91cdb5658bd4954ade78533a339cf9a', // * Possibly WiiU exclusive? + 'daf6227853bcbdce3d75baee8332b': '3eff548eac636e2bf45bb7b375e7b6b0', // * Possibly 3DS exclusive? + 'ea25c66c26b403376b4c5ed94ab9cdea': 'd137be62cb6a2b831cad8c013b92fb55', // * Possibly 3DS exclusive? }; -function nintendoClientHeaderCheck(request, response, next) { +export function nintendoClientHeaderCheck(request, response, next) { response.set('Content-Type', 'text/xml'); response.set('Server', 'Nintendo 3DS (http)'); response.set('X-Nintendo-Date', new Date().getTime()); @@ -35,4 +35,4 @@ function nintendoClientHeaderCheck(request, response, next) { return next(); } -module.exports = nintendoClientHeaderCheck; \ No newline at end of file +export default nintendoClientHeaderCheck; \ No newline at end of file diff --git a/src/middleware/device-certificate.js b/src/middleware/device-certificate.ts similarity index 56% rename from src/middleware/device-certificate.js rename to src/middleware/device-certificate.ts index 7598091..70909d2 100644 --- a/src/middleware/device-certificate.js +++ b/src/middleware/device-certificate.ts @@ -1,6 +1,6 @@ -const NintendoCertificate = require('../nintendo-certificate'); +import NintendoCertificate from '../nintendo-certificate'; -async function deviceCertificateMiddleware(request, response, next) { +export async function deviceCertificateMiddleware(request, _response, next) { const { headers } = request; if (!headers['x-nintendo-device-cert']) { @@ -13,4 +13,4 @@ async function deviceCertificateMiddleware(request, response, next) { return next(); } -module.exports = deviceCertificateMiddleware; \ No newline at end of file +export default deviceCertificateMiddleware; \ No newline at end of file diff --git a/src/middleware/nasc.js b/src/middleware/nasc.ts similarity index 93% rename from src/middleware/nasc.js rename to src/middleware/nasc.ts index 4bb91a9..ece25bb 100644 --- a/src/middleware/nasc.js +++ b/src/middleware/nasc.ts @@ -1,11 +1,12 @@ -const crypto = require('crypto'); -const { Device } = require('../models/device'); -const { NEXAccount } = require('../models/nex-account'); -const util = require('../util'); -const database = require('../database'); -const NintendoCertificate = require('../nintendo-certificate'); +import crypto from 'node:crypto'; +import { Device } from '../models/device'; +import { NEXAccount } from '../models/nex-account'; +import util from '../util'; +import database from '../database'; +import NintendoCertificate from '../nintendo-certificate'; +import logger from '../logger'; -async function NASCMiddleware(request, response, next) { +export async function NASCMiddleware(request, response, next) { const requestParams = request.body; if (!requestParams.action || @@ -210,4 +211,4 @@ function validNintendoMACAddress(macAddress) { return MAC_REGEX.test(macAddress); } -module.exports = NASCMiddleware; \ No newline at end of file +export default NASCMiddleware; \ No newline at end of file diff --git a/src/middleware/pnid.js b/src/middleware/pnid.ts similarity index 83% rename from src/middleware/pnid.js rename to src/middleware/pnid.ts index d28d745..3cbc428 100644 --- a/src/middleware/pnid.js +++ b/src/middleware/pnid.ts @@ -1,7 +1,7 @@ -const xmlbuilder = require('xmlbuilder'); -const database = require('../database'); +import xmlbuilder from 'xmlbuilder'; +import database from '../database'; -async function PNIDMiddleware(request, response, next) { +export async function PNIDMiddleware(request, response, next) { const { headers } = request; if (!headers.authorization || !(headers.authorization.startsWith('Bearer') || headers.authorization.startsWith('Basic'))) { @@ -62,4 +62,4 @@ async function PNIDMiddleware(request, response, next) { return next(); } -module.exports = PNIDMiddleware; \ No newline at end of file +export default PNIDMiddleware; \ No newline at end of file diff --git a/src/middleware/ratelimit.js b/src/middleware/ratelimit.ts similarity index 61% rename from src/middleware/ratelimit.js rename to src/middleware/ratelimit.ts index 0951794..ef213a5 100644 --- a/src/middleware/ratelimit.js +++ b/src/middleware/ratelimit.ts @@ -1,7 +1,7 @@ -const crypto = require('crypto'); -const ratelimit = require('express-rate-limit'); +import crypto from 'node:crypto'; +import ratelimit from 'express-rate-limit'; -module.exports = ratelimit({ +export default ratelimit({ windowMs: 60 * 1000, max: 1, keyGenerator: request => { diff --git a/src/middleware/xml-parser.js b/src/middleware/xml-parser.ts similarity index 79% rename from src/middleware/xml-parser.js rename to src/middleware/xml-parser.ts index 323e045..b6cc5e2 100644 --- a/src/middleware/xml-parser.js +++ b/src/middleware/xml-parser.ts @@ -1,7 +1,7 @@ -const xmlbuilder = require('xmlbuilder'); -const { document: xmlParser } = require('xmlbuilder2'); +import xmlbuilder from 'xmlbuilder'; +import { document as xmlParser } from 'xmlbuilder2'; -function XMLMiddleware(request, response, next) { +export function XMLMiddleware(request, response, next) { if (request.method == 'POST' || request.method == 'PUT') { const headers = request.headers; let body = ''; @@ -50,4 +50,4 @@ function XMLMiddleware(request, response, next) { } } -module.exports = XMLMiddleware; \ No newline at end of file +export default XMLMiddleware; \ No newline at end of file diff --git a/src/mii.js b/src/mii.js deleted file mode 100644 index 437ae16..0000000 --- a/src/mii.js +++ /dev/null @@ -1,214 +0,0 @@ -const KaitaiStream = require('kaitai-struct/KaitaiStream'); - -class Mii extends KaitaiStream { - constructor(arrayBuffer, byteOffset) { - super(arrayBuffer, byteOffset); - - this.decode(); - } - - decode() { - // Decode raw data - // A lot of this goes unused - this.unknown1 = this.readU1(); - this.characterSet = this.readBitsIntBe(2); - this.regionLock = this.readBitsIntBe(2); - this.profanityFlag = this.readBitsIntBe(1) !== 0; - this.copying = this.readBitsIntBe(1) !== 0; - this.unknown2 = this.readBitsIntBe(2); - this.slotIndex = this.readBitsIntBe(4); - this.pageIndex = this.readBitsIntBe(4); - this.version = this.readBitsIntBe(4); - this.unknown3 = this.readBitsIntBe(4); - this.systemId = Array(8).fill().map(() => this.readU1()); - this.avatarId = Array(4).fill().map(() => this.readU1()); - this.clientId = Array(6).fill().map(() => this.readU1()); - this.padding = this.readU2le(); - this.miiMetaData = this.readU2le(); - this.miiName = Buffer.from(this.readBytes(20)).toString('utf16le'); - this.height = this.readU1(); - this.build = this.readU1(); - this.faceColor = this.readBitsIntBe(3); - this.faceType = this.readBitsIntBe(4); - this.mingle = this.readBitsIntBe(1) !== 0; - this.faceMakeup = this.readBitsIntBe(4); - this.faceWrinkles = this.readBitsIntBe(4); - this.alignToByte(); - this.hairType = this.readU1(); - this.unknown5 = this.readBitsIntBe(4); - this.hairFlip = this.readBitsIntBe(1) !== 0; - this.hairColor = this.readBitsIntBe(3); - this.alignToByte(); - this.eyeData = this.readU4le(); - this.eyebrowData = this.readU4le(); - this.noseData = this.readU2le(); - this.mouthData = this.readU2le(); - this.mouthData2 = this.readU2le(); - this.facialHairData = this.readU2le(); - this.glassesData = this.readU2le(); - this.moleData = this.readU2le(); - this.creatorName = Buffer.from(this.readBytes(20)).toString('utf16le'); - this.padding2 = this.readU2le(); - this.checksum = this.readU2le(); - - // Carve out more specific data from the above values - // TODO: read these bits directly instead of getting them later - - this.gender = (this.miiMetaData & 1); - this.birthMonth = ((this.miiMetaData >> 1) & 15); - this.birthDay = ((this.miiMetaData >> 5) & 31); - this.favoriteColor = ((this.miiMetaData >> 10) & 15); - this.favorite = ((this.miiMetaData >> 14) & 1); - - this.eyeType = (this.eyeData & 63); - this.eyeColor = ((this.eyeData >> 6) & 7); - this.eyeSize = ((this.eyeData >> 9) & 7); - this.eyeStretch = ((this.eyeData >> 13) & 7); - this.eyeRotation = ((this.eyeData >> 16) & 31); - this.eyeHorizontal = ((this.eyeData >> 21) & 15); - this.eyeVertical = ((this.eyeData >> 25) & 31); - - this.eyebrowType = (this.eyebrowData & 31); - this.eyebrowColor = ((this.eyebrowData >> 5) & 7); - this.eyebrowSize = ((this.eyebrowData >> 8) & 15); - this.eyebrowStretch = ((this.eyebrowData >> 12) & 7); - this.eyebrowRotation = ((this.eyebrowData >> 16) & 15); - this.eyebrowHorizontal = ((this.eyebrowData >> 21) & 15); - this.eyebrowVertical = ((this.eyebrowData >> 25) & 31); - - this.noseType = (this.noseData & 31); - this.noseSize = ((this.noseData >> 5) & 15); - this.noseVertical = ((this.noseData >> 9) & 31); - - - this.mouthType = (this.mouthData & 63); - this.mouthColor = ((this.mouthData >> 6) & 7); - this.mouthSize = ((this.mouthData >> 9) & 15); - this.mouthStretch = ((this.mouthData >> 13) & 7); - - this.mouthVertical = (this.mouthData2 & 31); - this.facialHairMustache = ((this.mouthData2 >> 5) & 7); - - this.facialHairType = (this.facialHairData & 7); - this.facialHairColor = ((this.facialHairData >> 3) & 7); - this.facialHairSize = ((this.facialHairData >> 6) & 15); - this.facialHairVertical = ((this.facialHairData >> 10) & 31); - - this.glassesType = (this.glassesData & 15); - this.glassesColor = (this.glassesData >> 4) & 7; - this.glassesSize = (this.glassesData >> 7) & 15; - this.glassesVertical = (this.glassesData >> 11) & 15; - - this.moleEnable = (this.moleData >> 15); - this.moleSize = ((this.moleData >> 1) & 15); - this.moleHorizontal = ((this.moleData >> 5) & 31); - this.moleVertical = ((this.moleData >> 10) & 31); - } - - toStudioMii() { - /* - Can also disable randomization with: - - let miiStudioData = Buffer.alloc(0x2F); - let next = 256; - - and removing "randomizer" and the "miiStudioData.writeUInt8(randomizer);" call - */ - const miiStudioData = Buffer.alloc(0x2F); - const randomizer = Math.floor(256 * Math.random()); - let next = randomizer; - let pos = 1; - - function encodeMiiPart(partValue) { - const encoded = (7 + (partValue ^ next)) % 256; - next = encoded; - - miiStudioData.writeUInt8(encoded, pos); - pos++; - } - - miiStudioData.writeUInt8(randomizer); - - if (this.facialHairColor === 0) { - encodeMiiPart(8); - } else { - encodeMiiPart(this.facialHairColor); - } - - encodeMiiPart(this.facialHairType); - encodeMiiPart(this.build); - encodeMiiPart(this.eyeStretch); - encodeMiiPart(this.eyeColor + 8); - encodeMiiPart(this.eyeRotation); - encodeMiiPart(this.eyeSize); - encodeMiiPart(this.eyeType); - encodeMiiPart(this.eyeHorizontal); - encodeMiiPart(this.eyeVertical); - encodeMiiPart(this.eyebrowStretch); - - if (this.eyebrowColor === 0) { - encodeMiiPart(8); - } else { - encodeMiiPart(this.eyebrowColor); - } - - encodeMiiPart(this.eyebrowRotation); - encodeMiiPart(this.eyebrowSize); - encodeMiiPart(this.eyebrowType); - encodeMiiPart(this.eyebrowHorizontal); - encodeMiiPart(this.eyebrowVertical); - encodeMiiPart(this.faceColor); - encodeMiiPart(this.faceMakeup); - encodeMiiPart(this.faceType); - encodeMiiPart(this.faceWrinkles); - encodeMiiPart(this.favoriteColor); - encodeMiiPart(this.gender); - - if (this.glassesColor == 0) { - encodeMiiPart(8); - } else if (this.glassesColor < 6) { - encodeMiiPart(this.glassesColor + 13); - } else { - encodeMiiPart(0); - } - - encodeMiiPart(this.glassesSize); - encodeMiiPart(this.glassesType); - encodeMiiPart(this.glassesVertical); - - if (this.hairColor == 0) { - encodeMiiPart(8); - } else { - encodeMiiPart(this.hairColor); - } - - encodeMiiPart(this.hairFlip ? 1 : 0); - encodeMiiPart(this.hairType); - encodeMiiPart(this.height); - encodeMiiPart(this.moleSize); - encodeMiiPart(this.moleEnable); - encodeMiiPart(this.moleHorizontal); - encodeMiiPart(this.moleVertical); - encodeMiiPart(this.mouthStretch); - - if (this.mouthColor < 4) { - encodeMiiPart(this.mouthColor + 19); - } else { - encodeMiiPart(0); - } - - encodeMiiPart(this.mouthSize); - encodeMiiPart(this.mouthType); - encodeMiiPart(this.mouthVertical); - encodeMiiPart(this.facialHairSize); - encodeMiiPart(this.facialHairMustache); - encodeMiiPart(this.facialHairVertical); - encodeMiiPart(this.noseSize); - encodeMiiPart(this.noseType); - encodeMiiPart(this.noseVertical); - - return miiStudioData; - } -} - -module.exports = Mii; \ No newline at end of file diff --git a/src/models/device.js b/src/models/device.ts similarity index 62% rename from src/models/device.js rename to src/models/device.ts index 00d667b..820a3f4 100644 --- a/src/models/device.js +++ b/src/models/device.ts @@ -1,14 +1,14 @@ -const { Schema, model } = require('mongoose'); +import { Schema, model } from 'mongoose'; -const DeviceAttributeSchema = new Schema({ +export const DeviceAttributeSchema = new Schema({ created_date: String, name: String, - value: String, + value: String }); -const DeviceAttribute = model('DeviceAttribute', DeviceAttributeSchema); +export const DeviceAttribute: IDeviceAttributeModel = model('DeviceAttribute', DeviceAttributeSchema); -const DeviceSchema = new Schema({ +export const DeviceSchema = new Schema({ is_emulator: { type: Boolean, default: false @@ -22,7 +22,7 @@ const DeviceSchema = new Schema({ 'ftr', // Nintendo 2DS 'ktr', // New Nintendo 3DS 'red', // New Nintendo 3DS XL - 'jan' // New Nintendo 2DS XL + 'jan' // New Nintendo 2DS XL ] }, device_id: Number, @@ -48,9 +48,9 @@ const DeviceSchema = new Schema({ } }); -const Device = model('Device', DeviceSchema); +export const Device: IDeviceModel = model('Device', DeviceSchema); -module.exports = { +export default { DeviceSchema, Device, DeviceAttributeSchema, diff --git a/src/models/nex-account.js b/src/models/nex-account.ts similarity index 82% rename from src/models/nex-account.js rename to src/models/nex-account.ts index fbffd69..6a4b509 100644 --- a/src/models/nex-account.js +++ b/src/models/nex-account.ts @@ -1,7 +1,7 @@ -const { Schema, model } = require('mongoose'); -const uniqueValidator = require('mongoose-unique-validator'); +import { Schema, model } from 'mongoose'; +import uniqueValidator from 'mongoose-unique-validator'; -const NEXAccountSchema = new Schema({ +export const NEXAccountSchema = new Schema({ device_type: { type: String, enum: [ @@ -23,7 +23,7 @@ const NEXAccountSchema = new Schema({ server_access_level: { type: String, default: 'prod' // everyone is in production by default - }, + } }); NEXAccountSchema.plugin(uniqueValidator, { message: '{PATH} already in use.' }); @@ -44,9 +44,11 @@ NEXAccountSchema.methods.generatePID = async function () { const inuse = await NEXAccount.findOne({ pid }); - pid = (inuse ? await NEXAccount.generatePID() : pid); - - this.set('pid', pid); + if (inuse) { + await NEXAccount.generatePID(); + } else { + this.set('pid', pid); + } }; NEXAccountSchema.methods.generatePassword = function () { @@ -66,9 +68,9 @@ NEXAccountSchema.methods.generatePassword = function () { this.set('password', output.join('')); }; -const NEXAccount = model('NEXAccount', NEXAccountSchema); +export const NEXAccount: INEXAccountModel = model('NEXAccount', NEXAccountSchema); -module.exports = { +export default { NEXAccountSchema, - NEXAccount, + NEXAccount }; \ No newline at end of file diff --git a/src/models/pnid.js b/src/models/pnid.ts similarity index 77% rename from src/models/pnid.js rename to src/models/pnid.ts index 5cf0f99..73b0079 100644 --- a/src/models/pnid.js +++ b/src/models/pnid.ts @@ -1,15 +1,14 @@ -const { Schema, model } = require('mongoose'); -const uniqueValidator = require('mongoose-unique-validator'); -const bcrypt = require('bcrypt'); -const crypto = require('crypto'); -const imagePixels = require('image-pixels'); -const TGA = require('tga'); -const got = require('got'); -const util = require('../util'); -const { DeviceSchema } = require('./device'); -const Mii = require('mii-js'); +import crypto from 'node:crypto'; +import { Schema, model } from 'mongoose'; +import uniqueValidator from 'mongoose-unique-validator'; +import imagePixels from 'image-pixels'; +import TGA from 'tga'; +import got from 'got'; +import Mii from 'mii-js'; +import { DeviceSchema } from './device'; +import util from '../util'; -const PNIDSchema = new Schema({ +export const PNIDSchema = new Schema({ access_level: { type: Number, default: 0 // 0: standard, 1: tester, 2: mod?, 3: dev @@ -125,9 +124,11 @@ PNIDSchema.methods.generatePID = async function() { pid }); - pid = (inuse ? await PNID.generatePID() : pid); - - this.set('pid', pid); + if (inuse) { + await PNID.generatePID(); + } else { + this.set('pid', pid); + } }; PNIDSchema.methods.generateEmailValidationCode = async function() { @@ -145,33 +146,11 @@ PNIDSchema.methods.generateEmailValidationToken = async function() { 'identification.email_token': token }); - token = (inuse ? await PNID.generateEmailValidationToken() : token); - - this.set('identification.email_token', token); -}; - -PNIDSchema.methods.getDevice = async function(document) { - const devices = this.get('devices'); - - return devices.find(device => { - return ( - (device.device_id === document.device_id) && - (device.device_type === document.device_type) && - (device.serial === document.serial) - ); - }); -}; - -PNIDSchema.methods.addDevice = async function(device) { - this.devices.push(device); - - await this.save(); -}; - -PNIDSchema.methods.removeDevice = async function(device) { - this.devices = this.devices.filter(({ _id }) => _id !== device._id); - - await this.save(); + if (inuse) { + await PNID.generateEmailValidationToken(); + } else { + this.set('identification.email_token', token); + } }; PNIDSchema.methods.updateMii = async function({name, primary, data}) { @@ -231,9 +210,9 @@ PNIDSchema.methods.getServerMode = function () { return serverMode; }; -const PNID = model('PNID', PNIDSchema); +export const PNID: IPNIDModel = model('PNID', PNIDSchema); -module.exports = { +export default { PNIDSchema, PNID }; \ No newline at end of file diff --git a/src/models/server.js b/src/models/server.ts similarity index 66% rename from src/models/server.js rename to src/models/server.ts index 8b70a7a..e376aac 100644 --- a/src/models/server.js +++ b/src/models/server.ts @@ -1,7 +1,7 @@ -const { Schema, model } = require('mongoose'); -const uniqueValidator = require('mongoose-unique-validator'); +import { Schema, model } from 'mongoose'; +import uniqueValidator from 'mongoose-unique-validator'; -const ServerSchema = new Schema({ +export const ServerSchema = new Schema({ ip: String, // Example: 1.1.1.1 port: Number, // Example: 60000 service_name: String, // Example: friends @@ -15,9 +15,9 @@ const ServerSchema = new Schema({ ServerSchema.plugin(uniqueValidator, { message: '{PATH} already in use.' }); -const Server = model('Server', ServerSchema); +export const Server: IServerModel = model('Server', ServerSchema); -module.exports = { +export default { ServerSchema, Server, }; \ No newline at end of file diff --git a/src/nintendo-certificate.js b/src/nintendo-certificate.ts similarity index 92% rename from src/nintendo-certificate.js rename to src/nintendo-certificate.ts index 6df33ee..5a35e85 100644 --- a/src/nintendo-certificate.js +++ b/src/nintendo-certificate.ts @@ -1,7 +1,5 @@ -// Parse Nintendo certificates - -const crypto = require('crypto'); -const NodeRSA = require('node-rsa'); +import crypto from 'node:crypto'; +import NodeRSA from 'node-rsa'; const WIIU_DEVICE_PUB_PEM = `-----BEGIN PUBLIC KEY----- MFIwEAYHKoZIzj0CAQYFK4EEABsDPgAEAP1WBBgs8XUJIQDDCK5IOZEbb5+h1TqV @@ -70,7 +68,19 @@ const SIGNATURE_SIZES = { } }; -class NintendoCertificate { +export class NintendoCertificate { + _certificate: Buffer; + _certificateBody: Buffer; + signatureType: number; + signature: Buffer; + issuer: string; + keyType: number; + certificateName: string; + ngKeyId: number; + publicKey: Buffer; + valid: boolean; + publicKeyData: Buffer; + constructor(certificate) { this._certificate = null; this._certificateBody = null; @@ -185,10 +195,10 @@ class NintendoCertificate { // https://github.com/Myriachan _verifySignatureECDSA() { const pem = this.issuer == 'Root-CA00000003-MS00000012' ? WIIU_DEVICE_PUB_PEM : CTR_DEVICE_PUB_PEM; - const key = crypto.createPublicKey({ - key: pem - }); - key.dsaEncoding = 'ieee-p1363'; + const key = { + key: pem, + dsaEncoding: 'ieee-p1363' as crypto.DSAEncoding + }; this.valid = crypto.verify('sha256', this._certificateBody, key, this.signature); } @@ -205,4 +215,4 @@ class NintendoCertificate { } } -module.exports = NintendoCertificate; \ No newline at end of file +export default NintendoCertificate; \ No newline at end of file diff --git a/src/server.js b/src/server.ts similarity index 67% rename from src/server.js rename to src/server.ts index a0b86cd..a373b69 100644 --- a/src/server.js +++ b/src/server.ts @@ -4,27 +4,27 @@ const configManager = require('./config-manager'); configManager.configure(); -const express = require('express'); -const morgan = require('morgan'); -const xmlparser = require('./middleware/xml-parser'); -const cache = require('./cache'); -const database = require('./database'); -const util = require('./util'); -const logger = require('./logger'); +import express from 'express'; +import morgan from 'morgan'; +import xmlparser from './middleware/xml-parser'; +import cache from './cache'; +import database from './database'; +import util from './util'; +import logger from './logger'; + +import conntest from './services/conntest'; +import nnid from './services/nnid'; +import nasc from './services/nasc'; +import datastore from './services/datastore'; +import api from './services/api'; +import localcdn from './services/local-cdn'; +import assets from './services/assets'; const { config } = configManager; const { http: { port } } = config; const app = express(); -const conntest = require('./services/conntest'); -const nnid = require('./services/nnid'); -const nasc = require('./services/nasc'); -const datastore = require('./services/datastore'); -const api = require('./services/api'); -const localcdn = require('./services/local-cdn'); -const assets = require('./services/assets'); - // START APPLICATION // Create router @@ -55,7 +55,7 @@ app.use((request, response) => { response.set('Content-Type', 'text/xml'); response.set('Server', 'Nintendo 3DS (http)'); - response.set('X-Nintendo-Date', new Date().getTime()); + response.set('X-Nintendo-Date', new Date().getTime().toString()); response.status(404); response.send('0008Not Found'); @@ -63,7 +63,7 @@ app.use((request, response) => { // non-404 error handler logger.info('Creating non-404 status handler'); -app.use((error, request, response) => { +app.use((error, request, response, _next) => { const status = error.status || 500; const fullUrl = util.fullUrl(request); const deviceId = request.headers['X-Nintendo-Device-ID'] || 'Unknown'; diff --git a/src/services/api/index.js b/src/services/api/index.ts similarity index 74% rename from src/services/api/index.js rename to src/services/api/index.ts index 5aee4a6..56d7551 100644 --- a/src/services/api/index.js +++ b/src/services/api/index.ts @@ -1,11 +1,11 @@ // handles "api.nintendo.cc" endpoints -const express = require('express'); -const subdomain = require('express-subdomain'); -const cors = require('cors'); -const APIMiddleware = require('../../middleware/api'); -const logger = require('../../logger'); -const routes = require('./routes'); +import express from 'express'; +import subdomain from 'express-subdomain'; +import cors from 'cors'; +import APIMiddleware from '../../middleware/api'; +import routes from './routes'; +import logger from '../../logger'; // Router to handle the subdomain restriction const api = express.Router(); @@ -33,4 +33,4 @@ const router = express.Router(); logger.info('[USER API] Creating \'api\' subdomain'); router.use(subdomain('api', api)); -module.exports = router; \ No newline at end of file +export default router; \ No newline at end of file diff --git a/src/services/api/routes/index.js b/src/services/api/routes/index.js deleted file mode 100644 index 217a96d..0000000 --- a/src/services/api/routes/index.js +++ /dev/null @@ -1,11 +0,0 @@ -module.exports = { - V1: { - CONNECTIONS: require('./v1/connections'), - EMAIL: require('./v1/email'), - FORGOT_PASSWORD: require('./v1/forgotPassword'), - LOGIN: require('./v1/login'), - REGISTER: require('./v1/register'), - RESET_PASSWORD: require('./v1/resetPassword'), - USER: require('./v1/user'), - } -}; \ No newline at end of file diff --git a/src/services/api/routes/index.ts b/src/services/api/routes/index.ts new file mode 100644 index 0000000..9956240 --- /dev/null +++ b/src/services/api/routes/index.ts @@ -0,0 +1,19 @@ +import connections_v1 from './v1/connections'; +import email_v1 from './v1/email'; +import forgotPassword_v1 from './v1/forgotPassword'; +import login_v1 from './v1/login'; +import register_v1 from './v1/register'; +import resetPassword_v1 from './v1/resetPassword'; +import user_v1 from './v1/user'; + +export default { + V1: { + CONNECTIONS: connections_v1, + EMAIL: email_v1, + FORGOT_PASSWORD: forgotPassword_v1, + LOGIN: login_v1, + REGISTER: register_v1, + RESET_PASSWORD: resetPassword_v1, + USER: user_v1 + } +}; \ No newline at end of file diff --git a/src/services/api/routes/v1/connections.js b/src/services/api/routes/v1/connections.ts similarity index 92% rename from src/services/api/routes/v1/connections.js rename to src/services/api/routes/v1/connections.ts index 76e41c7..58aea18 100644 --- a/src/services/api/routes/v1/connections.js +++ b/src/services/api/routes/v1/connections.ts @@ -1,5 +1,7 @@ -const router = require('express').Router(); -const database = require('../../../../database'); +import { Router } from 'express'; +import database from '../../../../database'; + +const router = Router(); const VALID_CONNECTION_TYPES = [ 'discord' @@ -74,4 +76,4 @@ router.delete('/remove/:type', async (request, response) => { response.status(result.status).json(result); }); -module.exports = router; \ No newline at end of file +export default router; \ No newline at end of file diff --git a/src/services/api/routes/v1/email.js b/src/services/api/routes/v1/email.ts similarity index 67% rename from src/services/api/routes/v1/email.js rename to src/services/api/routes/v1/email.ts index 561e651..22e38c6 100644 --- a/src/services/api/routes/v1/email.js +++ b/src/services/api/routes/v1/email.ts @@ -1,10 +1,18 @@ -const router = require('express').Router(); -const moment = require('moment'); -const { PNID } = require('../../../../models/pnid'); -const util = require('../../../../util'); +import { Router } from 'express'; +import moment from 'moment'; +import { PNID } from '../../../../models/pnid'; +import util from '../../../../util'; + +const router = Router(); router.get('/verify', async (request, response) => { - const { token } = request.query; + let token: string; + + if (Array.isArray(request.query.token)) { + token = request.query.token[0] as string; + } else { + token = request.query.token as string; + } if (!token || token.trim() == '') { return response.status(400).json({ @@ -39,4 +47,4 @@ router.get('/verify', async (request, response) => { response.status(200).send('Email validated. You may close this window'); }); -module.exports = router; \ No newline at end of file +export default router; \ No newline at end of file diff --git a/src/services/api/routes/v1/forgotPassword.js b/src/services/api/routes/v1/forgotPassword.ts similarity index 72% rename from src/services/api/routes/v1/forgotPassword.js rename to src/services/api/routes/v1/forgotPassword.ts index 7dd4948..3a46580 100644 --- a/src/services/api/routes/v1/forgotPassword.js +++ b/src/services/api/routes/v1/forgotPassword.ts @@ -1,7 +1,9 @@ -const router = require('express').Router(); -const validator = require('validator'); -const database = require('../../../../database'); -const util = require('../../../../util'); +import { Router } from 'express'; +import validator from 'validator'; +import database from '../../../../database'; +import util from '../../../../util'; + +const router = Router(); router.post('/', async (request, response) => { const { body } = request; @@ -33,4 +35,4 @@ router.post('/', async (request, response) => { }); }); -module.exports = router; \ No newline at end of file +export default router; \ No newline at end of file diff --git a/src/services/api/routes/v1/login.js b/src/services/api/routes/v1/login.ts similarity index 91% rename from src/services/api/routes/v1/login.js rename to src/services/api/routes/v1/login.ts index e342ea5..7b63207 100644 --- a/src/services/api/routes/v1/login.js +++ b/src/services/api/routes/v1/login.ts @@ -1,9 +1,11 @@ -const router = require('express').Router(); -const bcrypt = require('bcrypt'); -const fs = require('fs-extra'); -const database = require('../../../../database'); -const cache = require('../../../../cache'); -const util = require('../../../../util'); +import { Router } from 'express'; +import bcrypt from 'bcrypt'; +import fs from 'fs-extra'; +import database from '../../../../database'; +import cache from '../../../../cache'; +import util from '../../../../util'; + +const router = Router(); /** * [POST] @@ -126,4 +128,4 @@ router.post('/', async (request, response) => { }); }); -module.exports = router; \ No newline at end of file +export default router; \ No newline at end of file diff --git a/src/services/api/routes/v1/register.js b/src/services/api/routes/v1/register.ts similarity index 92% rename from src/services/api/routes/v1/register.js rename to src/services/api/routes/v1/register.ts index 860fad4..8047a2c 100644 --- a/src/services/api/routes/v1/register.js +++ b/src/services/api/routes/v1/register.ts @@ -1,18 +1,21 @@ -const router = require('express').Router(); -const emailvalidator = require('email-validator'); -const fs = require('fs-extra'); -const moment = require('moment'); -const crypto = require('crypto'); -const hcaptcha = require('hcaptcha'); -const bcrypt = require('bcrypt'); -const Mii = require('mii-js'); -const { PNID } = require('../../../../models/pnid'); -const { NEXAccount } = require('../../../../models/nex-account'); -const database = require('../../../../database'); -const cache = require('../../../../cache'); -const util = require('../../../../util'); -const logger = require('../../../../logger'); -const { config, disabledFeatures } = require('../../../../config-manager'); + +import crypto from 'node:crypto'; +import { Router } from 'express'; +import emailvalidator from 'email-validator'; +import bcrypt from 'bcrypt'; +import fs from 'fs-extra'; +import moment from 'moment'; +import hcaptcha from 'hcaptcha'; +import Mii from 'mii-js'; +import database from '../../../../database'; +import cache from '../../../../cache'; +import util from '../../../../util'; +import logger from '../../../../logger'; +import { PNID } from '../../../../models/pnid'; +import { NEXAccount } from '../../../../models/nex-account'; +import { config, disabledFeatures } from '../../../../config-manager'; + +const router = Router(); const PNID_VALID_CHARACTERS_REGEX = /^[\w\-\.]*$/gm; const PNID_PUNCTUATION_START_REGEX = /^[\_\-\.]/gm; @@ -368,4 +371,4 @@ router.post('/', async (request, response) => { }); }); -module.exports = router; \ No newline at end of file +export default router; \ No newline at end of file diff --git a/src/services/api/routes/v1/resetPassword.js b/src/services/api/routes/v1/resetPassword.ts similarity index 93% rename from src/services/api/routes/v1/resetPassword.js rename to src/services/api/routes/v1/resetPassword.ts index 8afb3b1..e805e8d 100644 --- a/src/services/api/routes/v1/resetPassword.js +++ b/src/services/api/routes/v1/resetPassword.ts @@ -1,7 +1,9 @@ -const router = require('express').Router(); -const bcrypt = require('bcrypt'); -const { PNID } = require('../../../../models/pnid'); -const util = require('../../../../util'); +import { Router } from 'express'; +import bcrypt from 'bcrypt'; +import { PNID } from '../../../../models/pnid'; +import util from '../../../../util'; + +const router = Router(); // This sucks const PASSWORD_WORD_OR_NUMBER_REGEX = /(?=.*[a-zA-Z])(?=.*\d).*/; @@ -124,4 +126,4 @@ router.post('/', async (request, response) => { }); }); -module.exports = router; \ No newline at end of file +export default router; \ No newline at end of file diff --git a/src/services/api/routes/v1/user.js b/src/services/api/routes/v1/user.ts similarity index 92% rename from src/services/api/routes/v1/user.js rename to src/services/api/routes/v1/user.ts index 7008548..1a6f89b 100644 --- a/src/services/api/routes/v1/user.js +++ b/src/services/api/routes/v1/user.ts @@ -1,7 +1,9 @@ -const router = require('express').Router(); -const joi = require('joi'); -const { PNID } = require('../../../../models/pnid'); -const { config } = require('../../../../config-manager'); +import { Router } from 'express'; +import joi from 'joi'; +import { PNID } from '../../../../models/pnid'; +import { config } from '../../../../config-manager'; + +const router = Router(); // TODO: Extend this later with more settings const userSchema = joi.object({ @@ -124,4 +126,4 @@ router.post('/', async (request, response) => { }); }); -module.exports = router; \ No newline at end of file +export default router; \ No newline at end of file diff --git a/src/services/assets/index.js b/src/services/assets/index.ts similarity index 68% rename from src/services/assets/index.js rename to src/services/assets/index.ts index 5061bf3..b73a632 100644 --- a/src/services/assets/index.js +++ b/src/services/assets/index.ts @@ -1,9 +1,9 @@ // handles serving assets -const express = require('express'); -const subdomain = require('express-subdomain'); -const logger = require('../../logger'); -const path = require('path'); +import path from 'node:path'; +import express from 'express'; +import subdomain from 'express-subdomain'; +import logger from '../../logger'; // Router to handle the subdomain restriction const assets = express.Router(); @@ -19,4 +19,4 @@ const router = express.Router(); logger.info('[conntest] Creating \'assets\' subdomain'); router.use(subdomain('assets', assets)); -module.exports = router; \ No newline at end of file +export default router; \ No newline at end of file diff --git a/src/services/conntest/index.js b/src/services/conntest/index.ts similarity index 80% rename from src/services/conntest/index.js rename to src/services/conntest/index.ts index bd4b823..1601858 100644 --- a/src/services/conntest/index.js +++ b/src/services/conntest/index.ts @@ -1,8 +1,8 @@ // handles conntest endpoints -const express = require('express'); -const subdomain = require('express-subdomain'); -const logger = require('../../logger'); +import express from 'express'; +import subdomain from 'express-subdomain'; +import logger from '../../logger'; // Router to handle the subdomain restriction const conntest = express.Router(); @@ -33,4 +33,4 @@ const router = express.Router(); logger.info('[conntest] Creating \'conntest\' subdomain'); router.use(subdomain('conntest', conntest)); -module.exports = router; \ No newline at end of file +export default router; \ No newline at end of file diff --git a/src/services/datastore/index.js b/src/services/datastore/index.ts similarity index 66% rename from src/services/datastore/index.js rename to src/services/datastore/index.ts index a5803d1..7b670ea 100644 --- a/src/services/datastore/index.js +++ b/src/services/datastore/index.ts @@ -1,7 +1,7 @@ -const express = require('express'); -const subdomain = require('express-subdomain'); -const logger = require('../../logger'); -const routes = require('./routes'); +import express from 'express'; +import subdomain from 'express-subdomain'; +import routes from './routes'; +import logger from '../../logger'; // Router to handle the subdomain const datastore = express.Router(); @@ -17,4 +17,4 @@ const router = express.Router(); logger.info('[DATASTORE] Creating \'datastore\' subdomain'); router.use(subdomain('datastore', datastore)); -module.exports = router; \ No newline at end of file +export default router; \ No newline at end of file diff --git a/src/services/datastore/routes/index.js b/src/services/datastore/routes/index.js deleted file mode 100644 index 34215ff..0000000 --- a/src/services/datastore/routes/index.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = { - UPLOAD: require('./upload.js'), -}; \ No newline at end of file diff --git a/src/services/datastore/routes/index.ts b/src/services/datastore/routes/index.ts new file mode 100644 index 0000000..7e54748 --- /dev/null +++ b/src/services/datastore/routes/index.ts @@ -0,0 +1,5 @@ +import upload from './upload'; + +export default { + UPLOAD: upload +}; \ No newline at end of file diff --git a/src/services/datastore/routes/upload.js b/src/services/datastore/routes/upload.ts similarity index 89% rename from src/services/datastore/routes/upload.js rename to src/services/datastore/routes/upload.ts index 18f13b6..595ce5d 100644 --- a/src/services/datastore/routes/upload.js +++ b/src/services/datastore/routes/upload.ts @@ -1,8 +1,10 @@ -const crypto = require('crypto'); -const router = require('express').Router(); -const Dicer = require('dicer'); -const fs = require('fs'); -const util = require('../../../util'); +import fs from 'node:fs'; +import crypto from 'node:crypto'; +import { Router } from 'express'; +import Dicer from 'dicer'; +import util from '../../../util'; + +const router = Router(); const signatureSecret = fs.readFileSync(`${__dirname}/../../../../certs/nex/datastore/secret.key`); @@ -66,9 +68,9 @@ router.post('/upload', multipartParser, async (request, response) => { if (hmac !== signature.toString()) { return response.sendStatus(400); } - + await util.uploadCDNAsset(bucket.toString(), key.toString(), file, acl.toString()); response.sendStatus(200); }); -module.exports = router; \ No newline at end of file +export default router; \ No newline at end of file diff --git a/src/services/local-cdn/index.js b/src/services/local-cdn/index.js deleted file mode 100644 index ca8b894..0000000 --- a/src/services/local-cdn/index.js +++ /dev/null @@ -1,30 +0,0 @@ -const express = require('express'); -const subdomain = require('express-subdomain'); -const logger = require('../../logger'); -const { config, disabledFeatures } = require('../../config-manager'); - -if (!disabledFeatures.s3) { - // * s3 enabled, no need for this - - module.exports = express.Router(); - - return; -} - -const routes = require('./routes'); - -// Router to handle the subdomain -const localcdn = express.Router(); - -// Setup routes -logger.info('[LOCAL-CDN] Applying imported routes'); -localcdn.use(routes.GET); - -// Main router for endpoints -const router = express.Router(); - -// Create subdomains -logger.info(`[LOCAL-CDN] Creating '${config.cdn.subdomain}' subdomain`); -router.use(subdomain(config.cdn.subdomain, localcdn)); - -module.exports = router; \ No newline at end of file diff --git a/src/services/local-cdn/index.ts b/src/services/local-cdn/index.ts new file mode 100644 index 0000000..bf57e63 --- /dev/null +++ b/src/services/local-cdn/index.ts @@ -0,0 +1,26 @@ +import express from 'express'; +import subdomain from 'express-subdomain'; +import routes from './routes'; +import { config, disabledFeatures } from '../../config-manager'; +import logger from '../../logger'; + +const router = express.Router(); + +if (disabledFeatures.s3) { + // * s3 disabled, setup local CDN + + // * Router to handle the subdomain + const localcdn = express.Router(); + + // * Setup routes + logger.info('[LOCAL-CDN] Applying imported routes'); + localcdn.use(routes.GET); + + // * Create subdomains + logger.info(`[LOCAL-CDN] Creating '${config.cdn.subdomain}' subdomain`); + router.use(subdomain(config.cdn.subdomain, localcdn)); +} else { + logger.info('[LOCAL-CDN] s3 enabled, skipping local CDN'); +} + +export default router; \ No newline at end of file diff --git a/src/services/local-cdn/routes/get.js b/src/services/local-cdn/routes/get.ts similarity index 65% rename from src/services/local-cdn/routes/get.js rename to src/services/local-cdn/routes/get.ts index 7ae69b9..6dc45c7 100644 --- a/src/services/local-cdn/routes/get.js +++ b/src/services/local-cdn/routes/get.ts @@ -1,5 +1,7 @@ -const router = require('express').Router(); -const cache = require('../../../cache'); +import { Router } from 'express'; +import cache from '../../../cache'; + +const router = Router(); router.get('/*', async (request, response) => { const filePath = request.params[0]; @@ -13,4 +15,4 @@ router.get('/*', async (request, response) => { } }); -module.exports = router; \ No newline at end of file +export default router; \ No newline at end of file diff --git a/src/services/local-cdn/routes/index.js b/src/services/local-cdn/routes/index.js deleted file mode 100644 index 033fca1..0000000 --- a/src/services/local-cdn/routes/index.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = { - GET: require('./get.js'), -}; \ No newline at end of file diff --git a/src/services/local-cdn/routes/index.ts b/src/services/local-cdn/routes/index.ts new file mode 100644 index 0000000..d6b426e --- /dev/null +++ b/src/services/local-cdn/routes/index.ts @@ -0,0 +1,5 @@ +import get from './get'; + +export default { + GET: get, +}; \ No newline at end of file diff --git a/src/services/nasc/index.js b/src/services/nasc/index.ts similarity index 62% rename from src/services/nasc/index.js rename to src/services/nasc/index.ts index 9266a81..0b80c92 100644 --- a/src/services/nasc/index.js +++ b/src/services/nasc/index.ts @@ -1,10 +1,10 @@ // handles NASC endpoints -const express = require('express'); -const subdomain = require('express-subdomain'); -const NASCMiddleware = require('../../middleware/nasc'); -const logger = require('../../logger'); -const routes = require('./routes'); +import express from 'express'; +import subdomain from 'express-subdomain'; +import NASCMiddleware from '../../middleware/nasc'; +import routes from './routes'; +import logger from '../../logger'; // Router to handle the subdomain restriction const nasc = express.Router(); @@ -23,4 +23,4 @@ const router = express.Router(); logger.info('[NASC] Creating \'nasc\' subdomain'); router.use(subdomain('nasc', nasc)); -module.exports = router; \ No newline at end of file +export default router; \ No newline at end of file diff --git a/src/services/nasc/routes/ac.js b/src/services/nasc/routes/ac.ts similarity index 92% rename from src/services/nasc/routes/ac.js rename to src/services/nasc/routes/ac.ts index 66d08d4..d19c6c8 100644 --- a/src/services/nasc/routes/ac.js +++ b/src/services/nasc/routes/ac.ts @@ -1,8 +1,8 @@ -const fs = require('fs-extra'); -const express = require('express'); -const util = require('../../../util'); -const database = require('../../../database'); -const cache = require('../../../cache'); +import express from 'express'; +import fs from 'fs-extra'; +import util from '../../../util'; +import database from '../../../database'; +import cache from '../../../cache'; const router = express.Router(); @@ -103,4 +103,4 @@ async function processServiceTokenRequest(request) { return params; } -module.exports = router; \ No newline at end of file +export default router; \ No newline at end of file diff --git a/src/services/nasc/routes/index.js b/src/services/nasc/routes/index.js deleted file mode 100644 index 971f25d..0000000 --- a/src/services/nasc/routes/index.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = { - AC: require('./ac') -}; \ No newline at end of file diff --git a/src/services/nasc/routes/index.ts b/src/services/nasc/routes/index.ts new file mode 100644 index 0000000..0399fd3 --- /dev/null +++ b/src/services/nasc/routes/index.ts @@ -0,0 +1,5 @@ +import ac from './ac'; + +export default { + AC: ac +}; \ No newline at end of file diff --git a/src/services/nnid/index.js b/src/services/nnid/index.ts similarity index 69% rename from src/services/nnid/index.js rename to src/services/nnid/index.ts index 39334b6..b0f8321 100644 --- a/src/services/nnid/index.js +++ b/src/services/nnid/index.ts @@ -1,12 +1,12 @@ // handles "account.nintendo.net" endpoints -const express = require('express'); -const subdomain = require('express-subdomain'); -const clientHeaderCheck = require('../../middleware/client-header'); -const cemuMiddleware = require('../../middleware/cemu'); -const pnidMiddleware = require('../../middleware/pnid'); -const logger = require('../../logger'); -const routes = require('./routes'); +import express from 'express'; +import subdomain from 'express-subdomain'; +import clientHeaderCheck from '../../middleware/client-header'; +import cemuMiddleware from '../../middleware/cemu'; +import pnidMiddleware from '../../middleware/pnid'; +import routes from './routes'; +import logger from '../../logger'; // Router to handle the subdomain restriction const nnid = express.Router(); @@ -37,4 +37,4 @@ router.use(subdomain('account', nnid)); logger.info('[NNID] Creating \'c.account\' subdomain'); router.use(subdomain('c.account', nnid)); -module.exports = router; \ No newline at end of file +export default router; \ No newline at end of file diff --git a/src/services/nnid/routes/admin.js b/src/services/nnid/routes/admin.js deleted file mode 100644 index be0b9f1..0000000 --- a/src/services/nnid/routes/admin.js +++ /dev/null @@ -1,59 +0,0 @@ -const router = require('express').Router(); -const xmlbuilder = require('xmlbuilder'); -const { PNID } = require('../../../models/pnid'); - -/** - * [GET] - * Replacement for: https://account.nintendo.net/v1/api/admin/mapped_ids - * Description: Maps between NNID usernames and PIDs - */ -router.get('/mapped_ids', async (request, response) => { - - let { input: inputList, input_type: inputType, output_type: outputType } = request.query; - inputList = inputList.split(','); - inputList = inputList.filter(input => input); // remove nulls - - if (inputType === 'user_id') { - inputType = 'usernameLower'; - inputList = inputList.map(name => name.toLowerCase()); - } - - if (outputType === 'user_id') { - outputType = 'username'; - } - - // This is slower than PNID.where() - // but it ensures that each input - // ALWAYS has an output and filters - // out unwanted input/output types - const results = []; - const allowedTypes = ['pid', 'user_id']; - - for (const input of inputList) { - const result = { - in_id: input, - out_id: '' - }; - - if (allowedTypes.includes(request.query.input_type) && allowedTypes.includes(request.query.output_type)) { - const query = {}; - query[inputType] = input; - - const searchResult = await PNID.findOne(query); - - if (searchResult) { - result.out_id = searchResult.get(outputType); - } - } - - results.push(result); - } - - response.send(xmlbuilder.create({ - mapped_ids: { - mapped_id: results - } - }).end()); -}); - -module.exports = router; \ No newline at end of file diff --git a/src/services/nnid/routes/admin.ts b/src/services/nnid/routes/admin.ts new file mode 100644 index 0000000..b28f243 --- /dev/null +++ b/src/services/nnid/routes/admin.ts @@ -0,0 +1,88 @@ +import { Router } from 'express'; +import xmlbuilder from 'xmlbuilder'; +import { PNID } from '../../../models/pnid'; + +const router = Router(); + +/** + * [GET] + * Replacement for: https://account.nintendo.net/v1/api/admin/mapped_ids + * Description: Maps between NNID usernames and PIDs + */ +router.get('/mapped_ids', async (request, response) => { + + let inputType: string; + let inputList: string[]; + let outputType: string; + let queryInput: string; + let queryOutput: string; + + if (Array.isArray(request.query.input_type)) { + inputType = request.query.input_type[0] as string; + } else { + inputType = request.query.input_type as string; + } + + if (Array.isArray(request.query.input)) { + inputList = request.query.input as string[]; + } else { + const input = request.query.input as string; + inputList = input.split(','); + } + + if (Array.isArray(request.query.output_type)) { + outputType = request.query.output_type[0] as string; + } else { + outputType = request.query.output_type as string; + } + + inputList = inputList.filter(input => input); // * Remove null inputs + + if (inputType === 'user_id') { + queryInput = 'usernameLower'; + inputList = inputList.map(name => name.toLowerCase()); + } else { + queryInput = 'pid'; + } + + if (outputType === 'user_id') { + queryOutput = 'username'; + } else { + queryOutput = 'pid'; + } + + // This is slower than PNID.where() + // but it ensures that each input + // ALWAYS has an output and filters + // out unwanted input/output types + const results = []; + const allowedTypes = ['pid', 'user_id']; + + for (const input of inputList) { + const result = { + in_id: input, + out_id: '' + }; + + if (allowedTypes.includes(inputType) && allowedTypes.includes(outputType)) { + const query = {}; + query[queryInput] = input; + + const searchResult = await PNID.findOne(query); + + if (searchResult) { + result.out_id = searchResult.get(queryOutput); + } + } + + results.push(result); + } + + response.send(xmlbuilder.create({ + mapped_ids: { + mapped_id: results + } + }).end()); +}); + +export default router; \ No newline at end of file diff --git a/src/services/nnid/routes/content.js b/src/services/nnid/routes/content.ts similarity index 92% rename from src/services/nnid/routes/content.js rename to src/services/nnid/routes/content.ts index e8b9d70..16a0c69 100644 --- a/src/services/nnid/routes/content.js +++ b/src/services/nnid/routes/content.ts @@ -1,6 +1,8 @@ -const router = require('express').Router(); -const xmlbuilder = require('xmlbuilder'); -const timezones = require('../timezones.json'); +import { Router } from 'express'; +import xmlbuilder from 'xmlbuilder'; +import timezones from '../timezones.json'; + +const router = Router(); /** * [GET] @@ -10,7 +12,7 @@ const timezones = require('../timezones.json'); router.get('/agreements/:type/:region/:version', (request, response) => { response.set('Content-Type', 'text/xml'); response.set('Server', 'Nintendo 3DS (http)'); - response.set('X-Nintendo-Date', new Date().getTime()); + response.set('X-Nintendo-Date', new Date().getTime().toString()); response.send(xmlbuilder.create({ agreements: { @@ -127,7 +129,7 @@ router.get('/agreements/:type/:region/:version', (request, response) => { router.get('/time_zones/:countryCode/:language', (request, response) => { response.set('Content-Type', 'text/xml'); response.set('Server', 'Nintendo 3DS (http)'); - response.set('X-Nintendo-Date', new Date().getTime()); + response.set('X-Nintendo-Date', new Date().getTime().toString()); /* // Old method. Crashes WiiU when sending a list with over 32 entries, but otherwise works @@ -159,4 +161,4 @@ router.get('/time_zones/:countryCode/:language', (request, response) => { }).end()); }); -module.exports = router; +export default router; \ No newline at end of file diff --git a/src/services/nnid/routes/devices.js b/src/services/nnid/routes/devices.ts similarity index 65% rename from src/services/nnid/routes/devices.js rename to src/services/nnid/routes/devices.ts index 4dd3e4e..ec6224e 100644 --- a/src/services/nnid/routes/devices.js +++ b/src/services/nnid/routes/devices.ts @@ -1,5 +1,7 @@ -const router = require('express').Router(); -const xmlbuilder = require('xmlbuilder'); +import { Router } from 'express'; +import xmlbuilder from 'xmlbuilder'; + +const router = Router(); /** * [GET] @@ -12,4 +14,4 @@ router.get('/@current/status', async (request, response) => { }).end()); }); -module.exports = router; \ No newline at end of file +export default router; \ No newline at end of file diff --git a/src/services/nnid/routes/index.js b/src/services/nnid/routes/index.js deleted file mode 100644 index b5becf4..0000000 --- a/src/services/nnid/routes/index.js +++ /dev/null @@ -1,10 +0,0 @@ -module.exports = { - ADMIN: require('./admin'), - CONTENT: require('./content'), - DEVICES: require('./devices'), - MIIS: require('./miis'), - OAUTH: require('./oauth'), - PEOPLE: require('./people'), - PROVIDER: require('./provider'), - SUPPORT: require('./support'), -}; \ No newline at end of file diff --git a/src/services/nnid/routes/index.ts b/src/services/nnid/routes/index.ts new file mode 100644 index 0000000..67e2938 --- /dev/null +++ b/src/services/nnid/routes/index.ts @@ -0,0 +1,19 @@ +import admin from './admin'; +import content from './content'; +import devices from './devices'; +import miis from './miis'; +import oauth from './oauth'; +import people from './people'; +import provider from './provider'; +import support from './support'; + +export default { + ADMIN: admin, + CONTENT: content, + DEVICES: devices, + MIIS: miis, + OAUTH: oauth, + PEOPLE: people, + PROVIDER: provider, + SUPPORT: support, +}; \ No newline at end of file diff --git a/src/services/nnid/routes/miis.js b/src/services/nnid/routes/miis.ts similarity index 87% rename from src/services/nnid/routes/miis.js rename to src/services/nnid/routes/miis.ts index 3c5603c..62e60df 100644 --- a/src/services/nnid/routes/miis.js +++ b/src/services/nnid/routes/miis.ts @@ -1,7 +1,9 @@ -const router = require('express').Router(); -const xmlbuilder = require('xmlbuilder'); -const { PNID } = require('../../../models/pnid'); -const { config } = require('../../../config-manager'); +import { Router } from 'express'; +import xmlbuilder from 'xmlbuilder'; +import { PNID } from '../../../models/pnid'; +import { config } from '../../../config-manager'; + +const router = Router(); /** * [GET] @@ -91,4 +93,4 @@ router.get('/', async (request, response) => { }).end()); }); -module.exports = router; \ No newline at end of file +export default router; \ No newline at end of file diff --git a/src/services/nnid/routes/oauth.js b/src/services/nnid/routes/oauth.ts similarity index 87% rename from src/services/nnid/routes/oauth.js rename to src/services/nnid/routes/oauth.ts index 21516ad..751a3b4 100644 --- a/src/services/nnid/routes/oauth.js +++ b/src/services/nnid/routes/oauth.ts @@ -1,9 +1,11 @@ -const router = require('express').Router(); -const xmlbuilder = require('xmlbuilder'); -const bcrypt = require('bcrypt'); -const fs = require('fs-extra'); -const database = require('../../../database'); -const util = require('../../../util'); +import { Router } from 'express'; +import xmlbuilder from 'xmlbuilder'; +import bcrypt from 'bcrypt'; +import fs from 'fs-extra'; +import database from '../../../database'; +import util from '../../../util'; + +const router = Router(); /** * [POST] @@ -117,4 +119,4 @@ router.post('/access_token/generate', async (request, response) => { }).end()); }); -module.exports = router; \ No newline at end of file +export default router; \ No newline at end of file diff --git a/src/services/nnid/routes/people.js b/src/services/nnid/routes/people.ts similarity index 82% rename from src/services/nnid/routes/people.js rename to src/services/nnid/routes/people.ts index 6460877..9eb06c5 100644 --- a/src/services/nnid/routes/people.js +++ b/src/services/nnid/routes/people.ts @@ -1,16 +1,18 @@ -const router = require('express').Router(); -const xmlbuilder = require('xmlbuilder'); -const moment = require('moment'); -const crypto = require('crypto'); -const bcrypt = require('bcrypt'); -const { PNID } = require('../../../models/pnid'); -const { NEXAccount } = require('../../../models/nex-account'); -const deviceCertificateMiddleware = require('../../../middleware/device-certificate'); -const ratelimit = require('../../../middleware/ratelimit'); -const database = require('../../../database'); -const util = require('../../../util'); -const logger = require('../../../logger'); -require('moment-timezone'); +import crypto from 'node:crypto'; +import { Router } from 'express'; +import xmlbuilder from 'xmlbuilder'; +import bcrypt from 'bcrypt'; +import moment from 'moment'; +import deviceCertificateMiddleware from '../../../middleware/device-certificate'; +import ratelimit from '../../../middleware/ratelimit'; +import database from '../../../database'; +import util from '../../../util'; +import { PNID } from '../../../models/pnid'; +import { NEXAccount } from '../../../models/nex-account'; +import logger from '../../../logger'; +import timezones from '../timezones.json'; + +const router = Router(); /** * [GET] @@ -102,6 +104,14 @@ router.post('/', ratelimit, deviceCertificateMiddleware, async (request, respons const primaryPasswordHash = util.nintendoPasswordHash(person.get('password'), nexAccount.get('pid')); const passwordHash = await bcrypt.hash(primaryPasswordHash, 10); + const countryCode = person.get('country'); + const language = person.get('language'); + const timezoneName = person.get('tz_name'); + + const regionLanguages = timezones[countryCode]; + const regionTimezones = regionLanguages[language] ? regionLanguages[language] : Object.values(regionLanguages)[0]; + const timezone = regionTimezones.find(tz => tz.area === timezoneName); + pnid = new PNID({ pid: nexAccount.get('pid'), creation_date: creationDate, @@ -111,8 +121,8 @@ router.post('/', ratelimit, deviceCertificateMiddleware, async (request, respons password: passwordHash, birthdate: person.get('birth_date'), gender: person.get('gender'), - country: person.get('country'), - language: person.get('language'), + country: countryCode, + language: language, email: { address: person.get('email').get('address').toLowerCase(), primary: person.get('email').get('primary') === 'Y', @@ -123,8 +133,8 @@ router.post('/', ratelimit, deviceCertificateMiddleware, async (request, respons }, region: person.get('region'), timezone: { - name: person.get('tz_name'), - offset: (moment.tz(person.get('tz_name')).utcOffset() * 60) + name: timezoneName, + offset: Number(timezone.utc_offset) }, mii: { name: person.get('mii').get('name'), @@ -190,7 +200,7 @@ router.post('/', ratelimit, deviceCertificateMiddleware, async (request, respons router.get('/@me/profile', async (request, response) => { response.set('Content-Type', 'text/xml'); response.set('Server', 'Nintendo 3DS (http)'); - response.set('X-Nintendo-Date', new Date().getTime()); + response.set('X-Nintendo-Date', new Date().getTime().toString()); const { pnid } = request; @@ -209,7 +219,7 @@ router.get('/@me/profile', async (request, response) => { router.post('/@me/devices', async (request, response) => { response.set('Content-Type', 'text/xml'); response.set('Server', 'Nintendo 3DS (http)'); - response.set('X-Nintendo-Date', new Date().getTime()); + response.set('X-Nintendo-Date', new Date().getTime().toString()); // We don't care about the device attributes // The console ignores them and PNIDs are not tied to consoles anyway @@ -232,7 +242,7 @@ router.post('/@me/devices', async (request, response) => { router.get('/@me/devices', async (request, response) => { response.set('Content-Type', 'text/xml'); response.set('Server', 'Nintendo 3DS (http)'); - response.set('X-Nintendo-Date', new Date().getTime()); + response.set('X-Nintendo-Date', new Date().getTime().toString()); const { pnid, headers } = request; @@ -265,7 +275,7 @@ router.get('/@me/devices', async (request, response) => { router.get('/@me/devices/owner', async (request, response) => { response.set('Content-Type', 'text/xml'); response.set('Server', 'Nintendo 3DS (http)'); - response.set('X-Nintendo-Date', moment().add(5, 'h')); + response.set('X-Nintendo-Date', moment().add(5, 'h').toString()); const { pnid } = request; @@ -284,7 +294,7 @@ router.get('/@me/devices/owner', async (request, response) => { router.get('/@me/devices/status', async (request, response) => { response.set('Content-Type', 'text/xml'); response.set('Server', 'Nintendo 3DS (http)'); - response.set('X-Nintendo-Date', moment().add(5, 'h')); + response.set('X-Nintendo-Date', moment().add(5, 'h').toString()); response.send(xmlbuilder.create({ device: {} @@ -316,7 +326,7 @@ router.put('/@me/miis/@primary', async (request, response) => { */ router.put('/@me/devices/@current/inactivate', async (request, response) => { response.set('Server', 'Nintendo 3DS (http)'); - response.set('X-Nintendo-Date', new Date().getTime()); + response.set('X-Nintendo-Date', new Date().getTime().toString()); const { pnid } = request; @@ -390,21 +400,27 @@ router.put('/@me', async (request, response) => { const gender = person.get('gender') ? person.get('gender') : pnid.get('gender'); const region = person.get('region') ? person.get('region') : pnid.get('region'); + const countryCode = person.get('country') ? person.get('country') : pnid.get('country'); + const language = person.get('language') ? person.get('language') : pnid.get('language'); const timezoneName = person.get('tz_name') ? person.get('tz_name') : pnid.get('timezone.name'); const marketingFlag = person.get('marketing_flag') ? person.get('marketing_flag') === 'Y' : pnid.get('flags.marketing'); const offDeviceFlag = person.get('off_device_flag') ? person.get('off_device_flag') === 'Y' : pnid.get('flags.off_device'); + const regionLanguages = timezones[countryCode]; + const regionTimezones = regionLanguages[language] ? regionLanguages[language] : Object.values(regionLanguages)[0]; + const timezone = regionTimezones.find(tz => tz.area === timezoneName); + if (person.get('password')) { const primaryPasswordHash = util.nintendoPasswordHash(person.get('password'), pnid.get('pid')); const passwordHash = await bcrypt.hash(primaryPasswordHash, 10); - + pnid.password = passwordHash; } pnid.gender = gender; pnid.region = region; pnid.timezone.name = timezoneName; - pnid.timezone.offset = (moment.tz(timezoneName).utcOffset() * 60); + pnid.timezone.offset = Number(timezone.utc_offset); pnid.timezone.marketing = marketingFlag; pnid.timezone.off_device = offDeviceFlag; @@ -493,4 +509,4 @@ router.put('/@me/emails/@primary', async (request, response) => { response.send(''); }); -module.exports = router; \ No newline at end of file +export default router; \ No newline at end of file diff --git a/src/services/nnid/routes/provider.js b/src/services/nnid/routes/provider.ts similarity index 86% rename from src/services/nnid/routes/provider.js rename to src/services/nnid/routes/provider.ts index 86fd9d1..a15de46 100644 --- a/src/services/nnid/routes/provider.js +++ b/src/services/nnid/routes/provider.ts @@ -1,10 +1,12 @@ -const router = require('express').Router(); -const xmlbuilder = require('xmlbuilder'); -const fs = require('fs-extra'); -const { NEXAccount } = require('../../../models/nex-account'); -const util = require('../../../util'); -const database = require('../../../database'); -const cache = require('../../../cache'); +import { Router } from 'express'; +import xmlbuilder from 'xmlbuilder'; +import fs from 'fs-extra'; +import database from '../../../database'; +import util from '../../../util'; +import cache from '../../../cache'; +import { NEXAccount } from '../../../models/nex-account'; + +const router = Router(); /** * [GET] @@ -14,7 +16,7 @@ const cache = require('../../../cache'); router.get('/service_token/@me', async (request, response) => { const { pnid } = request; - const titleId = request.headers['x-nintendo-title-id']; + const titleId = request.headers['x-nintendo-title-id'] as string; const serverAccessLevel = pnid.get('server_access_level'); const server = await database.getServerByTitleId(titleId, serverAccessLevel); @@ -110,7 +112,7 @@ router.get('/nex_token/@me', async (request, response) => { } const { service_name, service_type, ip, port, device } = server; - const titleId = request.headers['x-nintendo-title-id']; + const titleId = request.headers['x-nintendo-title-id'] as string; const cryptoPath = `${__dirname}/../../../../certs/${service_type}/${service_name}`; @@ -169,4 +171,4 @@ router.get('/nex_token/@me', async (request, response) => { }).end()); }); -module.exports = router; \ No newline at end of file +export default router; \ No newline at end of file diff --git a/src/services/nnid/routes/support.js b/src/services/nnid/routes/support.ts similarity index 88% rename from src/services/nnid/routes/support.js rename to src/services/nnid/routes/support.ts index d63fea3..f44d033 100644 --- a/src/services/nnid/routes/support.js +++ b/src/services/nnid/routes/support.ts @@ -1,9 +1,11 @@ -const router = require('express').Router(); -const dns = require('dns'); -const xmlbuilder = require('xmlbuilder'); -const moment = require('moment'); -const util = require('../../../util'); -const database = require('../../../database'); +import dns from 'node:dns'; +import { Router } from 'express'; +import xmlbuilder from 'xmlbuilder'; +import moment from 'moment'; +import database from '../../../database'; +import util from '../../../util'; + +const router = Router(); /** * [POST] @@ -145,4 +147,4 @@ router.get('/forgotten_password/:pid', async (request, response) => { response.status(200).send(''); }); -module.exports = router; \ No newline at end of file +export default router; \ No newline at end of file diff --git a/src/util.js b/src/util.ts similarity index 96% rename from src/util.js rename to src/util.ts index 0050b38..3494f67 100644 --- a/src/util.js +++ b/src/util.ts @@ -303,7 +303,7 @@ async function sendForgotPasswordEmail(pnid) { }); } -module.exports = { +export default { nintendoPasswordHash, nintendoBase64Decode, nintendoBase64Encode, diff --git a/types/express.d.ts b/types/express.d.ts new file mode 100644 index 0000000..ca16356 --- /dev/null +++ b/types/express.d.ts @@ -0,0 +1,12 @@ +import NintendoCertificate from '../src/nintendo-certificate' + +declare global { + namespace Express { + interface Request { + pnid?: IPNID; + isCemu?: boolean; + files?: Record; + certificate?: NintendoCertificate; + } + } +} \ No newline at end of file diff --git a/types/mongoose/device-attribute.d.ts b/types/mongoose/device-attribute.d.ts new file mode 100644 index 0000000..8be84dd --- /dev/null +++ b/types/mongoose/device-attribute.d.ts @@ -0,0 +1,13 @@ +import { Document, Model } from 'mongoose'; + +declare global { + interface IDeviceAttributeDocument extends Document { + created_date: string; + name: string; + value: string; + } + + interface IDeviceAttribute extends IDeviceAttributeDocument {} + + interface IDeviceAttributeModel extends Model {} +} \ No newline at end of file diff --git a/types/mongoose/device.d.ts b/types/mongoose/device.d.ts new file mode 100644 index 0000000..2303607 --- /dev/null +++ b/types/mongoose/device.d.ts @@ -0,0 +1,32 @@ +import { Document, Model } from 'mongoose'; +import { DeviceAttributeSchema } from '../../src/models/device'; + +type MODEL = 'wup' | 'ctr' | 'spr' | 'ftr' | 'ktr' | 'red' | 'jan'; +type ACCESS_LEVEL = 0 | 1 | 2 | 3; +type SERVER_ACCESS_LEVEL = 'prod' | 'test' | 'dev'; + +declare global { + interface IDeviceDocument extends Document { + is_emulator: boolean; + model: MODEL; + device_id: number; + device_type: number; + serial: string; + device_attributes: typeof DeviceAttributeSchema[]; + soap: { + token: string; + account_id: number; + }, + // * 3DS-specific stuff + environment: string; + mac_hash: string; + fcdcert_hash: string; + linked_pids: number[]; + access_level: ACCESS_LEVEL; + server_access_level: SERVER_ACCESS_LEVEL; + } + + interface IDevice extends IDeviceDocument {} + + interface IDeviceModel extends Model {} +} \ No newline at end of file diff --git a/types/mongoose/nex-account.d.ts b/types/mongoose/nex-account.d.ts new file mode 100644 index 0000000..51ab37d --- /dev/null +++ b/types/mongoose/nex-account.d.ts @@ -0,0 +1,23 @@ +import { Document, Model } from 'mongoose'; + +type DEVICE = 'wiiu' | '3ds'; +type ACCESS_LEVEL = 0 | 1 | 2 | 3; + +declare global { + interface INEXAccountDocument extends Document { + device_type: DEVICE; + pid: number; + password: string; + owning_pid: number; + access_level: ACCESS_LEVEL; + server_access_level: string; + } + + interface INEXAccount extends INEXAccountDocument { + generatePID(): void; + } + + interface INEXAccountModel extends Model { + generatePID(): void; + } +} \ No newline at end of file diff --git a/types/mongoose/pnid.d.ts b/types/mongoose/pnid.d.ts new file mode 100644 index 0000000..145523b --- /dev/null +++ b/types/mongoose/pnid.d.ts @@ -0,0 +1,81 @@ +import { Document, Model } from 'mongoose'; +import { DeviceSchema } from '../../src/models/device'; + +declare global { + interface IPNIDDocument extends Document { + access_level: number; + server_access_level: string; + pid: number; + creation_date: string; + updated: string; + username: string; + usernameLower: string; + password: string; + birthdate: string; + gender: string; + country: string; + language: string; + email: { + address: string; + primary: boolean; + parent: boolean; + reachable: boolean; + validated: boolean; + validated_date: string; + id: number; + }; + region: number; + timezone: { + name: string; + offset: number; + marketing: boolean; + off_device: boolean; + }; + mii: { + name: string; + primary: boolean; + data: string; + id: number; + hash: string; + image_url: string; + image_id: number; + }; + flags: { + active: boolean; + marketing: boolean; + off_device: boolean; + }; + devices: typeof DeviceSchema[]; + identification: { // user identification tokens + email_code: string; + email_token: string; + access_token: { + value: string; + ttl: number; + }, + refresh_token: { + value: string; + ttl: number; + } + }; + connections: { + discord: { + id: string; + } + }; + } + + interface IPNID extends IPNIDDocument { + generatePID(): void; + generateEmailValidationCode(): void; + generateEmailValidationToken(): void; + updateMii(mii: { name: string, primary: boolean, data: Buffer}): void; + generateMiiImages(): void; + getServerMode(): string; + } + + interface IPNIDModel extends Model { + generatePID(): void; + generateEmailValidationToken(): void; + } +} \ No newline at end of file diff --git a/types/mongoose/server.d.ts b/types/mongoose/server.d.ts new file mode 100644 index 0000000..574f21e --- /dev/null +++ b/types/mongoose/server.d.ts @@ -0,0 +1,19 @@ +import { Document, Model } from 'mongoose'; + +declare global { + interface IServerDocument extends Document { + ip: string; // Example: 1.1.1.1 + port: Number; // Example: 60000 + service_name: string; // Example: friends + service_type: string; // Example: nex + game_server_id: string; // Example: 00003200 + title_ids: string[]; // Example: ["000500001018DB00", "000500001018DC00", "000500001018DD00"] + access_mode: string; // Example: prod + maintenance_mode: boolean; // Example: false + device: number; // Example: 1 (WiiU) + } + + interface IServer extends IServerDocument {} + + interface IServerModel extends Model {} +} \ No newline at end of file From 75de643ac5c59ef2b5e0a981f13a3be987977aed Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Fri, 3 Mar 2023 12:45:49 -0500 Subject: [PATCH 016/219] Bump version to 2.0.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c4acc78..1814e4b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "account", - "version": "1.0.0", + "version": "2.0.0", "description": "", "main": "./dist/server.js", "scripts": { From d05a1eed915c36ec346cef440bff3f4cf6c2e9e6 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Fri, 3 Mar 2023 20:59:05 -0500 Subject: [PATCH 017/219] Better typed Mongoose models --- package-lock.json | 621 +++++++++++---------------- package.json | 2 +- src/database.ts | 6 +- src/models/device.ts | 8 +- src/models/nex-account.ts | 14 +- src/models/pnid.ts | 32 +- src/models/server.ts | 4 +- src/services/api/routes/v1/user.ts | 64 +-- src/services/nnid/routes/people.ts | 2 +- types/express.d.ts | 3 +- types/mongoose/device-attribute.d.ts | 8 +- types/mongoose/device.d.ts | 8 +- types/mongoose/nex-account.d.ts | 13 +- types/mongoose/pnid.d.ts | 21 +- types/mongoose/server.d.ts | 6 +- 15 files changed, 341 insertions(+), 471 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1473949..c670965 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,19 +1,18 @@ { "name": "account", - "version": "1.0.0", + "version": "2.0.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "account", - "version": "1.0.0", + "version": "2.0.0", "license": "ISC", "dependencies": { "aws-sdk": "^2.978.0", "bcrypt": "^5.0.0", "colors": "^1.4.0", "cors": "^2.8.5", - "countries-and-timezones": "^3.4.1", "dicer": "^0.2.5", "dotenv": "^16.0.3", "email-validator": "^2.0.4", @@ -25,15 +24,13 @@ "hcaptcha": "^0.1.0", "image-pixels": "^1.1.1", "joi": "^17.8.3", - "kaitai-struct": "^0.9.0", "lodash.get": "^4.4.2", "lodash.set": "^4.3.2", "mii-js": "github:PretendoNetwork/mii-js", "moment": "^2.29.4", - "mongoose": "^5.8.3", + "mongoose": "^7.0.0", "mongoose-unique-validator": "^2.0.3", "morgan": "^1.9.1", - "neat-config": "^1.0.0", "nodemailer": "^6.4.2", "redis": "^4.3.1", "tga": "^1.0.4", @@ -312,14 +309,6 @@ "@types/node": "*" } }, - "node_modules/@types/bson": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/@types/bson/-/bson-4.0.5.tgz", - "integrity": "sha512-vVLwMUqhYJSQ/WKcE60eFqcyuWse5fGH+NMAXHuKrUAPoryq3ATxk5o4bgYNtg5aOM4APVg7Hnb3ASqUYG0PKg==", - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/cacheable-request": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz", @@ -411,15 +400,6 @@ "integrity": "sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==", "dev": true }, - "node_modules/@types/mongodb": { - "version": "3.6.20", - "resolved": "https://registry.npmjs.org/@types/mongodb/-/mongodb-3.6.20.tgz", - "integrity": "sha512-WcdpPJCakFzcWWD9juKoZbRtQxKIMYF/JIAM4JrNHrMcnJL6/a2NWjXxW7fo9hxboxxkg+icff8d7+WIEvKgYQ==", - "dependencies": { - "@types/bson": "*", - "@types/node": "*" - } - }, "node_modules/@types/morgan": { "version": "1.9.4", "resolved": "https://registry.npmjs.org/@types/morgan/-/morgan-1.9.4.tgz", @@ -464,6 +444,20 @@ "@types/node": "*" } }, + "node_modules/@types/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-xTE1E+YF4aWPJJeUzaZI5DRntlkY3+BCVJi0axFptnjGmAoWxkyREIh/XMrfxVLejwQxMCfDXdICo0VLxThrog==" + }, + "node_modules/@types/whatwg-url": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-8.2.2.tgz", + "integrity": "sha512-FtQu10RWgn3D9U4aazdwIE2yzphmTJREDqNdODHrbrZmmMqI0vMheC/6NE/J1Yveaj8H+ela+YwWTjq5PGmuhA==", + "dependencies": { + "@types/node": "*", + "@types/webidl-conversions": "*" + } + }, "node_modules/abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", @@ -768,47 +762,6 @@ "resolved": "https://registry.npmjs.org/bit-buffer/-/bit-buffer-0.2.5.tgz", "integrity": "sha512-x1yGnmXvFg6e3DiyRztElbcn1bsCTFSoM/ncAzY62uE0JdTl5xlKJd0ooqLYoPbhdsnpehSIQrdIvclcZJYwiA==" }, - "node_modules/bl": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/bl/-/bl-2.2.1.tgz", - "integrity": "sha512-6Pesp1w0DEX1N550i/uGV/TqucVL4AM/pgThFSN/Qq9si1/DF9aIHs1BxD8V/QU0HoeHO6cQRTAuYnLPKq1e4g==", - "dependencies": { - "readable-stream": "^2.3.5", - "safe-buffer": "^5.1.1" - } - }, - "node_modules/bl/node_modules/readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/bl/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "node_modules/bl/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/bluebird": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", - "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==" - }, "node_modules/bmp-js": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/bmp-js/-/bmp-js-0.1.0.tgz", @@ -861,11 +814,11 @@ } }, "node_modules/bson": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/bson/-/bson-1.1.6.tgz", - "integrity": "sha512-EvVNVeGo4tHxwi8L6bPj3y3itEvStdwvvlojVxxbyYfoaxJ6keLgrTuKdyfEAszFK+H3olzBuafE0yoh0D1gdg==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/bson/-/bson-5.0.1.tgz", + "integrity": "sha512-y09gBGusgHtinMon/GVbv1J6FrXhnr/+6hqLlSmEFzkz6PodqF6TxjyvfvY3AfO+oG1mgUtbC86xSbOlwvM62Q==", "engines": { - "node": ">=0.6.19" + "node": ">=14.20.1" } }, "node_modules/buffer": { @@ -1115,15 +1068,6 @@ "node": ">= 0.10" } }, - "node_modules/countries-and-timezones": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/countries-and-timezones/-/countries-and-timezones-3.4.1.tgz", - "integrity": "sha512-INeHGCony4XUUR8iGL/lmt9s1Oi+n+gFHeJAMfbV5hJfYeDOB8JG1oxz5xFQu5oBZoRCJe/87k1Vzue9DoIauA==", - "engines": { - "node": ">=8.x", - "npm": ">=5.x" - } - }, "node_modules/cycle": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/cycle/-/cycle-1.0.3.tgz", @@ -1217,14 +1161,6 @@ "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==" }, - "node_modules/denque": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/denque/-/denque-1.5.1.tgz", - "integrity": "sha512-XwE+iZ4D6ZUB7mfYRMb5wByE8L74HCn30FBN7sWnXksWc1LO1bPDl67pBR9o/kC4z/xSNAwkMYcGgqDV3BE3Hw==", - "engines": { - "node": ">=0.10" - } - }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -2093,6 +2029,11 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, + "node_modules/ip": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz", + "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==" + }, "node_modules/ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -2326,15 +2267,13 @@ "node": ">=0.6.0" } }, - "node_modules/kaitai-struct": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/kaitai-struct/-/kaitai-struct-0.9.0.tgz", - "integrity": "sha512-mfoBu9+IGqaY3ykG1TyAy9omOAZWtheqESQOvo/HKIQVTz+gRPVCNBnhjbO+8wAQ77RD33wYvLBWmITuXIviQg==" - }, "node_modules/kareem": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.3.2.tgz", - "integrity": "sha512-STHz9P7X2L4Kwn72fA4rGyqyXdmrMSdxqHx9IXon/FXluXieaFA6KJ2upcHAHxQPQ0LeM/OjLrhFxifHewOALQ==" + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.5.1.tgz", + "integrity": "sha512-7jFxRVm+jD+rkq3kY0iZDJfsO2/t4BBPeEb2qKn2lR/9KhuksYk5hxzfRYWMPV8P/x2d0kHD306YyWLzjjH+uA==", + "engines": { + "node": ">=12.0.0" + } }, "node_modules/keyv": { "version": "4.5.2", @@ -2585,90 +2524,98 @@ } }, "node_modules/mongodb": { - "version": "3.7.3", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.7.3.tgz", - "integrity": "sha512-Psm+g3/wHXhjBEktkxXsFMZvd3nemI0r3IPsE0bU+4//PnvNWKkzhZcEsbPcYiWqe8XqXJJEg4Tgtr7Raw67Yw==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-5.1.0.tgz", + "integrity": "sha512-qgKb7y+EI90y4weY3z5+lIgm8wmexbonz0GalHkSElQXVKtRuwqXuhXKccyvIjXCJVy9qPV82zsinY0W1FBnJw==", "dependencies": { - "bl": "^2.2.1", - "bson": "^1.1.4", - "denque": "^1.4.1", - "optional-require": "^1.1.8", - "safe-buffer": "^5.1.2" + "bson": "^5.0.1", + "mongodb-connection-string-url": "^2.6.0", + "socks": "^2.7.1" }, "engines": { - "node": ">=4" + "node": ">=14.20.1" }, "optionalDependencies": { - "saslprep": "^1.0.0" + "saslprep": "^1.0.3" + }, + "peerDependencies": { + "@aws-sdk/credential-providers": "^3.201.0", + "mongodb-client-encryption": "^2.3.0", + "snappy": "^7.2.2" }, "peerDependenciesMeta": { - "aws4": { - "optional": true - }, - "bson-ext": { - "optional": true - }, - "kerberos": { + "@aws-sdk/credential-providers": { "optional": true }, "mongodb-client-encryption": { "optional": true }, - "mongodb-extjson": { - "optional": true - }, "snappy": { "optional": true } } }, - "node_modules/mongodb/node_modules/optional-require": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/optional-require/-/optional-require-1.1.8.tgz", - "integrity": "sha512-jq83qaUb0wNg9Krv1c5OQ+58EK+vHde6aBPzLvPPqJm89UQWsvSuFy9X/OSNJnFeSOKo7btE0n8Nl2+nE+z5nA==", + "node_modules/mongodb-connection-string-url": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.6.0.tgz", + "integrity": "sha512-WvTZlI9ab0QYtTYnuMLgobULWhokRjtC7db9LtcVfJ+Hsnyr5eo6ZtNAt3Ly24XZScGMelOcGtm7lSn0332tPQ==", "dependencies": { - "require-at": "^1.0.6" + "@types/whatwg-url": "^8.2.1", + "whatwg-url": "^11.0.0" + } + }, + "node_modules/mongodb-connection-string-url/node_modules/tr46": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", + "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", + "dependencies": { + "punycode": "^2.1.1" }, "engines": { - "node": ">=4" + "node": ">=12" + } + }, + "node_modules/mongodb-connection-string-url/node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "engines": { + "node": ">=12" + } + }, + "node_modules/mongodb-connection-string-url/node_modules/whatwg-url": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", + "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", + "dependencies": { + "tr46": "^3.0.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=12" } }, "node_modules/mongoose": { - "version": "5.13.16", - "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-5.13.16.tgz", - "integrity": "sha512-kBNB+BfaQjn3Jjh1SfdZZub70pde9dI0sA8VN6AnnCOeK4TzbLDyB0lBmPBOajppm6U9orde5YfTRyyVa1U45w==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-7.0.0.tgz", + "integrity": "sha512-U0YPURDld+k/nvvSG1mRClQSjZMRXwQKSU5yb9PslRnOmVz0UlBD7SjSnjUuGT0yk+7BH+kJNimsKqMxYAKkMA==", "dependencies": { - "@types/bson": "1.x || 4.0.x", - "@types/mongodb": "^3.5.27", - "bson": "^1.1.4", - "kareem": "2.3.2", - "mongodb": "3.7.3", - "mongoose-legacy-pluralize": "1.0.2", - "mpath": "0.8.4", - "mquery": "3.2.5", - "ms": "2.1.2", - "optional-require": "1.0.x", - "regexp-clone": "1.0.0", - "safe-buffer": "5.2.1", - "sift": "13.5.2", - "sliced": "1.0.1" + "bson": "^5.0.1", + "kareem": "2.5.1", + "mongodb": "5.1.0", + "mpath": "0.9.0", + "mquery": "5.0.0", + "ms": "2.1.3", + "sift": "16.0.1" }, "engines": { - "node": ">=4.0.0" + "node": ">=14.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/mongoose" } }, - "node_modules/mongoose-legacy-pluralize": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/mongoose-legacy-pluralize/-/mongoose-legacy-pluralize-1.0.2.tgz", - "integrity": "sha512-Yo/7qQU4/EyIS8YDFSeenIvXxZN+ld7YdV9LqFVQJzTLye8unujAWPZ4NWKfFA+RNjh+wvTWKY9Z3E5XM6ZZiQ==", - "peerDependencies": { - "mongoose": "*" - } - }, "node_modules/mongoose-unique-validator": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/mongoose-unique-validator/-/mongoose-unique-validator-2.0.3.tgz", @@ -2682,9 +2629,9 @@ } }, "node_modules/mongoose/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, "node_modules/morgan": { "version": "1.10.0", @@ -2713,40 +2660,44 @@ } }, "node_modules/mpath": { - "version": "0.8.4", - "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.8.4.tgz", - "integrity": "sha512-DTxNZomBcTWlrMW76jy1wvV37X/cNNxPW1y2Jzd4DZkAaC5ZGsm8bfGfNOthcDuRJujXLqiuS6o3Tpy0JEoh7g==", + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz", + "integrity": "sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew==", "engines": { "node": ">=4.0.0" } }, "node_modules/mquery": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/mquery/-/mquery-3.2.5.tgz", - "integrity": "sha512-VjOKHHgU84wij7IUoZzFRU07IAxd5kWJaDmyUzQlbjHjyoeK5TNeeo8ZsFDtTYnSgpW6n/nMNIHvE3u8Lbrf4A==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/mquery/-/mquery-5.0.0.tgz", + "integrity": "sha512-iQMncpmEK8R8ncT8HJGsGc9Dsp8xcgYMVSbs5jgnm1lFHTZqMJTUWTDx1LBO8+mK3tPNZWFLBghQEIOULSTHZg==", "dependencies": { - "bluebird": "3.5.1", - "debug": "3.1.0", - "regexp-clone": "^1.0.0", - "safe-buffer": "5.1.2", - "sliced": "1.0.1" + "debug": "4.x" }, "engines": { - "node": ">=4.0.0" + "node": ">=14.0.0" } }, "node_modules/mquery/node_modules/debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dependencies": { - "ms": "2.0.0" + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/mquery/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + "node_modules/mquery/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "node_modules/ms": { "version": "2.0.0", @@ -2759,14 +2710,6 @@ "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", "dev": true }, - "node_modules/neat-config": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/neat-config/-/neat-config-1.0.0.tgz", - "integrity": "sha512-EKvHxsZ9yOUHWhx6MxxzbTDhNK0wiSbfaU42s2OnQuVgR+FwpswaiSm3xX7ST4TcN+rJiEw+GuhfK015vHKwrA==", - "dependencies": { - "joi": "^17.6.0" - } - }, "node_modules/negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", @@ -2913,14 +2856,6 @@ "wrappy": "1" } }, - "node_modules/optional-require": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/optional-require/-/optional-require-1.0.3.tgz", - "integrity": "sha512-RV2Zp2MY2aeYK5G+B/Sps8lW5NHAzE5QClbFP15j+PWmP+T9PxlJXBOOLoSAdgwFvS4t0aMR4vpedMkbHfh0nA==", - "engines": { - "node": ">=4" - } - }, "node_modules/optionator": { "version": "0.8.3", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", @@ -3175,11 +3110,6 @@ "@redis/time-series": "1.0.4" } }, - "node_modules/regexp-clone": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/regexp-clone/-/regexp-clone-1.0.0.tgz", - "integrity": "sha512-TuAasHQNamyyJ2hb97IuBEif4qBHGjPHBS64sZwytpLEqtBQ1gPJTnOaQ6qmpET16cK14kkjbazl6+p0RRv0yw==" - }, "node_modules/request": { "version": "2.88.2", "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", @@ -3228,14 +3158,6 @@ "uuid": "bin/uuid" } }, - "node_modules/require-at": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/require-at/-/require-at-1.0.6.tgz", - "integrity": "sha512-7i1auJbMUrXEAZCOQ0VNJgmcT2VOKPRl2YGJwgpHpC9CE91Mv4/4UYIUm4chGJaI381ZDq1JUicFii64Hapd8g==", - "engines": { - "node": ">=4" - } - }, "node_modules/resolve": { "version": "1.22.1", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", @@ -3436,19 +3358,36 @@ } }, "node_modules/sift": { - "version": "13.5.2", - "resolved": "https://registry.npmjs.org/sift/-/sift-13.5.2.tgz", - "integrity": "sha512-+gxdEOMA2J+AI+fVsCqeNn7Tgx3M9ZN9jdi95939l1IJ8cZsqS8sqpJyOkic2SJk+1+98Uwryt/gL6XDaV+UZA==" + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/sift/-/sift-16.0.1.tgz", + "integrity": "sha512-Wv6BjQ5zbhW7VFefWusVP33T/EM0vYikCaQ2qR8yULbsilAT8/wQaXvuQ3ptGLpoKx+lihJE3y2UTgKDyyNHZQ==" }, "node_modules/signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" }, - "node_modules/sliced": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/sliced/-/sliced-1.0.1.tgz", - "integrity": "sha512-VZBmZP8WU3sMOZm1bdgTadsQbcscK0UM8oKxKVBs4XAhUo2Xxzm/OFMGBkPusxw9xL3Uy8LrzEqGqJhclsr0yA==" + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz", + "integrity": "sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==", + "dependencies": { + "ip": "^2.0.0", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.13.0", + "npm": ">= 3.0.0" + } }, "node_modules/source-map": { "version": "0.6.1", @@ -4240,14 +4179,6 @@ "@types/node": "*" } }, - "@types/bson": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/@types/bson/-/bson-4.0.5.tgz", - "integrity": "sha512-vVLwMUqhYJSQ/WKcE60eFqcyuWse5fGH+NMAXHuKrUAPoryq3ATxk5o4bgYNtg5aOM4APVg7Hnb3ASqUYG0PKg==", - "requires": { - "@types/node": "*" - } - }, "@types/cacheable-request": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz", @@ -4338,15 +4269,6 @@ "integrity": "sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==", "dev": true }, - "@types/mongodb": { - "version": "3.6.20", - "resolved": "https://registry.npmjs.org/@types/mongodb/-/mongodb-3.6.20.tgz", - "integrity": "sha512-WcdpPJCakFzcWWD9juKoZbRtQxKIMYF/JIAM4JrNHrMcnJL6/a2NWjXxW7fo9hxboxxkg+icff8d7+WIEvKgYQ==", - "requires": { - "@types/bson": "*", - "@types/node": "*" - } - }, "@types/morgan": { "version": "1.9.4", "resolved": "https://registry.npmjs.org/@types/morgan/-/morgan-1.9.4.tgz", @@ -4391,6 +4313,20 @@ "@types/node": "*" } }, + "@types/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-xTE1E+YF4aWPJJeUzaZI5DRntlkY3+BCVJi0axFptnjGmAoWxkyREIh/XMrfxVLejwQxMCfDXdICo0VLxThrog==" + }, + "@types/whatwg-url": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-8.2.2.tgz", + "integrity": "sha512-FtQu10RWgn3D9U4aazdwIE2yzphmTJREDqNdODHrbrZmmMqI0vMheC/6NE/J1Yveaj8H+ela+YwWTjq5PGmuhA==", + "requires": { + "@types/node": "*", + "@types/webidl-conversions": "*" + } + }, "abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", @@ -4626,49 +4562,6 @@ "resolved": "https://registry.npmjs.org/bit-buffer/-/bit-buffer-0.2.5.tgz", "integrity": "sha512-x1yGnmXvFg6e3DiyRztElbcn1bsCTFSoM/ncAzY62uE0JdTl5xlKJd0ooqLYoPbhdsnpehSIQrdIvclcZJYwiA==" }, - "bl": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/bl/-/bl-2.2.1.tgz", - "integrity": "sha512-6Pesp1w0DEX1N550i/uGV/TqucVL4AM/pgThFSN/Qq9si1/DF9aIHs1BxD8V/QU0HoeHO6cQRTAuYnLPKq1e4g==", - "requires": { - "readable-stream": "^2.3.5", - "safe-buffer": "^5.1.1" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "bluebird": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", - "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==" - }, "bmp-js": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/bmp-js/-/bmp-js-0.1.0.tgz", @@ -4714,9 +4607,9 @@ } }, "bson": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/bson/-/bson-1.1.6.tgz", - "integrity": "sha512-EvVNVeGo4tHxwi8L6bPj3y3itEvStdwvvlojVxxbyYfoaxJ6keLgrTuKdyfEAszFK+H3olzBuafE0yoh0D1gdg==" + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/bson/-/bson-5.0.1.tgz", + "integrity": "sha512-y09gBGusgHtinMon/GVbv1J6FrXhnr/+6hqLlSmEFzkz6PodqF6TxjyvfvY3AfO+oG1mgUtbC86xSbOlwvM62Q==" }, "buffer": { "version": "4.9.2", @@ -4919,11 +4812,6 @@ "vary": "^1" } }, - "countries-and-timezones": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/countries-and-timezones/-/countries-and-timezones-3.4.1.tgz", - "integrity": "sha512-INeHGCony4XUUR8iGL/lmt9s1Oi+n+gFHeJAMfbV5hJfYeDOB8JG1oxz5xFQu5oBZoRCJe/87k1Vzue9DoIauA==" - }, "cycle": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/cycle/-/cycle-1.0.3.tgz", @@ -4995,11 +4883,6 @@ "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==" }, - "denque": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/denque/-/denque-1.5.1.tgz", - "integrity": "sha512-XwE+iZ4D6ZUB7mfYRMb5wByE8L74HCn30FBN7sWnXksWc1LO1bPDl67pBR9o/kC4z/xSNAwkMYcGgqDV3BE3Hw==" - }, "depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -5703,6 +5586,11 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, + "ip": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz", + "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==" + }, "ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -5871,15 +5759,10 @@ "verror": "1.10.0" } }, - "kaitai-struct": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/kaitai-struct/-/kaitai-struct-0.9.0.tgz", - "integrity": "sha512-mfoBu9+IGqaY3ykG1TyAy9omOAZWtheqESQOvo/HKIQVTz+gRPVCNBnhjbO+8wAQ77RD33wYvLBWmITuXIviQg==" - }, "kareem": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.3.2.tgz", - "integrity": "sha512-STHz9P7X2L4Kwn72fA4rGyqyXdmrMSdxqHx9IXon/FXluXieaFA6KJ2upcHAHxQPQ0LeM/OjLrhFxifHewOALQ==" + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.5.1.tgz", + "integrity": "sha512-7jFxRVm+jD+rkq3kY0iZDJfsO2/t4BBPeEb2qKn2lR/9KhuksYk5hxzfRYWMPV8P/x2d0kHD306YyWLzjjH+uA==" }, "keyv": { "version": "4.5.2", @@ -6069,62 +5952,70 @@ "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==" }, "mongodb": { - "version": "3.7.3", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.7.3.tgz", - "integrity": "sha512-Psm+g3/wHXhjBEktkxXsFMZvd3nemI0r3IPsE0bU+4//PnvNWKkzhZcEsbPcYiWqe8XqXJJEg4Tgtr7Raw67Yw==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-5.1.0.tgz", + "integrity": "sha512-qgKb7y+EI90y4weY3z5+lIgm8wmexbonz0GalHkSElQXVKtRuwqXuhXKccyvIjXCJVy9qPV82zsinY0W1FBnJw==", "requires": { - "bl": "^2.2.1", - "bson": "^1.1.4", - "denque": "^1.4.1", - "optional-require": "^1.1.8", - "safe-buffer": "^5.1.2", - "saslprep": "^1.0.0" + "bson": "^5.0.1", + "mongodb-connection-string-url": "^2.6.0", + "saslprep": "^1.0.3", + "socks": "^2.7.1" + } + }, + "mongodb-connection-string-url": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.6.0.tgz", + "integrity": "sha512-WvTZlI9ab0QYtTYnuMLgobULWhokRjtC7db9LtcVfJ+Hsnyr5eo6ZtNAt3Ly24XZScGMelOcGtm7lSn0332tPQ==", + "requires": { + "@types/whatwg-url": "^8.2.1", + "whatwg-url": "^11.0.0" }, "dependencies": { - "optional-require": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/optional-require/-/optional-require-1.1.8.tgz", - "integrity": "sha512-jq83qaUb0wNg9Krv1c5OQ+58EK+vHde6aBPzLvPPqJm89UQWsvSuFy9X/OSNJnFeSOKo7btE0n8Nl2+nE+z5nA==", + "tr46": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", + "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", "requires": { - "require-at": "^1.0.6" + "punycode": "^2.1.1" + } + }, + "webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==" + }, + "whatwg-url": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", + "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", + "requires": { + "tr46": "^3.0.0", + "webidl-conversions": "^7.0.0" } } } }, "mongoose": { - "version": "5.13.16", - "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-5.13.16.tgz", - "integrity": "sha512-kBNB+BfaQjn3Jjh1SfdZZub70pde9dI0sA8VN6AnnCOeK4TzbLDyB0lBmPBOajppm6U9orde5YfTRyyVa1U45w==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-7.0.0.tgz", + "integrity": "sha512-U0YPURDld+k/nvvSG1mRClQSjZMRXwQKSU5yb9PslRnOmVz0UlBD7SjSnjUuGT0yk+7BH+kJNimsKqMxYAKkMA==", "requires": { - "@types/bson": "1.x || 4.0.x", - "@types/mongodb": "^3.5.27", - "bson": "^1.1.4", - "kareem": "2.3.2", - "mongodb": "3.7.3", - "mongoose-legacy-pluralize": "1.0.2", - "mpath": "0.8.4", - "mquery": "3.2.5", - "ms": "2.1.2", - "optional-require": "1.0.x", - "regexp-clone": "1.0.0", - "safe-buffer": "5.2.1", - "sift": "13.5.2", - "sliced": "1.0.1" + "bson": "^5.0.1", + "kareem": "2.5.1", + "mongodb": "5.1.0", + "mpath": "0.9.0", + "mquery": "5.0.0", + "ms": "2.1.3", + "sift": "16.0.1" }, "dependencies": { "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" } } }, - "mongoose-legacy-pluralize": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/mongoose-legacy-pluralize/-/mongoose-legacy-pluralize-1.0.2.tgz", - "integrity": "sha512-Yo/7qQU4/EyIS8YDFSeenIvXxZN+ld7YdV9LqFVQJzTLye8unujAWPZ4NWKfFA+RNjh+wvTWKY9Z3E5XM6ZZiQ==", - "requires": {} - }, "mongoose-unique-validator": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/mongoose-unique-validator/-/mongoose-unique-validator-2.0.3.tgz", @@ -6157,34 +6048,30 @@ } }, "mpath": { - "version": "0.8.4", - "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.8.4.tgz", - "integrity": "sha512-DTxNZomBcTWlrMW76jy1wvV37X/cNNxPW1y2Jzd4DZkAaC5ZGsm8bfGfNOthcDuRJujXLqiuS6o3Tpy0JEoh7g==" + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz", + "integrity": "sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew==" }, "mquery": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/mquery/-/mquery-3.2.5.tgz", - "integrity": "sha512-VjOKHHgU84wij7IUoZzFRU07IAxd5kWJaDmyUzQlbjHjyoeK5TNeeo8ZsFDtTYnSgpW6n/nMNIHvE3u8Lbrf4A==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/mquery/-/mquery-5.0.0.tgz", + "integrity": "sha512-iQMncpmEK8R8ncT8HJGsGc9Dsp8xcgYMVSbs5jgnm1lFHTZqMJTUWTDx1LBO8+mK3tPNZWFLBghQEIOULSTHZg==", "requires": { - "bluebird": "3.5.1", - "debug": "3.1.0", - "regexp-clone": "^1.0.0", - "safe-buffer": "5.1.2", - "sliced": "1.0.1" + "debug": "4.x" }, "dependencies": { "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "requires": { - "ms": "2.0.0" + "ms": "2.1.2" } }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" } } }, @@ -6199,14 +6086,6 @@ "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", "dev": true }, - "neat-config": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/neat-config/-/neat-config-1.0.0.tgz", - "integrity": "sha512-EKvHxsZ9yOUHWhx6MxxzbTDhNK0wiSbfaU42s2OnQuVgR+FwpswaiSm3xX7ST4TcN+rJiEw+GuhfK015vHKwrA==", - "requires": { - "joi": "^17.6.0" - } - }, "negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", @@ -6309,11 +6188,6 @@ "wrappy": "1" } }, - "optional-require": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/optional-require/-/optional-require-1.0.3.tgz", - "integrity": "sha512-RV2Zp2MY2aeYK5G+B/Sps8lW5NHAzE5QClbFP15j+PWmP+T9PxlJXBOOLoSAdgwFvS4t0aMR4vpedMkbHfh0nA==" - }, "optionator": { "version": "0.8.3", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", @@ -6515,11 +6389,6 @@ "@redis/time-series": "1.0.4" } }, - "regexp-clone": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/regexp-clone/-/regexp-clone-1.0.0.tgz", - "integrity": "sha512-TuAasHQNamyyJ2hb97IuBEif4qBHGjPHBS64sZwytpLEqtBQ1gPJTnOaQ6qmpET16cK14kkjbazl6+p0RRv0yw==" - }, "request": { "version": "2.88.2", "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", @@ -6559,11 +6428,6 @@ } } }, - "require-at": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/require-at/-/require-at-1.0.6.tgz", - "integrity": "sha512-7i1auJbMUrXEAZCOQ0VNJgmcT2VOKPRl2YGJwgpHpC9CE91Mv4/4UYIUm4chGJaI381ZDq1JUicFii64Hapd8g==" - }, "resolve": { "version": "1.22.1", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", @@ -6716,19 +6580,28 @@ } }, "sift": { - "version": "13.5.2", - "resolved": "https://registry.npmjs.org/sift/-/sift-13.5.2.tgz", - "integrity": "sha512-+gxdEOMA2J+AI+fVsCqeNn7Tgx3M9ZN9jdi95939l1IJ8cZsqS8sqpJyOkic2SJk+1+98Uwryt/gL6XDaV+UZA==" + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/sift/-/sift-16.0.1.tgz", + "integrity": "sha512-Wv6BjQ5zbhW7VFefWusVP33T/EM0vYikCaQ2qR8yULbsilAT8/wQaXvuQ3ptGLpoKx+lihJE3y2UTgKDyyNHZQ==" }, "signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" }, - "sliced": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/sliced/-/sliced-1.0.1.tgz", - "integrity": "sha512-VZBmZP8WU3sMOZm1bdgTadsQbcscK0UM8oKxKVBs4XAhUo2Xxzm/OFMGBkPusxw9xL3Uy8LrzEqGqJhclsr0yA==" + "smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==" + }, + "socks": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz", + "integrity": "sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==", + "requires": { + "ip": "^2.0.0", + "smart-buffer": "^4.2.0" + } }, "source-map": { "version": "0.6.1", diff --git a/package.json b/package.json index 1814e4b..a02b631 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ "lodash.set": "^4.3.2", "mii-js": "github:PretendoNetwork/mii-js", "moment": "^2.29.4", - "mongoose": "^5.8.3", + "mongoose": "^7.0.0", "mongoose-unique-validator": "^2.0.3", "morgan": "^1.9.1", "nodemailer": "^6.4.2", diff --git a/src/database.ts b/src/database.ts index 93bffff..c5a47a9 100644 --- a/src/database.ts +++ b/src/database.ts @@ -38,7 +38,7 @@ export async function getUserByUsername(username) { const user = await PNID.findOne({ usernameLower: username.toLowerCase() - }); + }) as mongoose.HydratedDocument; return user; } @@ -48,7 +48,7 @@ export async function getUserByPID(pid) { const user = await PNID.findOne({ pid - }); + }) as mongoose.HydratedDocument; return user; } @@ -58,7 +58,7 @@ export async function getUserByEmailAddress(email) { const user = await PNID.findOne({ 'email.address': new RegExp(email, 'i') // * Ignore case - }); + }) as mongoose.HydratedDocument; return user; } diff --git a/src/models/device.ts b/src/models/device.ts index 820a3f4..22ac5c1 100644 --- a/src/models/device.ts +++ b/src/models/device.ts @@ -1,14 +1,14 @@ import { Schema, model } from 'mongoose'; -export const DeviceAttributeSchema = new Schema({ +export const DeviceAttributeSchema = new Schema({ created_date: String, name: String, value: String }); -export const DeviceAttribute: IDeviceAttributeModel = model('DeviceAttribute', DeviceAttributeSchema); +export const DeviceAttribute: DeviceAttributeModel = model('DeviceAttribute', DeviceAttributeSchema); -export const DeviceSchema = new Schema({ +export const DeviceSchema = new Schema({ is_emulator: { type: Boolean, default: false @@ -48,7 +48,7 @@ export const DeviceSchema = new Schema({ } }); -export const Device: IDeviceModel = model('Device', DeviceSchema); +export const Device: DeviceModel = model('Device', DeviceSchema); export default { DeviceSchema, diff --git a/src/models/nex-account.ts b/src/models/nex-account.ts index 6a4b509..cfbc818 100644 --- a/src/models/nex-account.ts +++ b/src/models/nex-account.ts @@ -1,7 +1,7 @@ import { Schema, model } from 'mongoose'; import uniqueValidator from 'mongoose-unique-validator'; -export const NEXAccountSchema = new Schema({ +export const NEXAccountSchema = new Schema({ device_type: { type: String, enum: [ @@ -36,7 +36,7 @@ NEXAccountSchema.plugin(uniqueValidator, { message: '{PATH} already in use.' }); https://account.nintendo.net/v1/api/admin/mapped_ids?input_type=pid&output_type=user_id&input=1799999999 returns `prodtest1` and the next few accounts counting down seem to be admin, service and internal test accounts */ -NEXAccountSchema.methods.generatePID = async function () { +NEXAccountSchema.method('generatePID', async function generatePID(): Promise { const min = 1000000000; // The console (WiiU) seems to not accept PIDs smaller than this const max = 1799999999; @@ -45,13 +45,13 @@ NEXAccountSchema.methods.generatePID = async function () { const inuse = await NEXAccount.findOne({ pid }); if (inuse) { - await NEXAccount.generatePID(); + await this.generatePID(); } else { this.set('pid', pid); } -}; +}); -NEXAccountSchema.methods.generatePassword = function () { +NEXAccountSchema.method('generatePassword', function generatePassword(): void { function character() { const offset = Math.floor(Math.random() * 62); if (offset < 10) return offset; @@ -66,9 +66,9 @@ NEXAccountSchema.methods.generatePassword = function () { } this.set('password', output.join('')); -}; +}); -export const NEXAccount: INEXAccountModel = model('NEXAccount', NEXAccountSchema); +export const NEXAccount: NEXAccountModel = model('NEXAccount', NEXAccountSchema); export default { NEXAccountSchema, diff --git a/src/models/pnid.ts b/src/models/pnid.ts index 73b0079..4adc913 100644 --- a/src/models/pnid.ts +++ b/src/models/pnid.ts @@ -8,7 +8,7 @@ import Mii from 'mii-js'; import { DeviceSchema } from './device'; import util from '../util'; -export const PNIDSchema = new Schema({ +export const PNIDSchema = new Schema({ access_level: { type: Number, default: 0 // 0: standard, 1: tester, 2: mod?, 3: dev @@ -114,7 +114,7 @@ PNIDSchema.plugin(uniqueValidator, {message: '{PATH} already in use.'}); https://account.nintendo.net/v1/api/admin/mapped_ids?input_type=pid&output_type=user_id&input=1799999999 returns `prodtest1` and the next few accounts counting down seem to be admin, service and internal test accounts */ -PNIDSchema.methods.generatePID = async function() { +PNIDSchema.method('generatePID', async function generatePID(): Promise { const min = 1000000000; // The console (WiiU) seems to not accept PIDs smaller than this const max = 1799999999; @@ -125,21 +125,21 @@ PNIDSchema.methods.generatePID = async function() { }); if (inuse) { - await PNID.generatePID(); + await this.generatePID(); } else { this.set('pid', pid); } -}; +}); -PNIDSchema.methods.generateEmailValidationCode = async function() { +PNIDSchema.method('generateEmailValidationCode', async function generateEmailValidationCode(): Promise { // WiiU passes the PID along with the email code // Does not actually need to be unique to all users const code = Math.random().toFixed(6).split('.')[1]; // Dirty one-liner to generate numbers of 6 length and padded 0 this.set('identification.email_code', code); -}; +}); -PNIDSchema.methods.generateEmailValidationToken = async function() { +PNIDSchema.method('generateEmailValidationToken', async function generateEmailValidationToken(): Promise { let token = crypto.randomBytes(32).toString('hex'); const inuse = await PNID.findOne({ @@ -147,13 +147,13 @@ PNIDSchema.methods.generateEmailValidationToken = async function() { }); if (inuse) { - await PNID.generateEmailValidationToken(); + await this.generateEmailValidationToken(); } else { this.set('identification.email_token', token); } -}; +}); -PNIDSchema.methods.updateMii = async function({name, primary, data}) { +PNIDSchema.method('updateMii', async function updateMii({name, primary, data}): Promise { this.set('mii.name', name); this.set('mii.primary', primary === 'Y'); this.set('mii.data', data); @@ -164,9 +164,9 @@ PNIDSchema.methods.updateMii = async function({name, primary, data}) { await this.generateMiiImages(); await this.save(); -}; +}); -PNIDSchema.methods.generateMiiImages = async function() { +PNIDSchema.method('generateMiiImages', async function generateMiiImages(): Promise { const miiData = this.get('mii.data'); const mii = new Mii(Buffer.from(miiData, 'base64')); const miiStudioUrl = mii.studioUrl({ @@ -202,15 +202,15 @@ PNIDSchema.methods.generateMiiImages = async function() { }); const miiStudioBodyImageData = await got(miiStudioBodyUrl).buffer(); await util.uploadCDNAsset('pn-cdn', `${userMiiKey}/body.png`, miiStudioBodyImageData, 'public-read'); -}; +}); -PNIDSchema.methods.getServerMode = function () { +PNIDSchema.method('getServerMode', function getServerMode(): string { const serverMode = this.get('server_mode') || 'prod'; return serverMode; -}; +}); -export const PNID: IPNIDModel = model('PNID', PNIDSchema); +export const PNID: PNIDModel = model('PNID', PNIDSchema); export default { PNIDSchema, diff --git a/src/models/server.ts b/src/models/server.ts index e376aac..b23dbe6 100644 --- a/src/models/server.ts +++ b/src/models/server.ts @@ -1,7 +1,7 @@ import { Schema, model } from 'mongoose'; import uniqueValidator from 'mongoose-unique-validator'; -export const ServerSchema = new Schema({ +export const ServerSchema = new Schema({ ip: String, // Example: 1.1.1.1 port: Number, // Example: 60000 service_name: String, // Example: friends @@ -15,7 +15,7 @@ export const ServerSchema = new Schema({ ServerSchema.plugin(uniqueValidator, { message: '{PATH} already in use.' }); -export const Server: IServerModel = model('Server', ServerSchema); +export const Server: ServerModel = model('Server', ServerSchema); export default { ServerSchema, diff --git a/src/services/api/routes/v1/user.ts b/src/services/api/routes/v1/user.ts index 1a6f89b..6f7a299 100644 --- a/src/services/api/routes/v1/user.ts +++ b/src/services/api/routes/v1/user.ts @@ -31,32 +31,32 @@ router.get('/', async (request, response) => { } return response.json({ - access_level: pnid.get('access_level'), - server_access_level: pnid.get('server_access_level'), - pid: pnid.get('pid'), - creation_date: pnid.get('creation_date'), - updated: pnid.get('updated'), - username: pnid.get('username'), - birthdate: pnid.get('birthdate'), - gender: pnid.get('gender'), - country: pnid.get('country'), + access_level: pnid.access_level, + server_access_level: pnid.server_access_level, + pid: pnid.pid, + creation_date: pnid.creation_date, + updated: pnid.updated, + username: pnid.username, + birthdate: pnid.birthdate, + gender: pnid.gender, + country: pnid.country, email: { - address: pnid.get('email.address'), + address: pnid.email.address, }, timezone: { - name: pnid.get('timezone.name') + name: pnid.timezone.name }, mii: { - data: pnid.get('mii.data'), - name: pnid.get('mii.name'), - image_url: `${config.cdn.base_url}/mii/${pnid.get('pid')}/normal_face.png` + data: pnid.mii.data, + name: pnid.mii.name, + image_url: `${config.cdn.base_url}/mii/${pnid.pid}/normal_face.png` }, flags: { - marketing: pnid.get('flags.marketing') + marketing: pnid.flags.marketing }, connections: { discord: { - id: pnid.get('connections.discord.id') + id: pnid.connections.discord.id } } }); @@ -95,32 +95,32 @@ router.post('/', async (request, response) => { await PNID.updateOne({ pid }, { $set: updateData }).exec(); return response.json({ - access_level: pnid.get('access_level'), - server_access_level: pnid.get('server_access_level'), - pid: pnid.get('pid'), - creation_date: pnid.get('creation_date'), - updated: pnid.get('updated'), - username: pnid.get('username'), - birthdate: pnid.get('birthdate'), - gender: pnid.get('gender'), - country: pnid.get('country'), + access_level: pnid.access_level, + server_access_level: pnid.server_access_level, + pid: pnid.pid, + creation_date: pnid.creation_date, + updated: pnid.updated, + username: pnid.username, + birthdate: pnid.birthdate, + gender: pnid.gender, + country: pnid.country, email: { - address: pnid.get('email.address'), + address: pnid.email.address, }, timezone: { - name: pnid.get('timezone.name') + name: pnid.timezone.name }, mii: { - data: pnid.get('mii.data'), - name: pnid.get('mii.name'), - image_url: `${config.cdn.base_url}/mii/${pnid.get('pid')}/normal_face.png` + data: pnid.mii.data, + name: pnid.mii.name, + image_url: `${config.cdn.base_url}/mii/${pnid.pid}/normal_face.png` }, flags: { - marketing: pnid.get('flags.marketing') + marketing: pnid.flags.marketing }, connections: { discord: { - id: pnid.get('connections.discord.id') + id: pnid.connections.discord.id } } }); diff --git a/src/services/nnid/routes/people.ts b/src/services/nnid/routes/people.ts index 9eb06c5..6c22374 100644 --- a/src/services/nnid/routes/people.ts +++ b/src/services/nnid/routes/people.ts @@ -227,7 +227,7 @@ router.post('/@me/devices', async (request, response) => { const { pnid } = request; - const person = await database.getUserProfileJSONByPID(pnid.get('pid')); + const person = await database.getUserProfileJSONByPID(pnid.pid); response.send(xmlbuilder.create({ person diff --git a/types/express.d.ts b/types/express.d.ts index ca16356..1dbe78b 100644 --- a/types/express.d.ts +++ b/types/express.d.ts @@ -1,9 +1,10 @@ +import { HydratedDocument } from 'mongoose'; import NintendoCertificate from '../src/nintendo-certificate' declare global { namespace Express { interface Request { - pnid?: IPNID; + pnid?: HydratedDocument; isCemu?: boolean; files?: Record; certificate?: NintendoCertificate; diff --git a/types/mongoose/device-attribute.d.ts b/types/mongoose/device-attribute.d.ts index 8be84dd..98a7de1 100644 --- a/types/mongoose/device-attribute.d.ts +++ b/types/mongoose/device-attribute.d.ts @@ -1,13 +1,13 @@ -import { Document, Model } from 'mongoose'; +import { Model } from 'mongoose'; declare global { - interface IDeviceAttributeDocument extends Document { + interface IDeviceAttribute { created_date: string; name: string; value: string; } - interface IDeviceAttribute extends IDeviceAttributeDocument {} + interface IDeviceAttributeMethods {} - interface IDeviceAttributeModel extends Model {} + interface DeviceAttributeModel extends Model {} } \ No newline at end of file diff --git a/types/mongoose/device.d.ts b/types/mongoose/device.d.ts index 2303607..786953b 100644 --- a/types/mongoose/device.d.ts +++ b/types/mongoose/device.d.ts @@ -1,4 +1,4 @@ -import { Document, Model } from 'mongoose'; +import { Model } from 'mongoose'; import { DeviceAttributeSchema } from '../../src/models/device'; type MODEL = 'wup' | 'ctr' | 'spr' | 'ftr' | 'ktr' | 'red' | 'jan'; @@ -6,7 +6,7 @@ type ACCESS_LEVEL = 0 | 1 | 2 | 3; type SERVER_ACCESS_LEVEL = 'prod' | 'test' | 'dev'; declare global { - interface IDeviceDocument extends Document { + interface IDevice { is_emulator: boolean; model: MODEL; device_id: number; @@ -26,7 +26,7 @@ declare global { server_access_level: SERVER_ACCESS_LEVEL; } - interface IDevice extends IDeviceDocument {} + interface IDeviceMethods {} - interface IDeviceModel extends Model {} + interface DeviceModel extends Model {} } \ No newline at end of file diff --git a/types/mongoose/nex-account.d.ts b/types/mongoose/nex-account.d.ts index 51ab37d..e51119a 100644 --- a/types/mongoose/nex-account.d.ts +++ b/types/mongoose/nex-account.d.ts @@ -1,10 +1,10 @@ -import { Document, Model } from 'mongoose'; +import { Model } from 'mongoose'; type DEVICE = 'wiiu' | '3ds'; type ACCESS_LEVEL = 0 | 1 | 2 | 3; declare global { - interface INEXAccountDocument extends Document { + interface INEXAccount { device_type: DEVICE; pid: number; password: string; @@ -13,11 +13,10 @@ declare global { server_access_level: string; } - interface INEXAccount extends INEXAccountDocument { - generatePID(): void; + interface INEXAccountMethods { + generatePID(): Promise; + generatePassword(): void; } - interface INEXAccountModel extends Model { - generatePID(): void; - } + interface NEXAccountModel extends Model {} } \ No newline at end of file diff --git a/types/mongoose/pnid.d.ts b/types/mongoose/pnid.d.ts index 145523b..e0bea47 100644 --- a/types/mongoose/pnid.d.ts +++ b/types/mongoose/pnid.d.ts @@ -1,8 +1,8 @@ -import { Document, Model } from 'mongoose'; +import { Model } from 'mongoose'; import { DeviceSchema } from '../../src/models/device'; declare global { - interface IPNIDDocument extends Document { + interface IPNID { access_level: number; server_access_level: string; pid: number; @@ -65,17 +65,14 @@ declare global { }; } - interface IPNID extends IPNIDDocument { - generatePID(): void; - generateEmailValidationCode(): void; - generateEmailValidationToken(): void; - updateMii(mii: { name: string, primary: boolean, data: Buffer}): void; - generateMiiImages(): void; + interface IPNIDMethods { + generatePID(): Promise; + generateEmailValidationCode(): Promise; + generateEmailValidationToken(): Promise; + updateMii(mii: { name: string, primary: string, data: string}): Promise; + generateMiiImages(): Promise; getServerMode(): string; } - interface IPNIDModel extends Model { - generatePID(): void; - generateEmailValidationToken(): void; - } + interface PNIDModel extends Model {} } \ No newline at end of file diff --git a/types/mongoose/server.d.ts b/types/mongoose/server.d.ts index 574f21e..6261929 100644 --- a/types/mongoose/server.d.ts +++ b/types/mongoose/server.d.ts @@ -1,7 +1,7 @@ import { Document, Model } from 'mongoose'; declare global { - interface IServerDocument extends Document { + interface IServer { ip: string; // Example: 1.1.1.1 port: Number; // Example: 60000 service_name: string; // Example: friends @@ -13,7 +13,7 @@ declare global { device: number; // Example: 1 (WiiU) } - interface IServer extends IServerDocument {} + interface IServerMethods {} - interface IServerModel extends Model {} + interface ServerModel extends Model {} } \ No newline at end of file From 45a5fd986240adf764847a8116c2ac73052dca91 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Sat, 4 Mar 2023 09:45:12 -0500 Subject: [PATCH 018/219] Add import aliases --- package.json | 2 +- src/cache.ts | 2 +- src/config-manager.ts | 2 +- src/database.ts | 10 ++++---- src/mailer.ts | 2 +- src/middleware/api.ts | 2 +- src/middleware/device-certificate.ts | 2 +- src/middleware/nasc.ts | 14 +++++------ src/middleware/pnid.ts | 2 +- src/models/pnid.ts | 4 +-- src/server.ts | 26 ++++++++++---------- src/services/api/index.ts | 6 ++--- src/services/api/routes/index.ts | 14 +++++------ src/services/api/routes/v1/connections.ts | 2 +- src/services/api/routes/v1/email.ts | 4 +-- src/services/api/routes/v1/forgotPassword.ts | 4 +-- src/services/api/routes/v1/login.ts | 6 ++--- src/services/api/routes/v1/register.ts | 14 +++++------ src/services/api/routes/v1/resetPassword.ts | 4 +-- src/services/api/routes/v1/user.ts | 4 +-- src/services/assets/index.ts | 2 +- src/services/conntest/index.ts | 2 +- src/services/datastore/index.ts | 4 +-- src/services/datastore/routes/index.ts | 2 +- src/services/datastore/routes/upload.ts | 2 +- src/services/local-cdn/index.ts | 6 ++--- src/services/local-cdn/routes/get.ts | 2 +- src/services/local-cdn/routes/index.ts | 2 +- src/services/nasc/index.ts | 6 ++--- src/services/nasc/routes/ac.ts | 6 ++--- src/services/nasc/routes/index.ts | 2 +- src/services/nnid/index.ts | 10 ++++---- src/services/nnid/routes/admin.ts | 2 +- src/services/nnid/routes/content.ts | 2 +- src/services/nnid/routes/index.ts | 16 ++++++------ src/services/nnid/routes/miis.ts | 4 +-- src/services/nnid/routes/oauth.ts | 4 +-- src/services/nnid/routes/people.ts | 16 ++++++------ src/services/nnid/routes/provider.ts | 12 ++++----- src/services/nnid/routes/support.ts | 4 +-- src/util.ts | 6 ++--- tsconfig.json | 8 ++++-- types/express.d.ts | 2 +- types/mongoose/device.d.ts | 2 +- types/mongoose/pnid.d.ts | 2 +- types/mongoose/server.d.ts | 2 +- 46 files changed, 129 insertions(+), 125 deletions(-) diff --git a/package.json b/package.json index a02b631..25f687b 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "main": "./dist/server.js", "scripts": { "lint": "./node_modules/.bin/eslint .", - "build": "npm run clean && tsc && npm run copy-static", + "build": "npm run clean && tsc && tsc-alias && npm run copy-static", "clean": "rm -rf ./dist", "copy-static": "npm run copy-assets && npm run copy-timezones", "copy-assets": "cp -r ./src/assets ./dist/assets", diff --git a/src/cache.ts b/src/cache.ts index 946209e..32a0e19 100644 --- a/src/cache.ts +++ b/src/cache.ts @@ -1,6 +1,6 @@ import fs from 'fs-extra'; import redis from 'redis'; -import { config, disabledFeatures } from './config-manager'; +import { config, disabledFeatures } from '@config-manager'; let client; diff --git a/src/config-manager.ts b/src/config-manager.ts index 15a79d5..9509604 100644 --- a/src/config-manager.ts +++ b/src/config-manager.ts @@ -2,7 +2,7 @@ import fs from 'fs-extra'; import get from 'lodash.get'; import set from 'lodash.set'; import dotenv from 'dotenv'; -import logger from './logger'; +import logger from '@logger'; dotenv.config(); diff --git a/src/database.ts b/src/database.ts index c5a47a9..5d6025a 100644 --- a/src/database.ts +++ b/src/database.ts @@ -1,11 +1,11 @@ import mongoose from 'mongoose'; import bcrypt from 'bcrypt'; import joi from 'joi'; -import util from './util'; -import { PNID } from './models/pnid'; -import { Server } from './models/server'; -import logger from './logger'; -import { config } from './config-manager'; +import util from '@util'; +import { PNID } from '@models/pnid'; +import { Server } from '@models/server'; +import logger from '@logger'; +import { config } from '@config-manager'; const { connection_string, options } = config.mongoose; diff --git a/src/mailer.ts b/src/mailer.ts index 6d2a878..6acbb86 100644 --- a/src/mailer.ts +++ b/src/mailer.ts @@ -1,7 +1,7 @@ import path from 'node:path'; import fs from 'node:fs'; import nodemailer from 'nodemailer'; -import { config, disabledFeatures } from './config-manager'; +import { config, disabledFeatures } from '@config-manager'; const genericEmailTemplate = fs.readFileSync(path.join(__dirname, './assets/emails/genericTemplate.html'), 'utf8'); const confirmationEmailTemplate = fs.readFileSync(path.join(__dirname, './assets/emails/confirmationTemplate.html'), 'utf8'); diff --git a/src/middleware/api.ts b/src/middleware/api.ts index b8a3b3e..3edfbfa 100644 --- a/src/middleware/api.ts +++ b/src/middleware/api.ts @@ -1,5 +1,5 @@ import xmlbuilder from 'xmlbuilder'; -import database from '../database'; +import database from '@database'; export async function APIMiddleware(request, _response, next) { const { headers } = request; diff --git a/src/middleware/device-certificate.ts b/src/middleware/device-certificate.ts index 70909d2..b78c0ec 100644 --- a/src/middleware/device-certificate.ts +++ b/src/middleware/device-certificate.ts @@ -1,4 +1,4 @@ -import NintendoCertificate from '../nintendo-certificate'; +import NintendoCertificate from '@nintendo-certificate'; export async function deviceCertificateMiddleware(request, _response, next) { const { headers } = request; diff --git a/src/middleware/nasc.ts b/src/middleware/nasc.ts index ece25bb..b6903fb 100644 --- a/src/middleware/nasc.ts +++ b/src/middleware/nasc.ts @@ -1,10 +1,10 @@ import crypto from 'node:crypto'; -import { Device } from '../models/device'; -import { NEXAccount } from '../models/nex-account'; -import util from '../util'; -import database from '../database'; -import NintendoCertificate from '../nintendo-certificate'; -import logger from '../logger'; +import { Device } from '@models/device'; +import { NEXAccount } from '@models/nex-account'; +import util from '@util'; +import database from '@database'; +import NintendoCertificate from '@nintendo-certificate'; +import logger from '@logger'; export async function NASCMiddleware(request, response, next) { const requestParams = request.body; @@ -50,7 +50,7 @@ export async function NASCMiddleware(request, response, next) { } const cert = new NintendoCertificate(fcdcert); - + if (!cert.valid) { return response.status(200).send(util.nascError('121')); } diff --git a/src/middleware/pnid.ts b/src/middleware/pnid.ts index 3cbc428..fbe89ab 100644 --- a/src/middleware/pnid.ts +++ b/src/middleware/pnid.ts @@ -1,5 +1,5 @@ import xmlbuilder from 'xmlbuilder'; -import database from '../database'; +import database from '@database'; export async function PNIDMiddleware(request, response, next) { const { headers } = request; diff --git a/src/models/pnid.ts b/src/models/pnid.ts index 4adc913..6934d8d 100644 --- a/src/models/pnid.ts +++ b/src/models/pnid.ts @@ -5,8 +5,8 @@ import imagePixels from 'image-pixels'; import TGA from 'tga'; import got from 'got'; import Mii from 'mii-js'; -import { DeviceSchema } from './device'; -import util from '../util'; +import { DeviceSchema } from '@models/device'; +import util from '@util'; export const PNIDSchema = new Schema({ access_level: { diff --git a/src/server.ts b/src/server.ts index a373b69..3762318 100644 --- a/src/server.ts +++ b/src/server.ts @@ -1,24 +1,24 @@ process.title = 'Pretendo - Account'; -const configManager = require('./config-manager'); +const configManager = require('@config-manager'); configManager.configure(); import express from 'express'; import morgan from 'morgan'; -import xmlparser from './middleware/xml-parser'; -import cache from './cache'; -import database from './database'; -import util from './util'; -import logger from './logger'; +import xmlparser from '@middleware/xml-parser'; +import cache from '@cache'; +import database from '@database'; +import util from '@util'; +import logger from '@logger'; -import conntest from './services/conntest'; -import nnid from './services/nnid'; -import nasc from './services/nasc'; -import datastore from './services/datastore'; -import api from './services/api'; -import localcdn from './services/local-cdn'; -import assets from './services/assets'; +import conntest from '@services/conntest'; +import nnid from '@services/nnid'; +import nasc from '@services/nasc'; +import datastore from '@services/datastore'; +import api from '@services/api'; +import localcdn from '@services/local-cdn'; +import assets from '@services/assets'; const { config } = configManager; diff --git a/src/services/api/index.ts b/src/services/api/index.ts index 56d7551..3f79071 100644 --- a/src/services/api/index.ts +++ b/src/services/api/index.ts @@ -3,9 +3,9 @@ import express from 'express'; import subdomain from 'express-subdomain'; import cors from 'cors'; -import APIMiddleware from '../../middleware/api'; -import routes from './routes'; -import logger from '../../logger'; +import APIMiddleware from '@middleware/api'; +import routes from '@services/api/routes'; +import logger from '@logger'; // Router to handle the subdomain restriction const api = express.Router(); diff --git a/src/services/api/routes/index.ts b/src/services/api/routes/index.ts index 9956240..4b0f452 100644 --- a/src/services/api/routes/index.ts +++ b/src/services/api/routes/index.ts @@ -1,10 +1,10 @@ -import connections_v1 from './v1/connections'; -import email_v1 from './v1/email'; -import forgotPassword_v1 from './v1/forgotPassword'; -import login_v1 from './v1/login'; -import register_v1 from './v1/register'; -import resetPassword_v1 from './v1/resetPassword'; -import user_v1 from './v1/user'; +import connections_v1 from '@services/api/routes/v1/connections'; +import email_v1 from '@services/api/routes/v1/email'; +import forgotPassword_v1 from '@services/api/routes/v1/forgotPassword'; +import login_v1 from '@services/api/routes/v1/login'; +import register_v1 from '@services/api/routes/v1/register'; +import resetPassword_v1 from '@services/api/routes/v1/resetPassword'; +import user_v1 from '@services/api/routes/v1/user'; export default { V1: { diff --git a/src/services/api/routes/v1/connections.ts b/src/services/api/routes/v1/connections.ts index 58aea18..a15924e 100644 --- a/src/services/api/routes/v1/connections.ts +++ b/src/services/api/routes/v1/connections.ts @@ -1,5 +1,5 @@ import { Router } from 'express'; -import database from '../../../../database'; +import database from '@database'; const router = Router(); diff --git a/src/services/api/routes/v1/email.ts b/src/services/api/routes/v1/email.ts index 22e38c6..7c33101 100644 --- a/src/services/api/routes/v1/email.ts +++ b/src/services/api/routes/v1/email.ts @@ -1,7 +1,7 @@ import { Router } from 'express'; import moment from 'moment'; -import { PNID } from '../../../../models/pnid'; -import util from '../../../../util'; +import { PNID } from '@models/pnid'; +import util from '@util'; const router = Router(); diff --git a/src/services/api/routes/v1/forgotPassword.ts b/src/services/api/routes/v1/forgotPassword.ts index 3a46580..9634663 100644 --- a/src/services/api/routes/v1/forgotPassword.ts +++ b/src/services/api/routes/v1/forgotPassword.ts @@ -1,7 +1,7 @@ import { Router } from 'express'; import validator from 'validator'; -import database from '../../../../database'; -import util from '../../../../util'; +import database from '@database'; +import util from '@util'; const router = Router(); diff --git a/src/services/api/routes/v1/login.ts b/src/services/api/routes/v1/login.ts index 7b63207..c925030 100644 --- a/src/services/api/routes/v1/login.ts +++ b/src/services/api/routes/v1/login.ts @@ -1,9 +1,9 @@ import { Router } from 'express'; import bcrypt from 'bcrypt'; import fs from 'fs-extra'; -import database from '../../../../database'; -import cache from '../../../../cache'; -import util from '../../../../util'; +import database from '@database'; +import cache from '@cache'; +import util from '@util'; const router = Router(); diff --git a/src/services/api/routes/v1/register.ts b/src/services/api/routes/v1/register.ts index 8047a2c..017092f 100644 --- a/src/services/api/routes/v1/register.ts +++ b/src/services/api/routes/v1/register.ts @@ -7,13 +7,13 @@ import fs from 'fs-extra'; import moment from 'moment'; import hcaptcha from 'hcaptcha'; import Mii from 'mii-js'; -import database from '../../../../database'; -import cache from '../../../../cache'; -import util from '../../../../util'; -import logger from '../../../../logger'; -import { PNID } from '../../../../models/pnid'; -import { NEXAccount } from '../../../../models/nex-account'; -import { config, disabledFeatures } from '../../../../config-manager'; +import database from '@database'; +import cache from '@cache'; +import util from '@util'; +import logger from '@logger'; +import { PNID } from '@models/pnid'; +import { NEXAccount } from '@models/nex-account'; +import { config, disabledFeatures } from '@config-manager'; const router = Router(); diff --git a/src/services/api/routes/v1/resetPassword.ts b/src/services/api/routes/v1/resetPassword.ts index e805e8d..8642439 100644 --- a/src/services/api/routes/v1/resetPassword.ts +++ b/src/services/api/routes/v1/resetPassword.ts @@ -1,7 +1,7 @@ import { Router } from 'express'; import bcrypt from 'bcrypt'; -import { PNID } from '../../../../models/pnid'; -import util from '../../../../util'; +import { PNID } from '@models/pnid'; +import util from '@util'; const router = Router(); diff --git a/src/services/api/routes/v1/user.ts b/src/services/api/routes/v1/user.ts index 6f7a299..6e5d4fa 100644 --- a/src/services/api/routes/v1/user.ts +++ b/src/services/api/routes/v1/user.ts @@ -1,7 +1,7 @@ import { Router } from 'express'; import joi from 'joi'; -import { PNID } from '../../../../models/pnid'; -import { config } from '../../../../config-manager'; +import { PNID } from '@models/pnid'; +import { config } from '@config-manager'; const router = Router(); diff --git a/src/services/assets/index.ts b/src/services/assets/index.ts index b73a632..d00a3f7 100644 --- a/src/services/assets/index.ts +++ b/src/services/assets/index.ts @@ -3,7 +3,7 @@ import path from 'node:path'; import express from 'express'; import subdomain from 'express-subdomain'; -import logger from '../../logger'; +import logger from '@logger'; // Router to handle the subdomain restriction const assets = express.Router(); diff --git a/src/services/conntest/index.ts b/src/services/conntest/index.ts index 1601858..73b8545 100644 --- a/src/services/conntest/index.ts +++ b/src/services/conntest/index.ts @@ -2,7 +2,7 @@ import express from 'express'; import subdomain from 'express-subdomain'; -import logger from '../../logger'; +import logger from '@logger'; // Router to handle the subdomain restriction const conntest = express.Router(); diff --git a/src/services/datastore/index.ts b/src/services/datastore/index.ts index 7b670ea..c493f1d 100644 --- a/src/services/datastore/index.ts +++ b/src/services/datastore/index.ts @@ -1,7 +1,7 @@ import express from 'express'; import subdomain from 'express-subdomain'; -import routes from './routes'; -import logger from '../../logger'; +import routes from '@services/datastore/routes'; +import logger from '@logger'; // Router to handle the subdomain const datastore = express.Router(); diff --git a/src/services/datastore/routes/index.ts b/src/services/datastore/routes/index.ts index 7e54748..de121ba 100644 --- a/src/services/datastore/routes/index.ts +++ b/src/services/datastore/routes/index.ts @@ -1,4 +1,4 @@ -import upload from './upload'; +import upload from '@services/datastore/routes/upload'; export default { UPLOAD: upload diff --git a/src/services/datastore/routes/upload.ts b/src/services/datastore/routes/upload.ts index 595ce5d..118b7e5 100644 --- a/src/services/datastore/routes/upload.ts +++ b/src/services/datastore/routes/upload.ts @@ -2,7 +2,7 @@ import fs from 'node:fs'; import crypto from 'node:crypto'; import { Router } from 'express'; import Dicer from 'dicer'; -import util from '../../../util'; +import util from '@util'; const router = Router(); diff --git a/src/services/local-cdn/index.ts b/src/services/local-cdn/index.ts index bf57e63..507924a 100644 --- a/src/services/local-cdn/index.ts +++ b/src/services/local-cdn/index.ts @@ -1,8 +1,8 @@ import express from 'express'; import subdomain from 'express-subdomain'; -import routes from './routes'; -import { config, disabledFeatures } from '../../config-manager'; -import logger from '../../logger'; +import routes from '@services/local-cdn/routes'; +import { config, disabledFeatures } from '@config-manager'; +import logger from '@logger'; const router = express.Router(); diff --git a/src/services/local-cdn/routes/get.ts b/src/services/local-cdn/routes/get.ts index 6dc45c7..fd70a25 100644 --- a/src/services/local-cdn/routes/get.ts +++ b/src/services/local-cdn/routes/get.ts @@ -1,5 +1,5 @@ import { Router } from 'express'; -import cache from '../../../cache'; +import cache from '@cache'; const router = Router(); diff --git a/src/services/local-cdn/routes/index.ts b/src/services/local-cdn/routes/index.ts index d6b426e..8f02619 100644 --- a/src/services/local-cdn/routes/index.ts +++ b/src/services/local-cdn/routes/index.ts @@ -1,4 +1,4 @@ -import get from './get'; +import get from '@services/local-cdn/routes/get'; export default { GET: get, diff --git a/src/services/nasc/index.ts b/src/services/nasc/index.ts index 0b80c92..5ddf5e9 100644 --- a/src/services/nasc/index.ts +++ b/src/services/nasc/index.ts @@ -2,9 +2,9 @@ import express from 'express'; import subdomain from 'express-subdomain'; -import NASCMiddleware from '../../middleware/nasc'; -import routes from './routes'; -import logger from '../../logger'; +import NASCMiddleware from '@middleware/nasc'; +import routes from '@services/nasc/routes'; +import logger from '@logger'; // Router to handle the subdomain restriction const nasc = express.Router(); diff --git a/src/services/nasc/routes/ac.ts b/src/services/nasc/routes/ac.ts index d19c6c8..1351dfd 100644 --- a/src/services/nasc/routes/ac.ts +++ b/src/services/nasc/routes/ac.ts @@ -1,8 +1,8 @@ import express from 'express'; import fs from 'fs-extra'; -import util from '../../../util'; -import database from '../../../database'; -import cache from '../../../cache'; +import util from '@util'; +import database from '@database'; +import cache from '@cache'; const router = express.Router(); diff --git a/src/services/nasc/routes/index.ts b/src/services/nasc/routes/index.ts index 0399fd3..1f3ab33 100644 --- a/src/services/nasc/routes/index.ts +++ b/src/services/nasc/routes/index.ts @@ -1,4 +1,4 @@ -import ac from './ac'; +import ac from '@services/nasc/routes/ac'; export default { AC: ac diff --git a/src/services/nnid/index.ts b/src/services/nnid/index.ts index b0f8321..0f24f01 100644 --- a/src/services/nnid/index.ts +++ b/src/services/nnid/index.ts @@ -2,11 +2,11 @@ import express from 'express'; import subdomain from 'express-subdomain'; -import clientHeaderCheck from '../../middleware/client-header'; -import cemuMiddleware from '../../middleware/cemu'; -import pnidMiddleware from '../../middleware/pnid'; -import routes from './routes'; -import logger from '../../logger'; +import clientHeaderCheck from '@middleware/client-header'; +import cemuMiddleware from '@middleware/cemu'; +import pnidMiddleware from '@middleware/pnid'; +import routes from '@services/nnid/routes'; +import logger from '@logger'; // Router to handle the subdomain restriction const nnid = express.Router(); diff --git a/src/services/nnid/routes/admin.ts b/src/services/nnid/routes/admin.ts index b28f243..2c92dbb 100644 --- a/src/services/nnid/routes/admin.ts +++ b/src/services/nnid/routes/admin.ts @@ -1,6 +1,6 @@ import { Router } from 'express'; import xmlbuilder from 'xmlbuilder'; -import { PNID } from '../../../models/pnid'; +import { PNID } from '@models/pnid'; const router = Router(); diff --git a/src/services/nnid/routes/content.ts b/src/services/nnid/routes/content.ts index 16a0c69..9a31d61 100644 --- a/src/services/nnid/routes/content.ts +++ b/src/services/nnid/routes/content.ts @@ -1,6 +1,6 @@ import { Router } from 'express'; import xmlbuilder from 'xmlbuilder'; -import timezones from '../timezones.json'; +import timezones from '@services/nnid/timezones.json'; const router = Router(); diff --git a/src/services/nnid/routes/index.ts b/src/services/nnid/routes/index.ts index 67e2938..4fb9855 100644 --- a/src/services/nnid/routes/index.ts +++ b/src/services/nnid/routes/index.ts @@ -1,11 +1,11 @@ -import admin from './admin'; -import content from './content'; -import devices from './devices'; -import miis from './miis'; -import oauth from './oauth'; -import people from './people'; -import provider from './provider'; -import support from './support'; +import admin from '@services/nnid/routes/admin'; +import content from '@services/nnid/routes/content'; +import devices from '@services/nnid/routes/devices'; +import miis from '@services/nnid/routes/miis'; +import oauth from '@services/nnid/routes/oauth'; +import people from '@services/nnid/routes/people'; +import provider from '@services/nnid/routes/provider'; +import support from '@services/nnid/routes/support'; export default { ADMIN: admin, diff --git a/src/services/nnid/routes/miis.ts b/src/services/nnid/routes/miis.ts index 62e60df..502bc16 100644 --- a/src/services/nnid/routes/miis.ts +++ b/src/services/nnid/routes/miis.ts @@ -1,7 +1,7 @@ import { Router } from 'express'; import xmlbuilder from 'xmlbuilder'; -import { PNID } from '../../../models/pnid'; -import { config } from '../../../config-manager'; +import { PNID } from '@models/pnid'; +import { config } from '@config-manager'; const router = Router(); diff --git a/src/services/nnid/routes/oauth.ts b/src/services/nnid/routes/oauth.ts index 751a3b4..b3db6a2 100644 --- a/src/services/nnid/routes/oauth.ts +++ b/src/services/nnid/routes/oauth.ts @@ -2,8 +2,8 @@ import { Router } from 'express'; import xmlbuilder from 'xmlbuilder'; import bcrypt from 'bcrypt'; import fs from 'fs-extra'; -import database from '../../../database'; -import util from '../../../util'; +import database from '@database'; +import util from '@util'; const router = Router(); diff --git a/src/services/nnid/routes/people.ts b/src/services/nnid/routes/people.ts index 6c22374..9f0ca9f 100644 --- a/src/services/nnid/routes/people.ts +++ b/src/services/nnid/routes/people.ts @@ -3,14 +3,14 @@ import { Router } from 'express'; import xmlbuilder from 'xmlbuilder'; import bcrypt from 'bcrypt'; import moment from 'moment'; -import deviceCertificateMiddleware from '../../../middleware/device-certificate'; -import ratelimit from '../../../middleware/ratelimit'; -import database from '../../../database'; -import util from '../../../util'; -import { PNID } from '../../../models/pnid'; -import { NEXAccount } from '../../../models/nex-account'; -import logger from '../../../logger'; -import timezones from '../timezones.json'; +import deviceCertificateMiddleware from '@middleware/device-certificate'; +import ratelimit from '@middleware/ratelimit'; +import database from '@database'; +import util from '@util'; +import { PNID } from '@models/pnid'; +import { NEXAccount } from '@models/nex-account'; +import logger from '@logger'; +import timezones from '@services/nnid/timezones.json'; const router = Router(); diff --git a/src/services/nnid/routes/provider.ts b/src/services/nnid/routes/provider.ts index a15de46..9fba2bb 100644 --- a/src/services/nnid/routes/provider.ts +++ b/src/services/nnid/routes/provider.ts @@ -1,10 +1,10 @@ import { Router } from 'express'; import xmlbuilder from 'xmlbuilder'; import fs from 'fs-extra'; -import database from '../../../database'; -import util from '../../../util'; -import cache from '../../../cache'; -import { NEXAccount } from '../../../models/nex-account'; +import database from '@database'; +import util from '@util'; +import cache from '@cache'; +import { NEXAccount } from '@models/nex-account'; const router = Router(); @@ -33,7 +33,7 @@ router.get('/service_token/@me', async (request, response) => { const { service_name, service_type, device } = server; - const cryptoPath = `${__dirname}/../../../../certs/${service_type}/${service_name}`; + const cryptoPath = `${__dirname}/@../certs/${service_type}/${service_name}`; if (!await fs.pathExists(cryptoPath)) { // Need to generate keys @@ -114,7 +114,7 @@ router.get('/nex_token/@me', async (request, response) => { const { service_name, service_type, ip, port, device } = server; const titleId = request.headers['x-nintendo-title-id'] as string; - const cryptoPath = `${__dirname}/../../../../certs/${service_type}/${service_name}`; + const cryptoPath = `${__dirname}/@../certs/${service_type}/${service_name}`; if (!await fs.pathExists(cryptoPath)) { // Need to generate keys diff --git a/src/services/nnid/routes/support.ts b/src/services/nnid/routes/support.ts index f44d033..4c1bb1f 100644 --- a/src/services/nnid/routes/support.ts +++ b/src/services/nnid/routes/support.ts @@ -2,8 +2,8 @@ import dns from 'node:dns'; import { Router } from 'express'; import xmlbuilder from 'xmlbuilder'; import moment from 'moment'; -import database from '../../../database'; -import util from '../../../util'; +import database from '@database'; +import util from '@util'; const router = Router(); diff --git a/src/util.ts b/src/util.ts index 3494f67..fd36a02 100644 --- a/src/util.ts +++ b/src/util.ts @@ -3,9 +3,9 @@ const path = require('path'); const NodeRSA = require('node-rsa'); const aws = require('aws-sdk'); const fs = require('fs-extra'); -const mailer = require('./mailer'); -const cache = require('./cache'); -const { config, disabledFeatures } = require('./config-manager'); +const mailer = require('@mailer'); +const cache = require('@cache'); +const { config, disabledFeatures } = require('@config-manager'); let s3; diff --git a/tsconfig.json b/tsconfig.json index 9d94299..a3d9165 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,10 +4,14 @@ "module": "commonjs", "esModuleInterop": true, "moduleResolution": "node", - "outDir": "./dist", + "baseUrl": "src", + "outDir": "dist", "allowJs": true, "target": "es2022", - "noEmitOnError": true + "noEmitOnError": true, + "paths": { + "@*": ["./*"] + } }, "include": ["src", "types"] } \ No newline at end of file diff --git a/types/express.d.ts b/types/express.d.ts index 1dbe78b..e5f87dd 100644 --- a/types/express.d.ts +++ b/types/express.d.ts @@ -1,5 +1,5 @@ import { HydratedDocument } from 'mongoose'; -import NintendoCertificate from '../src/nintendo-certificate' +import NintendoCertificate from '@nintendo-certificate'; declare global { namespace Express { diff --git a/types/mongoose/device.d.ts b/types/mongoose/device.d.ts index 786953b..90b723d 100644 --- a/types/mongoose/device.d.ts +++ b/types/mongoose/device.d.ts @@ -1,5 +1,5 @@ import { Model } from 'mongoose'; -import { DeviceAttributeSchema } from '../../src/models/device'; +import { DeviceAttributeSchema } from '@models/device'; type MODEL = 'wup' | 'ctr' | 'spr' | 'ftr' | 'ktr' | 'red' | 'jan'; type ACCESS_LEVEL = 0 | 1 | 2 | 3; diff --git a/types/mongoose/pnid.d.ts b/types/mongoose/pnid.d.ts index e0bea47..b7f9441 100644 --- a/types/mongoose/pnid.d.ts +++ b/types/mongoose/pnid.d.ts @@ -1,5 +1,5 @@ import { Model } from 'mongoose'; -import { DeviceSchema } from '../../src/models/device'; +import { DeviceSchema } from '@models/device'; declare global { interface IPNID { diff --git a/types/mongoose/server.d.ts b/types/mongoose/server.d.ts index 6261929..11cfb4e 100644 --- a/types/mongoose/server.d.ts +++ b/types/mongoose/server.d.ts @@ -1,4 +1,4 @@ -import { Document, Model } from 'mongoose'; +import { Model } from 'mongoose'; declare global { interface IServer { From a836f67b47cdf1d1afe8524d69a8c32f4be89a29 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Sat, 4 Mar 2023 10:03:06 -0500 Subject: [PATCH 019/219] Bump mongoose to 7 and use mongoose-unique-validator fork --- package-lock.json | 209 ++++++++++++++++++++++++---------------------- package.json | 2 +- 2 files changed, 111 insertions(+), 100 deletions(-) diff --git a/package-lock.json b/package-lock.json index c670965..6e0a7ce 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29,7 +29,7 @@ "mii-js": "github:PretendoNetwork/mii-js", "moment": "^2.29.4", "mongoose": "^7.0.0", - "mongoose-unique-validator": "^2.0.3", + "mongoose-unique-validator": "github:stenneepro/mongoose-unique-validator#bump/mongoose7", "morgan": "^1.9.1", "nodemailer": "^6.4.2", "redis": "^4.3.1", @@ -410,9 +410,9 @@ } }, "node_modules/@types/node": { - "version": "18.14.4", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.14.4.tgz", - "integrity": "sha512-VhCw7I7qO2X49+jaKcAUwi3rR+hbxT5VcYF493+Z5kMLI0DL568b7JI4IDJaxWFH0D/xwmGJNoXisyX+w7GH/g==" + "version": "18.14.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.14.6.tgz", + "integrity": "sha512-93+VvleD3mXwlLI/xASjw0FzKcwzl3OdTCzm1LaRfqgS21gfFtK3zDXM5Op9TeeMsJVOaJ2VRDpT9q4Y3d0AvA==" }, "node_modules/@types/qs": { "version": "6.9.7", @@ -664,9 +664,9 @@ } }, "node_modules/aws-sdk": { - "version": "2.1325.0", - "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1325.0.tgz", - "integrity": "sha512-ztg9HG5aoUHTprY+/eqjqb25E4joCgz+8ToxsP4OSKFQCtaBcF6my03j4e/J2j3fmpPifJnZSPMu4kV7DBj8WA==", + "version": "2.1328.0", + "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1328.0.tgz", + "integrity": "sha512-ud8ieE+hGX/cWHkQ9kMxQw6w+onbv71PDrcPmP2j8cmYv5IPlM5Zh8/tpsmXApLYDmQMuZ3TtssiB1KmoSbzgA==", "dependencies": { "buffer": "4.9.2", "events": "1.1.1", @@ -2311,6 +2311,11 @@ "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==" }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" + }, "node_modules/lodash.set": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/lodash.set/-/lodash.set-4.3.2.tgz", @@ -2564,37 +2569,6 @@ "whatwg-url": "^11.0.0" } }, - "node_modules/mongodb-connection-string-url/node_modules/tr46": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", - "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", - "dependencies": { - "punycode": "^2.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/mongodb-connection-string-url/node_modules/webidl-conversions": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", - "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", - "engines": { - "node": ">=12" - } - }, - "node_modules/mongodb-connection-string-url/node_modules/whatwg-url": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", - "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", - "dependencies": { - "tr46": "^3.0.0", - "webidl-conversions": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/mongoose": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-7.0.0.tgz", @@ -2617,15 +2591,16 @@ } }, "node_modules/mongoose-unique-validator": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/mongoose-unique-validator/-/mongoose-unique-validator-2.0.3.tgz", - "integrity": "sha512-3/8pmvAC1acBZS6eWKAWQUiZBlARE1wyWtjga4iQ2wDJeOfRlIKmAvTNHSZXKaAf7RCRUd7wh7as6yWAOrjpQg==", + "version": "3.1.0", + "resolved": "git+ssh://git@github.com/stenneepro/mongoose-unique-validator.git#1cd976b70ddd8e8e65fed1d9a387677713a8d177", + "license": "MIT", "dependencies": { "lodash.foreach": "^4.1.0", - "lodash.get": "^4.0.2" + "lodash.get": "^4.0.2", + "lodash.merge": "^4.6.2" }, "peerDependencies": { - "mongoose": "^5.2.1" + "mongoose": "^7.0.0" } }, "node_modules/mongoose/node_modules/ms": { @@ -2747,6 +2722,25 @@ } } }, + "node_modules/node-fetch/node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "node_modules/node-fetch/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/node-fetch/node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/node-rsa": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/node-rsa/-/node-rsa-1.1.1.tgz", @@ -3667,9 +3661,15 @@ } }, "node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", + "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", + "dependencies": { + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=12" + } }, "node_modules/tunnel-agent": { "version": "0.6.0", @@ -3826,17 +3826,23 @@ "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==" }, "node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "engines": { + "node": ">=12" + } }, "node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", + "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" + "tr46": "^3.0.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=12" } }, "node_modules/which-typed-array": { @@ -4279,9 +4285,9 @@ } }, "@types/node": { - "version": "18.14.4", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.14.4.tgz", - "integrity": "sha512-VhCw7I7qO2X49+jaKcAUwi3rR+hbxT5VcYF493+Z5kMLI0DL568b7JI4IDJaxWFH0D/xwmGJNoXisyX+w7GH/g==" + "version": "18.14.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.14.6.tgz", + "integrity": "sha512-93+VvleD3mXwlLI/xASjw0FzKcwzl3OdTCzm1LaRfqgS21gfFtK3zDXM5Op9TeeMsJVOaJ2VRDpT9q4Y3d0AvA==" }, "@types/qs": { "version": "6.9.7", @@ -4489,9 +4495,9 @@ "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==" }, "aws-sdk": { - "version": "2.1325.0", - "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1325.0.tgz", - "integrity": "sha512-ztg9HG5aoUHTprY+/eqjqb25E4joCgz+8ToxsP4OSKFQCtaBcF6my03j4e/J2j3fmpPifJnZSPMu4kV7DBj8WA==", + "version": "2.1328.0", + "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1328.0.tgz", + "integrity": "sha512-ud8ieE+hGX/cWHkQ9kMxQw6w+onbv71PDrcPmP2j8cmYv5IPlM5Zh8/tpsmXApLYDmQMuZ3TtssiB1KmoSbzgA==", "requires": { "buffer": "4.9.2", "events": "1.1.1", @@ -5797,6 +5803,11 @@ "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==" }, + "lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" + }, "lodash.set": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/lodash.set/-/lodash.set-4.3.2.tgz", @@ -5969,30 +5980,6 @@ "requires": { "@types/whatwg-url": "^8.2.1", "whatwg-url": "^11.0.0" - }, - "dependencies": { - "tr46": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", - "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", - "requires": { - "punycode": "^2.1.1" - } - }, - "webidl-conversions": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", - "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==" - }, - "whatwg-url": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", - "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", - "requires": { - "tr46": "^3.0.0", - "webidl-conversions": "^7.0.0" - } - } } }, "mongoose": { @@ -6017,12 +6004,12 @@ } }, "mongoose-unique-validator": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/mongoose-unique-validator/-/mongoose-unique-validator-2.0.3.tgz", - "integrity": "sha512-3/8pmvAC1acBZS6eWKAWQUiZBlARE1wyWtjga4iQ2wDJeOfRlIKmAvTNHSZXKaAf7RCRUd7wh7as6yWAOrjpQg==", + "version": "git+ssh://git@github.com/stenneepro/mongoose-unique-validator.git#1cd976b70ddd8e8e65fed1d9a387677713a8d177", + "from": "mongoose-unique-validator@https://github.com/stenneepro/mongoose-unique-validator.git#bump/mongoose7", "requires": { "lodash.foreach": "^4.1.0", - "lodash.get": "^4.0.2" + "lodash.get": "^4.0.2", + "lodash.merge": "^4.6.2" } }, "morgan": { @@ -6107,6 +6094,27 @@ "integrity": "sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==", "requires": { "whatwg-url": "^5.0.0" + }, + "dependencies": { + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + } } }, "node-rsa": { @@ -6842,9 +6850,12 @@ } }, "tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", + "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", + "requires": { + "punycode": "^2.1.1" + } }, "tunnel-agent": { "version": "0.6.0", @@ -6975,17 +6986,17 @@ } }, "webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==" }, "whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", + "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", "requires": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" + "tr46": "^3.0.0", + "webidl-conversions": "^7.0.0" } }, "which-typed-array": { diff --git a/package.json b/package.json index 25f687b..b03b646 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,7 @@ "mii-js": "github:PretendoNetwork/mii-js", "moment": "^2.29.4", "mongoose": "^7.0.0", - "mongoose-unique-validator": "^2.0.3", + "mongoose-unique-validator": "github:stenneepro/mongoose-unique-validator#bump/mongoose7", "morgan": "^1.9.1", "nodemailer": "^6.4.2", "redis": "^4.3.1", From 706435ed7af7dbbe76586112f1c22e5d1779a489 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Sat, 4 Mar 2023 10:41:27 -0500 Subject: [PATCH 020/219] Fixed cryptoPath checks in provider NNID endpoint --- src/services/nnid/routes/provider.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/services/nnid/routes/provider.ts b/src/services/nnid/routes/provider.ts index 9fba2bb..64ac262 100644 --- a/src/services/nnid/routes/provider.ts +++ b/src/services/nnid/routes/provider.ts @@ -31,9 +31,9 @@ router.get('/service_token/@me', async (request, response) => { }).end()); } - const { service_name, service_type, device } = server; + const { service_name, device } = server; - const cryptoPath = `${__dirname}/@../certs/${service_type}/${service_name}`; + const cryptoPath = `${__dirname}/../../../../certs/service/${service_name}`; if (!await fs.pathExists(cryptoPath)) { // Need to generate keys @@ -111,10 +111,10 @@ router.get('/nex_token/@me', async (request, response) => { }).end()); } - const { service_name, service_type, ip, port, device } = server; + const { service_name, ip, port, device } = server; const titleId = request.headers['x-nintendo-title-id'] as string; - const cryptoPath = `${__dirname}/@../certs/${service_type}/${service_name}`; + const cryptoPath = `${__dirname}/../../../../certs/nex/${service_name}`; if (!await fs.pathExists(cryptoPath)) { // Need to generate keys From c73b820342ac310e0b62f628ab9095211ac98342 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Sat, 4 Mar 2023 10:42:13 -0500 Subject: [PATCH 021/219] Removed PNID.deleteOne from deletion endpoint. Does not actually delete a NNID --- src/services/nnid/routes/people.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/services/nnid/routes/people.ts b/src/services/nnid/routes/people.ts index 9f0ca9f..f00a66f 100644 --- a/src/services/nnid/routes/people.ts +++ b/src/services/nnid/routes/people.ts @@ -370,8 +370,6 @@ router.put('/@me/deletion', async (request, response) => { }).end()); } - await PNID.deleteOne({ pid: pnid.get('pid') }); - response.send(''); }); From 6f570f6c2bf51de1124db4743be32021e03f8d49 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Sat, 4 Mar 2023 11:29:48 -0500 Subject: [PATCH 022/219] Disable id virtual getter on mongoose documents --- src/models/pnid.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/models/pnid.ts b/src/models/pnid.ts index 6934d8d..988e428 100644 --- a/src/models/pnid.ts +++ b/src/models/pnid.ts @@ -102,7 +102,7 @@ export const PNIDSchema = new Schema({ id: String } } -}); +}, { id: false }); PNIDSchema.plugin(uniqueValidator, {message: '{PATH} already in use.'}); From 66f6dfc5df21a7600dd6e1d8b3d6cc9b00781554 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Sat, 4 Mar 2023 12:41:53 -0500 Subject: [PATCH 023/219] Return DB connection from function --- src/database.ts | 12 ++++++++---- src/middleware/nasc.ts | 2 +- src/services/api/routes/v1/register.ts | 2 +- src/services/nnid/routes/people.ts | 2 +- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/database.ts b/src/database.ts index 5d6025a..13fa7be 100644 --- a/src/database.ts +++ b/src/database.ts @@ -14,17 +14,21 @@ const discordConnectionSchema = joi.object({ id: joi.string() }); -let connection; +let _connection; export async function connect() { await mongoose.connect(connection_string, options); - connection = mongoose.connection; - connection.on('error', console.error.bind(console, 'connection error:')); + _connection = mongoose.connection; + _connection.on('error', console.error.bind(console, 'connection error:')); +} + +export function connection() { + return _connection; } function verifyConnected() { - if (!connection) { + if (!connection()) { throw new Error('Cannot make database requets without being connected'); } } diff --git a/src/middleware/nasc.ts b/src/middleware/nasc.ts index b6903fb..778dbdd 100644 --- a/src/middleware/nasc.ts +++ b/src/middleware/nasc.ts @@ -111,7 +111,7 @@ export async function NASCMiddleware(request, response, next) { if (password && !pid && !pidHmac) { // Register new user - const session = await database.connection.startSession(); + const session = await database.connection().startSession(); await session.startTransaction(); try { diff --git a/src/services/api/routes/v1/register.ts b/src/services/api/routes/v1/register.ts index 017092f..50d291c 100644 --- a/src/services/api/routes/v1/register.ts +++ b/src/services/api/routes/v1/register.ts @@ -229,7 +229,7 @@ router.post('/', async (request, response) => { let pnid; let nexAccount; - const session = await database.connection.startSession(); + const session = await database.connection().startSession(); await session.startTransaction(); try { diff --git a/src/services/nnid/routes/people.ts b/src/services/nnid/routes/people.ts index f00a66f..440852a 100644 --- a/src/services/nnid/routes/people.ts +++ b/src/services/nnid/routes/people.ts @@ -81,7 +81,7 @@ router.post('/', ratelimit, deviceCertificateMiddleware, async (request, respons let pnid; let nexAccount; - const session = await database.connection.startSession(); + const session = await database.connection().startSession(); await session.startTransaction(); try { From ffc4e783a8f197b88d703d96b5c90662746e77cd Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Sat, 4 Mar 2023 15:48:08 -0500 Subject: [PATCH 024/219] Removed unused variables from NASC --- src/services/nasc/routes/ac.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/services/nasc/routes/ac.ts b/src/services/nasc/routes/ac.ts index 1351dfd..5308627 100644 --- a/src/services/nasc/routes/ac.ts +++ b/src/services/nasc/routes/ac.ts @@ -1,5 +1,4 @@ import express from 'express'; -import fs from 'fs-extra'; import util from '@util'; import database from '@database'; import cache from '@cache'; @@ -29,7 +28,7 @@ router.post('/', async (request, response) => { }); /** - * + * * @param {express.Request} request */ async function processLoginRequest(request) { @@ -53,8 +52,6 @@ async function processLoginRequest(request) { const { service_name, ip, port } = server; - const cryptoPath = `${__dirname}/../../../../certs/nex/${service_name}`; - const publicKey = await cache.getNEXPublicKey(service_name); const secretKey = await cache.getNEXSecretKey(service_name); From 498d3838a342f9afaa8e41efebe8e03ecdcc1c7a Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Sat, 4 Mar 2023 15:51:28 -0500 Subject: [PATCH 025/219] Added nexUser to express.Request type --- src/middleware/nasc.ts | 3 ++- types/express.d.ts | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/middleware/nasc.ts b/src/middleware/nasc.ts index 778dbdd..53252a8 100644 --- a/src/middleware/nasc.ts +++ b/src/middleware/nasc.ts @@ -1,4 +1,5 @@ import crypto from 'node:crypto'; +import { HydratedDocument } from 'mongoose'; import { Device } from '@models/device'; import { NEXAccount } from '@models/nex-account'; import util from '@util'; @@ -161,7 +162,7 @@ export async function NASCMiddleware(request, response, next) { } } - const nexUser = await NEXAccount.findOne({ pid }); + const nexUser = await NEXAccount.findOne({ pid }) as HydratedDocument; if (!nexUser || nexUser.get('access_level') < 0) { return response.status(200).send(util.nascError('102')); diff --git a/types/express.d.ts b/types/express.d.ts index e5f87dd..1e3c92a 100644 --- a/types/express.d.ts +++ b/types/express.d.ts @@ -5,6 +5,7 @@ declare global { namespace Express { interface Request { pnid?: HydratedDocument; + nexUser?: HydratedDocument; isCemu?: boolean; files?: Record; certificate?: NintendoCertificate; From 55c9bff86245f10f27454dc9af0e4701a96b211a Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Sat, 4 Mar 2023 16:01:01 -0500 Subject: [PATCH 026/219] Replaced jsdoc with TS types --- src/services/nasc/routes/ac.ts | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/src/services/nasc/routes/ac.ts b/src/services/nasc/routes/ac.ts index 5308627..0545682 100644 --- a/src/services/nasc/routes/ac.ts +++ b/src/services/nasc/routes/ac.ts @@ -27,11 +27,7 @@ router.post('/', async (request, response) => { response.status(200).send(responseData.toString()); }); -/** - * - * @param {express.Request} request - */ -async function processLoginRequest(request) { +async function processLoginRequest(request: express.Request) { const requestParams = request.body; const titleID = util.nintendoBase64Decode(requestParams.titleid).toString(); const { nexUser } = request; @@ -83,11 +79,7 @@ async function processLoginRequest(request) { return params; } -/** - * - * @param {express.Request} request - */ -async function processServiceTokenRequest(request) { +async function processServiceTokenRequest(request: express.Request) { const params = new URLSearchParams({ retry: util.nintendoBase64Encode('0'), returncd: util.nintendoBase64Encode('007'), From e438253d3b775257044a3c2d04e5ba77c489fb17 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Sat, 4 Mar 2023 20:25:01 -0500 Subject: [PATCH 027/219] Moved types folder into src and changed alias root to @/ --- src/cache.ts | 2 +- src/config-manager.ts | 2 +- src/database.ts | 11 +-- src/mailer.ts | 2 +- src/middleware/api.ts | 2 +- src/middleware/device-certificate.ts | 2 +- src/middleware/nasc.ts | 13 ++-- src/middleware/pnid.ts | 2 +- src/models/device.ts | 2 + src/models/nex-account.ts | 1 + src/models/pnid.ts | 5 +- src/models/server.ts | 1 + src/server.ts | 24 +++--- src/services/api/index.ts | 6 +- src/services/api/routes/index.ts | 14 ++-- src/services/api/routes/v1/connections.ts | 2 +- src/services/api/routes/v1/email.ts | 4 +- src/services/api/routes/v1/forgotPassword.ts | 4 +- src/services/api/routes/v1/login.ts | 6 +- src/services/api/routes/v1/register.ts | 14 ++-- src/services/api/routes/v1/resetPassword.ts | 4 +- src/services/api/routes/v1/user.ts | 4 +- src/services/assets/index.ts | 2 +- src/services/conntest/index.ts | 2 +- src/services/datastore/index.ts | 4 +- src/services/datastore/routes/index.ts | 2 +- src/services/datastore/routes/upload.ts | 2 +- src/services/local-cdn/index.ts | 6 +- src/services/local-cdn/routes/get.ts | 2 +- src/services/local-cdn/routes/index.ts | 2 +- src/services/nasc/index.ts | 6 +- src/services/nasc/routes/ac.ts | 6 +- src/services/nasc/routes/index.ts | 2 +- src/services/nnid/index.ts | 10 +-- src/services/nnid/routes/admin.ts | 2 +- src/services/nnid/routes/content.ts | 2 +- src/services/nnid/routes/index.ts | 16 ++-- src/services/nnid/routes/miis.ts | 4 +- src/services/nnid/routes/oauth.ts | 4 +- src/services/nnid/routes/people.ts | 16 ++-- src/services/nnid/routes/provider.ts | 8 +- src/services/nnid/routes/support.ts | 4 +- {types => src/types}/express.d.ts | 4 +- src/types/mongoose/device-attribute.ts | 11 +++ src/types/mongoose/device.ts | 30 ++++++++ src/types/mongoose/nex-account.ts | 20 +++++ src/types/mongoose/pnid.ts | 76 +++++++++++++++++++ src/types/mongoose/server.ts | 17 +++++ src/util.ts | 15 ++-- tsconfig.json | 4 +- types/mongoose/device-attribute.d.ts | 13 ---- types/mongoose/device.d.ts | 32 -------- types/mongoose/nex-account.d.ts | 22 ------ types/mongoose/pnid.d.ts | 78 -------------------- types/mongoose/server.d.ts | 19 ----- 55 files changed, 284 insertions(+), 286 deletions(-) rename {types => src/types}/express.d.ts (61%) create mode 100644 src/types/mongoose/device-attribute.ts create mode 100644 src/types/mongoose/device.ts create mode 100644 src/types/mongoose/nex-account.ts create mode 100644 src/types/mongoose/pnid.ts create mode 100644 src/types/mongoose/server.ts delete mode 100644 types/mongoose/device-attribute.d.ts delete mode 100644 types/mongoose/device.d.ts delete mode 100644 types/mongoose/nex-account.d.ts delete mode 100644 types/mongoose/pnid.d.ts delete mode 100644 types/mongoose/server.d.ts diff --git a/src/cache.ts b/src/cache.ts index 32a0e19..5049b67 100644 --- a/src/cache.ts +++ b/src/cache.ts @@ -1,6 +1,6 @@ import fs from 'fs-extra'; import redis from 'redis'; -import { config, disabledFeatures } from '@config-manager'; +import { config, disabledFeatures } from '@/config-manager'; let client; diff --git a/src/config-manager.ts b/src/config-manager.ts index 9509604..37bc0c2 100644 --- a/src/config-manager.ts +++ b/src/config-manager.ts @@ -2,7 +2,7 @@ import fs from 'fs-extra'; import get from 'lodash.get'; import set from 'lodash.set'; import dotenv from 'dotenv'; -import logger from '@logger'; +import logger from '@/logger'; dotenv.config(); diff --git a/src/database.ts b/src/database.ts index 13fa7be..dc3a31b 100644 --- a/src/database.ts +++ b/src/database.ts @@ -1,11 +1,12 @@ import mongoose from 'mongoose'; import bcrypt from 'bcrypt'; import joi from 'joi'; -import util from '@util'; -import { PNID } from '@models/pnid'; -import { Server } from '@models/server'; -import logger from '@logger'; -import { config } from '@config-manager'; +import util from '@/util'; +import { PNID } from '@/models/pnid'; +import { Server } from '@/models/server'; +import logger from '@/logger'; +import { config } from '@/config-manager'; +import { IPNID, IPNIDMethods } from '@/types/mongoose/pnid'; const { connection_string, options } = config.mongoose; diff --git a/src/mailer.ts b/src/mailer.ts index 6acbb86..c39d039 100644 --- a/src/mailer.ts +++ b/src/mailer.ts @@ -1,7 +1,7 @@ import path from 'node:path'; import fs from 'node:fs'; import nodemailer from 'nodemailer'; -import { config, disabledFeatures } from '@config-manager'; +import { config, disabledFeatures } from '@/config-manager'; const genericEmailTemplate = fs.readFileSync(path.join(__dirname, './assets/emails/genericTemplate.html'), 'utf8'); const confirmationEmailTemplate = fs.readFileSync(path.join(__dirname, './assets/emails/confirmationTemplate.html'), 'utf8'); diff --git a/src/middleware/api.ts b/src/middleware/api.ts index 3edfbfa..cbf942d 100644 --- a/src/middleware/api.ts +++ b/src/middleware/api.ts @@ -1,5 +1,5 @@ import xmlbuilder from 'xmlbuilder'; -import database from '@database'; +import database from '@/database'; export async function APIMiddleware(request, _response, next) { const { headers } = request; diff --git a/src/middleware/device-certificate.ts b/src/middleware/device-certificate.ts index b78c0ec..dc4dcfa 100644 --- a/src/middleware/device-certificate.ts +++ b/src/middleware/device-certificate.ts @@ -1,4 +1,4 @@ -import NintendoCertificate from '@nintendo-certificate'; +import NintendoCertificate from '@/nintendo-certificate'; export async function deviceCertificateMiddleware(request, _response, next) { const { headers } = request; diff --git a/src/middleware/nasc.ts b/src/middleware/nasc.ts index 53252a8..b718024 100644 --- a/src/middleware/nasc.ts +++ b/src/middleware/nasc.ts @@ -1,11 +1,12 @@ import crypto from 'node:crypto'; import { HydratedDocument } from 'mongoose'; -import { Device } from '@models/device'; -import { NEXAccount } from '@models/nex-account'; -import util from '@util'; -import database from '@database'; -import NintendoCertificate from '@nintendo-certificate'; -import logger from '@logger'; +import { Device } from '@/models/device'; +import { NEXAccount } from '@/models/nex-account'; +import util from '@/util'; +import database from '@/database'; +import NintendoCertificate from '@/nintendo-certificate'; +import logger from '@/logger'; +import { INEXAccount, INEXAccountMethods } from '@/types/mongoose/nex-account'; export async function NASCMiddleware(request, response, next) { const requestParams = request.body; diff --git a/src/middleware/pnid.ts b/src/middleware/pnid.ts index fbe89ab..604b7de 100644 --- a/src/middleware/pnid.ts +++ b/src/middleware/pnid.ts @@ -1,5 +1,5 @@ import xmlbuilder from 'xmlbuilder'; -import database from '@database'; +import database from '@/database'; export async function PNIDMiddleware(request, response, next) { const { headers } = request; diff --git a/src/models/device.ts b/src/models/device.ts index 22ac5c1..d23c120 100644 --- a/src/models/device.ts +++ b/src/models/device.ts @@ -1,4 +1,6 @@ import { Schema, model } from 'mongoose'; +import { IDeviceAttribute, IDeviceAttributeMethods, DeviceAttributeModel } from '@/types/mongoose/device-attribute'; +import { IDevice, IDeviceMethods, DeviceModel } from '@/types/mongoose/device'; export const DeviceAttributeSchema = new Schema({ created_date: String, diff --git a/src/models/nex-account.ts b/src/models/nex-account.ts index cfbc818..2683a4f 100644 --- a/src/models/nex-account.ts +++ b/src/models/nex-account.ts @@ -1,5 +1,6 @@ import { Schema, model } from 'mongoose'; import uniqueValidator from 'mongoose-unique-validator'; +import { INEXAccount, INEXAccountMethods, NEXAccountModel } from '@/types/mongoose/nex-account'; export const NEXAccountSchema = new Schema({ device_type: { diff --git a/src/models/pnid.ts b/src/models/pnid.ts index 988e428..3ccdd2d 100644 --- a/src/models/pnid.ts +++ b/src/models/pnid.ts @@ -5,8 +5,9 @@ import imagePixels from 'image-pixels'; import TGA from 'tga'; import got from 'got'; import Mii from 'mii-js'; -import { DeviceSchema } from '@models/device'; -import util from '@util'; +import { DeviceSchema } from '@/models/device'; +import util from '@/util'; +import { IPNID, IPNIDMethods, PNIDModel } from '@/types/mongoose/pnid'; export const PNIDSchema = new Schema({ access_level: { diff --git a/src/models/server.ts b/src/models/server.ts index b23dbe6..229142c 100644 --- a/src/models/server.ts +++ b/src/models/server.ts @@ -1,5 +1,6 @@ import { Schema, model } from 'mongoose'; import uniqueValidator from 'mongoose-unique-validator'; +import { IServer, IServerMethods, ServerModel } from '@/types/mongoose/server'; export const ServerSchema = new Schema({ ip: String, // Example: 1.1.1.1 diff --git a/src/server.ts b/src/server.ts index 3762318..3869de0 100644 --- a/src/server.ts +++ b/src/server.ts @@ -6,19 +6,19 @@ configManager.configure(); import express from 'express'; import morgan from 'morgan'; -import xmlparser from '@middleware/xml-parser'; -import cache from '@cache'; -import database from '@database'; -import util from '@util'; -import logger from '@logger'; +import xmlparser from '@/middleware/xml-parser'; +import cache from '@/cache'; +import database from '@/database'; +import util from '@/util'; +import logger from '@/logger'; -import conntest from '@services/conntest'; -import nnid from '@services/nnid'; -import nasc from '@services/nasc'; -import datastore from '@services/datastore'; -import api from '@services/api'; -import localcdn from '@services/local-cdn'; -import assets from '@services/assets'; +import conntest from '@/services/conntest'; +import nnid from '@/services/nnid'; +import nasc from '@/services/nasc'; +import datastore from '@/services/datastore'; +import api from '@/services/api'; +import localcdn from '@/services/local-cdn'; +import assets from '@/services/assets'; const { config } = configManager; diff --git a/src/services/api/index.ts b/src/services/api/index.ts index 3f79071..0d8a952 100644 --- a/src/services/api/index.ts +++ b/src/services/api/index.ts @@ -3,9 +3,9 @@ import express from 'express'; import subdomain from 'express-subdomain'; import cors from 'cors'; -import APIMiddleware from '@middleware/api'; -import routes from '@services/api/routes'; -import logger from '@logger'; +import APIMiddleware from '@/middleware/api'; +import routes from '@/services/api/routes'; +import logger from '@/logger'; // Router to handle the subdomain restriction const api = express.Router(); diff --git a/src/services/api/routes/index.ts b/src/services/api/routes/index.ts index 4b0f452..61691f2 100644 --- a/src/services/api/routes/index.ts +++ b/src/services/api/routes/index.ts @@ -1,10 +1,10 @@ -import connections_v1 from '@services/api/routes/v1/connections'; -import email_v1 from '@services/api/routes/v1/email'; -import forgotPassword_v1 from '@services/api/routes/v1/forgotPassword'; -import login_v1 from '@services/api/routes/v1/login'; -import register_v1 from '@services/api/routes/v1/register'; -import resetPassword_v1 from '@services/api/routes/v1/resetPassword'; -import user_v1 from '@services/api/routes/v1/user'; +import connections_v1 from '@/services/api/routes/v1/connections'; +import email_v1 from '@/services/api/routes/v1/email'; +import forgotPassword_v1 from '@/services/api/routes/v1/forgotPassword'; +import login_v1 from '@/services/api/routes/v1/login'; +import register_v1 from '@/services/api/routes/v1/register'; +import resetPassword_v1 from '@/services/api/routes/v1/resetPassword'; +import user_v1 from '@/services/api/routes/v1/user'; export default { V1: { diff --git a/src/services/api/routes/v1/connections.ts b/src/services/api/routes/v1/connections.ts index a15924e..4ec11c5 100644 --- a/src/services/api/routes/v1/connections.ts +++ b/src/services/api/routes/v1/connections.ts @@ -1,5 +1,5 @@ import { Router } from 'express'; -import database from '@database'; +import database from '@/database'; const router = Router(); diff --git a/src/services/api/routes/v1/email.ts b/src/services/api/routes/v1/email.ts index 7c33101..1f28f18 100644 --- a/src/services/api/routes/v1/email.ts +++ b/src/services/api/routes/v1/email.ts @@ -1,7 +1,7 @@ import { Router } from 'express'; import moment from 'moment'; -import { PNID } from '@models/pnid'; -import util from '@util'; +import { PNID } from '@/models/pnid'; +import util from '@/util'; const router = Router(); diff --git a/src/services/api/routes/v1/forgotPassword.ts b/src/services/api/routes/v1/forgotPassword.ts index 9634663..eb926b4 100644 --- a/src/services/api/routes/v1/forgotPassword.ts +++ b/src/services/api/routes/v1/forgotPassword.ts @@ -1,7 +1,7 @@ import { Router } from 'express'; import validator from 'validator'; -import database from '@database'; -import util from '@util'; +import database from '@/database'; +import util from '@/util'; const router = Router(); diff --git a/src/services/api/routes/v1/login.ts b/src/services/api/routes/v1/login.ts index c925030..e6c8c1f 100644 --- a/src/services/api/routes/v1/login.ts +++ b/src/services/api/routes/v1/login.ts @@ -1,9 +1,9 @@ import { Router } from 'express'; import bcrypt from 'bcrypt'; import fs from 'fs-extra'; -import database from '@database'; -import cache from '@cache'; -import util from '@util'; +import database from '@/database'; +import cache from '@/cache'; +import util from '@/util'; const router = Router(); diff --git a/src/services/api/routes/v1/register.ts b/src/services/api/routes/v1/register.ts index 50d291c..95f79d4 100644 --- a/src/services/api/routes/v1/register.ts +++ b/src/services/api/routes/v1/register.ts @@ -7,13 +7,13 @@ import fs from 'fs-extra'; import moment from 'moment'; import hcaptcha from 'hcaptcha'; import Mii from 'mii-js'; -import database from '@database'; -import cache from '@cache'; -import util from '@util'; -import logger from '@logger'; -import { PNID } from '@models/pnid'; -import { NEXAccount } from '@models/nex-account'; -import { config, disabledFeatures } from '@config-manager'; +import database from '@/database'; +import cache from '@/cache'; +import util from '@/util'; +import logger from '@/logger'; +import { PNID } from '@/models/pnid'; +import { NEXAccount } from '@/models/nex-account'; +import { config, disabledFeatures } from '@/config-manager'; const router = Router(); diff --git a/src/services/api/routes/v1/resetPassword.ts b/src/services/api/routes/v1/resetPassword.ts index 8642439..3f9a823 100644 --- a/src/services/api/routes/v1/resetPassword.ts +++ b/src/services/api/routes/v1/resetPassword.ts @@ -1,7 +1,7 @@ import { Router } from 'express'; import bcrypt from 'bcrypt'; -import { PNID } from '@models/pnid'; -import util from '@util'; +import { PNID } from '@/models/pnid'; +import util from '@/util'; const router = Router(); diff --git a/src/services/api/routes/v1/user.ts b/src/services/api/routes/v1/user.ts index 6e5d4fa..2ce5fae 100644 --- a/src/services/api/routes/v1/user.ts +++ b/src/services/api/routes/v1/user.ts @@ -1,7 +1,7 @@ import { Router } from 'express'; import joi from 'joi'; -import { PNID } from '@models/pnid'; -import { config } from '@config-manager'; +import { PNID } from '@/models/pnid'; +import { config } from '@/config-manager'; const router = Router(); diff --git a/src/services/assets/index.ts b/src/services/assets/index.ts index d00a3f7..dc77afb 100644 --- a/src/services/assets/index.ts +++ b/src/services/assets/index.ts @@ -3,7 +3,7 @@ import path from 'node:path'; import express from 'express'; import subdomain from 'express-subdomain'; -import logger from '@logger'; +import logger from '@/logger'; // Router to handle the subdomain restriction const assets = express.Router(); diff --git a/src/services/conntest/index.ts b/src/services/conntest/index.ts index 73b8545..8033849 100644 --- a/src/services/conntest/index.ts +++ b/src/services/conntest/index.ts @@ -2,7 +2,7 @@ import express from 'express'; import subdomain from 'express-subdomain'; -import logger from '@logger'; +import logger from '@/logger'; // Router to handle the subdomain restriction const conntest = express.Router(); diff --git a/src/services/datastore/index.ts b/src/services/datastore/index.ts index c493f1d..6969988 100644 --- a/src/services/datastore/index.ts +++ b/src/services/datastore/index.ts @@ -1,7 +1,7 @@ import express from 'express'; import subdomain from 'express-subdomain'; -import routes from '@services/datastore/routes'; -import logger from '@logger'; +import routes from '@/services/datastore/routes'; +import logger from '@/logger'; // Router to handle the subdomain const datastore = express.Router(); diff --git a/src/services/datastore/routes/index.ts b/src/services/datastore/routes/index.ts index de121ba..5e00989 100644 --- a/src/services/datastore/routes/index.ts +++ b/src/services/datastore/routes/index.ts @@ -1,4 +1,4 @@ -import upload from '@services/datastore/routes/upload'; +import upload from '@/services/datastore/routes/upload'; export default { UPLOAD: upload diff --git a/src/services/datastore/routes/upload.ts b/src/services/datastore/routes/upload.ts index 118b7e5..a17c15d 100644 --- a/src/services/datastore/routes/upload.ts +++ b/src/services/datastore/routes/upload.ts @@ -2,7 +2,7 @@ import fs from 'node:fs'; import crypto from 'node:crypto'; import { Router } from 'express'; import Dicer from 'dicer'; -import util from '@util'; +import util from '@/util'; const router = Router(); diff --git a/src/services/local-cdn/index.ts b/src/services/local-cdn/index.ts index 507924a..e8891ac 100644 --- a/src/services/local-cdn/index.ts +++ b/src/services/local-cdn/index.ts @@ -1,8 +1,8 @@ import express from 'express'; import subdomain from 'express-subdomain'; -import routes from '@services/local-cdn/routes'; -import { config, disabledFeatures } from '@config-manager'; -import logger from '@logger'; +import routes from '@/services/local-cdn/routes'; +import { config, disabledFeatures } from '@/config-manager'; +import logger from '@/logger'; const router = express.Router(); diff --git a/src/services/local-cdn/routes/get.ts b/src/services/local-cdn/routes/get.ts index fd70a25..7a68dc8 100644 --- a/src/services/local-cdn/routes/get.ts +++ b/src/services/local-cdn/routes/get.ts @@ -1,5 +1,5 @@ import { Router } from 'express'; -import cache from '@cache'; +import cache from '@/cache'; const router = Router(); diff --git a/src/services/local-cdn/routes/index.ts b/src/services/local-cdn/routes/index.ts index 8f02619..5038ab4 100644 --- a/src/services/local-cdn/routes/index.ts +++ b/src/services/local-cdn/routes/index.ts @@ -1,4 +1,4 @@ -import get from '@services/local-cdn/routes/get'; +import get from '@/services/local-cdn/routes/get'; export default { GET: get, diff --git a/src/services/nasc/index.ts b/src/services/nasc/index.ts index 5ddf5e9..a55de55 100644 --- a/src/services/nasc/index.ts +++ b/src/services/nasc/index.ts @@ -2,9 +2,9 @@ import express from 'express'; import subdomain from 'express-subdomain'; -import NASCMiddleware from '@middleware/nasc'; -import routes from '@services/nasc/routes'; -import logger from '@logger'; +import NASCMiddleware from '@/middleware/nasc'; +import routes from '@/services/nasc/routes'; +import logger from '@/logger'; // Router to handle the subdomain restriction const nasc = express.Router(); diff --git a/src/services/nasc/routes/ac.ts b/src/services/nasc/routes/ac.ts index 0545682..f468f1a 100644 --- a/src/services/nasc/routes/ac.ts +++ b/src/services/nasc/routes/ac.ts @@ -1,7 +1,7 @@ import express from 'express'; -import util from '@util'; -import database from '@database'; -import cache from '@cache'; +import util from '@/util'; +import database from '@/database'; +import cache from '@/cache'; const router = express.Router(); diff --git a/src/services/nasc/routes/index.ts b/src/services/nasc/routes/index.ts index 1f3ab33..e8dfff6 100644 --- a/src/services/nasc/routes/index.ts +++ b/src/services/nasc/routes/index.ts @@ -1,4 +1,4 @@ -import ac from '@services/nasc/routes/ac'; +import ac from '@/services/nasc/routes/ac'; export default { AC: ac diff --git a/src/services/nnid/index.ts b/src/services/nnid/index.ts index 0f24f01..9c9cdd3 100644 --- a/src/services/nnid/index.ts +++ b/src/services/nnid/index.ts @@ -2,11 +2,11 @@ import express from 'express'; import subdomain from 'express-subdomain'; -import clientHeaderCheck from '@middleware/client-header'; -import cemuMiddleware from '@middleware/cemu'; -import pnidMiddleware from '@middleware/pnid'; -import routes from '@services/nnid/routes'; -import logger from '@logger'; +import clientHeaderCheck from '@/middleware/client-header'; +import cemuMiddleware from '@/middleware/cemu'; +import pnidMiddleware from '@/middleware/pnid'; +import routes from '@/services/nnid/routes'; +import logger from '@/logger'; // Router to handle the subdomain restriction const nnid = express.Router(); diff --git a/src/services/nnid/routes/admin.ts b/src/services/nnid/routes/admin.ts index 2c92dbb..115b5ab 100644 --- a/src/services/nnid/routes/admin.ts +++ b/src/services/nnid/routes/admin.ts @@ -1,6 +1,6 @@ import { Router } from 'express'; import xmlbuilder from 'xmlbuilder'; -import { PNID } from '@models/pnid'; +import { PNID } from '@/models/pnid'; const router = Router(); diff --git a/src/services/nnid/routes/content.ts b/src/services/nnid/routes/content.ts index 9a31d61..92f4701 100644 --- a/src/services/nnid/routes/content.ts +++ b/src/services/nnid/routes/content.ts @@ -1,6 +1,6 @@ import { Router } from 'express'; import xmlbuilder from 'xmlbuilder'; -import timezones from '@services/nnid/timezones.json'; +import timezones from '@/services/nnid/timezones.json'; const router = Router(); diff --git a/src/services/nnid/routes/index.ts b/src/services/nnid/routes/index.ts index 4fb9855..7510b1f 100644 --- a/src/services/nnid/routes/index.ts +++ b/src/services/nnid/routes/index.ts @@ -1,11 +1,11 @@ -import admin from '@services/nnid/routes/admin'; -import content from '@services/nnid/routes/content'; -import devices from '@services/nnid/routes/devices'; -import miis from '@services/nnid/routes/miis'; -import oauth from '@services/nnid/routes/oauth'; -import people from '@services/nnid/routes/people'; -import provider from '@services/nnid/routes/provider'; -import support from '@services/nnid/routes/support'; +import admin from '@/services/nnid/routes/admin'; +import content from '@/services/nnid/routes/content'; +import devices from '@/services/nnid/routes/devices'; +import miis from '@/services/nnid/routes/miis'; +import oauth from '@/services/nnid/routes/oauth'; +import people from '@/services/nnid/routes/people'; +import provider from '@/services/nnid/routes/provider'; +import support from '@/services/nnid/routes/support'; export default { ADMIN: admin, diff --git a/src/services/nnid/routes/miis.ts b/src/services/nnid/routes/miis.ts index 502bc16..7219ed4 100644 --- a/src/services/nnid/routes/miis.ts +++ b/src/services/nnid/routes/miis.ts @@ -1,7 +1,7 @@ import { Router } from 'express'; import xmlbuilder from 'xmlbuilder'; -import { PNID } from '@models/pnid'; -import { config } from '@config-manager'; +import { PNID } from '@/models/pnid'; +import { config } from '@/config-manager'; const router = Router(); diff --git a/src/services/nnid/routes/oauth.ts b/src/services/nnid/routes/oauth.ts index b3db6a2..ab1ca40 100644 --- a/src/services/nnid/routes/oauth.ts +++ b/src/services/nnid/routes/oauth.ts @@ -2,8 +2,8 @@ import { Router } from 'express'; import xmlbuilder from 'xmlbuilder'; import bcrypt from 'bcrypt'; import fs from 'fs-extra'; -import database from '@database'; -import util from '@util'; +import database from '@/database'; +import util from '@/util'; const router = Router(); diff --git a/src/services/nnid/routes/people.ts b/src/services/nnid/routes/people.ts index 440852a..8733e00 100644 --- a/src/services/nnid/routes/people.ts +++ b/src/services/nnid/routes/people.ts @@ -3,14 +3,14 @@ import { Router } from 'express'; import xmlbuilder from 'xmlbuilder'; import bcrypt from 'bcrypt'; import moment from 'moment'; -import deviceCertificateMiddleware from '@middleware/device-certificate'; -import ratelimit from '@middleware/ratelimit'; -import database from '@database'; -import util from '@util'; -import { PNID } from '@models/pnid'; -import { NEXAccount } from '@models/nex-account'; -import logger from '@logger'; -import timezones from '@services/nnid/timezones.json'; +import deviceCertificateMiddleware from '@/middleware/device-certificate'; +import ratelimit from '@/middleware/ratelimit'; +import database from '@/database'; +import util from '@/util'; +import { PNID } from '@/models/pnid'; +import { NEXAccount } from '@/models/nex-account'; +import logger from '@/logger'; +import timezones from '@/services/nnid/timezones.json'; const router = Router(); diff --git a/src/services/nnid/routes/provider.ts b/src/services/nnid/routes/provider.ts index 64ac262..804839a 100644 --- a/src/services/nnid/routes/provider.ts +++ b/src/services/nnid/routes/provider.ts @@ -1,10 +1,10 @@ import { Router } from 'express'; import xmlbuilder from 'xmlbuilder'; import fs from 'fs-extra'; -import database from '@database'; -import util from '@util'; -import cache from '@cache'; -import { NEXAccount } from '@models/nex-account'; +import database from '@/database'; +import util from '@/util'; +import cache from '@/cache'; +import { NEXAccount } from '@/models/nex-account'; const router = Router(); diff --git a/src/services/nnid/routes/support.ts b/src/services/nnid/routes/support.ts index 4c1bb1f..b07bcff 100644 --- a/src/services/nnid/routes/support.ts +++ b/src/services/nnid/routes/support.ts @@ -2,8 +2,8 @@ import dns from 'node:dns'; import { Router } from 'express'; import xmlbuilder from 'xmlbuilder'; import moment from 'moment'; -import database from '@database'; -import util from '@util'; +import database from '@/database'; +import util from '@/util'; const router = Router(); diff --git a/types/express.d.ts b/src/types/express.d.ts similarity index 61% rename from types/express.d.ts rename to src/types/express.d.ts index 1e3c92a..e6669ab 100644 --- a/types/express.d.ts +++ b/src/types/express.d.ts @@ -1,5 +1,7 @@ import { HydratedDocument } from 'mongoose'; -import NintendoCertificate from '@nintendo-certificate'; +import NintendoCertificate from '@/nintendo-certificate'; +import { IPNID, IPNIDMethods } from '@/types/mongoose/pnid'; +import { INEXAccount, INEXAccountMethods } from '@/types/mongoose/nex-account'; declare global { namespace Express { diff --git a/src/types/mongoose/device-attribute.ts b/src/types/mongoose/device-attribute.ts new file mode 100644 index 0000000..2c5a2bf --- /dev/null +++ b/src/types/mongoose/device-attribute.ts @@ -0,0 +1,11 @@ +import { Model } from 'mongoose'; + +export interface IDeviceAttribute { + created_date: string; + name: string; + value: string; +} + +export interface IDeviceAttributeMethods {} + +export interface DeviceAttributeModel extends Model {} \ No newline at end of file diff --git a/src/types/mongoose/device.ts b/src/types/mongoose/device.ts new file mode 100644 index 0000000..9fe4ba3 --- /dev/null +++ b/src/types/mongoose/device.ts @@ -0,0 +1,30 @@ +import { Model } from 'mongoose'; +import { DeviceAttributeSchema } from '@/models/device'; + +type MODEL = 'wup' | 'ctr' | 'spr' | 'ftr' | 'ktr' | 'red' | 'jan'; +type ACCESS_LEVEL = 0 | 1 | 2 | 3; +type SERVER_ACCESS_LEVEL = 'prod' | 'test' | 'dev'; + +export interface IDevice { + is_emulator: boolean; + model: MODEL; + device_id: number; + device_type: number; + serial: string; + device_attributes: typeof DeviceAttributeSchema[]; + soap: { + token: string; + account_id: number; + }, + // * 3DS-specific stuff + environment: string; + mac_hash: string; + fcdcert_hash: string; + linked_pids: number[]; + access_level: ACCESS_LEVEL; + server_access_level: SERVER_ACCESS_LEVEL; +} + +export interface IDeviceMethods {} + +export interface DeviceModel extends Model {} \ No newline at end of file diff --git a/src/types/mongoose/nex-account.ts b/src/types/mongoose/nex-account.ts new file mode 100644 index 0000000..b109527 --- /dev/null +++ b/src/types/mongoose/nex-account.ts @@ -0,0 +1,20 @@ +import { Model } from 'mongoose'; + +type DEVICE = 'wiiu' | '3ds'; +type ACCESS_LEVEL = 0 | 1 | 2 | 3; + +export interface INEXAccount { + device_type: DEVICE; + pid: number; + password: string; + owning_pid: number; + access_level: ACCESS_LEVEL; + server_access_level: string; +} + +export interface INEXAccountMethods { + generatePID(): Promise; + generatePassword(): void; +} + +export interface NEXAccountModel extends Model {} \ No newline at end of file diff --git a/src/types/mongoose/pnid.ts b/src/types/mongoose/pnid.ts new file mode 100644 index 0000000..e52f064 --- /dev/null +++ b/src/types/mongoose/pnid.ts @@ -0,0 +1,76 @@ +import { Model } from 'mongoose'; +import { DeviceSchema } from '@/models/device'; + +export interface IPNID { + access_level: number; + server_access_level: string; + pid: number; + creation_date: string; + updated: string; + username: string; + usernameLower: string; + password: string; + birthdate: string; + gender: string; + country: string; + language: string; + email: { + address: string; + primary: boolean; + parent: boolean; + reachable: boolean; + validated: boolean; + validated_date: string; + id: number; + }; + region: number; + timezone: { + name: string; + offset: number; + marketing: boolean; + off_device: boolean; + }; + mii: { + name: string; + primary: boolean; + data: string; + id: number; + hash: string; + image_url: string; + image_id: number; + }; + flags: { + active: boolean; + marketing: boolean; + off_device: boolean; + }; + devices: typeof DeviceSchema[]; + identification: { // user identification tokens + email_code: string; + email_token: string; + access_token: { + value: string; + ttl: number; + }, + refresh_token: { + value: string; + ttl: number; + } + }; + connections: { + discord: { + id: string; + } + }; +} + +export interface IPNIDMethods { + generatePID(): Promise; + generateEmailValidationCode(): Promise; + generateEmailValidationToken(): Promise; + updateMii(mii: { name: string, primary: string, data: string}): Promise; + generateMiiImages(): Promise; + getServerMode(): string; +} + +export interface PNIDModel extends Model {} \ No newline at end of file diff --git a/src/types/mongoose/server.ts b/src/types/mongoose/server.ts new file mode 100644 index 0000000..f25198e --- /dev/null +++ b/src/types/mongoose/server.ts @@ -0,0 +1,17 @@ +import { Model } from 'mongoose'; + +export interface IServer { + ip: string; // Example: 1.1.1.1 + port: Number; // Example: 60000 + service_name: string; // Example: friends + service_type: string; // Example: nex + game_server_id: string; // Example: 00003200 + title_ids: string[]; // Example: ["000500001018DB00", "000500001018DC00", "000500001018DD00"] + access_mode: string; // Example: prod + maintenance_mode: boolean; // Example: false + device: number; // Example: 1 (WiiU) +} + +export interface IServerMethods {} + +export interface ServerModel extends Model {} \ No newline at end of file diff --git a/src/util.ts b/src/util.ts index fd36a02..d3aaef3 100644 --- a/src/util.ts +++ b/src/util.ts @@ -3,9 +3,9 @@ const path = require('path'); const NodeRSA = require('node-rsa'); const aws = require('aws-sdk'); const fs = require('fs-extra'); -const mailer = require('@mailer'); -const cache = require('@cache'); -const { config, disabledFeatures } = require('@config-manager'); +const mailer = require('@/mailer'); +const cache = require('@/cache'); +const { config, disabledFeatures } = require('@/config-manager'); let s3; @@ -17,7 +17,7 @@ if (!disabledFeatures.s3) { }); } -function nintendoPasswordHash(password, pid) { +function nintendoPasswordHash(password: string, pid: number): string { const pidBuffer = Buffer.alloc(4); pidBuffer.writeUInt32LE(pid); @@ -31,18 +31,17 @@ function nintendoPasswordHash(password, pid) { return hashed; } -function nintendoBase64Decode(encoded) { +function nintendoBase64Decode(encoded: string): Buffer { encoded = encoded.replaceAll('.', '+').replaceAll('-', '/').replaceAll('*', '='); return Buffer.from(encoded, 'base64'); } -function nintendoBase64Encode(decoded) { +function nintendoBase64Encode(decoded: string | Buffer): string { const encoded = Buffer.from(decoded).toString('base64'); return encoded.replaceAll('+', '.').replaceAll('/', '-').replaceAll('=', '*'); } -async function generateToken(cryptoOptions, tokenOptions) { - +async function generateToken(cryptoOptions, tokenOptions): Promise { // Access and refresh tokens use a different format since they must be much smaller // They take no extra crypto options if (!cryptoOptions) { diff --git a/tsconfig.json b/tsconfig.json index a3d9165..16ad818 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -10,8 +10,8 @@ "target": "es2022", "noEmitOnError": true, "paths": { - "@*": ["./*"] + "@/*": ["./*"] } }, - "include": ["src", "types"] + "include": ["src"] } \ No newline at end of file diff --git a/types/mongoose/device-attribute.d.ts b/types/mongoose/device-attribute.d.ts deleted file mode 100644 index 98a7de1..0000000 --- a/types/mongoose/device-attribute.d.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Model } from 'mongoose'; - -declare global { - interface IDeviceAttribute { - created_date: string; - name: string; - value: string; - } - - interface IDeviceAttributeMethods {} - - interface DeviceAttributeModel extends Model {} -} \ No newline at end of file diff --git a/types/mongoose/device.d.ts b/types/mongoose/device.d.ts deleted file mode 100644 index 90b723d..0000000 --- a/types/mongoose/device.d.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { Model } from 'mongoose'; -import { DeviceAttributeSchema } from '@models/device'; - -type MODEL = 'wup' | 'ctr' | 'spr' | 'ftr' | 'ktr' | 'red' | 'jan'; -type ACCESS_LEVEL = 0 | 1 | 2 | 3; -type SERVER_ACCESS_LEVEL = 'prod' | 'test' | 'dev'; - -declare global { - interface IDevice { - is_emulator: boolean; - model: MODEL; - device_id: number; - device_type: number; - serial: string; - device_attributes: typeof DeviceAttributeSchema[]; - soap: { - token: string; - account_id: number; - }, - // * 3DS-specific stuff - environment: string; - mac_hash: string; - fcdcert_hash: string; - linked_pids: number[]; - access_level: ACCESS_LEVEL; - server_access_level: SERVER_ACCESS_LEVEL; - } - - interface IDeviceMethods {} - - interface DeviceModel extends Model {} -} \ No newline at end of file diff --git a/types/mongoose/nex-account.d.ts b/types/mongoose/nex-account.d.ts deleted file mode 100644 index e51119a..0000000 --- a/types/mongoose/nex-account.d.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { Model } from 'mongoose'; - -type DEVICE = 'wiiu' | '3ds'; -type ACCESS_LEVEL = 0 | 1 | 2 | 3; - -declare global { - interface INEXAccount { - device_type: DEVICE; - pid: number; - password: string; - owning_pid: number; - access_level: ACCESS_LEVEL; - server_access_level: string; - } - - interface INEXAccountMethods { - generatePID(): Promise; - generatePassword(): void; - } - - interface NEXAccountModel extends Model {} -} \ No newline at end of file diff --git a/types/mongoose/pnid.d.ts b/types/mongoose/pnid.d.ts deleted file mode 100644 index b7f9441..0000000 --- a/types/mongoose/pnid.d.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { Model } from 'mongoose'; -import { DeviceSchema } from '@models/device'; - -declare global { - interface IPNID { - access_level: number; - server_access_level: string; - pid: number; - creation_date: string; - updated: string; - username: string; - usernameLower: string; - password: string; - birthdate: string; - gender: string; - country: string; - language: string; - email: { - address: string; - primary: boolean; - parent: boolean; - reachable: boolean; - validated: boolean; - validated_date: string; - id: number; - }; - region: number; - timezone: { - name: string; - offset: number; - marketing: boolean; - off_device: boolean; - }; - mii: { - name: string; - primary: boolean; - data: string; - id: number; - hash: string; - image_url: string; - image_id: number; - }; - flags: { - active: boolean; - marketing: boolean; - off_device: boolean; - }; - devices: typeof DeviceSchema[]; - identification: { // user identification tokens - email_code: string; - email_token: string; - access_token: { - value: string; - ttl: number; - }, - refresh_token: { - value: string; - ttl: number; - } - }; - connections: { - discord: { - id: string; - } - }; - } - - interface IPNIDMethods { - generatePID(): Promise; - generateEmailValidationCode(): Promise; - generateEmailValidationToken(): Promise; - updateMii(mii: { name: string, primary: string, data: string}): Promise; - generateMiiImages(): Promise; - getServerMode(): string; - } - - interface PNIDModel extends Model {} -} \ No newline at end of file diff --git a/types/mongoose/server.d.ts b/types/mongoose/server.d.ts deleted file mode 100644 index 11cfb4e..0000000 --- a/types/mongoose/server.d.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Model } from 'mongoose'; - -declare global { - interface IServer { - ip: string; // Example: 1.1.1.1 - port: Number; // Example: 60000 - service_name: string; // Example: friends - service_type: string; // Example: nex - game_server_id: string; // Example: 00003200 - title_ids: string[]; // Example: ["000500001018DB00", "000500001018DC00", "000500001018DD00"] - access_mode: string; // Example: prod - maintenance_mode: boolean; // Example: false - device: number; // Example: 1 (WiiU) - } - - interface IServerMethods {} - - interface ServerModel extends Model {} -} \ No newline at end of file From bfba5a025968ecb21a4876f809e91b786b6ca131 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Sat, 4 Mar 2023 22:50:43 -0500 Subject: [PATCH 028/219] Migrate to TS imports in util.ts (forgot, oops) --- src/util.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/util.ts b/src/util.ts index d3aaef3..e383f93 100644 --- a/src/util.ts +++ b/src/util.ts @@ -1,11 +1,11 @@ -const crypto = require('crypto'); -const path = require('path'); -const NodeRSA = require('node-rsa'); -const aws = require('aws-sdk'); -const fs = require('fs-extra'); -const mailer = require('@/mailer'); -const cache = require('@/cache'); -const { config, disabledFeatures } = require('@/config-manager'); +import crypto from 'node:crypto'; +import path from 'node:path'; +import NodeRSA from 'node-rsa'; +import aws from 'aws-sdk'; +import fs from 'fs-extra'; +import mailer from '@/mailer'; +import cache from '@/cache'; +import { config, disabledFeatures } from '@/config-manager'; let s3; From d23a8b7c212690819f67c4efa2ffbafdb9c54c6d Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Sun, 5 Mar 2023 20:14:31 -0500 Subject: [PATCH 029/219] Remade config manager. Removed JSON file support --- src/config-manager.ts | 394 +++++++++++++------------------------ src/types/common/config.ts | 47 +++++ 2 files changed, 181 insertions(+), 260 deletions(-) create mode 100644 src/types/common/config.ts diff --git a/src/config-manager.ts b/src/config-manager.ts index 37bc0c2..db16603 100644 --- a/src/config-manager.ts +++ b/src/config-manager.ts @@ -1,289 +1,163 @@ import fs from 'fs-extra'; -import get from 'lodash.get'; -import set from 'lodash.set'; +import mongoose from 'mongoose'; import dotenv from 'dotenv'; import logger from '@/logger'; +import { Config, DisabledFeatures } from '@/types/common/config'; dotenv.config(); -export let config: Record = {}; - -export const disabledFeatures = { +export const disabledFeatures: DisabledFeatures = { redis: false, email: false, captcha: false, s3: false }; -const requiredFields = [ - ['http.port', 'PN_ACT_CONFIG_HTTP_PORT', Number], - ['mongoose.connection_string', 'PN_ACT_CONFIG_MONGO_CONNECTION_STRING'], - ['cdn.base_url', 'PN_ACT_CONFIG_CDN_BASE_URL'] -]; +logger.info('Loading config'); -export function configure() { - const usingEnv = process.env.PN_ACT_PREFER_ENV_CONFIG === 'true'; +let mongooseConnectOptions: mongoose.ConnectOptions; - if (usingEnv) { - logger.info('Loading config from environment variable'); +if (process.env.PN_ACT_CONFIG_MONGOOSE_CONNECT_OPTIONS_PATH) { + mongooseConnectOptions = fs.readJSONSync(process.env.PN_ACT_CONFIG_MONGOOSE_CONNECT_OPTIONS_PATH); +} - config = { - http: { - port: Number(process.env.PN_ACT_CONFIG_HTTP_PORT) - }, - mongoose: { - connection_string: process.env.PN_ACT_CONFIG_MONGO_CONNECTION_STRING, - options: Object.keys(process.env) - .filter(key => key.startsWith('PN_ACT_CONFIG_MONGOOSE_OPTION_')) - .reduce((obj, key) => { - obj[key.split('_').pop()] = process.env[key]; - return obj; - }, {}) - }, - redis: { - client: { - url: process.env.PN_ACT_CONFIG_REDIS_URL - } - }, - email: { - host: process.env.PN_ACT_CONFIG_EMAIL_HOST, - port: Number(process.env.PN_ACT_CONFIG_EMAIL_PORT), - secure: Boolean(process.env.PN_ACT_CONFIG_EMAIL_SECURE), - auth: { - user: process.env.PN_ACT_CONFIG_EMAIL_USERNAME, - pass: process.env.PN_ACT_CONFIG_EMAIL_PASSWORD - }, - from: process.env.PN_ACT_CONFIG_EMAIL_FROM - }, - s3: { - endpoint: process.env.PN_ACT_CONFIG_S3_ENDPOINT, - key: process.env.PN_ACT_CONFIG_S3_ACCESS_KEY, - secret: process.env.PN_ACT_CONFIG_S3_ACCESS_SECRET - }, - hcaptcha: { - secret: process.env.PN_ACT_CONFIG_HCAPTCHA_SECRET - }, - cdn: { - subdomain: process.env.PN_ACT_CONFIG_CDN_SUBDOMAIN, - disk_path: process.env.PN_ACT_CONFIG_CDN_DISK_PATH, - base_url: process.env.PN_ACT_CONFIG_CDN_BASE_URL - }, - website_base: process.env.PN_ACT_CONFIG_WEBSITE_BASE - }; - } else { - logger.info('Loading config from config.json'); - - if (!fs.pathExistsSync(`${__dirname}/../config.json`)) { - logger.error('Failed to locate config.json file'); - process.exit(0); +export const config: Config = { + http: { + port: Number(process.env.PN_ACT_CONFIG_HTTP_PORT) + }, + mongoose: { + connection_string: process.env.PN_ACT_CONFIG_MONGO_CONNECTION_STRING, + options: mongooseConnectOptions + }, + redis: { + client: { + url: process.env.PN_ACT_CONFIG_REDIS_URL } + }, + email: { + host: process.env.PN_ACT_CONFIG_EMAIL_HOST, + port: Number(process.env.PN_ACT_CONFIG_EMAIL_PORT), + secure: Boolean(process.env.PN_ACT_CONFIG_EMAIL_SECURE), + auth: { + user: process.env.PN_ACT_CONFIG_EMAIL_USERNAME, + pass: process.env.PN_ACT_CONFIG_EMAIL_PASSWORD + }, + from: process.env.PN_ACT_CONFIG_EMAIL_FROM + }, + s3: { + endpoint: process.env.PN_ACT_CONFIG_S3_ENDPOINT, + key: process.env.PN_ACT_CONFIG_S3_ACCESS_KEY, + secret: process.env.PN_ACT_CONFIG_S3_ACCESS_SECRET + }, + hcaptcha: { + secret: process.env.PN_ACT_CONFIG_HCAPTCHA_SECRET + }, + cdn: { + subdomain: process.env.PN_ACT_CONFIG_CDN_SUBDOMAIN, + disk_path: process.env.PN_ACT_CONFIG_CDN_DISK_PATH, + base_url: process.env.PN_ACT_CONFIG_CDN_BASE_URL + }, + website_base: process.env.PN_ACT_CONFIG_WEBSITE_BASE +}; - config = require(`${__dirname}/../config.json`); +logger.info('Config loaded, checking integrity'); + +if (!config.http.port) { + logger.error('Failed to find HTTP port. Set the PN_ACT_CONFIG_HTTP_PORT environment variable'); + process.exit(0); +} + +if (!config.mongoose.connection_string) { + logger.error('Failed to find MongoDB connection string. Set the PN_ACT_CONFIG_MONGO_CONNECTION_STRING environment variable'); + process.exit(0); +} + +if (!config.cdn.base_url) { + logger.error('Failed to find asset CDN base URL. Set the PN_ACT_CONFIG_CDN_BASE_URL environment variable'); + process.exit(0); +} + +if (!config.redis.client.url) { + logger.warn('Failed to find Redis connection url. Disabling feature and using in-memory cache. To enable feature set the PN_ACT_CONFIG_REDIS_URL environment variable'); + disabledFeatures.redis = true; +} + +if (!config.email.host) { + logger.warn('Failed to find email SMTP host. Disabling feature. To enable feature set the PN_ACT_CONFIG_EMAIL_HOST environment variable'); + disabledFeatures.email = true; +} + +if (!config.email.port) { + logger.warn('Failed to find email SMTP port. Disabling feature. To enable feature set the PN_ACT_CONFIG_EMAIL_PORT environment variable'); + disabledFeatures.email = true; +} + +if (config.email.secure === undefined) { + logger.warn('Failed to find email SMTP secure flag. Disabling feature. To enable feature set the PN_ACT_CONFIG_EMAIL_SECURE environment variable'); + disabledFeatures.email = true; +} + +if (!config.email.auth.user) { + logger.warn('Failed to find email account username. Disabling feature. To enable feature set the auth.user environment variable'); + disabledFeatures.email = true; +} + +if (!config.email.auth.pass) { + logger.warn('Failed to find email account password. Disabling feature. To enable feature set the PN_ACT_CONFIG_EMAIL_PASSWORD environment variable'); + disabledFeatures.email = true; +} + +if (!config.email.from) { + logger.warn('Failed to find email from config. Disabling feature. To enable feature set the PN_ACT_CONFIG_EMAIL_FROM environment variable'); + disabledFeatures.email = true; +} + +if (!disabledFeatures.email) { + if (!config.website_base) { + logger.error('Email sending is enabled and no website base was configured. Set the PN_ACT_CONFIG_WEBSITE_BASE environment variable'); + process.exit(0); + } +} + +if (!config.hcaptcha.secret) { + logger.warn('Failed to find captcha secret config. Disabling feature. To enable feature set the PN_ACT_CONFIG_HCAPTCHA_SECRET environment variable'); + disabledFeatures.captcha = true; +} + +if (!config.s3.endpoint) { + logger.warn('Failed to find s3 endpoint config. Disabling feature. To enable feature set the PN_ACT_CONFIG_S3_ENDPOINT environment variable'); + disabledFeatures.s3 = true; +} + +if (!config.s3.key) { + logger.warn('Failed to find s3 access key config. Disabling feature. To enable feature set the PN_ACT_CONFIG_S3_ACCESS_KEY environment variable'); + disabledFeatures.s3 = true; +} + +if (!config.s3.secret) { + logger.warn('Failed to find s3 secret key config. Disabling feature. To enable feature set the PN_ACT_CONFIG_S3_ACCESS_SECRET environment variable'); + disabledFeatures.s3 = true; +} + +if (disabledFeatures.s3) { + if (!config.cdn.subdomain) { + logger.error('s3 file storage is disabled and no CDN subdomain was set. Set the PN_ACT_CONFIG_CDN_SUBDOMAIN environment variable'); + process.exit(0); } - logger.info('Config loaded, checking integrity'); - - // * Check for required settings - for (const requiredField of requiredFields) { - const [keyPath, envVarName, convertType] = requiredField; - - const configValue = get(config, keyPath); - const envValue = get(process.env, envVarName); - - if (!configValue || (typeof configValue === 'string' && configValue.trim() === '')) { - if (!envValue || envValue.trim() === '') { - logger.error(`Failed to locate required field ${keyPath}. Set ${keyPath} in config.json or the ${envVarName} environment variable`); - - process.exit(0); - } else { - logger.info(`${keyPath} not found in config, using environment variable ${envVarName}`); - - const newValue = envValue; - - set(config, keyPath, convertType ? (convertType as Function)(newValue) : newValue); - } - } + if (!config.cdn.disk_path) { + logger.error('s3 file storage is disabled and no CDN disk path was set. Set the PN_ACT_CONFIG_CDN_DISK_PATH environment variable'); + process.exit(0); } - // * Check for optional settings + logger.warn(`s3 file storage disabled. Using disk-based file storage. Please ensure cdn.base_url config or PN_ACT_CONFIG_CDN_BASE env variable is set to point to this server with the subdomain being ${config.cdn.subdomain}`); - const redisCheck = get(config, 'redis.client.url'); - - if (!redisCheck || redisCheck.trim() === '') { - if (usingEnv) { - logger.warn('Failed to find Redis connection url. Disabling feature and using in-memory cache. To enable feature set the PN_ACT_CONFIG_REDIS_URL environment variable'); - - } else { - logger.warn('Failed to find Redis connection url. Disabling feature and using in-memory cache. To enable feature set redis.client.url in your config.json'); - - } - - disabledFeatures.redis = true; - } - - const emailHostCheck = get(config, 'email.host'); - - if (!emailHostCheck || emailHostCheck.trim() === '') { - if (usingEnv) { - logger.warn('Failed to find email SMTP host. Disabling feature. To enable feature set the PN_ACT_CONFIG_EMAIL_HOST environment variable'); - } else { - logger.warn('Failed to find email SMTP host. Disabling feature. To enable feature set email.host in your config.json'); - } - - - disabledFeatures.email = true; - } - - const emailPortCheck = get(config, 'email.port'); - - if (!emailPortCheck) { - if (usingEnv) { - logger.warn('Failed to find email SMTP port. Disabling feature. To enable feature set the PN_ACT_CONFIG_EMAIL_PORT environment variable'); - } else { - logger.warn('Failed to find email SMTP port. Disabling feature. To enable feature set email.port in your config.json'); - } - - disabledFeatures.email = true; - } - - const emailSecureCheck = get(config, 'email.secure'); - - if (emailSecureCheck === undefined) { - if (usingEnv) { - logger.warn('Failed to find email SMTP secure flag. Disabling feature. To enable feature set the PN_ACT_CONFIG_EMAIL_SECURE environment variable'); - } else { - - logger.warn('Failed to find email SMTP secure flag. Disabling feature. To enable feature set email.secure in your config.json'); - } - - disabledFeatures.email = true; - } - - const emailUsernameCheck = get(config, 'email.auth.user'); - - if (!emailUsernameCheck || emailUsernameCheck.trim() === '') { - if (usingEnv) { - logger.warn('Failed to find email account username. Disabling feature. To enable feature set the auth.user environment variable'); - } else { - - logger.warn('Failed to find email account username. Disabling feature. To enable feature set email.auth.user in your config.json'); - } - - disabledFeatures.email = true; - } - - const emailPasswordCheck = get(config, 'email.auth.pass'); - - if (!emailPasswordCheck || emailPasswordCheck.trim() === '') { - if (usingEnv) { - logger.warn('Failed to find email account password. Disabling feature. To enable feature set the PN_ACT_CONFIG_EMAIL_PASSWORD environment variable'); - } else { - - logger.warn('Failed to find email account password. Disabling feature. To enable feature set email.auth.pass in your config.json'); - } - - disabledFeatures.email = true; - } - - const emailFromCheck = get(config, 'email.from'); - - if (!emailFromCheck || emailFromCheck.trim() === '') { - if (usingEnv) { - logger.warn('Failed to find email from config. Disabling feature. To enable feature set the PN_ACT_CONFIG_EMAIL_FROM environment variable'); - } else { - - logger.warn('Failed to find email from config. Disabling feature. To enable feature set email.from in your config.json'); - } - - disabledFeatures.email = true; - } - - if (!disabledFeatures.email) { - const websiteBaseCheck = get(config, 'website_base'); - - if (!websiteBaseCheck || websiteBaseCheck.trim() === '') { - if (usingEnv) { - logger.error('Email sending is enabled and no website base was configured. Set the PN_ACT_CONFIG_WEBSITE_BASE environment variable'); - } else { - logger.error('Email sending is enabled and no website base was configured. Set website_base in your config.json'); - } - - process.exit(0); - } - } - - const captchaSecretCheck = get(config, 'hcaptcha.secret'); - - if (!captchaSecretCheck || captchaSecretCheck.trim() === '') { - if (usingEnv) { - logger.warn('Failed to find captcha secret config. Disabling feature. To enable feature set the PN_ACT_CONFIG_HCAPTCHA_SECRET environment variable'); - } else { - logger.warn('Failed to find captcha secret config. Disabling feature. To enable feature set hcaptcha.secret in your config.json'); - } - - disabledFeatures.captcha = true; - } - - const s3EndpointCheck = get(config, 's3.endpoint'); - - if (!s3EndpointCheck || s3EndpointCheck.trim() === '') { - if (usingEnv) { - logger.warn('Failed to find s3 endpoint config. Disabling feature. To enable feature set the PN_ACT_CONFIG_S3_ENDPOINT environment variable'); - } else { - logger.warn('Failed to find s3 endpoint config. Disabling feature. To enable feature set s3.endpoint in your config.json'); - } - - disabledFeatures.s3 = true; - } else { - } - - const s3AccessKeyCheck = get(config, 's3.key'); - - if (!s3AccessKeyCheck || s3AccessKeyCheck.trim() === '') { - if (usingEnv) { - logger.warn('Failed to find s3 access key config. Disabling feature. To enable feature set the PN_ACT_CONFIG_S3_ACCESS_KEY environment variable'); - } else { - logger.warn('Failed to find s3 access key config. Disabling feature. To enable feature set s3.key in your config.json'); - } - - disabledFeatures.s3 = true; - } - - const s3SecretKeyCheck = get(config, 's3.secret'); - - if (!s3SecretKeyCheck || s3SecretKeyCheck.trim() === '') { - if (usingEnv) { - logger.warn('Failed to find s3 secret key config. Disabling feature. To enable feature set the PN_ACT_CONFIG_S3_ACCESS_SECRET environment variable'); - } else { - logger.warn('Failed to find s3 secret key config. Disabling feature. To enable feature set s3.secret in your config.json'); - } - - disabledFeatures.s3 = true; - } - - if (disabledFeatures.s3) { - const cdnSubdomainCheck = get(config, 'cdn.subdomain'); - - if (!cdnSubdomainCheck || cdnSubdomainCheck.trim() === '') { - if (usingEnv) { - logger.error('s3 file storage is disabled and no CDN subdomain was set. Set the PN_ACT_CONFIG_CDN_SUBDOMAIN environment variable'); - } else { - logger.error('s3 file storage is disabled and no CDN subdomain was set. Set cdn.subdomain in your config.json'); - } - - process.exit(0); - } - - if (disabledFeatures.redis) { - logger.warn('Both s3 and Redis are disabled. Large CDN files will use the in-memory cache, which may result in high memory use. Please enable s3 if you\'re running a production server.'); - } - - logger.warn(`s3 file storage disabled. Using disk-based file storage. Please ensure cdn.base_url config or PN_ACT_CONFIG_CDN_BASE env variable is set to point to this server with the subdomain being ${config.cdn.subdomain}`); + if (disabledFeatures.redis) { + logger.warn('Both s3 and Redis are disabled. Large CDN files will use the in-memory cache, which may result in high memory use. Please enable s3 if you\'re running a production server.'); } } export default { - configure, config, disabledFeatures }; \ No newline at end of file diff --git a/src/types/common/config.ts b/src/types/common/config.ts new file mode 100644 index 0000000..ed3fbb2 --- /dev/null +++ b/src/types/common/config.ts @@ -0,0 +1,47 @@ +import mongoose from 'mongoose'; + +export interface Config { + http: { + port: number; + }; + mongoose: { + connection_string: string; + options: mongoose.ConnectOptions; + }; + redis: { + client: { + url: string; + } + }; + email: { + host: string; + port: number; + secure: boolean; + auth: { + user: string; + pass: string; + }; + from: string; + }; + s3: { + endpoint: string; + key: string; + secret: string; + }; + hcaptcha: { + secret: string; + }; + cdn: { + subdomain: string; + disk_path: string; + base_url: string; + }; + website_base: string; +}; + +export interface DisabledFeatures { + redis: boolean; + email: boolean; + captcha: boolean; + s3: boolean +} \ No newline at end of file From 9fafca77fd5e6a7684671bc9830d9e10a168120d Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Mon, 6 Mar 2023 21:01:15 -0500 Subject: [PATCH 030/219] Typed literally everything and eslint is happy --- .eslintignore | 1 + .eslintrc.json | 19 +- package-lock.json | 2590 ++++++++++++++++- package.json | 13 +- src/cache.ts | 100 +- src/database.ts | 143 +- src/logger.ts | 20 +- src/mailer.ts | 13 +- src/middleware/api.ts | 13 +- src/middleware/cemu.ts | 6 +- src/middleware/client-header.ts | 22 +- src/middleware/device-certificate.ts | 8 +- src/middleware/nasc.ts | 82 +- src/middleware/pnid.ts | 26 +- src/middleware/ratelimit.ts | 5 +- src/middleware/xml-parser.ts | 18 +- src/nintendo-certificate.ts | 33 +- src/server.ts | 26 +- src/services/api/index.ts | 4 +- src/services/api/routes/v1/connections.ts | 27 +- src/services/api/routes/v1/email.ts | 19 +- src/services/api/routes/v1/forgotPassword.ts | 12 +- src/services/api/routes/v1/login.ts | 52 +- src/services/api/routes/v1/register.ts | 79 +- src/services/api/routes/v1/resetPassword.ts | 33 +- src/services/api/routes/v1/user.ts | 27 +- src/services/assets/index.ts | 4 +- src/services/conntest/index.ts | 6 +- src/services/datastore/index.ts | 4 +- src/services/datastore/routes/upload.ts | 65 +- src/services/local-cdn/index.ts | 4 +- src/services/local-cdn/routes/get.ts | 10 +- src/services/nasc/index.ts | 4 +- src/services/nasc/routes/ac.ts | 51 +- src/services/nnid/routes/admin.ts | 44 +- src/services/nnid/routes/content.ts | 17 +- src/services/nnid/routes/devices.ts | 7 +- src/services/nnid/routes/miis.ts | 149 +- src/services/nnid/routes/oauth.ts | 29 +- src/services/nnid/routes/people.ts | 155 +- src/services/nnid/routes/provider.ts | 65 +- src/services/nnid/routes/support.ts | 34 +- src/types/common/config.ts | 2 +- src/types/common/crypto-options.ts | 4 + src/types/common/mailer-options.ts | 16 + src/types/common/signature-size.ts | 4 + src/types/common/token-options.ts | 8 + src/types/common/token.ts | 8 + src/types/common/yes-no-bool-string.ts | 1 + src/types/express.d.ts | 9 +- src/types/mongoose/device-attribute.ts | 6 +- src/types/mongoose/device.ts | 14 +- src/types/mongoose/nex-account.ts | 8 +- src/types/mongoose/pnid.ts | 14 +- src/types/mongoose/server.ts | 10 +- src/types/services/api/connection-data.ts | 4 + src/types/services/api/connection-response.ts | 5 + .../services/api/discord-connection-data.ts | 3 + src/types/services/api/update-user-request.ts | 7 + src/types/services/nasc/request-params.ts | 11 + src/types/services/nnid/person.ts | 44 + src/types/services/nnid/pnid-profile.ts | 53 + src/types/services/nnid/region-languages.ts | 5 + src/types/services/nnid/region-timezones.ts | 9 + src/util.ts | 181 +- tsconfig.json | 6 +- 66 files changed, 3673 insertions(+), 798 deletions(-) create mode 100644 src/types/common/crypto-options.ts create mode 100644 src/types/common/mailer-options.ts create mode 100644 src/types/common/signature-size.ts create mode 100644 src/types/common/token-options.ts create mode 100644 src/types/common/token.ts create mode 100644 src/types/common/yes-no-bool-string.ts create mode 100644 src/types/services/api/connection-data.ts create mode 100644 src/types/services/api/connection-response.ts create mode 100644 src/types/services/api/discord-connection-data.ts create mode 100644 src/types/services/api/update-user-request.ts create mode 100644 src/types/services/nasc/request-params.ts create mode 100644 src/types/services/nnid/person.ts create mode 100644 src/types/services/nnid/pnid-profile.ts create mode 100644 src/types/services/nnid/region-languages.ts create mode 100644 src/types/services/nnid/region-timezones.ts diff --git a/.eslintignore b/.eslintignore index e69de29..53c37a1 100644 --- a/.eslintignore +++ b/.eslintignore @@ -0,0 +1 @@ +dist \ No newline at end of file diff --git a/.eslintrc.json b/.eslintrc.json index c7da300..d66114d 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -4,13 +4,17 @@ "commonjs": true, "es6": true }, - "parserOptions": { - "ecmaVersion": 2020 - }, + "parser": "@typescript-eslint/parser", "globals": { "BigInt": true }, - "extends": "eslint:recommended", + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/recommended" + ], + "plugins": [ + "@typescript-eslint" + ], "rules": { "require-atomic-updates": "warn", "no-case-declarations": "off", @@ -20,6 +24,13 @@ "no-global-assign": "off", "prefer-const": "error", "no-var": "error", + "no-unused-vars": "off", + "@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_" }], + "no-extra-semi": "off", + "@typescript-eslint/no-extra-semi": "error", + "@typescript-eslint/no-empty-interface": "warn", + "@typescript-eslint/no-inferrable-types": "off", + "@typescript-eslint/explicit-function-return-type": "error", "one-var": [ "error", "never" diff --git a/package-lock.json b/package-lock.json index 6e0a7ce..612c092 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,13 +24,12 @@ "hcaptcha": "^0.1.0", "image-pixels": "^1.1.1", "joi": "^17.8.3", - "lodash.get": "^4.4.2", - "lodash.set": "^4.3.2", "mii-js": "github:PretendoNetwork/mii-js", "moment": "^2.29.4", "mongoose": "^7.0.0", "mongoose-unique-validator": "github:stenneepro/mongoose-unique-validator#bump/mongoose7", "morgan": "^1.9.1", + "node-rsa": "^1.0.7", "nodemailer": "^6.4.2", "redis": "^4.3.1", "tga": "^1.0.4", @@ -39,13 +38,19 @@ "xmlbuilder2": "0.0.4" }, "devDependencies": { - "@types/colors": "^1.2.1", + "@hcaptcha/types": "^1.0.3", + "@types/dicer": "^0.2.2", "@types/express": "^4.17.17", "@types/fs-extra": "^11.0.1", "@types/morgan": "^1.9.4", "@types/node": "^18.14.4", - "node-rsa": "^1.0.7", + "@types/node-rsa": "^1.1.1", + "@types/nodemailer": "^6.4.7", + "@typescript-eslint/eslint-plugin": "^5.54.1", + "@typescript-eslint/parser": "^5.54.1", + "eslint": "^8.35.0", "prompt": "^1.0.0", + "typescript": "^4.9.5", "yesno": "^0.4.0" } }, @@ -58,6 +63,61 @@ "node": ">=0.1.90" } }, + "node_modules/@eslint/eslintrc": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.0.tgz", + "integrity": "sha512-fluIaaV+GyV24CCu/ggiHdV+j4RNh85yQnAYS/G2mZODZgGmmlrgCydjUcV3YvxCm9x8nMAfThsqTni4KiXT4A==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.4.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@eslint/eslintrc/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/@eslint/js": { + "version": "8.35.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.35.0.tgz", + "integrity": "sha512-JXdzbRiWclLVoD8sNUjR443VVlYqiYmDVT6rGUEIEHU5YJW0gaVZwV2xgM7D4arkvASqD0IlLUVjHiFuxaftRw==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, "node_modules/@hapi/hoek": { "version": "9.3.0", "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", @@ -71,6 +131,68 @@ "@hapi/hoek": "^9.0.0" } }, + "node_modules/@hcaptcha/types": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@hcaptcha/types/-/types-1.0.3.tgz", + "integrity": "sha512-1mbU6eSGawRrqeahRrOzZo/SVLI6oZ5/azuBpSyVrRRR96CnS3fOVDWfzxpngfxKD0/I9Rwu6c/3ITqD8rXeTQ==", + "dev": true + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.8", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", + "integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/@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, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true + }, "node_modules/@mapbox/node-pre-gyp": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.10.tgz", @@ -90,6 +212,41 @@ "node-pre-gyp": "bin/node-pre-gyp" } }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/@oozcitak/dom": { "version": "0.0.11", "resolved": "https://registry.npmjs.org/@oozcitak/dom/-/dom-0.0.11.tgz", @@ -320,16 +477,6 @@ "@types/responselike": "^1.0.0" } }, - "node_modules/@types/colors": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@types/colors/-/colors-1.2.1.tgz", - "integrity": "sha512-7jNkpfN2lVO07nJ1RWzyMnNhH/I5N9iWuMPx9pedptxJ4MODf8rRV0lbJi6RakQ4sKQk231Fw4e2W9n3D7gZ3w==", - "deprecated": "This is a stub types definition. colors provides its own type definitions, so you don't need this installed.", - "dev": true, - "dependencies": { - "colors": "*" - } - }, "node_modules/@types/connect": { "version": "3.4.35", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", @@ -339,6 +486,15 @@ "@types/node": "*" } }, + "node_modules/@types/dicer": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@types/dicer/-/dicer-0.2.2.tgz", + "integrity": "sha512-UPLqCYey+jn5Mf57KFDwxD/7VZYDsbYUi3iyTehLFVjlbvl/JcUTPaot8uKNYLO0EoZpey+rC/s5AF3VxfeC2Q==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/express": { "version": "4.17.17", "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.17.tgz", @@ -377,6 +533,12 @@ "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz", "integrity": "sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==" }, + "node_modules/@types/json-schema": { + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", + "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", + "dev": true + }, "node_modules/@types/jsonfile": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/@types/jsonfile/-/jsonfile-6.1.1.tgz", @@ -414,6 +576,24 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-18.14.6.tgz", "integrity": "sha512-93+VvleD3mXwlLI/xASjw0FzKcwzl3OdTCzm1LaRfqgS21gfFtK3zDXM5Op9TeeMsJVOaJ2VRDpT9q4Y3d0AvA==" }, + "node_modules/@types/node-rsa": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@types/node-rsa/-/node-rsa-1.1.1.tgz", + "integrity": "sha512-itzxtaBgk4OMbrCawVCvas934waMZWjW17v7EYgFVlfYS/cl0/P7KZdojWCq9SDJMI5cnLQLUP8ayhVCTY8TEg==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/nodemailer": { + "version": "6.4.7", + "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.7.tgz", + "integrity": "sha512-f5qCBGAn/f0qtRcd4SEn88c8Fp3Swct1731X4ryPKqS61/A3LmmzN8zaEz7hneJvpjFbUUgY7lru/B/7ODTazg==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/qs": { "version": "6.9.7", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", @@ -434,6 +614,12 @@ "@types/node": "*" } }, + "node_modules/@types/semver": { + "version": "7.3.13", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz", + "integrity": "sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==", + "dev": true + }, "node_modules/@types/serve-static": { "version": "1.15.1", "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.1.tgz", @@ -458,6 +644,299 @@ "@types/webidl-conversions": "*" } }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "5.54.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.54.1.tgz", + "integrity": "sha512-a2RQAkosH3d3ZIV08s3DcL/mcGc2M/UC528VkPULFxR9VnVPT8pBu0IyBAJJmVsCmhVfwQX1v6q+QGnmSe1bew==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "5.54.1", + "@typescript-eslint/type-utils": "5.54.1", + "@typescript-eslint/utils": "5.54.1", + "debug": "^4.3.4", + "grapheme-splitter": "^1.0.4", + "ignore": "^5.2.0", + "natural-compare-lite": "^1.4.0", + "regexpp": "^3.2.0", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^5.0.0", + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/@typescript-eslint/parser": { + "version": "5.54.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.54.1.tgz", + "integrity": "sha512-8zaIXJp/nG9Ff9vQNh7TI+C3nA6q6iIsGJ4B4L6MhZ7mHnTMR4YP5vp2xydmFXIy8rpyIVbNAG44871LMt6ujg==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "5.54.1", + "@typescript-eslint/types": "5.54.1", + "@typescript-eslint/typescript-estree": "5.54.1", + "debug": "^4.3.4" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "5.54.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.54.1.tgz", + "integrity": "sha512-zWKuGliXxvuxyM71UA/EcPxaviw39dB2504LqAmFDjmkpO8qNLHcmzlh6pbHs1h/7YQ9bnsO8CCcYCSA8sykUg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.54.1", + "@typescript-eslint/visitor-keys": "5.54.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "5.54.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.54.1.tgz", + "integrity": "sha512-WREHsTz0GqVYLIbzIZYbmUUr95DKEKIXZNH57W3s+4bVnuF1TKe2jH8ZNH8rO1CeMY3U4j4UQeqPNkHMiGem3g==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "5.54.1", + "@typescript-eslint/utils": "5.54.1", + "debug": "^4.3.4", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/@typescript-eslint/types": { + "version": "5.54.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.54.1.tgz", + "integrity": "sha512-G9+1vVazrfAfbtmCapJX8jRo2E4MDXxgm/IMOF4oGh3kq7XuK3JRkOg6y2Qu1VsTRmWETyTkWt1wxy7X7/yLkw==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "5.54.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.54.1.tgz", + "integrity": "sha512-bjK5t+S6ffHnVwA0qRPTZrxKSaFYocwFIkZx5k7pvWfsB1I57pO/0M0Skatzzw1sCkjJ83AfGTL0oFIFiDX3bg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.54.1", + "@typescript-eslint/visitor-keys": "5.54.1", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/@typescript-eslint/utils": { + "version": "5.54.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.54.1.tgz", + "integrity": "sha512-IY5dyQM8XD1zfDe5X8jegX6r2EVU5o/WJnLu/znLPWCBF7KNGC+adacXnt5jEYS9JixDcoccI6CvE4RCjHMzCQ==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.54.1", + "@typescript-eslint/types": "5.54.1", + "@typescript-eslint/typescript-estree": "5.54.1", + "eslint-scope": "^5.1.1", + "eslint-utils": "^3.0.0", + "semver": "^7.3.7" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "5.54.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.54.1.tgz", + "integrity": "sha512-q8iSoHTgwCfgcRJ2l2x+xCbu8nBlRAlsQ33k24Adj8eoVBE0f8dUeI+bAa8F84Mv05UGbAx57g2zrRsYIooqQg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.54.1", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, "node_modules/abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", @@ -486,6 +965,15 @@ "node": ">=0.4.0" } }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, "node_modules/acorn-node": { "version": "1.8.2", "resolved": "https://registry.npmjs.org/acorn-node/-/acorn-node-1.8.2.tgz", @@ -559,6 +1047,21 @@ "node": ">=8" } }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/aproba": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", @@ -597,6 +1100,12 @@ "safe-buffer": "~5.2.0" } }, + "node_modules/argparse": { + "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 + }, "node_modules/arr-flatten": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", @@ -615,6 +1124,15 @@ "resolved": "https://registry.npmjs.org/array-from/-/array-from-2.1.1.tgz", "integrity": "sha512-GQTc6Uupx1FCavi5mPzBvVT7nEOeWMmUA9P95wpfpW1XwMSKs+KaymD5C2Up7KAUKg/mYwbsUYzdZWcoajlNZg==" }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/arraybuffer-to-string": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/arraybuffer-to-string/-/arraybuffer-to-string-1.0.2.tgz", @@ -799,6 +1317,18 @@ "concat-map": "0.0.1" } }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/brfs": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/brfs/-/brfs-2.0.2.tgz", @@ -899,11 +1429,36 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/caseless": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==" }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/chownr": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", @@ -936,6 +1491,24 @@ "node": ">=0.10.0" } }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, "node_modules/color-support": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", @@ -1068,6 +1641,20 @@ "node": ">= 0.10" } }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/cycle": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/cycle/-/cycle-1.0.3.tgz", @@ -1198,6 +1785,30 @@ "node": ">=0.8.0" } }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/dotenv": { "version": "16.0.3", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz", @@ -1375,6 +1986,18 @@ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/escodegen": { "version": "1.14.3", "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz", @@ -1396,6 +2019,224 @@ "source-map": "~0.6.1" } }, + "node_modules/eslint": { + "version": "8.35.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.35.0.tgz", + "integrity": "sha512-BxAf1fVL7w+JLRQhWl2pzGeSiGqbWumV4WNvc9Rhp6tiCtm4oHnyPBSEtMGZwrQgudFQ+otqzWoPB7x+hxoWsw==", + "dev": true, + "dependencies": { + "@eslint/eslintrc": "^2.0.0", + "@eslint/js": "8.35.0", + "@humanwhocodes/config-array": "^0.11.8", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.1.1", + "eslint-utils": "^3.0.0", + "eslint-visitor-keys": "^3.3.0", + "espree": "^9.4.0", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "grapheme-splitter": "^1.0.4", + "ignore": "^5.2.0", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-sdsl": "^4.1.4", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "regexpp": "^3.2.0", + "strip-ansi": "^6.0.1", + "strip-json-comments": "^3.1.0", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-scope": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", + "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/eslint-scope/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/eslint-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^2.0.0" + }, + "engines": { + "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=5" + } + }, + "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", + "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/eslint/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/eslint/node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/eslint/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/eslint/node_modules/optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/eslint/node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/eslint/node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/espree": { + "version": "9.4.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.4.1.tgz", + "integrity": "sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg==", + "dev": true, + "dependencies": { + "acorn": "^8.8.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree/node_modules/acorn": { + "version": "8.8.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", + "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", @@ -1408,6 +2249,48 @@ "node": ">=4" } }, + "node_modules/esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esquery/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, "node_modules/estraverse": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", @@ -1545,6 +2428,34 @@ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, + "node_modules/fast-glob": { + "version": "3.2.12", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", + "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", @@ -1555,6 +2466,27 @@ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==" }, + "node_modules/fastq": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, "node_modules/file-type": { "version": "10.11.0", "resolved": "https://registry.npmjs.org/file-type/-/file-type-10.11.0.tgz", @@ -1563,6 +2495,18 @@ "node": ">=6" } }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/finalhandler": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", @@ -1580,6 +2524,41 @@ "node": ">= 0.8" } }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "dependencies": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", + "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", + "dev": true + }, "node_modules/flatten-vertex-data": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/flatten-vertex-data/-/flatten-vertex-data-1.0.2.tgz", @@ -1769,6 +2748,53 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "13.20.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", + "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/gopd": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", @@ -1809,6 +2835,12 @@ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" }, + "node_modules/grapheme-splitter": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", + "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", + "dev": true + }, "node_modules/har-schema": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", @@ -1841,6 +2873,15 @@ "node": ">= 0.4.0" } }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/has-symbols": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", @@ -1971,6 +3012,15 @@ "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==" }, + "node_modules/ignore": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, "node_modules/image-pixels": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/image-pixels/-/image-pixels-1.1.1.tgz", @@ -2015,6 +3065,31 @@ "node": ">=6" } }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -2122,6 +3197,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -2144,6 +3228,36 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/is-plain-obj": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", @@ -2190,6 +3304,12 @@ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, "node_modules/isstream": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", @@ -2220,6 +3340,28 @@ "resolved": "https://registry.npmjs.org/jpeg-js/-/jpeg-js-0.3.7.tgz", "integrity": "sha512-9IXdWudL61npZjvLuVe/ktHiA41iE8qFyLB+4VDTblEsWBzeg8WQTlktdUK4CdncUqtUgUg0bbOmTE2bKBKaBQ==" }, + "node_modules/js-sdsl": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.3.0.tgz", + "integrity": "sha512-mifzlm2+5nZ+lEcLJMoBK0/IH/bDg8XnJfd/Wq6IP+xoCjLZsTOnV2QpxlVbX9bMnkl5PdEjNtBJ9Cj1NjifhQ==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/js-sdsl" + } + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, "node_modules/jsbn": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", @@ -2240,6 +3382,12 @@ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, "node_modules/json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", @@ -2295,6 +3443,21 @@ "node": ">= 0.8.0" } }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", @@ -2316,11 +3479,6 @@ "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" }, - "node_modules/lodash.set": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/lodash.set/-/lodash.set-4.3.2.tgz", - "integrity": "sha512-4hNPN5jlm/N/HLMCO43v8BXKq9Z7QdAGc/VGrRD61w8gN9g/6jF9A4L1pbUgBLCffi0w9VsXfTOij5x8iTyFvg==" - }, "node_modules/lowercase-keys": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", @@ -2405,6 +3563,15 @@ "node": ">=0.10.0" } }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, "node_modules/methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", @@ -2413,6 +3580,19 @@ "node": ">= 0.6" } }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, "node_modules/mii-js": { "version": "1.0.4", "resolved": "git+ssh://git@github.com/PretendoNetwork/mii-js.git#5d8eb8013514a13b0df6eb4a5bfd8b5a63fb9861", @@ -2685,6 +3865,18 @@ "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", "dev": true }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/natural-compare-lite": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", + "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", + "dev": true + }, "node_modules/negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", @@ -2745,7 +3937,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/node-rsa/-/node-rsa-1.1.1.tgz", "integrity": "sha512-Jd4cvbJMryN21r5HgxQOpMEqv+ooke/korixNNK3mGqfGJmy0M77WDDzo/05969+OkMy3XW1UuZsSmW9KQm7Fw==", - "dev": true, "dependencies": { "asn1": "^0.2.4" } @@ -2874,6 +4065,48 @@ "node": ">=8" } }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/parse-rect": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/parse-rect/-/parse-rect-1.2.0.tgz", @@ -2890,6 +4123,15 @@ "node": ">= 0.8" } }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", @@ -2898,6 +4140,15 @@ "node": ">=0.10.0" } }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/path-parse": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", @@ -2908,6 +4159,15 @@ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", @@ -2918,6 +4178,18 @@ "resolved": "https://registry.npmjs.org/pick-by-alias/-/pick-by-alias-1.2.0.tgz", "integrity": "sha512-ESj2+eBxhGrcA1azgHs7lARG5+5iLakc/6nlfbpjcLl00HuuUOIuORhYXN4D1HfvMSKuVtFQjAlnwi1JHEeDIw==" }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/pngjs": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-3.4.0.tgz", @@ -3017,6 +4289,26 @@ "node": ">=0.4.x" } }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/quick-lru": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", @@ -3104,6 +4396,18 @@ "@redis/time-series": "1.0.4" } }, + "node_modules/regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, "node_modules/request": { "version": "2.88.2", "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", @@ -3173,6 +4477,15 @@ "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==" }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/responselike": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz", @@ -3189,6 +4502,16 @@ "resolved": "https://registry.npmjs.org/restructure/-/restructure-2.0.1.tgz", "integrity": "sha512-e0dOpjm5DseomnXx2M5lpdZ5zoHqF1+bqdMJUohoYVVQa7cBdnk7fdmeI6byNWP/kiME72EeTiSypTCVnpLiDg==" }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, "node_modules/revalidator": { "version": "0.1.8", "resolved": "https://registry.npmjs.org/revalidator/-/revalidator-0.1.8.tgz", @@ -3212,6 +4535,29 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -3338,6 +4684,27 @@ "resolved": "https://registry.npmjs.org/shallow-copy/-/shallow-copy-0.0.1.tgz", "integrity": "sha512-b6i4ZpVuUxB9h5gfCxPiusKYkqTMOjEbBs4wMaFbkfia4yFv92UKZ6Df8WXcKbn08JNL/abvg3FnMAOfakDvUw==" }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/side-channel": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", @@ -3361,6 +4728,15 @@ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/smart-buffer": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", @@ -3555,6 +4931,30 @@ "node": ">=8" } }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/supports-preserve-symlinks-flag": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", @@ -3582,6 +4982,12 @@ "node": ">=10" } }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, "node_modules/tga": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/tga/-/tga-1.0.7.tgz", @@ -3640,6 +5046,18 @@ "string-to-arraybuffer": "^1.0.0" } }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, "node_modules/toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", @@ -3671,6 +5089,27 @@ "node": ">=12" } }, + "node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "node_modules/tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "dependencies": { + "tslib": "^1.8.1" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + } + }, "node_modules/tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", @@ -3703,6 +5142,18 @@ "node": ">= 0.8.0" } }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", @@ -3720,6 +5171,19 @@ "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==" }, + "node_modules/typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, "node_modules/universalify": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", @@ -3845,6 +5309,21 @@ "node": ">=12" } }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/which-typed-array": { "version": "1.1.9", "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz", @@ -3976,6 +5455,18 @@ "resolved": "https://registry.npmjs.org/yesno/-/yesno-0.4.0.tgz", "integrity": "sha512-tdBxmHvbXPBKYIg81bMCB7bVeDmHkRzk5rVJyYYXurwKkHq/MCd8rz4HSJUP7hW0H2NlXiq8IFiWvYKEHhlotA==", "dev": true + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } } }, "dependencies": { @@ -3985,6 +5476,46 @@ "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", "dev": true }, + "@eslint/eslintrc": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.0.tgz", + "integrity": "sha512-fluIaaV+GyV24CCu/ggiHdV+j4RNh85yQnAYS/G2mZODZgGmmlrgCydjUcV3YvxCm9x8nMAfThsqTni4KiXT4A==", + "dev": true, + "requires": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.4.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "@eslint/js": { + "version": "8.35.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.35.0.tgz", + "integrity": "sha512-JXdzbRiWclLVoD8sNUjR443VVlYqiYmDVT6rGUEIEHU5YJW0gaVZwV2xgM7D4arkvASqD0IlLUVjHiFuxaftRw==", + "dev": true + }, "@hapi/hoek": { "version": "9.3.0", "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", @@ -3998,6 +5529,52 @@ "@hapi/hoek": "^9.0.0" } }, + "@hcaptcha/types": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@hcaptcha/types/-/types-1.0.3.tgz", + "integrity": "sha512-1mbU6eSGawRrqeahRrOzZo/SVLI6oZ5/azuBpSyVrRRR96CnS3fOVDWfzxpngfxKD0/I9Rwu6c/3ITqD8rXeTQ==", + "dev": true + }, + "@humanwhocodes/config-array": { + "version": "0.11.8", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", + "integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==", + "dev": true, + "requires": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.5" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "@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": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true + }, "@mapbox/node-pre-gyp": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.10.tgz", @@ -4014,6 +5591,32 @@ "tar": "^6.1.11" } }, + "@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + } + }, + "@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true + }, + "@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "requires": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + } + }, "@oozcitak/dom": { "version": "0.0.11", "resolved": "https://registry.npmjs.org/@oozcitak/dom/-/dom-0.0.11.tgz", @@ -4196,15 +5799,6 @@ "@types/responselike": "^1.0.0" } }, - "@types/colors": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@types/colors/-/colors-1.2.1.tgz", - "integrity": "sha512-7jNkpfN2lVO07nJ1RWzyMnNhH/I5N9iWuMPx9pedptxJ4MODf8rRV0lbJi6RakQ4sKQk231Fw4e2W9n3D7gZ3w==", - "dev": true, - "requires": { - "colors": "*" - } - }, "@types/connect": { "version": "3.4.35", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", @@ -4214,6 +5808,15 @@ "@types/node": "*" } }, + "@types/dicer": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@types/dicer/-/dicer-0.2.2.tgz", + "integrity": "sha512-UPLqCYey+jn5Mf57KFDwxD/7VZYDsbYUi3iyTehLFVjlbvl/JcUTPaot8uKNYLO0EoZpey+rC/s5AF3VxfeC2Q==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/express": { "version": "4.17.17", "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.17.tgz", @@ -4252,6 +5855,12 @@ "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz", "integrity": "sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==" }, + "@types/json-schema": { + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", + "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", + "dev": true + }, "@types/jsonfile": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/@types/jsonfile/-/jsonfile-6.1.1.tgz", @@ -4289,6 +5898,24 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-18.14.6.tgz", "integrity": "sha512-93+VvleD3mXwlLI/xASjw0FzKcwzl3OdTCzm1LaRfqgS21gfFtK3zDXM5Op9TeeMsJVOaJ2VRDpT9q4Y3d0AvA==" }, + "@types/node-rsa": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@types/node-rsa/-/node-rsa-1.1.1.tgz", + "integrity": "sha512-itzxtaBgk4OMbrCawVCvas934waMZWjW17v7EYgFVlfYS/cl0/P7KZdojWCq9SDJMI5cnLQLUP8ayhVCTY8TEg==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/nodemailer": { + "version": "6.4.7", + "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.7.tgz", + "integrity": "sha512-f5qCBGAn/f0qtRcd4SEn88c8Fp3Swct1731X4ryPKqS61/A3LmmzN8zaEz7hneJvpjFbUUgY7lru/B/7ODTazg==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/qs": { "version": "6.9.7", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", @@ -4309,6 +5936,12 @@ "@types/node": "*" } }, + "@types/semver": { + "version": "7.3.13", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz", + "integrity": "sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==", + "dev": true + }, "@types/serve-static": { "version": "1.15.1", "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.1.tgz", @@ -4333,6 +5966,185 @@ "@types/webidl-conversions": "*" } }, + "@typescript-eslint/eslint-plugin": { + "version": "5.54.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.54.1.tgz", + "integrity": "sha512-a2RQAkosH3d3ZIV08s3DcL/mcGc2M/UC528VkPULFxR9VnVPT8pBu0IyBAJJmVsCmhVfwQX1v6q+QGnmSe1bew==", + "dev": true, + "requires": { + "@typescript-eslint/scope-manager": "5.54.1", + "@typescript-eslint/type-utils": "5.54.1", + "@typescript-eslint/utils": "5.54.1", + "debug": "^4.3.4", + "grapheme-splitter": "^1.0.4", + "ignore": "^5.2.0", + "natural-compare-lite": "^1.4.0", + "regexpp": "^3.2.0", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "@typescript-eslint/parser": { + "version": "5.54.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.54.1.tgz", + "integrity": "sha512-8zaIXJp/nG9Ff9vQNh7TI+C3nA6q6iIsGJ4B4L6MhZ7mHnTMR4YP5vp2xydmFXIy8rpyIVbNAG44871LMt6ujg==", + "dev": true, + "requires": { + "@typescript-eslint/scope-manager": "5.54.1", + "@typescript-eslint/types": "5.54.1", + "@typescript-eslint/typescript-estree": "5.54.1", + "debug": "^4.3.4" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "@typescript-eslint/scope-manager": { + "version": "5.54.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.54.1.tgz", + "integrity": "sha512-zWKuGliXxvuxyM71UA/EcPxaviw39dB2504LqAmFDjmkpO8qNLHcmzlh6pbHs1h/7YQ9bnsO8CCcYCSA8sykUg==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.54.1", + "@typescript-eslint/visitor-keys": "5.54.1" + } + }, + "@typescript-eslint/type-utils": { + "version": "5.54.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.54.1.tgz", + "integrity": "sha512-WREHsTz0GqVYLIbzIZYbmUUr95DKEKIXZNH57W3s+4bVnuF1TKe2jH8ZNH8rO1CeMY3U4j4UQeqPNkHMiGem3g==", + "dev": true, + "requires": { + "@typescript-eslint/typescript-estree": "5.54.1", + "@typescript-eslint/utils": "5.54.1", + "debug": "^4.3.4", + "tsutils": "^3.21.0" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "@typescript-eslint/types": { + "version": "5.54.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.54.1.tgz", + "integrity": "sha512-G9+1vVazrfAfbtmCapJX8jRo2E4MDXxgm/IMOF4oGh3kq7XuK3JRkOg6y2Qu1VsTRmWETyTkWt1wxy7X7/yLkw==", + "dev": true + }, + "@typescript-eslint/typescript-estree": { + "version": "5.54.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.54.1.tgz", + "integrity": "sha512-bjK5t+S6ffHnVwA0qRPTZrxKSaFYocwFIkZx5k7pvWfsB1I57pO/0M0Skatzzw1sCkjJ83AfGTL0oFIFiDX3bg==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.54.1", + "@typescript-eslint/visitor-keys": "5.54.1", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "@typescript-eslint/utils": { + "version": "5.54.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.54.1.tgz", + "integrity": "sha512-IY5dyQM8XD1zfDe5X8jegX6r2EVU5o/WJnLu/znLPWCBF7KNGC+adacXnt5jEYS9JixDcoccI6CvE4RCjHMzCQ==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.54.1", + "@typescript-eslint/types": "5.54.1", + "@typescript-eslint/typescript-estree": "5.54.1", + "eslint-scope": "^5.1.1", + "eslint-utils": "^3.0.0", + "semver": "^7.3.7" + }, + "dependencies": { + "eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + } + } + } + }, + "@typescript-eslint/visitor-keys": { + "version": "5.54.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.54.1.tgz", + "integrity": "sha512-q8iSoHTgwCfgcRJ2l2x+xCbu8nBlRAlsQ33k24Adj8eoVBE0f8dUeI+bAa8F84Mv05UGbAx57g2zrRsYIooqQg==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.54.1", + "eslint-visitor-keys": "^3.3.0" + } + }, "abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", @@ -4352,6 +6164,13 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==" }, + "acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "requires": {} + }, "acorn-node": { "version": "1.8.2", "resolved": "https://registry.npmjs.org/acorn-node/-/acorn-node-1.8.2.tgz", @@ -4406,6 +6225,15 @@ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, "aproba": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", @@ -4440,6 +6268,12 @@ } } }, + "argparse": { + "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 + }, "arr-flatten": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", @@ -4455,6 +6289,12 @@ "resolved": "https://registry.npmjs.org/array-from/-/array-from-2.1.1.tgz", "integrity": "sha512-GQTc6Uupx1FCavi5mPzBvVT7nEOeWMmUA9P95wpfpW1XwMSKs+KaymD5C2Up7KAUKg/mYwbsUYzdZWcoajlNZg==" }, + "array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true + }, "arraybuffer-to-string": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/arraybuffer-to-string/-/arraybuffer-to-string-1.0.2.tgz", @@ -4601,6 +6441,15 @@ "concat-map": "0.0.1" } }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, "brfs": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/brfs/-/brfs-2.0.2.tgz", @@ -4680,11 +6529,27 @@ "get-intrinsic": "^1.0.2" } }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true + }, "caseless": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==" }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, "chownr": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", @@ -4708,6 +6573,21 @@ "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz", "integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==" }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, "color-support": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", @@ -4818,6 +6698,17 @@ "vary": "^1" } }, + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, "cycle": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/cycle/-/cycle-1.0.3.tgz", @@ -4913,6 +6804,24 @@ "streamsearch": "0.1.2" } }, + "dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "requires": { + "path-type": "^4.0.0" + } + }, + "doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, "dotenv": { "version": "16.0.3", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz", @@ -5075,6 +6984,12 @@ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + }, "escodegen": { "version": "1.14.3", "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz", @@ -5087,11 +7002,209 @@ "source-map": "~0.6.1" } }, + "eslint": { + "version": "8.35.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.35.0.tgz", + "integrity": "sha512-BxAf1fVL7w+JLRQhWl2pzGeSiGqbWumV4WNvc9Rhp6tiCtm4oHnyPBSEtMGZwrQgudFQ+otqzWoPB7x+hxoWsw==", + "dev": true, + "requires": { + "@eslint/eslintrc": "^2.0.0", + "@eslint/js": "8.35.0", + "@humanwhocodes/config-array": "^0.11.8", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.1.1", + "eslint-utils": "^3.0.0", + "eslint-visitor-keys": "^3.3.0", + "espree": "^9.4.0", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "grapheme-splitter": "^1.0.4", + "ignore": "^5.2.0", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-sdsl": "^4.1.4", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "regexpp": "^3.2.0", + "strip-ansi": "^6.0.1", + "strip-json-comments": "^3.1.0", + "text-table": "^0.2.0" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, + "requires": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + } + }, + "prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true + }, + "type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1" + } + } + } + }, + "eslint-scope": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", + "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "dependencies": { + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + } + } + }, + "eslint-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^2.0.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true + } + } + }, + "eslint-visitor-keys": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", + "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", + "dev": true + }, + "espree": { + "version": "9.4.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.4.1.tgz", + "integrity": "sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg==", + "dev": true, + "requires": { + "acorn": "^8.8.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.3.0" + }, + "dependencies": { + "acorn": { + "version": "8.8.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", + "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", + "dev": true + } + } + }, "esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" }, + "esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, + "requires": { + "estraverse": "^5.1.0" + }, + "dependencies": { + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + } + } + }, + "esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "requires": { + "estraverse": "^5.2.0" + }, + "dependencies": { + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + } + } + }, "estraverse": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", @@ -5210,6 +7323,30 @@ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, + "fast-glob": { + "version": "3.2.12", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", + "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "dependencies": { + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + } + } + }, "fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", @@ -5220,11 +7357,38 @@ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==" }, + "fastq": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "dev": true, + "requires": { + "reusify": "^1.0.4" + } + }, + "file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "requires": { + "flat-cache": "^3.0.4" + } + }, "file-type": { "version": "10.11.0", "resolved": "https://registry.npmjs.org/file-type/-/file-type-10.11.0.tgz", "integrity": "sha512-uzk64HRpUZyTGZtVuvrjP0FYxzQrBf4rojot6J65YMEbwBLB0CWm0CLojVpwpmFmxcE/lkvYICgfcGozbBq6rw==" }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, "finalhandler": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", @@ -5239,6 +7403,32 @@ "unpipe": "~1.0.0" } }, + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, + "flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "requires": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + } + }, + "flatted": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", + "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", + "dev": true + }, "flatten-vertex-data": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/flatten-vertex-data/-/flatten-vertex-data-1.0.2.tgz", @@ -5388,6 +7578,38 @@ "path-is-absolute": "^1.0.0" } }, + "glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "requires": { + "is-glob": "^4.0.3" + } + }, + "globals": { + "version": "13.20.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", + "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", + "dev": true, + "requires": { + "type-fest": "^0.20.2" + } + }, + "globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "requires": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + } + }, "gopd": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", @@ -5419,6 +7641,12 @@ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" }, + "grapheme-splitter": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", + "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", + "dev": true + }, "har-schema": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", @@ -5441,6 +7669,12 @@ "function-bind": "^1.1.1" } }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, "has-symbols": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", @@ -5537,6 +7771,12 @@ "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==" }, + "ignore": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "dev": true + }, "image-pixels": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/image-pixels/-/image-pixels-1.1.1.tgz", @@ -5578,6 +7818,22 @@ "file-type": "^10.9.0" } }, + "import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true + }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -5644,6 +7900,12 @@ "has": "^1.0.3" } }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true + }, "is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -5657,6 +7919,27 @@ "has-tostringtag": "^1.0.0" } }, + "is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true + }, "is-plain-obj": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", @@ -5694,6 +7977,12 @@ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, "isstream": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", @@ -5721,6 +8010,21 @@ "resolved": "https://registry.npmjs.org/jpeg-js/-/jpeg-js-0.3.7.tgz", "integrity": "sha512-9IXdWudL61npZjvLuVe/ktHiA41iE8qFyLB+4VDTblEsWBzeg8WQTlktdUK4CdncUqtUgUg0bbOmTE2bKBKaBQ==" }, + "js-sdsl": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.3.0.tgz", + "integrity": "sha512-mifzlm2+5nZ+lEcLJMoBK0/IH/bDg8XnJfd/Wq6IP+xoCjLZsTOnV2QpxlVbX9bMnkl5PdEjNtBJ9Cj1NjifhQ==", + "dev": true + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + }, "jsbn": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", @@ -5741,6 +8045,12 @@ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, "json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", @@ -5787,6 +8097,15 @@ "type-check": "~0.3.2" } }, + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "requires": { + "p-locate": "^5.0.0" + } + }, "lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", @@ -5808,11 +8127,6 @@ "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" }, - "lodash.set": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/lodash.set/-/lodash.set-4.3.2.tgz", - "integrity": "sha512-4hNPN5jlm/N/HLMCO43v8BXKq9Z7QdAGc/VGrRD61w8gN9g/6jF9A4L1pbUgBLCffi0w9VsXfTOij5x8iTyFvg==" - }, "lowercase-keys": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", @@ -5880,11 +8194,27 @@ } } }, + "merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true + }, "methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==" }, + "micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "requires": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + } + }, "mii-js": { "version": "git+ssh://git@github.com/PretendoNetwork/mii-js.git#5d8eb8013514a13b0df6eb4a5bfd8b5a63fb9861", "from": "mii-js@github:PretendoNetwork/mii-js", @@ -6005,7 +8335,7 @@ }, "mongoose-unique-validator": { "version": "git+ssh://git@github.com/stenneepro/mongoose-unique-validator.git#1cd976b70ddd8e8e65fed1d9a387677713a8d177", - "from": "mongoose-unique-validator@https://github.com/stenneepro/mongoose-unique-validator.git#bump/mongoose7", + "from": "mongoose-unique-validator@github:stenneepro/mongoose-unique-validator#bump/mongoose7", "requires": { "lodash.foreach": "^4.1.0", "lodash.get": "^4.0.2", @@ -6073,6 +8403,18 @@ "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", "dev": true }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "natural-compare-lite": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", + "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", + "dev": true + }, "negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", @@ -6121,7 +8463,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/node-rsa/-/node-rsa-1.1.1.tgz", "integrity": "sha512-Jd4cvbJMryN21r5HgxQOpMEqv+ooke/korixNNK3mGqfGJmy0M77WDDzo/05969+OkMy3XW1UuZsSmW9KQm7Fw==", - "dev": true, "requires": { "asn1": "^0.2.4" } @@ -6214,6 +8555,33 @@ "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==" }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "requires": { + "p-limit": "^3.0.2" + } + }, + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "requires": { + "callsites": "^3.0.0" + } + }, "parse-rect": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/parse-rect/-/parse-rect-1.2.0.tgz", @@ -6227,11 +8595,23 @@ "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==" }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, "path-parse": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", @@ -6242,6 +8622,12 @@ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" }, + "path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true + }, "performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", @@ -6252,6 +8638,12 @@ "resolved": "https://registry.npmjs.org/pick-by-alias/-/pick-by-alias-1.2.0.tgz", "integrity": "sha512-ESj2+eBxhGrcA1azgHs7lARG5+5iLakc/6nlfbpjcLl00HuuUOIuORhYXN4D1HfvMSKuVtFQjAlnwi1JHEeDIw==" }, + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true + }, "pngjs": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-3.4.0.tgz", @@ -6326,6 +8718,12 @@ "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", "integrity": "sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g==" }, + "queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true + }, "quick-lru": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", @@ -6397,6 +8795,12 @@ "@redis/time-series": "1.0.4" } }, + "regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true + }, "request": { "version": "2.88.2", "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", @@ -6451,6 +8855,12 @@ "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==" }, + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true + }, "responselike": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz", @@ -6464,6 +8874,12 @@ "resolved": "https://registry.npmjs.org/restructure/-/restructure-2.0.1.tgz", "integrity": "sha512-e0dOpjm5DseomnXx2M5lpdZ5zoHqF1+bqdMJUohoYVVQa7cBdnk7fdmeI6byNWP/kiME72EeTiSypTCVnpLiDg==" }, + "reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true + }, "revalidator": { "version": "0.1.8", "resolved": "https://registry.npmjs.org/revalidator/-/revalidator-0.1.8.tgz", @@ -6478,6 +8894,15 @@ "glob": "^7.1.3" } }, + "run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "requires": { + "queue-microtask": "^1.2.2" + } + }, "safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -6577,6 +9002,21 @@ "resolved": "https://registry.npmjs.org/shallow-copy/-/shallow-copy-0.0.1.tgz", "integrity": "sha512-b6i4ZpVuUxB9h5gfCxPiusKYkqTMOjEbBs4wMaFbkfia4yFv92UKZ6Df8WXcKbn08JNL/abvg3FnMAOfakDvUw==" }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, "side-channel": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", @@ -6597,6 +9037,12 @@ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + }, "smart-buffer": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", @@ -6760,6 +9206,21 @@ "ansi-regex": "^5.0.1" } }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, "supports-preserve-symlinks-flag": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", @@ -6778,6 +9239,12 @@ "yallist": "^4.0.0" } }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, "tga": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/tga/-/tga-1.0.7.tgz", @@ -6835,6 +9302,15 @@ "string-to-arraybuffer": "^1.0.0" } }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, "toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", @@ -6857,6 +9333,21 @@ "punycode": "^2.1.1" } }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "requires": { + "tslib": "^1.8.1" + } + }, "tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", @@ -6883,6 +9374,12 @@ "prelude-ls": "~1.1.2" } }, + "type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true + }, "type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", @@ -6897,6 +9394,12 @@ "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==" }, + "typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "dev": true + }, "universalify": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", @@ -6999,6 +9502,15 @@ "webidl-conversions": "^7.0.0" } }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, "which-typed-array": { "version": "1.1.9", "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz", @@ -7107,6 +9619,12 @@ "resolved": "https://registry.npmjs.org/yesno/-/yesno-0.4.0.tgz", "integrity": "sha512-tdBxmHvbXPBKYIg81bMCB7bVeDmHkRzk5rVJyYYXurwKkHq/MCd8rz4HSJUP7hW0H2NlXiq8IFiWvYKEHhlotA==", "dev": true + }, + "yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true } } } diff --git a/package.json b/package.json index b03b646..f97be5c 100644 --- a/package.json +++ b/package.json @@ -40,13 +40,12 @@ "hcaptcha": "^0.1.0", "image-pixels": "^1.1.1", "joi": "^17.8.3", - "lodash.get": "^4.4.2", - "lodash.set": "^4.3.2", "mii-js": "github:PretendoNetwork/mii-js", "moment": "^2.29.4", "mongoose": "^7.0.0", "mongoose-unique-validator": "github:stenneepro/mongoose-unique-validator#bump/mongoose7", "morgan": "^1.9.1", + "node-rsa": "^1.0.7", "nodemailer": "^6.4.2", "redis": "^4.3.1", "tga": "^1.0.4", @@ -55,13 +54,19 @@ "xmlbuilder2": "0.0.4" }, "devDependencies": { - "@types/colors": "^1.2.1", + "@hcaptcha/types": "^1.0.3", + "@types/dicer": "^0.2.2", "@types/express": "^4.17.17", "@types/fs-extra": "^11.0.1", "@types/morgan": "^1.9.4", "@types/node": "^18.14.4", - "node-rsa": "^1.0.7", + "@types/node-rsa": "^1.1.1", + "@types/nodemailer": "^6.4.7", + "@typescript-eslint/eslint-plugin": "^5.54.1", + "@typescript-eslint/parser": "^5.54.1", + "eslint": "^8.35.0", "prompt": "^1.0.0", + "typescript": "^4.9.5", "yesno": "^0.4.0" } } diff --git a/src/cache.ts b/src/cache.ts index 5049b67..7da86d2 100644 --- a/src/cache.ts +++ b/src/cache.ts @@ -2,15 +2,15 @@ import fs from 'fs-extra'; import redis from 'redis'; import { config, disabledFeatures } from '@/config-manager'; -let client; +let client: redis.RedisClientType; -const memoryCache = {}; +const memoryCache: { [key: string]: Buffer } = {}; -const SERVICE_CERTS_BASE = `${__dirname}/../certs/service`; -const NEX_CERTS_BASE = `${__dirname}/../certs/nex`; -const LOCAL_CDN_BASE = `${__dirname}/../cdn`; +const SERVICE_CERTS_BASE: string = `${__dirname}/../certs/service`; +const NEX_CERTS_BASE: string = `${__dirname}/../certs/nex`; +const LOCAL_CDN_BASE: string = `${__dirname}/../cdn`; -export async function connect() { +export async function connect(): Promise { if (!disabledFeatures.redis) { client = redis.createClient(config.redis.client); client.on('error', (err) => console.log('Redis Client Error', err)); @@ -19,7 +19,7 @@ export async function connect() { } } -export async function setCachedFile(fileName, value) { +export async function setCachedFile(fileName: string, value: Buffer): Promise { if (disabledFeatures.redis) { memoryCache[fileName] = value; } else { @@ -27,17 +27,16 @@ export async function setCachedFile(fileName, value) { } } -export async function getCachedFile(fileName, encoding?) { - let cachedFile; +export async function getCachedFile(fileName: string, encoding?: BufferEncoding): Promise { + let cachedFile: Buffer; if (disabledFeatures.redis) { cachedFile = memoryCache[fileName] || null; } else { - cachedFile = await client.get(fileName); - } - - if (cachedFile !== null) { - cachedFile = Buffer.from(cachedFile, encoding); + const redisValue: string = await client.get(fileName); + if (redisValue) { + cachedFile = Buffer.from(redisValue, encoding); + } } return cachedFile; @@ -45,33 +44,33 @@ export async function getCachedFile(fileName, encoding?) { // * NEX server cache functions -export async function getNEXPublicKey(name, encoding?) { - let publicKey = await getCachedFile(`nex:${name}:public_key`, encoding); +export async function getNEXPublicKey(name: string, encoding?: BufferEncoding): Promise { + let publicKey: Buffer = await getCachedFile(`nex:${name}:public_key`, encoding); if (publicKey === null) { - publicKey = await fs.readFile(`${NEX_CERTS_BASE}/${name}/public.pem`, { encoding }); + publicKey = await fs.readFile(`${NEX_CERTS_BASE}/${name}/public.pem`); await setNEXPublicKey(name, publicKey); } return publicKey; } -export async function getNEXPrivateKey(name, encoding?) { - let privateKey = await getCachedFile(`nex:${name}:private_key`, encoding); +export async function getNEXPrivateKey(name: string, encoding?: BufferEncoding): Promise { + let privateKey: Buffer = await getCachedFile(`nex:${name}:private_key`, encoding); if (privateKey === null) { - privateKey = await fs.readFile(`${NEX_CERTS_BASE}/${name}/private.pem`, { encoding }); + privateKey = await fs.readFile(`${NEX_CERTS_BASE}/${name}/private.pem`); await setNEXPrivateKey(name, privateKey); } return privateKey; } -export async function getNEXSecretKey(name, encoding?) { - let secretKey = await getCachedFile(`nex:${name}:secret_key`, encoding); +export async function getNEXSecretKey(name: string, encoding?: BufferEncoding): Promise { + let secretKey: Buffer = await getCachedFile(`nex:${name}:secret_key`, encoding); if (secretKey === null) { - const fileBuffer = await fs.readFile(`${NEX_CERTS_BASE}/${name}/secret.key`, { encoding: 'utf8' }); + const fileBuffer: string = await fs.readFile(`${NEX_CERTS_BASE}/${name}/secret.key`, { encoding: 'utf8' }); secretKey = Buffer.from(fileBuffer, encoding); await setNEXSecretKey(name, secretKey); } @@ -79,11 +78,11 @@ export async function getNEXSecretKey(name, encoding?) { return secretKey; } -export async function getNEXAESKey(name, encoding?) { - let aesKey = await getCachedFile(`nex:${name}:aes_key`, encoding); +export async function getNEXAESKey(name: string, encoding?: BufferEncoding): Promise { + let aesKey: Buffer = await getCachedFile(`nex:${name}:aes_key`, encoding); if (aesKey === null) { - const fileBuffer = await fs.readFile(`${NEX_CERTS_BASE}/${name}/aes.key`, { encoding: 'utf8' }); + const fileBuffer: string = await fs.readFile(`${NEX_CERTS_BASE}/${name}/aes.key`, { encoding: 'utf8' }); aesKey = Buffer.from(fileBuffer, encoding); await setNEXAESKey(name, aesKey); } @@ -91,51 +90,51 @@ export async function getNEXAESKey(name, encoding?) { return aesKey; } -export async function setNEXPublicKey(name, value) { +export async function setNEXPublicKey(name: string, value: Buffer): Promise { await setCachedFile(`nex:${name}:public_key`, value); } -export async function setNEXPrivateKey(name, value) { +export async function setNEXPrivateKey(name: string, value: Buffer): Promise { await setCachedFile(`nex:${name}:private_key`, value); } -export async function setNEXSecretKey(name, value) { +export async function setNEXSecretKey(name: string, value: Buffer): Promise { await setCachedFile(`nex:${name}:secret_key`, value); } -export async function setNEXAESKey(name, value) { +export async function setNEXAESKey(name: string, value: Buffer): Promise { await setCachedFile(`nex:${name}:aes_key`, value); } // * 3rd party service cache functions -export async function getServicePublicKey(name, encoding?) { - let publicKey = await getCachedFile(`service:${name}:public_key`, encoding); +export async function getServicePublicKey(name: string, encoding?: BufferEncoding): Promise { + let publicKey: Buffer = await getCachedFile(`service:${name}:public_key`, encoding); if (publicKey === null) { - publicKey = await fs.readFile(`${SERVICE_CERTS_BASE}/${name}/public.pem`, { encoding }); + publicKey = await fs.readFile(`${SERVICE_CERTS_BASE}/${name}/public.pem`); await setServicePublicKey(name, publicKey); } return publicKey; } -export async function getServicePrivateKey(name, encoding?) { - let privateKey = await getCachedFile(`service:${name}:private_key`, encoding); +export async function getServicePrivateKey(name: string, encoding?: BufferEncoding): Promise { + let privateKey: Buffer = await getCachedFile(`service:${name}:private_key`, encoding); if (privateKey === null) { - privateKey = await fs.readFile(`${SERVICE_CERTS_BASE}/${name}/private.pem`, { encoding }); + privateKey = await fs.readFile(`${SERVICE_CERTS_BASE}/${name}/private.pem`); await setServicePrivateKey(name, privateKey); } return privateKey; } -export async function getServiceSecretKey(name, encoding?) { - let secretKey = await getCachedFile(`service:${name}:secret_key`, encoding); +export async function getServiceSecretKey(name: string, encoding?: BufferEncoding): Promise { + let secretKey: Buffer = await getCachedFile(`service:${name}:secret_key`, encoding); if (secretKey === null) { - const fileBuffer = await fs.readFile(`${SERVICE_CERTS_BASE}/${name}/secret.key`, { encoding: 'utf8' }); + const fileBuffer: string = await fs.readFile(`${SERVICE_CERTS_BASE}/${name}/secret.key`, { encoding: 'utf8' }); secretKey = Buffer.from(fileBuffer, encoding); await setServiceSecretKey(name, secretKey); } @@ -143,11 +142,11 @@ export async function getServiceSecretKey(name, encoding?) { return secretKey; } -export async function getServiceAESKey(name, encoding?) { - let aesKey = await getCachedFile(`service:${name}:aes_key`, encoding); +export async function getServiceAESKey(name: string, encoding?: BufferEncoding): Promise { + let aesKey: Buffer = await getCachedFile(`service:${name}:aes_key`, encoding); if (aesKey === null) { - const fileBuffer = await fs.readFile(`${SERVICE_CERTS_BASE}/${name}/aes.key`, { encoding: 'utf8' }); + const fileBuffer: string = await fs.readFile(`${SERVICE_CERTS_BASE}/${name}/aes.key`, { encoding: 'utf8' }); aesKey = Buffer.from(fileBuffer, encoding); await setServiceAESKey(name, aesKey); } @@ -155,30 +154,31 @@ export async function getServiceAESKey(name, encoding?) { return aesKey; } -export async function setServicePublicKey(name, value) { +export async function setServicePublicKey(name: string, value: Buffer): Promise { await setCachedFile(`service:${name}:public_key`, value); } -export async function setServicePrivateKey(name, value) { +export async function setServicePrivateKey(name: string, value: Buffer): Promise { await setCachedFile(`service:${name}:private_key`, value); } -export async function setServiceSecretKey(name, value) { +export async function setServiceSecretKey(name: string, value: Buffer): Promise { await setCachedFile(`service:${name}:secret_key`, value); } -export async function setServiceAESKey(name, value) { +export async function setServiceAESKey(name: string, value: Buffer): Promise { await setCachedFile(`service:${name}:aes_key`, value); } // * Local CDN cache functions -export async function getLocalCDNFile(name, encoding?) { - let file = await getCachedFile(`local_cdn:${name}`, encoding); +export async function getLocalCDNFile(name: string, encoding?: BufferEncoding): Promise { + let file: Buffer = await getCachedFile(`local_cdn:${name}`, encoding); if (file === null) { if (await fs.pathExists(`${LOCAL_CDN_BASE}/${name}`)) { - file = await fs.readFile(`${LOCAL_CDN_BASE}/${name}`, { encoding }); + const fileBuffer: string = await fs.readFile(`${LOCAL_CDN_BASE}/${name}`, { encoding }); + file = Buffer.from(fileBuffer); await setLocalCDNFile(name, file); } } @@ -186,7 +186,7 @@ export async function getLocalCDNFile(name, encoding?) { return file; } -export async function setLocalCDNFile(name, value) { +export async function setLocalCDNFile(name: string, value: Buffer): Promise { await setCachedFile(`local_cdn:${name}`, value); } diff --git a/src/database.ts b/src/database.ts index dc3a31b..0bd36bb 100644 --- a/src/database.ts +++ b/src/database.ts @@ -6,87 +6,94 @@ import { PNID } from '@/models/pnid'; import { Server } from '@/models/server'; import logger from '@/logger'; import { config } from '@/config-manager'; -import { IPNID, IPNIDMethods } from '@/types/mongoose/pnid'; +import { HydratedPNIDDocument } from '@/types/mongoose/pnid'; +import { HydratedDeviceDocument } from '@/types/mongoose/device'; +import { HydratedServerDocument } from '@/types/mongoose/server'; +import { Token } from '@/types/common/token'; +import { PNIDProfile } from '@/types/services/nnid/pnid-profile'; +import { ConnectionData } from '@/types/services/api/connection-data'; +import { ConnectionResponse } from '@/types/services/api/connection-response'; +import { DiscordConnectionData } from '@/types/services/api/discord-connection-data'; -const { connection_string, options } = config.mongoose; +const connection_string: string = config.mongoose.connection_string; +const options: mongoose.ConnectOptions = config.mongoose.options; // TODO: Extend this later with more settings -const discordConnectionSchema = joi.object({ +const discordConnectionSchema: joi.ObjectSchema = joi.object({ id: joi.string() }); -let _connection; +let _connection: mongoose.Connection; -export async function connect() { +export async function connect(): Promise { await mongoose.connect(connection_string, options); _connection = mongoose.connection; _connection.on('error', console.error.bind(console, 'connection error:')); } -export function connection() { +export function connection(): mongoose.Connection { return _connection; } -function verifyConnected() { +function verifyConnected(): void { if (!connection()) { throw new Error('Cannot make database requets without being connected'); } } -export async function getUserByUsername(username) { +export async function getUserByUsername(username: string): Promise { verifyConnected(); if (typeof username !== 'string') { return null; } - const user = await PNID.findOne({ + return await PNID.findOne({ usernameLower: username.toLowerCase() - }) as mongoose.HydratedDocument; - - return user; + }) as HydratedPNIDDocument; } -export async function getUserByPID(pid) { +export async function getUserByPID(pid: number): Promise { verifyConnected(); - const user = await PNID.findOne({ + return await PNID.findOne({ pid - }) as mongoose.HydratedDocument; - - return user; + }) as HydratedPNIDDocument; } -export async function getUserByEmailAddress(email) { +export async function getUserByEmailAddress(email: string): Promise { verifyConnected(); - const user = await PNID.findOne({ + return await PNID.findOne({ 'email.address': new RegExp(email, 'i') // * Ignore case - }) as mongoose.HydratedDocument; - - return user; + }) as HydratedPNIDDocument; } -export async function doesUserExist(username) { +export async function doesUserExist(username: string): Promise { verifyConnected(); return !!await getUserByUsername(username); } -export async function getUserBasic(token) { +export async function getUserBasic(token: string): Promise { verifyConnected(); // * Wii U sends Basic auth as `username password`, where the password may not have spaces // * This is not to spec, but that is the consoles fault not ours - const [username, password] = Buffer.from(token, 'base64').toString().split(' '); - const user = await getUserByUsername(username); + const decoded: string = Buffer.from(token, 'base64').toString(); + const parts: string[] = decoded.split(' '); + + const username: string = parts[0]; + const password: string = parts[1]; + + const user: HydratedPNIDDocument = await getUserByUsername(username); if (!user) { return null; } - const hashedPassword = util.nintendoPasswordHash(password, user.pid); + const hashedPassword: string = util.nintendoPasswordHash(password, user.pid); if (!bcrypt.compareSync(hashedPassword, user.password)) { return null; @@ -95,17 +102,17 @@ export async function getUserBasic(token) { return user; } -export async function getUserBearer(token) { +export async function getUserBearer(token: string): Promise { verifyConnected(); try { - const decryptedToken = await util.decryptToken(Buffer.from(token, 'base64')); - const unpackedToken = util.unpackToken(decryptedToken); + const decryptedToken: Buffer = await util.decryptToken(Buffer.from(token, 'base64')); + const unpackedToken: Token = util.unpackToken(decryptedToken); - const user = await getUserByPID(unpackedToken.pid); + const user: HydratedPNIDDocument = await getUserByPID(unpackedToken.pid); if (user) { - const expireTime = Math.floor((Number(unpackedToken.expire_time) / 1000)); + const expireTime: number = Math.floor((Number(unpackedToken.expire_time) / 1000)); if (Math.floor(Date.now() / 1000) > expireTime) { return null; @@ -113,40 +120,38 @@ export async function getUserBearer(token) { } return user; - } catch (error) { + } catch (error: any) { // TODO: Handle error logger.error(error); return null; } } -export async function getUserProfileJSONByPID(pid) { +export async function getUserProfileJSONByPID(pid: number): Promise { verifyConnected(); - const user = await getUserByPID(pid); - const device = user.get('devices')[0]; // * Just grab the first device - let device_attributes; + const user: HydratedPNIDDocument = await getUserByPID(pid); + const device: HydratedDeviceDocument = user.get('devices')[0]; // * Just grab the first device + let device_attributes: [{ + device_attribute: { + name: string; + value: string; + created_date: string; + }; + }]; if (device) { - device_attributes = device.get('device_attributes').map(({name, value, created_date}) => { - const deviceAttributeDocument = { + device_attributes = device.get('device_attributes').map(({name, value, created_date}) => ({ + device_attribute: { name, value, - created_date: '' - }; - - if (created_date) { - deviceAttributeDocument.created_date = created_date; + created_date: created_date ? created_date : '' } - - return { - device_attribute: deviceAttributeDocument - }; - }); + })); } - const userObject = { - //accounts: {}, We need to figure this out, no idea what these values mean or what they do + return { + //accounts: {}, // * We need to figure this out, no idea what these values mean or what they do active_flag: user.get('flags.active') ? 'Y' : 'N', birth_date: user.get('birthdate'), country: user.get('country'), @@ -167,7 +172,7 @@ export async function getUserProfileJSONByPID(pid) { type: 'DEFAULT', updated_by: 'USER', // * Can also be INTERNAL WS, don't know the difference validated: user.get('email.validated') ? 'Y' : 'N', - validated_date: '' + validated_date: user.get('email.validated') ? user.get('email.validated_date') : '' }, mii: { status: 'COMPLETED', @@ -192,39 +197,33 @@ export async function getUserProfileJSONByPID(pid) { user_id: user.get('username'), utc_offset: user.get('timezone.offset') }; - - if (user.get('email.validated')) { - userObject.email.validated_date = user.get('email.validated_date'); - } - - return userObject; } -function getServer(gameServerId, accessMode) { - return Server.findOne({ +export async function getServer(gameServerId: string, accessMode: string): Promise { + return await Server.findOne({ game_server_id: gameServerId, - access_mode: accessMode, + access_mode: accessMode }); } -function getServerByTitleId(titleId, accessMode) { - return Server.findOne({ +export async function getServerByTitleId(titleId: string, accessMode: string): Promise { + return await Server.findOne({ title_ids: titleId, - access_mode: accessMode, + access_mode: accessMode }); } -export async function addUserConnection(pnid, data, type) { +export async function addUserConnection(pnid: HydratedPNIDDocument, data: ConnectionData, type: string): Promise { if (type === 'discord') { return await addUserConnectionDiscord(pnid, data); } } -export async function addUserConnectionDiscord(pnid, data) { - const valid = discordConnectionSchema.validate(data); +export async function addUserConnectionDiscord(pnid: HydratedPNIDDocument, data: DiscordConnectionData): Promise { + const valid: joi.ValidationResult = discordConnectionSchema.validate(data); if (valid.error) { - return { + return { app: 'api', status: 400, error: 'Invalid or missing connection data' @@ -237,27 +236,27 @@ export async function addUserConnectionDiscord(pnid, data) { } }); - return { + return { app: 'api', status: 200 }; } -export async function removeUserConnection(pnid, type) { +export async function removeUserConnection(pnid: HydratedPNIDDocument, type: string): Promise { // * Add more connections later? if (type === 'discord') { return await removeUserConnectionDiscord(pnid); } } -export async function removeUserConnectionDiscord(pnid) { +export async function removeUserConnectionDiscord(pnid: HydratedPNIDDocument): Promise { await PNID.updateOne({ pid: pnid.get('pid') }, { $set: { 'connections.discord.id': '' } }); - return { + return { app: 'api', status: 200 }; diff --git a/src/logger.ts b/src/logger.ts index 6d9cacf..64435a2 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -3,10 +3,10 @@ import colors from 'colors'; colors.enable(); -const root = process.env.PN_ACT_LOGGER_PATH ? process.env.PN_ACT_LOGGER_PATH : `${__dirname}/..`; +const root: string = process.env.PN_ACT_LOGGER_PATH ? process.env.PN_ACT_LOGGER_PATH : `${__dirname}/..`; fs.ensureDirSync(`${root}/logs`); -const streams = { +const streams: { [key: string]: fs.WriteStream } = { latest: fs.createWriteStream(`${root}/logs/latest.log`), success: fs.createWriteStream(`${root}/logs/success.log`), error: fs.createWriteStream(`${root}/logs/error.log`), @@ -14,32 +14,32 @@ const streams = { info: fs.createWriteStream(`${root}/logs/info.log`) }; -function success(input) { - const time = new Date(); +function success(input: string): void { + const time: Date = new Date(); input = `[${time.getHours()}:${time.getMinutes()}:${time.getSeconds()}] [SUCCESS]: ${input}`; streams.success.write(`${input}\n`); console.log(`${input}`.green.bold); } -function error(input) { - const time = new Date(); +function error(input: string): void { + const time: Date = new Date(); input = `[${time.getHours()}:${time.getMinutes()}:${time.getSeconds()}] [ERROR]: ${input}`; streams.error.write(`${input}\n`); console.log(`${input}`.red.bold); } -function warn(input) { - const time = new Date(); +function warn(input: string): void { + const time: Date = new Date(); input = `[${time.getHours()}:${time.getMinutes()}:${time.getSeconds()}] [WARN]: ${input}`; streams.warn.write(`${input}\n`); console.log(`${input}`.yellow.bold); } -function info(input) { - const time = new Date(); +function info(input: string): void { + const time: Date = new Date(); input = `[${time.getHours()}:${time.getMinutes()}:${time.getSeconds()}] [INFO]: ${input}`; streams.info.write(`${input}\n`); diff --git a/src/mailer.ts b/src/mailer.ts index c39d039..fd39b85 100644 --- a/src/mailer.ts +++ b/src/mailer.ts @@ -2,21 +2,22 @@ import path from 'node:path'; import fs from 'node:fs'; import nodemailer from 'nodemailer'; import { config, disabledFeatures } from '@/config-manager'; +import { MailerOptions } from '@/types/common/mailer-options'; -const genericEmailTemplate = fs.readFileSync(path.join(__dirname, './assets/emails/genericTemplate.html'), 'utf8'); -const confirmationEmailTemplate = fs.readFileSync(path.join(__dirname, './assets/emails/confirmationTemplate.html'), 'utf8'); +const genericEmailTemplate: string = fs.readFileSync(path.join(__dirname, './assets/emails/genericTemplate.html'), 'utf8'); +const confirmationEmailTemplate: string = fs.readFileSync(path.join(__dirname, './assets/emails/confirmationTemplate.html'), 'utf8'); -let transporter; +let transporter: nodemailer.Transporter; if (!disabledFeatures.email) { transporter = nodemailer.createTransport(config.email); } -export async function sendMail(options) { +export async function sendMail(options: MailerOptions): Promise { if (!disabledFeatures.email) { const { to, subject, username, paragraph, preview, text, link, confirmation } = options; - let html = confirmation ? confirmationEmailTemplate : genericEmailTemplate; + let html: string = confirmation ? confirmationEmailTemplate : genericEmailTemplate; html = html.replace(/{{username}}/g, username); html = html.replace(/{{paragraph}}/g, paragraph); @@ -27,7 +28,7 @@ export async function sendMail(options) { if (link) { const { href, text } = link; - const button = ` ${text}` + const button: string = ` ${text}` html = html.replace(//g, button); } diff --git a/src/middleware/api.ts b/src/middleware/api.ts index cbf942d..67edf9d 100644 --- a/src/middleware/api.ts +++ b/src/middleware/api.ts @@ -1,15 +1,16 @@ -import xmlbuilder from 'xmlbuilder'; +import express from 'express'; import database from '@/database'; +import { HydratedPNIDDocument } from '@/types/mongoose/pnid'; -export async function APIMiddleware(request, _response, next) { - const { headers } = request; +export async function APIMiddleware(request: express.Request, _response: express.Response, next: express.NextFunction): Promise { + const authHeader: string = request.headers.authorization; - if (!headers.authorization || !(headers.authorization.startsWith('Bearer'))) { + if (!authHeader || !(authHeader.startsWith('Bearer'))) { return next(); } - const token = headers.authorization.split(' ')[1]; - const user = await database.getUserBearer(token); + const token: string = authHeader.split(' ')[1]; + const user: HydratedPNIDDocument = await database.getUserBearer(token); request.pnid = user; diff --git a/src/middleware/cemu.ts b/src/middleware/cemu.ts index 22f3e73..029e861 100644 --- a/src/middleware/cemu.ts +++ b/src/middleware/cemu.ts @@ -1,5 +1,7 @@ -export async function CemuMiddleware(request, _response, next) { - const subdomain = request.subdomains.reverse().join('.'); +import express from 'express'; + +export function CemuMiddleware(request: express.Request, _response: express.Response, next: express.NextFunction): void { + const subdomain: string = request.subdomains.reverse().join('.'); request.isCemu = subdomain === 'c.account'; diff --git a/src/middleware/client-header.ts b/src/middleware/client-header.ts index 7f7a581..1353c89 100644 --- a/src/middleware/client-header.ts +++ b/src/middleware/client-header.ts @@ -1,6 +1,7 @@ +import express from 'express'; import xmlbuilder from 'xmlbuilder'; -const VALID_CLIENT_ID_SECRET_PAIRS = { +const VALID_CLIENT_ID_SECRET_PAIRS: { [key: string]: string } = { // * 'Key' is the client ID, 'Value' is the client secret 'a2efa818a34fa16b8afbc8a74eba3eda': 'c91cdb5658bd4954ade78533a339cf9a', // * Possibly WiiU exclusive? 'daf6227853bcbdce3d75baee8332b': '3eff548eac636e2bf45bb7b375e7b6b0', // * Possibly 3DS exclusive? @@ -8,20 +9,21 @@ const VALID_CLIENT_ID_SECRET_PAIRS = { }; -export function nintendoClientHeaderCheck(request, response, next) { +export function nintendoClientHeaderCheck(request: express.Request, response: express.Response, next: express.NextFunction): void { response.set('Content-Type', 'text/xml'); response.set('Server', 'Nintendo 3DS (http)'); - response.set('X-Nintendo-Date', new Date().getTime()); + response.set('X-Nintendo-Date', new Date().getTime().toString()); - const {headers} = request; + const clientId: string = request.headers['x-nintendo-client-id'] as string; + const clientSecret: string = request.headers['x-nintendo-client-secret'] as string; if ( - !headers['x-nintendo-client-id'] || - !headers['x-nintendo-client-secret'] || - !VALID_CLIENT_ID_SECRET_PAIRS[headers['x-nintendo-client-id']] || - headers['x-nintendo-client-secret'] !== VALID_CLIENT_ID_SECRET_PAIRS[headers['x-nintendo-client-id']] + !clientId || + !clientSecret || + !VALID_CLIENT_ID_SECRET_PAIRS[clientId] || + clientSecret !== VALID_CLIENT_ID_SECRET_PAIRS[clientId] ) { - return response.send(xmlbuilder.create({ + response.send(xmlbuilder.create({ errors: { error: { cause: 'client_id', @@ -30,6 +32,8 @@ export function nintendoClientHeaderCheck(request, response, next) { } } }).end()); + + return; } return next(); diff --git a/src/middleware/device-certificate.ts b/src/middleware/device-certificate.ts index dc4dcfa..7cc95a7 100644 --- a/src/middleware/device-certificate.ts +++ b/src/middleware/device-certificate.ts @@ -1,13 +1,13 @@ +import express from 'express'; import NintendoCertificate from '@/nintendo-certificate'; -export async function deviceCertificateMiddleware(request, _response, next) { - const { headers } = request; +export function deviceCertificateMiddleware(request: express.Request, _response: express.Response, next: express.NextFunction): void { + const certificate: string = request.headers['x-nintendo-device-cert'] as string; - if (!headers['x-nintendo-device-cert']) { + if (!certificate) { return next(); } - const certificate = headers['x-nintendo-device-cert']; request.certificate = new NintendoCertificate(certificate); return next(); diff --git a/src/middleware/nasc.ts b/src/middleware/nasc.ts index b718024..c3e5da6 100644 --- a/src/middleware/nasc.ts +++ b/src/middleware/nasc.ts @@ -1,15 +1,18 @@ import crypto from 'node:crypto'; -import { HydratedDocument } from 'mongoose'; +import express from 'express'; +import mongoose from 'mongoose'; import { Device } from '@/models/device'; import { NEXAccount } from '@/models/nex-account'; import util from '@/util'; import database from '@/database'; import NintendoCertificate from '@/nintendo-certificate'; import logger from '@/logger'; -import { INEXAccount, INEXAccountMethods } from '@/types/mongoose/nex-account'; +import { NASCRequestParams } from '@/types/services/nasc/request-params'; +import { HydratedNEXAccountDocument } from '@/types/mongoose/nex-account'; +import { HydratedDeviceDocument } from '@/types/mongoose/device'; -export async function NASCMiddleware(request, response, next) { - const requestParams = request.body; +export async function NASCMiddleware(request: express.Request, response: express.Response, next: express.NextFunction): Promise { + const requestParams: NASCRequestParams = request.body; if (!requestParams.action || !requestParams.fcdcert || @@ -18,25 +21,26 @@ export async function NASCMiddleware(request, response, next) { !requestParams.titleid || !requestParams.servertype ) { - return response.status(200).send(util.nascError('null')); // This is what Nintendo sends + response.status(200).send(util.nascError('null')); // This is what Nintendo sends + return; } - const action = util.nintendoBase64Decode(requestParams.action).toString(); - const fcdcert = util.nintendoBase64Decode(requestParams.fcdcert); - const serialNumber = util.nintendoBase64Decode(requestParams.csnum).toString(); - const macAddress = util.nintendoBase64Decode(requestParams.macadr).toString(); - const titleID = util.nintendoBase64Decode(requestParams.titleid).toString(); - const environment = util.nintendoBase64Decode(requestParams.servertype).toString(); + const action: string = util.nintendoBase64Decode(requestParams.action).toString(); + const fcdcert: Buffer = util.nintendoBase64Decode(requestParams.fcdcert); + const serialNumber: string = util.nintendoBase64Decode(requestParams.csnum).toString(); + const macAddress: string = util.nintendoBase64Decode(requestParams.macadr).toString(); + const titleID: string = util.nintendoBase64Decode(requestParams.titleid).toString(); + const environment: string = util.nintendoBase64Decode(requestParams.servertype).toString(); - const macAddressHash = crypto.createHash('sha256').update(macAddress).digest('base64'); - const fcdcertHash = crypto.createHash('sha256').update(fcdcert).digest('base64'); + const macAddressHash: string = crypto.createHash('sha256').update(macAddress).digest('base64'); + const fcdcertHash: string = crypto.createHash('sha256').update(fcdcert).digest('base64'); - let pid; - let pidHmac; - let password; + let pid: number; + let pidHmac: string; + let password: string; if (requestParams.userid) { - pid = util.nintendoBase64Decode(requestParams.userid).toString(); + pid = Number(util.nintendoBase64Decode(requestParams.userid).toString()); } if (requestParams.uidhmac) { @@ -48,20 +52,23 @@ export async function NASCMiddleware(request, response, next) { } if (action !== 'LOGIN' && action !== 'SVCLOC') { - return response.status(200).send(util.nascError('null')); // This is what Nintendo sends + response.status(200).send(util.nascError('null')); // This is what Nintendo sends + return; } - const cert = new NintendoCertificate(fcdcert); + const cert: NintendoCertificate = new NintendoCertificate(fcdcert); if (!cert.valid) { - return response.status(200).send(util.nascError('121')); + response.status(200).send(util.nascError('121')); + return; } if (!validNintendoMACAddress(macAddress)) { - return response.status(200).send(util.nascError('null')); + response.status(200).send(util.nascError('null')); + return; } - let model; + let model: string; switch (serialNumber[0]) { case 'C': model = 'ctr'; @@ -84,10 +91,11 @@ export async function NASCMiddleware(request, response, next) { } if (!model) { - return response.status(200).send(util.nascError('null')); + response.status(200).send(util.nascError('null')); + return; } - let device = await Device.findOne({ + let device: HydratedDeviceDocument = await Device.findOne({ model, serial: serialNumber, environment, @@ -97,14 +105,16 @@ export async function NASCMiddleware(request, response, next) { if (device) { if (device.get('access_level') < 0) { - return response.status(200).send(util.nascError('102')); + response.status(200).send(util.nascError('102')); + return; } if (pid) { const linkedPIDs = device.get('linked_pids'); if (!linkedPIDs.includes(pid)) { - return response.status(200).send(util.nascError('102')); + response.status(200).send(util.nascError('102')); + return; } } } @@ -113,12 +123,12 @@ export async function NASCMiddleware(request, response, next) { if (password && !pid && !pidHmac) { // Register new user - const session = await database.connection().startSession(); + const session: mongoose.ClientSession = await database.connection().startSession(); await session.startTransaction(); try { // Create new NEX account - const nexAccount = await new NEXAccount({ + const nexAccount: HydratedNEXAccountDocument = await new NEXAccount({ device_type: '3ds', password }); @@ -127,7 +137,7 @@ export async function NASCMiddleware(request, response, next) { await nexAccount.save({ session }); - pid = nexAccount.get('pid'); + pid = Number(nexAccount.get('pid')); // Set password @@ -154,7 +164,8 @@ export async function NASCMiddleware(request, response, next) { await session.abortTransaction(); // 3DS expects 200 even on error - return response.status(200).send(util.nascError('102')); + response.status(200).send(util.nascError('102')); + return; } finally { // * This runs regardless of failure // * Returning on catch will not prevent this from running @@ -163,10 +174,11 @@ export async function NASCMiddleware(request, response, next) { } } - const nexUser = await NEXAccount.findOne({ pid }) as HydratedDocument; + const nexUser: HydratedNEXAccountDocument = await NEXAccount.findOne({ pid }); if (!nexUser || nexUser.get('access_level') < 0) { - return response.status(200).send(util.nascError('102')); + response.status(200).send(util.nascError('102')); + return; } request.nexUser = nexUser; @@ -176,7 +188,7 @@ export async function NASCMiddleware(request, response, next) { // https://www.adminsub.net/mac-address-finder/nintendo // Saves us from doing an OUI lookup each time -const NINTENDO_VENDER_OUIS = [ +const NINTENDO_VENDER_OUIS: string[] = [ 'ECC40D', 'E84ECE', 'E0F6B5', 'E0E751', 'E00C7F', 'DC68EB', 'D86BF7', 'D4F057', 'CCFB65', 'CC9E00', 'B8AE6E', 'B88AEC', 'B87826', 'A4C0E1', 'A45C27', 'A438CC', '9CE635', '98E8FA', @@ -202,10 +214,10 @@ const NINTENDO_VENDER_OUIS = [ ]; // TODO: Make something better -const MAC_REGEX = /^[0-9a-fA-F]{12}$/; +const MAC_REGEX: RegExp = /^[0-9a-fA-F]{12}$/; // Maybe should later parse more data out -function validNintendoMACAddress(macAddress) { +function validNintendoMACAddress(macAddress: string): boolean { if (!NINTENDO_VENDER_OUIS.includes(macAddress.substring(0, 6).toUpperCase())) { return false; } diff --git a/src/middleware/pnid.ts b/src/middleware/pnid.ts index 604b7de..0f8b323 100644 --- a/src/middleware/pnid.ts +++ b/src/middleware/pnid.ts @@ -1,15 +1,19 @@ +import express from 'express'; import xmlbuilder from 'xmlbuilder'; import database from '@/database'; +import { HydratedPNIDDocument } from '@/types/mongoose/pnid'; -export async function PNIDMiddleware(request, response, next) { - const { headers } = request; +export async function PNIDMiddleware(request: express.Request, response: express.Response, next: express.NextFunction): Promise { + const authHeader: string = request.headers.authorization; - if (!headers.authorization || !(headers.authorization.startsWith('Bearer') || headers.authorization.startsWith('Basic'))) { + if (!authHeader || !(authHeader.startsWith('Bearer') || authHeader.startsWith('Basic'))) { return next(); } - let [type, token] = headers.authorization.split(' '); - let user; + const parts: string[] = authHeader.split(' '); + const type: string = parts[0]; + let token: string = parts[1]; + let user: HydratedPNIDDocument; if (request.isCemu) { token = Buffer.from(token, 'hex').toString('base64'); @@ -25,7 +29,7 @@ export async function PNIDMiddleware(request, response, next) { response.status(401); if (type === 'Bearer') { - return response.send(xmlbuilder.create({ + response.send(xmlbuilder.create({ errors: { error: { cause: 'access_token', @@ -34,9 +38,11 @@ export async function PNIDMiddleware(request, response, next) { } } }).end()); + + return; } - return response.send(xmlbuilder.create({ + response.send(xmlbuilder.create({ errors: { error: { code: '1105', @@ -44,10 +50,12 @@ export async function PNIDMiddleware(request, response, next) { } } }).end()); + + return; } if (user.get('access_level') < 0) { - return response.status(400).send(xmlbuilder.create({ + response.status(400).send(xmlbuilder.create({ errors: { error: { code: '0122', @@ -55,6 +63,8 @@ export async function PNIDMiddleware(request, response, next) { } } }).end()); + + return; } request.pnid = user; diff --git a/src/middleware/ratelimit.ts b/src/middleware/ratelimit.ts index ef213a5..e6909f2 100644 --- a/src/middleware/ratelimit.ts +++ b/src/middleware/ratelimit.ts @@ -1,11 +1,12 @@ import crypto from 'node:crypto'; +import express from 'express'; import ratelimit from 'express-rate-limit'; export default ratelimit({ windowMs: 60 * 1000, max: 1, - keyGenerator: request => { - const data = request.headers['x-nintendo-device-cert']; + keyGenerator: (request: express.Request) => { + const data: string = request.headers['x-nintendo-device-cert'] as string; return crypto.createHash('md5').update(data).digest('hex'); } }); \ No newline at end of file diff --git a/src/middleware/xml-parser.ts b/src/middleware/xml-parser.ts index b6cc5e2..f89b7f5 100644 --- a/src/middleware/xml-parser.ts +++ b/src/middleware/xml-parser.ts @@ -1,27 +1,29 @@ +import express from 'express'; import xmlbuilder from 'xmlbuilder'; import { document as xmlParser } from 'xmlbuilder2'; -export function XMLMiddleware(request, response, next) { +export function XMLMiddleware(request: express.Request, response: express.Response, next: express.NextFunction): void { if (request.method == 'POST' || request.method == 'PUT') { - const headers = request.headers; - let body = ''; + const contentType: string = request.headers['content-type']; + const contentLength: string = request.headers['content-length']; + let body: string = ''; if ( - !headers['content-type'] || - !headers['content-type'].toLowerCase().includes('xml') + !contentType || + !contentType.toLowerCase().includes('xml') ) { return next(); } if ( - !headers['content-length'] || - parseInt(headers['content-length']) === 0 + !contentLength || + parseInt(contentLength) === 0 ) { return next(); } request.setEncoding('utf-8'); - request.on('data', (chunk) => { + request.on('data', (chunk: string) => { body += chunk; }); diff --git a/src/nintendo-certificate.ts b/src/nintendo-certificate.ts index 5a35e85..07b93f8 100644 --- a/src/nintendo-certificate.ts +++ b/src/nintendo-certificate.ts @@ -1,5 +1,6 @@ import crypto from 'node:crypto'; import NodeRSA from 'node-rsa'; +import { SignatureSize } from '@/types/common/signature-size'; const WIIU_DEVICE_PUB_PEM = `-----BEGIN PUBLIC KEY----- MFIwEAYHKoZIzj0CAQYFK4EEABsDPgAEAP1WBBgs8XUJIQDDCK5IOZEbb5+h1TqV @@ -40,29 +41,31 @@ const CTR_LFCS_B_PUB = Buffer.from([ 0xAF, 0x07, 0xEB, 0x9C, 0xBF, 0xA9, 0xC9 ]); + + // Signature options -const SIGNATURE_SIZES = { - RSA_4096_SHA1: { +const SIGNATURE_SIZES: { [key: string]: SignatureSize } = { + RSA_4096_SHA1: { SIZE: 0x200, PADDING_SIZE: 0x3C }, - RSA_2048_SHA1: { + RSA_2048_SHA1: { SIZE: 0x100, PADDING_SIZE: 0x3C }, - ELLIPTIC_CURVE_SHA1: { + ELLIPTIC_CURVE_SHA1: { SIZE: 0x3C, PADDING_SIZE: 0x40 }, - RSA_4096_SHA256: { + RSA_4096_SHA256: { SIZE: 0x200, PADDING_SIZE: 0x3C }, - RSA_2048_SHA256: { + RSA_2048_SHA256: { SIZE: 0x100, PADDING_SIZE: 0x3C }, - ECDSA_SHA256: { + ECDSA_SHA256: { SIZE: 0x3C, PADDING_SIZE: 0x40 } @@ -81,7 +84,7 @@ export class NintendoCertificate { valid: boolean; publicKeyData: Buffer; - constructor(certificate) { + constructor(certificate: string | Buffer) { this._certificate = null; this._certificateBody = null; this.signatureType = null; @@ -116,7 +119,7 @@ export class NintendoCertificate { // Assume regular certificate this.signatureType = this._certificate.readUInt32BE(0x00); - const signatureTypeSizes = this._signatureTypeSizes(this.signatureType); + const signatureTypeSizes: SignatureSize = this._signatureTypeSizes(this.signatureType); this._certificateBody = this._certificate.subarray(0x4 + signatureTypeSizes.SIZE + signatureTypeSizes.PADDING_SIZE); @@ -129,7 +132,7 @@ export class NintendoCertificate { } } - _signatureTypeSizes(signatureType) { + _signatureTypeSizes(signatureType: number): SignatureSize { switch (signatureType) { case 0x10000: return SIGNATURE_SIZES.RSA_4096_SHA1; @@ -168,7 +171,7 @@ export class NintendoCertificate { } _verifySignatureRSA4096() { - const publicKey = new NodeRSA(); + const publicKey: NodeRSA = new NodeRSA(); publicKey.importKey({ n: this.publicKeyData.subarray(0x0, 0x200), @@ -179,7 +182,7 @@ export class NintendoCertificate { } _verifySignatureRSA2048() { - const publicKey = new NodeRSA(); + const publicKey: NodeRSA = new NodeRSA(); publicKey.importKey({ n: this.publicKeyData.subarray(0x0, 0x100), @@ -194,8 +197,8 @@ export class NintendoCertificate { // from bytes to PEM! // https://github.com/Myriachan _verifySignatureECDSA() { - const pem = this.issuer == 'Root-CA00000003-MS00000012' ? WIIU_DEVICE_PUB_PEM : CTR_DEVICE_PUB_PEM; - const key = { + const pem: string = this.issuer == 'Root-CA00000003-MS00000012' ? WIIU_DEVICE_PUB_PEM : CTR_DEVICE_PUB_PEM; + const key: crypto.VerifyPublicKeyInput = { key: pem, dsaEncoding: 'ieee-p1363' as crypto.DSAEncoding }; @@ -204,7 +207,7 @@ export class NintendoCertificate { } _verifySignatureLFCS() { - const publicKey = new NodeRSA(); + const publicKey: NodeRSA = new NodeRSA(); publicKey.importKey({ n: CTR_LFCS_B_PUB, diff --git a/src/server.ts b/src/server.ts index 3869de0..1d3710f 100644 --- a/src/server.ts +++ b/src/server.ts @@ -1,8 +1,8 @@ process.title = 'Pretendo - Account'; - -const configManager = require('@config-manager'); - -configManager.configure(); +process.on('uncaughtException', (err, origin) => { + console.log(err); + console.log(origin); +}); import express from 'express'; import morgan from 'morgan'; @@ -20,7 +20,7 @@ import api from '@/services/api'; import localcdn from '@/services/local-cdn'; import assets from '@/services/assets'; -const { config } = configManager; +import { config } from '@/config-manager'; const { http: { port } } = config; const app = express(); @@ -47,9 +47,9 @@ app.use(assets); // 404 handler logger.info('Creating 404 status handler'); -app.use((request, response) => { - const fullUrl = util.fullUrl(request); - const deviceId = request.headers['X-Nintendo-Device-ID'] || 'Unknown'; +app.use((request: express.Request, response: express.Response) => { + const fullUrl: string = util.fullUrl(request); + const deviceId: string = request.headers['X-Nintendo-Device-ID'] as string || 'Unknown'; logger.warn(`HTTP 404 at ${fullUrl} from ${deviceId}`); @@ -63,10 +63,10 @@ app.use((request, response) => { // non-404 error handler logger.info('Creating non-404 status handler'); -app.use((error, request, response, _next) => { - const status = error.status || 500; - const fullUrl = util.fullUrl(request); - const deviceId = request.headers['X-Nintendo-Device-ID'] || 'Unknown'; +app.use((error: any, request: express.Request, response: express.Response, _next: express.NextFunction) => { + const status: number = error.status || 500; + const fullUrl: string = util.fullUrl(request); + const deviceId: string = request.headers['X-Nintendo-Device-ID'] as string || 'Unknown'; logger.warn(`HTTP ${status} at ${fullUrl} from ${deviceId}: ${error.message}`); @@ -78,7 +78,7 @@ app.use((error, request, response, _next) => { }); }); -async function main() { +async function main(): Promise { // Starts the server logger.info('Starting server'); diff --git a/src/services/api/index.ts b/src/services/api/index.ts index 0d8a952..df383af 100644 --- a/src/services/api/index.ts +++ b/src/services/api/index.ts @@ -8,7 +8,7 @@ import routes from '@/services/api/routes'; import logger from '@/logger'; // Router to handle the subdomain restriction -const api = express.Router(); +const api: express.Router = express.Router(); logger.info('[USER API] Importing middleware'); api.use(APIMiddleware); @@ -27,7 +27,7 @@ api.use('/v1/user', routes.V1.USER); // Main router for endpoints -const router = express.Router(); +const router: express.Router = express.Router(); // Create subdomains logger.info('[USER API] Creating \'api\' subdomain'); diff --git a/src/services/api/routes/v1/connections.ts b/src/services/api/routes/v1/connections.ts index 4ec11c5..1a307e6 100644 --- a/src/services/api/routes/v1/connections.ts +++ b/src/services/api/routes/v1/connections.ts @@ -1,9 +1,12 @@ -import { Router } from 'express'; +import express from 'express'; import database from '@/database'; +import { ConnectionData } from '@/types/services/api/connection-data'; +import { ConnectionResponse } from '@/types/services/api/connection-response'; +import { HydratedPNIDDocument } from '@/types/mongoose/pnid'; -const router = Router(); +const router: express.Router = express.Router(); -const VALID_CONNECTION_TYPES = [ +const VALID_CONNECTION_TYPES: string[] = [ 'discord' ]; @@ -12,10 +15,10 @@ const VALID_CONNECTION_TYPES = [ * Implementation of for: https://api.pretendo.cc/v1/connections/add/TYPE * Description: Adds an account connection to the users PNID */ -router.post('/add/:type', async (request, response) => { - const { body, pnid } = request; - const { type } = request.params; - const { data } = body; +router.post('/add/:type', async (request: express.Request, response: express.Response) => { + const data: ConnectionData = request.body?.data; + const pnid: HydratedPNIDDocument = request.pnid; + const type: string = request.params.type; if (!pnid) { return response.status(400).json({ @@ -41,7 +44,7 @@ router.post('/add/:type', async (request, response) => { }); } - const result = await database.addUserConnection(pnid, data, type); + const result: ConnectionResponse = await database.addUserConnection(pnid, data, type); response.status(result.status).json(result); }); @@ -51,9 +54,9 @@ router.post('/add/:type', async (request, response) => { * Implementation of for: https://api.pretendo.cc/v1/connections/remove/TYPE * Description: Removes an account connection from the users PNID */ -router.delete('/remove/:type', async (request, response) => { - const { pnid } = request; - const { type } = request.params; +router.delete('/remove/:type', async (request: express.Request, response: express.Response) => { + const pnid: HydratedPNIDDocument = request.pnid; + const type: string = request.params.type; if (!pnid) { return response.status(400).json({ @@ -71,7 +74,7 @@ router.delete('/remove/:type', async (request, response) => { }); } - const result = await database.removeUserConnection(pnid, type); + const result: ConnectionResponse = await database.removeUserConnection(pnid, type); response.status(result.status).json(result); }); diff --git a/src/services/api/routes/v1/email.ts b/src/services/api/routes/v1/email.ts index 1f28f18..308bc84 100644 --- a/src/services/api/routes/v1/email.ts +++ b/src/services/api/routes/v1/email.ts @@ -1,18 +1,13 @@ -import { Router } from 'express'; +import express from 'express'; import moment from 'moment'; import { PNID } from '@/models/pnid'; import util from '@/util'; +import { HydratedPNIDDocument } from '@/types/mongoose/pnid'; -const router = Router(); +const router: express.Router = express.Router(); -router.get('/verify', async (request, response) => { - let token: string; - - if (Array.isArray(request.query.token)) { - token = request.query.token[0] as string; - } else { - token = request.query.token as string; - } +router.get('/verify', async (request: express.Request, response: express.Response) => { + let token: string = request.query.token as string; if (!token || token.trim() == '') { return response.status(400).json({ @@ -22,7 +17,7 @@ router.get('/verify', async (request, response) => { }); } - const pnid = await PNID.findOne({ + const pnid: HydratedPNIDDocument = await PNID.findOne({ 'identification.email_token': token }); @@ -34,7 +29,7 @@ router.get('/verify', async (request, response) => { }); } - const validatedDate = moment().format('YYYY-MM-DDTHH:MM:SS'); + const validatedDate: string = moment().format('YYYY-MM-DDTHH:MM:SS'); pnid.set('email.reachable', true); pnid.set('email.validated', true); diff --git a/src/services/api/routes/v1/forgotPassword.ts b/src/services/api/routes/v1/forgotPassword.ts index eb926b4..24148ba 100644 --- a/src/services/api/routes/v1/forgotPassword.ts +++ b/src/services/api/routes/v1/forgotPassword.ts @@ -1,13 +1,13 @@ -import { Router } from 'express'; +import express from 'express'; import validator from 'validator'; import database from '@/database'; import util from '@/util'; +import { HydratedPNIDDocument } from '@/types/mongoose/pnid'; -const router = Router(); +const router: express.Router = express.Router(); -router.post('/', async (request, response) => { - const { body } = request; - const { input } = body; +router.post('/', async (request: express.Request, response: express.Response) => { + const input: string = request.body?.input; if (!input || input.trim() === '') { return response.status(400).json({ @@ -17,7 +17,7 @@ router.post('/', async (request, response) => { }); } - let pnid; + let pnid: HydratedPNIDDocument; if (validator.isEmail(input)) { pnid = await database.getUserByEmailAddress(input); diff --git a/src/services/api/routes/v1/login.ts b/src/services/api/routes/v1/login.ts index e6c8c1f..4aa098a 100644 --- a/src/services/api/routes/v1/login.ts +++ b/src/services/api/routes/v1/login.ts @@ -1,11 +1,14 @@ -import { Router } from 'express'; +import express from 'express'; import bcrypt from 'bcrypt'; import fs from 'fs-extra'; import database from '@/database'; import cache from '@/cache'; import util from '@/util'; +import { CryptoOptions } from '@/types/common/crypto-options'; +import { TokenOptions } from '@/types/common/token-options'; +import { HydratedPNIDDocument } from '@/types/mongoose/pnid'; -const router = Router(); +const router: express.Router = express.Router(); /** * [POST] @@ -13,11 +16,13 @@ const router = Router(); * Description: Generates an access token for an API user * TODO: Replace this with a more robust OAuth2 implementation */ -router.post('/', async (request, response) => { - const { body } = request; - const { grant_type, username, password, refresh_token } = body; +router.post('/', async (request: express.Request, response: express.Response) => { + const grantType: string = request.body?.grantType; + const username: string = request.body?.username; + const password: string = request.body?.password; + const refreshToken: string = request.body?.refresh_token; - if (!['password', 'refresh_token'].includes(grant_type)) { + if (!['password', 'refresh_token'].includes(grantType)) { return response.status(400).json({ app: 'api', status: 400, @@ -25,7 +30,7 @@ router.post('/', async (request, response) => { }); } - if (grant_type === 'password' && (!username || username.trim() === '')) { + if (grantType === 'password' && (!username || username.trim() === '')) { return response.status(400).json({ app: 'api', status: 400, @@ -33,7 +38,7 @@ router.post('/', async (request, response) => { }); } - if (grant_type === 'password' && (!password || password.trim() === '')) { + if (grantType === 'password' && (!password || password.trim() === '')) { return response.status(400).json({ app: 'api', status: 400, @@ -41,7 +46,7 @@ router.post('/', async (request, response) => { }); } - if (grant_type === 'refresh_token' && (!refresh_token || refresh_token.trim() === '')) { + if (grantType === 'refresh_token' && (!refreshToken || refreshToken.trim() === '')) { return response.status(400).json({ app: 'api', status: 400, @@ -49,9 +54,11 @@ router.post('/', async (request, response) => { }); } - let pnid; - if (grant_type === 'password') { + let pnid: HydratedPNIDDocument; + + if (grantType === 'password') { pnid = await database.getUserByUsername(username); + if (!pnid) { return response.status(400).json({ app: 'api', @@ -60,7 +67,7 @@ router.post('/', async (request, response) => { }); } - const hashedPassword = util.nintendoPasswordHash(password, pnid.get('pid')); + const hashedPassword: string = util.nintendoPasswordHash(password, pnid.get('pid')); if (!pnid || !bcrypt.compareSync(hashedPassword, pnid.password)) { return response.status(400).json({ @@ -70,7 +77,8 @@ router.post('/', async (request, response) => { }); } } else { - pnid = await database.getUserBearer(refresh_token); + pnid = await database.getUserBearer(refreshToken); + if (!pnid) { return response.status(400).json({ app: 'api', @@ -80,7 +88,7 @@ router.post('/', async (request, response) => { } } - const cryptoPath = `${__dirname}/../../../../../certs/service/account`; + const cryptoPath: string = `${__dirname}/../../../../../certs/service/account`; if (!await fs.pathExists(cryptoPath)) { // Need to generate keys @@ -91,15 +99,15 @@ router.post('/', async (request, response) => { }); } - const publicKey = await cache.getServicePublicKey('account'); - const secretKey = await cache.getServiceSecretKey('account'); + const publicKey: Buffer = await cache.getServicePublicKey('account'); + const secretKey: Buffer = await cache.getServiceSecretKey('account'); - const cryptoOptions = { + const cryptoOptions: CryptoOptions = { public_key: publicKey, hmac_secret: secretKey }; - const accessTokenOptions = { + const accessTokenOptions: TokenOptions = { system_type: 0xF, // API token_type: 0x1, // OAuth Access, pid: pnid.get('pid'), @@ -108,7 +116,7 @@ router.post('/', async (request, response) => { expire_time: BigInt(Date.now() + (3600 * 1000)) }; - const refreshTokenOptions = { + const refreshTokenOptions: TokenOptions = { system_type: 0xF, // API token_type: 0x2, // OAuth Refresh, pid: pnid.get('pid'), @@ -117,14 +125,14 @@ router.post('/', async (request, response) => { expire_time: BigInt(Date.now() + (3600 * 1000)) }; - const accessToken = await util.generateToken(cryptoOptions, accessTokenOptions); - const refreshToken = await util.generateToken(cryptoOptions, refreshTokenOptions); + const accessToken: string = await util.generateToken(cryptoOptions, accessTokenOptions); + const newRefreshToken: string = await util.generateToken(cryptoOptions, refreshTokenOptions); response.json({ access_token: accessToken, token_type: 'Bearer', expires_in: 3600, - refresh_token: refreshToken + refresh_token: newRefreshToken }); }); diff --git a/src/services/api/routes/v1/register.ts b/src/services/api/routes/v1/register.ts index 95f79d4..7712d29 100644 --- a/src/services/api/routes/v1/register.ts +++ b/src/services/api/routes/v1/register.ts @@ -1,12 +1,13 @@ import crypto from 'node:crypto'; -import { Router } from 'express'; +import express from 'express'; import emailvalidator from 'email-validator'; import bcrypt from 'bcrypt'; import fs from 'fs-extra'; import moment from 'moment'; import hcaptcha from 'hcaptcha'; import Mii from 'mii-js'; +import mongoose from 'mongoose'; import database from '@/database'; import cache from '@/cache'; import util from '@/util'; @@ -14,36 +15,38 @@ import logger from '@/logger'; import { PNID } from '@/models/pnid'; import { NEXAccount } from '@/models/nex-account'; import { config, disabledFeatures } from '@/config-manager'; +import { CryptoOptions } from '@/types/common/crypto-options'; +import { TokenOptions } from '@/types/common/token-options'; +import { HydratedNEXAccountDocument } from '@/types/mongoose/nex-account'; +import { HydratedPNIDDocument } from '@/types/mongoose/pnid'; -const router = Router(); +const router: express.Router = express.Router(); -const PNID_VALID_CHARACTERS_REGEX = /^[\w\-\.]*$/gm; -const PNID_PUNCTUATION_START_REGEX = /^[\_\-\.]/gm; -const PNID_PUNCTUATION_END_REGEX = /[\_\-\.]$/gm; -const PNID_PUNCTUATION_DUPLICATE_REGEX = /[\_\-\.]{2,}/gm; +const PNID_VALID_CHARACTERS_REGEX: RegExp = /^[\w\-\.]*$/gm; +const PNID_PUNCTUATION_START_REGEX: RegExp = /^[\_\-\.]/gm; +const PNID_PUNCTUATION_END_REGEX: RegExp = /[\_\-\.]$/gm; +const PNID_PUNCTUATION_DUPLICATE_REGEX: RegExp = /[\_\-\.]{2,}/gm; // This sucks -const PASSWORD_WORD_OR_NUMBER_REGEX = /(?=.*[a-zA-Z])(?=.*\d).*/; -const PASSWORD_WORD_OR_PUNCTUATION_REGEX = /(?=.*[a-zA-Z])(?=.*[\_\-\.]).*/; -const PASSWORD_NUMBER_OR_PUNCTUATION_REGEX = /(?=.*\d)(?=.*[\_\-\.]).*/; -const PASSWORD_REPEATED_CHARACTER_REGEX = /(.)\1\1/; +const PASSWORD_WORD_OR_NUMBER_REGEX: RegExp = /(?=.*[a-zA-Z])(?=.*\d).*/; +const PASSWORD_WORD_OR_PUNCTUATION_REGEX: RegExp = /(?=.*[a-zA-Z])(?=.*[\_\-\.]).*/; +const PASSWORD_NUMBER_OR_PUNCTUATION_REGEX: RegExp = /(?=.*\d)(?=.*[\_\-\.]).*/; +const PASSWORD_REPEATED_CHARACTER_REGEX: RegExp = /(.)\1\1/; -const DEFAULT_MII_DATA = Buffer.from('AwAAQOlVognnx0GC2/uogAOzuI0n2QAAAEBEAGUAZgBhAHUAbAB0AAAAAAAAAEBAAAAhAQJoRBgmNEYUgRIXaA0AACkAUkhQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGm9', 'base64'); +const DEFAULT_MII_DATA: Buffer = Buffer.from('AwAAQOlVognnx0GC2/uogAOzuI0n2QAAAEBEAGUAZgBhAHUAbAB0AAAAAAAAAEBAAAAhAQJoRBgmNEYUgRIXaA0AACkAUkhQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGm9', 'base64'); /** * [POST] * Implementation of: https://api.pretendo.cc/v1/register * Description: Creates a new user PNID */ -router.post('/', async (request, response) => { - const { body } = request; - - const email = body.email?.trim(); - const username = body.username?.trim(); - const miiName = body.mii_name?.trim(); - const password = body.password?.trim(); - const passwordConfirm = body.password_confirm?.trim(); - const hCaptchaResponse = body.hCaptchaResponse?.trim(); +router.post('/', async (request: express.Request, response: express.Response) => { + const email: string = request.body.email?.trim(); + const username: string = request.body.username?.trim(); + const miiName: string = request.body.mii_name?.trim(); + const password: string = request.body.password?.trim(); + const passwordConfirm: string = request.body.password_confirm?.trim(); + const hCaptchaResponse: string = request.body.hCaptchaResponse?.trim(); if (!disabledFeatures.captcha) { if (!hCaptchaResponse || hCaptchaResponse === '') { @@ -54,7 +57,7 @@ router.post('/', async (request, response) => { }); } - const captchaVerify = await hcaptcha.verify(config.hcaptcha.secret, hCaptchaResponse); + const captchaVerify: VerifyResponse = await hcaptcha.verify(config.hcaptcha.secret, hCaptchaResponse); if (!captchaVerify.success) { return response.status(400).json({ @@ -138,7 +141,7 @@ router.post('/', async (request, response) => { }); } - const userExists = await database.doesUserExist(username); + const userExists: boolean = await database.doesUserExist(username); if (userExists) { return response.status(400).json({ @@ -212,7 +215,7 @@ router.post('/', async (request, response) => { }); } - const miiNameBuffer = Buffer.from(miiName, 'utf16le'); // UTF8 to UTF16 + const miiNameBuffer: Buffer = Buffer.from(miiName, 'utf16le'); // UTF8 to UTF16 if (miiNameBuffer.length > 0x14) { return response.status(400).json({ @@ -222,14 +225,14 @@ router.post('/', async (request, response) => { }); } - const mii = new Mii(DEFAULT_MII_DATA); + const mii: Mii = new Mii(DEFAULT_MII_DATA); mii.miiName = miiName; - const creationDate = moment().format('YYYY-MM-DDTHH:MM:SS'); - let pnid; - let nexAccount; + const creationDate: string = moment().format('YYYY-MM-DDTHH:MM:SS'); + let pnid: HydratedPNIDDocument; + let nexAccount: HydratedNEXAccountDocument; - const session = await database.connection().startSession(); + const session: mongoose.ClientSession = await database.connection().startSession(); await session.startTransaction(); try { @@ -251,8 +254,8 @@ router.post('/', async (request, response) => { await nexAccount.save({ session }); - const primaryPasswordHash = util.nintendoPasswordHash(password, nexAccount.get('pid')); - const passwordHash = await bcrypt.hash(primaryPasswordHash, 10); + const primaryPasswordHash: string = util.nintendoPasswordHash(password, nexAccount.get('pid')); + const passwordHash: string = await bcrypt.hash(primaryPasswordHash, 10); pnid = new PNID({ pid: nexAccount.get('pid'), @@ -323,7 +326,7 @@ router.post('/', async (request, response) => { await util.sendConfirmationEmail(pnid); - const cryptoPath = `${__dirname}/../../../../../certs/service/account`; + const cryptoPath: string = `${__dirname}/../../../../../certs/service/account`; if (!await fs.pathExists(cryptoPath)) { // Need to generate keys @@ -334,15 +337,15 @@ router.post('/', async (request, response) => { }); } - const publicKey = await cache.getServicePublicKey('account'); - const secretKey = await cache.getServiceSecretKey('account'); + const publicKey: Buffer = await cache.getServicePublicKey('account'); + const secretKey: Buffer = await cache.getServiceSecretKey('account'); - const cryptoOptions = { + const cryptoOptions: CryptoOptions = { public_key: publicKey, hmac_secret: secretKey }; - const accessTokenOptions = { + const accessTokenOptions: TokenOptions = { system_type: 0xF, // API token_type: 0x1, // OAuth Access, pid: pnid.get('pid'), @@ -351,7 +354,7 @@ router.post('/', async (request, response) => { expire_time: BigInt(Date.now() + (3600 * 1000)) }; - const refreshTokenOptions = { + const refreshTokenOptions: TokenOptions = { system_type: 0xF, // API token_type: 0x2, // OAuth Refresh, pid: pnid.get('pid'), @@ -360,8 +363,8 @@ router.post('/', async (request, response) => { expire_time: BigInt(Date.now() + (3600 * 1000)) }; - const accessToken = await util.generateToken(cryptoOptions, accessTokenOptions); - const refreshToken = await util.generateToken(cryptoOptions, refreshTokenOptions); + const accessToken: string = await util.generateToken(cryptoOptions, accessTokenOptions); + const refreshToken: string = await util.generateToken(cryptoOptions, refreshTokenOptions); response.json({ access_token: accessToken, diff --git a/src/services/api/routes/v1/resetPassword.ts b/src/services/api/routes/v1/resetPassword.ts index 3f9a823..8f570b5 100644 --- a/src/services/api/routes/v1/resetPassword.ts +++ b/src/services/api/routes/v1/resetPassword.ts @@ -1,21 +1,22 @@ -import { Router } from 'express'; +import express from 'express'; import bcrypt from 'bcrypt'; import { PNID } from '@/models/pnid'; import util from '@/util'; +import { Token } from '@/types/common/token'; +import { HydratedPNIDDocument } from '@/types/mongoose/pnid'; -const router = Router(); +const router: express.Router = express.Router(); // This sucks -const PASSWORD_WORD_OR_NUMBER_REGEX = /(?=.*[a-zA-Z])(?=.*\d).*/; -const PASSWORD_WORD_OR_PUNCTUATION_REGEX = /(?=.*[a-zA-Z])(?=.*[\_\-\.]).*/; -const PASSWORD_NUMBER_OR_PUNCTUATION_REGEX = /(?=.*\d)(?=.*[\_\-\.]).*/; -const PASSWORD_REPEATED_CHARACTER_REGEX = /(.)\1\1/; +const PASSWORD_WORD_OR_NUMBER_REGEX: RegExp = /(?=.*[a-zA-Z])(?=.*\d).*/; +const PASSWORD_WORD_OR_PUNCTUATION_REGEX: RegExp = /(?=.*[a-zA-Z])(?=.*[\_\-\.]).*/; +const PASSWORD_NUMBER_OR_PUNCTUATION_REGEX: RegExp = /(?=.*\d)(?=.*[\_\-\.]).*/; +const PASSWORD_REPEATED_CHARACTER_REGEX: RegExp = /(.)\1\1/; -router.post('/', async (request, response) => { - const { body } = request; - const password = body.password?.trim(); - const passwordConfirm = body.password_confirm?.trim(); - const token = body.token?.trim(); +router.post('/', async (request: express.Request, response: express.Response) => { + const password: string = request.body.password?.trim(); + const passwordConfirm: string = request.body.password_confirm?.trim(); + const token: string = request.body.token?.trim(); if (!token || token === '') { return response.status(400).json({ @@ -25,9 +26,9 @@ router.post('/', async (request, response) => { }); } - let unpackedToken; + let unpackedToken: Token; try { - const decryptedToken = await util.decryptToken(Buffer.from(token, 'base64')); + const decryptedToken: Buffer = await util.decryptToken(Buffer.from(token, 'base64')); unpackedToken = util.unpackToken(decryptedToken); } catch (error) { console.log(error); @@ -46,7 +47,7 @@ router.post('/', async (request, response) => { }); } - const pnid = await PNID.findOne({ pid: unpackedToken.pid }); + const pnid: HydratedPNIDDocument = await PNID.findOne({ pid: unpackedToken.pid }); if (!pnid) { return response.status(400).json({ @@ -113,8 +114,8 @@ router.post('/', async (request, response) => { }); } - const primaryPasswordHash = util.nintendoPasswordHash(password, pnid.get('pid')); - const passwordHash = await bcrypt.hash(primaryPasswordHash, 10); + const primaryPasswordHash: string = util.nintendoPasswordHash(password, pnid.get('pid')); + const passwordHash: string = await bcrypt.hash(primaryPasswordHash, 10); pnid.password = passwordHash; diff --git a/src/services/api/routes/v1/user.ts b/src/services/api/routes/v1/user.ts index 2ce5fae..ef03c25 100644 --- a/src/services/api/routes/v1/user.ts +++ b/src/services/api/routes/v1/user.ts @@ -1,12 +1,14 @@ -import { Router } from 'express'; +import express from 'express'; import joi from 'joi'; import { PNID } from '@/models/pnid'; import { config } from '@/config-manager'; +import { HydratedPNIDDocument } from '@/types/mongoose/pnid'; +import { UpdateUserRequest } from '@/types/services/api/update-user-request'; -const router = Router(); +const router: express.Router = express.Router(); // TODO: Extend this later with more settings -const userSchema = joi.object({ +const userSchema: joi.ObjectSchema = joi.object({ mii: joi.object({ name: joi.string(), primary: joi.string(), @@ -19,8 +21,8 @@ const userSchema = joi.object({ * Implementation of for: https://api.pretendo.cc/v1/user * Description: Gets PNID details about the current user */ -router.get('/', async (request, response) => { - const { pnid } = request; +router.get('/', async (request: express.Request, response: express.Response) => { + const pnid: HydratedPNIDDocument = request.pnid; if (!pnid) { return response.status(400).json({ @@ -67,8 +69,9 @@ router.get('/', async (request, response) => { * Implementation of for: https://api.pretendo.cc/v1/user * Description: Updates PNID certain details about the current user */ -router.post('/', async (request, response) => { - const { body, pnid } = request; +router.post('/', async (request: express.Request, response: express.Response) => { + const pnid: HydratedPNIDDocument = request.pnid; + const updateUserRequest: UpdateUserRequest = request.body; if (!pnid) { return response.status(400).json({ @@ -78,7 +81,7 @@ router.post('/', async (request, response) => { }); } - const valid = userSchema.validate(body); + const valid: joi.ValidationResult = userSchema.validate(updateUserRequest); if (valid.error) { return response.status(400).json({ @@ -88,11 +91,13 @@ router.post('/', async (request, response) => { }); } - const { pid } = pnid; + // TODO - Make this do something - const updateData = {}; + //const pid: number = pnid.pid; - await PNID.updateOne({ pid }, { $set: updateData }).exec(); + //const updateData = {}; + + //await PNID.updateOne({ pid }, { $set: updateData }).exec(); return response.json({ access_level: pnid.access_level, diff --git a/src/services/assets/index.ts b/src/services/assets/index.ts index dc77afb..f40cd56 100644 --- a/src/services/assets/index.ts +++ b/src/services/assets/index.ts @@ -6,14 +6,14 @@ import subdomain from 'express-subdomain'; import logger from '@/logger'; // Router to handle the subdomain restriction -const assets = express.Router(); +const assets: express.Router = express.Router(); // Setup public folder logger.info('[assets] Setting up public folder'); assets.use(express.static(path.join(__dirname, '../../assets'))); // Main router for endpoints -const router = express.Router(); +const router: express.Router = express.Router(); // Create subdomains logger.info('[conntest] Creating \'assets\' subdomain'); diff --git a/src/services/conntest/index.ts b/src/services/conntest/index.ts index 8033849..610cdda 100644 --- a/src/services/conntest/index.ts +++ b/src/services/conntest/index.ts @@ -5,11 +5,11 @@ import subdomain from 'express-subdomain'; import logger from '@/logger'; // Router to handle the subdomain restriction -const conntest = express.Router(); +const conntest: express.Router = express.Router(); // Setup route logger.info('[conntest] Applying imported routes'); -conntest.get('/', async (request, response) => { +conntest.get('/', async (request: express.Request, response: express.Response) => { response.set('Content-Type', 'text/html'); response.set('X-Organization', 'Nintendo'); @@ -27,7 +27,7 @@ This is test.html page }); // Main router for endpoints -const router = express.Router(); +const router: express.Router = express.Router(); // Create subdomains logger.info('[conntest] Creating \'conntest\' subdomain'); diff --git a/src/services/datastore/index.ts b/src/services/datastore/index.ts index 6969988..a06640d 100644 --- a/src/services/datastore/index.ts +++ b/src/services/datastore/index.ts @@ -4,14 +4,14 @@ import routes from '@/services/datastore/routes'; import logger from '@/logger'; // Router to handle the subdomain -const datastore = express.Router(); +const datastore: express.Router = express.Router(); // Setup routes logger.info('[DATASTORE] Applying imported routes'); datastore.use(routes.UPLOAD); // Main router for endpoints -const router = express.Router(); +const router: express.Router = express.Router(); // Create subdomains logger.info('[DATASTORE] Creating \'datastore\' subdomain'); diff --git a/src/services/datastore/routes/upload.ts b/src/services/datastore/routes/upload.ts index a17c15d..5b47857 100644 --- a/src/services/datastore/routes/upload.ts +++ b/src/services/datastore/routes/upload.ts @@ -1,30 +1,35 @@ import fs from 'node:fs'; import crypto from 'node:crypto'; -import { Router } from 'express'; +import express from 'express'; import Dicer from 'dicer'; import util from '@/util'; -const router = Router(); +const router: express.Router = express.Router(); -const signatureSecret = fs.readFileSync(`${__dirname}/../../../../certs/nex/datastore/secret.key`); +const signatureSecret: Buffer = fs.readFileSync(`${__dirname}/../../../../certs/nex/datastore/secret.key`); -function multipartParser(request, response, next) { - const RE_BOUNDARY = /^multipart\/.+?(?:; boundary=(?:(?:"(.+)")|(?:([^\s]+))))$/i; - const RE_FILE_NAME = /name="(.*)"/; - const boundary = RE_BOUNDARY.exec(request.header('content-type')); - const dicer = new Dicer({ boundary: boundary[1] || boundary[2] }); - const files = {}; +function multipartParser(request: express.Request, response: express.Response, next: express.NextFunction) { + const RE_BOUNDARY: RegExp = /^multipart\/.+?(?:; boundary=(?:(?:"(.+)")|(?:([^\s]+))))$/i; + const RE_FILE_NAME: RegExp = /name="(.*)"/; - dicer.on('part', part => { - let fileBuffer = Buffer.alloc(0); - let fileName = ''; + const boundary: RegExpExecArray = RE_BOUNDARY.exec(request.header('content-type')); + const dicer: Dicer = new Dicer({ boundary: boundary[1] || boundary[2] }); + const files: { [key: string]: Buffer } = {}; + + dicer.on('part', (part: Dicer.PartStream) => { + let fileBuffer: Buffer = Buffer.alloc(0); + let fileName: string = ''; part.on('header', header => { fileName = RE_FILE_NAME.exec(header['content-disposition'][0])[1]; }); - part.on('data', data => { - fileBuffer = Buffer.concat([fileBuffer, data]); + part.on('data', (data: Buffer | string) => { + if (data instanceof String) { + data = Buffer.from(data); + } + + fileBuffer = Buffer.concat([fileBuffer, data as Buffer]); }); part.on('end', () => { @@ -40,36 +45,34 @@ function multipartParser(request, response, next) { request.pipe(dicer); } -router.post('/upload', multipartParser, async (request, response) => { - const { - bucket, // Space name - key, // path - file, // the file content - acl, // S3 ACL - pid, // uploading user PID - date, // upload time - signature, // data signature - } = request.files; +router.post('/upload', multipartParser, async (request: express.Request, response: express.Response) => { + const bucket: string = request.files.bucket.toString(); + const key: string = request.files.key.toString(); + const file: Buffer = request.files.file; + const acl: string = request.files.acl.toString(); + const pid: string = request.files.pid.toString(); + const date: string = request.files.date.toString(); + const signature: string = request.files.signature.toString(); // Signatures only good for 1 minute - const minute = 1000 * 60; - const minuteAgo = Date.now() - minute; + const minute: number = 1000 * 60; + const minuteAgo: number = Date.now() - minute; if (Number(date) < Math.floor(minuteAgo / 1000)) { return response.sendStatus(400); } - const data = pid.toString() + bucket.toString() + key.toString() + date.toString(); + const data: string = `${pid}${bucket}${key}${date}`; - const hmac = crypto.createHmac('sha256', signatureSecret).update(data).digest('hex'); + const hmac: string = crypto.createHmac('sha256', signatureSecret).update(data).digest('hex'); - console.log(hmac, signature.toString()); + console.log(hmac, signature); - if (hmac !== signature.toString()) { + if (hmac !== signature) { return response.sendStatus(400); } - await util.uploadCDNAsset(bucket.toString(), key.toString(), file, acl.toString()); + await util.uploadCDNAsset(bucket, key, file, acl); response.sendStatus(200); }); diff --git a/src/services/local-cdn/index.ts b/src/services/local-cdn/index.ts index e8891ac..9f9fe3f 100644 --- a/src/services/local-cdn/index.ts +++ b/src/services/local-cdn/index.ts @@ -4,13 +4,13 @@ import routes from '@/services/local-cdn/routes'; import { config, disabledFeatures } from '@/config-manager'; import logger from '@/logger'; -const router = express.Router(); +const router: express.Router = express.Router(); if (disabledFeatures.s3) { // * s3 disabled, setup local CDN // * Router to handle the subdomain - const localcdn = express.Router(); + const localcdn: express.Router = express.Router(); // * Setup routes logger.info('[LOCAL-CDN] Applying imported routes'); diff --git a/src/services/local-cdn/routes/get.ts b/src/services/local-cdn/routes/get.ts index 7a68dc8..1492c13 100644 --- a/src/services/local-cdn/routes/get.ts +++ b/src/services/local-cdn/routes/get.ts @@ -1,12 +1,12 @@ -import { Router } from 'express'; +import express from 'express'; import cache from '@/cache'; -const router = Router(); +const router: express.Router = express.Router(); -router.get('/*', async (request, response) => { - const filePath = request.params[0]; +router.get('/*', async (request: express.Request, response: express.Response) => { + const filePath: string = request.params[0] as string; - const file = await cache.getLocalCDNFile(filePath); + const file: Buffer = await cache.getLocalCDNFile(filePath); if (file) { response.send(file); diff --git a/src/services/nasc/index.ts b/src/services/nasc/index.ts index a55de55..38f1d60 100644 --- a/src/services/nasc/index.ts +++ b/src/services/nasc/index.ts @@ -7,7 +7,7 @@ import routes from '@/services/nasc/routes'; import logger from '@/logger'; // Router to handle the subdomain restriction -const nasc = express.Router(); +const nasc: express.Router = express.Router(); logger.info('[NASC] Importing middleware'); nasc.use(NASCMiddleware); @@ -17,7 +17,7 @@ logger.info('[NASC] Applying imported routes'); nasc.use('/ac', routes.AC); // Main router for endpoints -const router = express.Router(); +const router: express.Router = express.Router(); // Create subdomains logger.info('[NASC] Creating \'nasc\' subdomain'); diff --git a/src/services/nasc/routes/ac.ts b/src/services/nasc/routes/ac.ts index f468f1a..6e4589a 100644 --- a/src/services/nasc/routes/ac.ts +++ b/src/services/nasc/routes/ac.ts @@ -2,18 +2,23 @@ import express from 'express'; import util from '@/util'; import database from '@/database'; import cache from '@/cache'; +import { CryptoOptions } from '@/types/common/crypto-options'; +import { TokenOptions } from '@/types/common/token-options'; +import { NASCRequestParams } from '@/types/services/nasc/request-params'; +import { HydratedNEXAccountDocument } from '@/types/mongoose/nex-account'; +import { HydratedServerDocument } from '@/types/mongoose/server'; -const router = express.Router(); +const router: express.Router = express.Router(); /** * [POST] * Replacement for: https://nasc.nintendowifi.net/ac * Description: Gets a NEX server address and token */ -router.post('/', async (request, response) => { - const requestParams = request.body; - const action = util.nintendoBase64Decode(requestParams.action).toString(); - let responseData; +router.post('/', async (request: express.Request, response: express.Response) => { + const requestParams: NASCRequestParams = request.body; + const action: string = util.nintendoBase64Decode(requestParams.action).toString(); + let responseData: URLSearchParams; switch (action) { case 'LOGIN': @@ -27,36 +32,38 @@ router.post('/', async (request, response) => { response.status(200).send(responseData.toString()); }); -async function processLoginRequest(request: express.Request) { - const requestParams = request.body; - const titleID = util.nintendoBase64Decode(requestParams.titleid).toString(); - const { nexUser } = request; +async function processLoginRequest(request: express.Request): Promise { + const requestParams: NASCRequestParams = request.body; + const titleID: string = util.nintendoBase64Decode(requestParams.titleid).toString(); + const nexUser: HydratedNEXAccountDocument = request.nexUser; // TODO: REMOVE AFTER PUBLIC LAUNCH // LET EVERYONE IN THE `test` FRIENDS SERVER // THAT WAY EVERYONE CAN GET AN ASSIGNED PID - let serverAccessLevel = 'test'; + let serverAccessLevel: string = 'test'; if (titleID !== '0004013000003202') { serverAccessLevel = nexUser.get('server_access_level'); } - const server = await database.getServerByTitleId(titleID, serverAccessLevel); + const server: HydratedServerDocument = await database.getServerByTitleId(titleID, serverAccessLevel); if (!server || !server.service_name || !server.ip || !server.port) { return util.nascError('110'); } - const { service_name, ip, port } = server; + const serverName: string = server.service_name; + const ip: string = server.ip; + const port: number = server.port; - const publicKey = await cache.getNEXPublicKey(service_name); - const secretKey = await cache.getNEXSecretKey(service_name); + const publicKey: Buffer = await cache.getNEXPublicKey(serverName); + const secretKey: Buffer = await cache.getNEXSecretKey(serverName); - const cryptoOptions = { + const cryptoOptions: CryptoOptions = { public_key: publicKey, hmac_secret: secretKey }; - const tokenOptions = { + const tokenOptions: TokenOptions = { system_type: 0x2, // 3DS token_type: 0x3, // nex token, pid: nexUser.get('pid'), @@ -65,22 +72,20 @@ async function processLoginRequest(request: express.Request) { expire_time: BigInt(Date.now() + (3600 * 1000)) }; - let nexToken = await util.generateToken(cryptoOptions, tokenOptions); + let nexToken: string = await util.generateToken(cryptoOptions, tokenOptions); nexToken = util.nintendoBase64Encode(Buffer.from(nexToken, 'base64')); - const params = new URLSearchParams({ + return new URLSearchParams({ locator: util.nintendoBase64Encode(`${ip}:${port}`), retry: util.nintendoBase64Encode('0'), returncd: util.nintendoBase64Encode('001'), token: nexToken, datetime: util.nintendoBase64Encode(Date.now().toString()), }); - - return params; } -async function processServiceTokenRequest(request: express.Request) { - const params = new URLSearchParams({ +async function processServiceTokenRequest(_request: express.Request): Promise { + return new URLSearchParams({ retry: util.nintendoBase64Encode('0'), returncd: util.nintendoBase64Encode('007'), servicetoken: util.nintendoBase64Encode(Buffer.alloc(64).toString()), // hard coded for now @@ -88,8 +93,6 @@ async function processServiceTokenRequest(request: express.Request) { svchost: util.nintendoBase64Encode('n/a'), datetime: util.nintendoBase64Encode(Date.now().toString()), }); - - return params; } export default router; \ No newline at end of file diff --git a/src/services/nnid/routes/admin.ts b/src/services/nnid/routes/admin.ts index 115b5ab..11bc7d6 100644 --- a/src/services/nnid/routes/admin.ts +++ b/src/services/nnid/routes/admin.ts @@ -1,28 +1,22 @@ -import { Router } from 'express'; +import express from 'express'; import xmlbuilder from 'xmlbuilder'; import { PNID } from '@/models/pnid'; +import { HydratedPNIDDocument } from '@/types/mongoose/pnid'; -const router = Router(); +const router: express.Router = express.Router(); /** * [GET] * Replacement for: https://account.nintendo.net/v1/api/admin/mapped_ids * Description: Maps between NNID usernames and PIDs */ -router.get('/mapped_ids', async (request, response) => { - - let inputType: string; +router.get('/mapped_ids', async (request: express.Request, response: express.Response) => { + const inputType: string = request.query.input_type as string; + const outputType: string = request.query.output_type as string; let inputList: string[]; - let outputType: string; let queryInput: string; let queryOutput: string; - if (Array.isArray(request.query.input_type)) { - inputType = request.query.input_type[0] as string; - } else { - inputType = request.query.input_type as string; - } - if (Array.isArray(request.query.input)) { inputList = request.query.input as string[]; } else { @@ -30,12 +24,6 @@ router.get('/mapped_ids', async (request, response) => { inputList = input.split(','); } - if (Array.isArray(request.query.output_type)) { - outputType = request.query.output_type[0] as string; - } else { - outputType = request.query.output_type as string; - } - inputList = inputList.filter(input => input); // * Remove null inputs if (inputType === 'user_id') { @@ -55,20 +43,30 @@ router.get('/mapped_ids', async (request, response) => { // but it ensures that each input // ALWAYS has an output and filters // out unwanted input/output types - const results = []; - const allowedTypes = ['pid', 'user_id']; + const results: { + in_id: string; + out_id: string; + }[] = []; + const allowedTypes: string[] = ['pid', 'user_id']; for (const input of inputList) { - const result = { + const result: { + in_id: string; + out_id: string; + } = { in_id: input, out_id: '' }; if (allowedTypes.includes(inputType) && allowedTypes.includes(outputType)) { - const query = {}; + const query: { + usernameLower?: string; + pid?: number; + } = {}; + query[queryInput] = input; - const searchResult = await PNID.findOne(query); + const searchResult: HydratedPNIDDocument = await PNID.findOne(query); if (searchResult) { result.out_id = searchResult.get(queryOutput); diff --git a/src/services/nnid/routes/content.ts b/src/services/nnid/routes/content.ts index 92f4701..5d45aa6 100644 --- a/src/services/nnid/routes/content.ts +++ b/src/services/nnid/routes/content.ts @@ -1,15 +1,17 @@ -import { Router } from 'express'; +import express from 'express'; import xmlbuilder from 'xmlbuilder'; import timezones from '@/services/nnid/timezones.json'; +import { RegionLanguages } from '@/types/services/nnid/region-languages'; +import { RegionTimezones } from '@/types/services/nnid/region-timezones'; -const router = Router(); +const router: express.Router = express.Router(); /** * [GET] * Replacement for: https://account.nintendo.net/v1/api/content/agreements/TYPE/REGION/VERSION * Description: Sends the client requested agreement */ -router.get('/agreements/:type/:region/:version', (request, response) => { +router.get('/agreements/:type/:region/:version', (request: express.Request, response: express.Response) => { response.set('Content-Type', 'text/xml'); response.set('Server', 'Nintendo 3DS (http)'); response.set('X-Nintendo-Date', new Date().getTime().toString()); @@ -126,7 +128,7 @@ router.get('/agreements/:type/:region/:version', (request, response) => { * Replacement for: https://account.nintendo.net/v1/api/content/time_zones/COUNTRY/LANGUAGE * Description: Sends the client the requested timezones */ -router.get('/time_zones/:countryCode/:language', (request, response) => { +router.get('/time_zones/:countryCode/:language', (request: express.Request, response: express.Response) => { response.set('Content-Type', 'text/xml'); response.set('Server', 'Nintendo 3DS (http)'); response.set('X-Nintendo-Date', new Date().getTime().toString()); @@ -149,10 +151,11 @@ router.get('/time_zones/:countryCode/:language', (request, response) => { }); */ - const { countryCode, language } = request.params; + const countryCode: string = request.params.countryCode; + const language: string = request.params.language; - const regionLanguages = timezones[countryCode]; - const regionTimezones = regionLanguages[language] ? regionLanguages[language] : Object.values(regionLanguages)[0]; + const regionLanguages: RegionLanguages = timezones[countryCode]; + const regionTimezones: RegionTimezones = regionLanguages[language] ? regionLanguages[language] : Object.values(regionLanguages)[0]; response.send(xmlbuilder.create({ timezones: { diff --git a/src/services/nnid/routes/devices.ts b/src/services/nnid/routes/devices.ts index ec6224e..3626fb1 100644 --- a/src/services/nnid/routes/devices.ts +++ b/src/services/nnid/routes/devices.ts @@ -1,14 +1,15 @@ -import { Router } from 'express'; +import express from 'express'; import xmlbuilder from 'xmlbuilder'; -const router = Router(); +const router: express.Router = express.Router(); /** * [GET] * Replacement for: https://account.nintendo.net/v1/api/devices/@current/status * Description: Unknown use */ -router.get('/@current/status', async (request, response) => { +router.get('/@current/status', async (request: express.Request, response: express.Response) => { + // TODO - Finish this response.send(xmlbuilder.create({ device: '' }).end()); diff --git a/src/services/nnid/routes/miis.ts b/src/services/nnid/routes/miis.ts index 7219ed4..368cb9e 100644 --- a/src/services/nnid/routes/miis.ts +++ b/src/services/nnid/routes/miis.ts @@ -1,81 +1,110 @@ -import { Router } from 'express'; +import express from 'express'; import xmlbuilder from 'xmlbuilder'; import { PNID } from '@/models/pnid'; import { config } from '@/config-manager'; +import { HydratedPNIDDocument } from '@/types/mongoose/pnid'; +import { YesNoBoolString } from '@/types/common/yes-no-bool-string'; -const router = Router(); +const router: express.Router = express.Router(); /** * [GET] - * Replacement for: https://account.pretendo.cc/v1/api/miis + * Replacement for: https://account.nintendo.net/v1/api/miis * Description: Returns a list of NNID miis */ -router.get('/', async (request, response) => { +router.get('/', async (request: express.Request, response: express.Response) => { + let pids: number[]; - const { pids } = request.query; + if (Array.isArray(request.query.pids)) { + pids = request.query.pids.map(pid => Number(pid)); + } else { + const input = request.query.pids as string; + pids = input.split(',').map(pid => Number(pid)); + } - const results = await PNID.where('pid', pids); - const miis = []; + const results: HydratedPNIDDocument[] = await PNID.where('pid', pids); + const miis: { + data: string; + id: number; + images: { + image: { + cached_url: string; + id: number; + url: string; + type: string; + }[] + }; + name: string; + pid: number; + primary: YesNoBoolString; + user_id: string; + }[] = []; for (const user of results) { - const { mii } = user; - - const miiImages = [ - { - cached_url: `${config.cdn.base_url}/mii/${user.pid}/normal_face.png`, - id: mii.id, - url: `${config.cdn.base_url}/mii/${user.pid}/normal_face.png`, - type: 'standard' - }, - { - cached_url: `${config.cdn.base_url}/mii/${user.pid}/frustrated.png`, - id: mii.id, - url: `${config.cdn.base_url}/mii/${user.pid}/frustrated.png`, - type: 'frustrated_face' - }, - { - cached_url: `${config.cdn.base_url}/mii/${user.pid}/smile_open_mouth.png`, - id: mii.id, - url: `${config.cdn.base_url}/mii/${user.pid}/smile_open_mouth.png`, - type: 'happy_face' - }, - { - cached_url: `${config.cdn.base_url}/mii/${user.pid}/wink_left.png`, - id: mii.id, - url: `${config.cdn.base_url}/mii/${user.pid}/wink_left.png`, - type: 'like_face' - }, - { - cached_url: `${config.cdn.base_url}/mii/${user.pid}/normal_face.png`, - id: mii.id, - url: `${config.cdn.base_url}/mii/${user.pid}/normal_face.png`, - type: 'normal_face' - }, - { - cached_url: `${config.cdn.base_url}/mii/${user.pid}/sorrow.png`, - id: mii.id, - url: `${config.cdn.base_url}/mii/${user.pid}/sorrow.png`, - type: 'puzzled_face' - }, - { - cached_url: `${config.cdn.base_url}/mii/${user.pid}/surprised_open_mouth.png`, - id: mii.id, - url: `${config.cdn.base_url}/mii/${user.pid}/surprised_open_mouth.png`, - type: 'surprised_face' - }, - { - cached_url: `${config.cdn.base_url}/mii/${user.pid}/body.png`, - id: mii.id, - url: `${config.cdn.base_url}/mii/${user.pid}/body.png`, - type: 'whole_body' - } - ]; + const mii: { + name: string; + primary: boolean; + data: string; + id: number; + hash: string; + image_url: string; + image_id: number; + } = user.mii; miis.push({ data: mii.data.replace(/(\r\n|\n|\r)/gm, ''), id: mii.id, images: { - image: miiImages + image: [ + { + cached_url: `${config.cdn.base_url}/mii/${user.pid}/normal_face.png`, + id: mii.id, + url: `${config.cdn.base_url}/mii/${user.pid}/normal_face.png`, + type: 'standard' + }, + { + cached_url: `${config.cdn.base_url}/mii/${user.pid}/frustrated.png`, + id: mii.id, + url: `${config.cdn.base_url}/mii/${user.pid}/frustrated.png`, + type: 'frustrated_face' + }, + { + cached_url: `${config.cdn.base_url}/mii/${user.pid}/smile_open_mouth.png`, + id: mii.id, + url: `${config.cdn.base_url}/mii/${user.pid}/smile_open_mouth.png`, + type: 'happy_face' + }, + { + cached_url: `${config.cdn.base_url}/mii/${user.pid}/wink_left.png`, + id: mii.id, + url: `${config.cdn.base_url}/mii/${user.pid}/wink_left.png`, + type: 'like_face' + }, + { + cached_url: `${config.cdn.base_url}/mii/${user.pid}/normal_face.png`, + id: mii.id, + url: `${config.cdn.base_url}/mii/${user.pid}/normal_face.png`, + type: 'normal_face' + }, + { + cached_url: `${config.cdn.base_url}/mii/${user.pid}/sorrow.png`, + id: mii.id, + url: `${config.cdn.base_url}/mii/${user.pid}/sorrow.png`, + type: 'puzzled_face' + }, + { + cached_url: `${config.cdn.base_url}/mii/${user.pid}/surprised_open_mouth.png`, + id: mii.id, + url: `${config.cdn.base_url}/mii/${user.pid}/surprised_open_mouth.png`, + type: 'surprised_face' + }, + { + cached_url: `${config.cdn.base_url}/mii/${user.pid}/body.png`, + id: mii.id, + url: `${config.cdn.base_url}/mii/${user.pid}/body.png`, + type: 'whole_body' + } + ] }, name: mii.name, pid: user.pid, diff --git a/src/services/nnid/routes/oauth.ts b/src/services/nnid/routes/oauth.ts index ab1ca40..800cad5 100644 --- a/src/services/nnid/routes/oauth.ts +++ b/src/services/nnid/routes/oauth.ts @@ -1,22 +1,25 @@ -import { Router } from 'express'; +import express from 'express'; import xmlbuilder from 'xmlbuilder'; import bcrypt from 'bcrypt'; import fs from 'fs-extra'; import database from '@/database'; import util from '@/util'; +import { TokenOptions } from '@/types/common/token-options'; +import { HydratedPNIDDocument } from '@/types/mongoose/pnid'; -const router = Router(); +const router: express.Router = express.Router(); /** * [POST] * Replacement for: https://account.nintendo.net/v1/api/oauth20/access_token/generate * Description: Generates an access token for a user */ -router.post('/access_token/generate', async (request, response) => { - const { body } = request; - const { grant_type, user_id, password } = body; +router.post('/access_token/generate', async (request: express.Request, response: express.Response) => { + const grantType: string = request.body?.grant_type; + const username: string = request.body?.user_id; + const password: string = request.body?.password; - if (!['password', 'refresh_token'].includes(grant_type)) { + if (!['password', 'refresh_token'].includes(grantType)) { response.status(400); return response.send(xmlbuilder.create({ error: { @@ -27,7 +30,7 @@ router.post('/access_token/generate', async (request, response) => { }).end()); } - if (!user_id || user_id.trim() === '') { + if (!username || username.trim() === '') { response.status(400); return response.send(xmlbuilder.create({ error: { @@ -49,7 +52,7 @@ router.post('/access_token/generate', async (request, response) => { }).end()); } - const pnid = await database.getUserByUsername(user_id); + const pnid: HydratedPNIDDocument = await database.getUserByUsername(username); if (!pnid || !await bcrypt.compare(password, pnid.password)) { response.status(400); @@ -72,7 +75,7 @@ router.post('/access_token/generate', async (request, response) => { }).end()); } - const cryptoPath = `${__dirname}/../../../../certs/service/account`; + const cryptoPath: string = `${__dirname}/../../../../certs/service/account`; if (!await fs.pathExists(cryptoPath)) { // Need to generate keys @@ -86,22 +89,22 @@ router.post('/access_token/generate', async (request, response) => { }).end()); } - const accessTokenOptions = { + const accessTokenOptions: TokenOptions = { system_type: 0x1, // WiiU token_type: 0x1, // OAuth Access, pid: pnid.get('pid'), expire_time: BigInt(Date.now() + (3600 * 1000)) }; - const refreshTokenOptions = { + const refreshTokenOptions: TokenOptions = { system_type: 0x1, // WiiU token_type: 0x2, // OAuth Refresh, pid: pnid.get('pid'), expire_time: BigInt(Date.now() + (3600 * 1000)) }; - let accessToken = await util.generateToken(null, accessTokenOptions); - let refreshToken = await util.generateToken(null, refreshTokenOptions); + let accessToken: string = await util.generateToken(null, accessTokenOptions); + let refreshToken: string = await util.generateToken(null, refreshTokenOptions); if (request.isCemu) { accessToken = Buffer.from(accessToken, 'base64').toString('hex'); diff --git a/src/services/nnid/routes/people.ts b/src/services/nnid/routes/people.ts index 8733e00..9c508f2 100644 --- a/src/services/nnid/routes/people.ts +++ b/src/services/nnid/routes/people.ts @@ -1,8 +1,9 @@ import crypto from 'node:crypto'; -import { Router } from 'express'; +import express from 'express'; import xmlbuilder from 'xmlbuilder'; import bcrypt from 'bcrypt'; import moment from 'moment'; +import mongoose from 'mongoose'; import deviceCertificateMiddleware from '@/middleware/device-certificate'; import ratelimit from '@/middleware/ratelimit'; import database from '@/database'; @@ -11,18 +12,24 @@ import { PNID } from '@/models/pnid'; import { NEXAccount } from '@/models/nex-account'; import logger from '@/logger'; import timezones from '@/services/nnid/timezones.json'; +import { HydratedPNIDDocument } from '@/types/mongoose/pnid'; +import { HydratedNEXAccountDocument } from '@/types/mongoose/nex-account'; +import { RegionLanguages } from '@/types/services/nnid/region-languages'; +import { RegionTimezone, RegionTimezones } from '@/types/services/nnid/region-timezones'; +import { Person } from '@/types/services/nnid/person'; +import { PNIDProfile } from '@/types/services/nnid/pnid-profile'; -const router = Router(); +const router: express.Router = express.Router(); /** * [GET] * Replacement for: https://account.nintendo.net/v1/api/people/:USERNAME * Description: Checks if a username is in use */ -router.get('/:username', async (request, response) => { - const { username } = request.params; +router.get('/:username', async (request: express.Request, response: express.Response) => { + const username: string = request.params.username; - const userExists = await database.doesUserExist(username); + const userExists: boolean = await database.doesUserExist(username); if (userExists) { response.status(400); @@ -46,7 +53,7 @@ router.get('/:username', async (request, response) => { * Replacement for: https://account.nintendo.net/v1/api/people * Description: Registers a new NNID */ -router.post('/', ratelimit, deviceCertificateMiddleware, async (request, response) => { +router.post('/', ratelimit, deviceCertificateMiddleware, async (request: express.Request, response: express.Response) => { if (!request.certificate.valid) { // TODO: Change this to a different error response.status(400); @@ -60,9 +67,10 @@ router.post('/', ratelimit, deviceCertificateMiddleware, async (request, respons }).end()); } - const person = request.body.get('person'); + // * request.body is a Map, as is request.body.person + const person: Person = request.body.get('person'); - const userExists = await database.doesUserExist(person.get('user_id')); + const userExists: boolean = await database.doesUserExist(person.get('user_id')); if (userExists) { response.status(400); @@ -77,11 +85,11 @@ router.post('/', ratelimit, deviceCertificateMiddleware, async (request, respons }).end()); } - const creationDate = moment().format('YYYY-MM-DDTHH:MM:SS'); - let pnid; - let nexAccount; + const creationDate: string = moment().format('YYYY-MM-DDTHH:MM:SS'); + let pnid: HydratedPNIDDocument; + let nexAccount: HydratedNEXAccountDocument; - const session = await database.connection().startSession(); + const session: mongoose.ClientSession = await database.connection().startSession(); await session.startTransaction(); try { @@ -101,16 +109,16 @@ router.post('/', ratelimit, deviceCertificateMiddleware, async (request, respons await nexAccount.save({ session }); - const primaryPasswordHash = util.nintendoPasswordHash(person.get('password'), nexAccount.get('pid')); - const passwordHash = await bcrypt.hash(primaryPasswordHash, 10); + const primaryPasswordHash: string = util.nintendoPasswordHash(person.get('password'), nexAccount.get('pid')); + const passwordHash: string = await bcrypt.hash(primaryPasswordHash, 10); - const countryCode = person.get('country'); - const language = person.get('language'); - const timezoneName = person.get('tz_name'); + const countryCode: string = person.get('country'); + const language: string = person.get('language'); + const timezoneName: string = person.get('tz_name'); - const regionLanguages = timezones[countryCode]; - const regionTimezones = regionLanguages[language] ? regionLanguages[language] : Object.values(regionLanguages)[0]; - const timezone = regionTimezones.find(tz => tz.area === timezoneName); + const regionLanguages: RegionLanguages = timezones[countryCode]; + const regionTimezones: RegionTimezones = regionLanguages[language] ? regionLanguages[language] : Object.values(regionLanguages)[0]; + const timezone: RegionTimezone = regionTimezones.find(tz => tz.area === timezoneName); pnid = new PNID({ pid: nexAccount.get('pid'), @@ -197,14 +205,14 @@ router.post('/', ratelimit, deviceCertificateMiddleware, async (request, respons * Replacement for: https://account.nintendo.net/v1/api/people/@me/profile * Description: Gets a users profile */ -router.get('/@me/profile', async (request, response) => { +router.get('/@me/profile', async (request: express.Request, response: express.Response) => { response.set('Content-Type', 'text/xml'); response.set('Server', 'Nintendo 3DS (http)'); response.set('X-Nintendo-Date', new Date().getTime().toString()); - const { pnid } = request; + const pnid: HydratedPNIDDocument = request.pnid; - const person = await database.getUserProfileJSONByPID(pnid.get('pid')); + const person: PNIDProfile = await database.getUserProfileJSONByPID(pnid.get('pid')); response.send(xmlbuilder.create({ person @@ -216,7 +224,7 @@ router.get('/@me/profile', async (request, response) => { * Replacement for: https://account.nintendo.net/v1/api/people/@me/devices * Description: Gets user profile, seems to be the same as https://account.nintendo.net/v1/api/people/@me/profile */ -router.post('/@me/devices', async (request, response) => { +router.post('/@me/devices', async (request: express.Request, response: express.Response) => { response.set('Content-Type', 'text/xml'); response.set('Server', 'Nintendo 3DS (http)'); response.set('X-Nintendo-Date', new Date().getTime().toString()); @@ -225,9 +233,11 @@ router.post('/@me/devices', async (request, response) => { // The console ignores them and PNIDs are not tied to consoles anyway // So the server also ignores them and does not save the ones posted here - const { pnid } = request; + // TODO - CHANGE THIS. WE NEED TO SAVE CONSOLE DETAILS !!! - const person = await database.getUserProfileJSONByPID(pnid.pid); + const pnid: HydratedPNIDDocument = request.pnid; + + const person: PNIDProfile = await database.getUserProfileJSONByPID(pnid.pid); response.send(xmlbuilder.create({ person @@ -239,26 +249,32 @@ router.post('/@me/devices', async (request, response) => { * Replacement for: https://account.nintendo.net/v1/api/people/@me/devices * Description: Returns only user devices */ -router.get('/@me/devices', async (request, response) => { +router.get('/@me/devices', async (request: express.Request, response: express.Response) => { response.set('Content-Type', 'text/xml'); response.set('Server', 'Nintendo 3DS (http)'); response.set('X-Nintendo-Date', new Date().getTime().toString()); - const { pnid, headers } = request; + const pnid: HydratedPNIDDocument = request.pnid; + const deviceId: string = request.headers['x-nintendo-device-id'] as string; + const acceptLanguage: string = request.headers['accept-language'] as string; + const platformId: string = request.headers['x-nintendo-platform-id'] as string; + const region: string = request.headers['x-nintendo-region'] as string; + const serialNumber: string = request.headers['x-nintendo-serial-number'] as string; + const systemVersion: string = request.headers['x-nintendo-system-version'] as string; response.send(xmlbuilder.create({ devices: [ { device: { - device_id: headers['x-nintendo-device-id'], - language: headers['accept-language'], + device_id: deviceId, + language: acceptLanguage, updated: moment().format('YYYY-MM-DDTHH:MM:SS'), pid: pnid.get('pid'), - platform_id: headers['x-nintendo-platform-id'], - region: headers['x-nintendo-region'], - serial_number: headers['x-nintendo-serial-number'], + platform_id: platformId, + region: region, + serial_number: serialNumber, status: 'ACTIVE', - system_version: headers['x-nintendo-system-version'], + system_version: systemVersion, type: 'RETAIL', updated_by: 'USER' } @@ -272,14 +288,14 @@ router.get('/@me/devices', async (request, response) => { * Replacement for: https://account.nintendo.net/v1/api/people/@me/devices/owner * Description: Gets user profile, seems to be the same as https://account.nintendo.net/v1/api/people/@me/profile */ -router.get('/@me/devices/owner', async (request, response) => { +router.get('/@me/devices/owner', async (request: express.Request, response: express.Response) => { response.set('Content-Type', 'text/xml'); response.set('Server', 'Nintendo 3DS (http)'); response.set('X-Nintendo-Date', moment().add(5, 'h').toString()); - const { pnid } = request; + const pnid: HydratedPNIDDocument = request.pnid; - const person = await database.getUserProfileJSONByPID(pnid.get('pid')); + const person: PNIDProfile = await database.getUserProfileJSONByPID(pnid.get('pid')); response.send(xmlbuilder.create({ person @@ -291,7 +307,7 @@ router.get('/@me/devices/owner', async (request, response) => { * Replacement for: https://account.nintendo.net/v1/api/people/@me/devices/status * Description: Unknown use */ -router.get('/@me/devices/status', async (request, response) => { +router.get('/@me/devices/status', async (_request: express.Request, response: express.Response) => { response.set('Content-Type', 'text/xml'); response.set('Server', 'Nintendo 3DS (http)'); response.set('X-Nintendo-Date', moment().add(5, 'h').toString()); @@ -307,12 +323,15 @@ router.get('/@me/devices/status', async (request, response) => { * Replacement for: https://account.nintendo.net/v1/api/people/@me/miis/@primary * Description: Updates a users Mii */ -router.put('/@me/miis/@primary', async (request, response) => { - const { pnid } = request; +router.put('/@me/miis/@primary', async (request: express.Request, response: express.Response) => { + const pnid: HydratedPNIDDocument = request.pnid; - const mii = request.body.get('mii'); + // TODO - Make this more strictly typed? + const mii: Map = request.body.get('mii'); - const [name, primary, data] = [mii.get('name'), mii.get('primary'), mii.get('data')]; + const name: string = mii.get('name'); + const primary: string = mii.get('primary'); + const data: string = mii.get('data'); await pnid.updateMii({ name, primary, data }); @@ -324,11 +343,11 @@ router.put('/@me/miis/@primary', async (request, response) => { * Replacement for: https://account.nintendo.net/v1/api/people/@me/devices/@current/inactivate * Description: Deactivates a user from a console */ -router.put('/@me/devices/@current/inactivate', async (request, response) => { +router.put('/@me/devices/@current/inactivate', async (request: express.Request, response: express.Response) => { response.set('Server', 'Nintendo 3DS (http)'); response.set('X-Nintendo-Date', new Date().getTime().toString()); - const { pnid } = request; + const pnid: HydratedPNIDDocument = request.pnid; if (!pnid) { response.status(400); @@ -353,8 +372,8 @@ router.put('/@me/devices/@current/inactivate', async (request, response) => { * Replacement for: https://account.nintendo.net/v1/api/people/@me/deletion * Description: Deletes a NNID */ -router.put('/@me/deletion', async (request, response) => { - const { pnid } = request; +router.put('/@me/deletion', async (request: express.Request, response: express.Response) => { + const pnid: HydratedPNIDDocument = request.pnid; if (!pnid) { response.status(400); @@ -378,9 +397,9 @@ router.put('/@me/deletion', async (request, response) => { * Replacement for: https://account.nintendo.net/v1/api/people/@me/ * Description: Updates a PNIDs account details */ -router.put('/@me', async (request, response) => { - const { pnid } = request; - const person = request.body.get('person'); +router.put('/@me', async (request: express.Request, response: express.Response) => { + const pnid: HydratedPNIDDocument = request.pnid; + const person: Person = request.body.get('person'); if (!pnid) { response.status(400); @@ -396,27 +415,27 @@ router.put('/@me', async (request, response) => { }).end()); } - const gender = person.get('gender') ? person.get('gender') : pnid.get('gender'); - const region = person.get('region') ? person.get('region') : pnid.get('region'); - const countryCode = person.get('country') ? person.get('country') : pnid.get('country'); - const language = person.get('language') ? person.get('language') : pnid.get('language'); - const timezoneName = person.get('tz_name') ? person.get('tz_name') : pnid.get('timezone.name'); - const marketingFlag = person.get('marketing_flag') ? person.get('marketing_flag') === 'Y' : pnid.get('flags.marketing'); - const offDeviceFlag = person.get('off_device_flag') ? person.get('off_device_flag') === 'Y' : pnid.get('flags.off_device'); + const gender: string = person.get('gender') ? person.get('gender') : pnid.get('gender'); + const region: string = person.get('region') ? person.get('region') : pnid.get('region'); + const countryCode: string = person.get('country') ? person.get('country') : pnid.get('country'); + const language: string = person.get('language') ? person.get('language') : pnid.get('language'); + const timezoneName: string = person.get('tz_name') ? person.get('tz_name') : pnid.get('timezone.name'); + const marketingFlag: boolean = person.get('marketing_flag') ? person.get('marketing_flag') === 'Y' : pnid.get('flags.marketing'); + const offDeviceFlag: boolean = person.get('off_device_flag') ? person.get('off_device_flag') === 'Y' : pnid.get('flags.off_device'); - const regionLanguages = timezones[countryCode]; - const regionTimezones = regionLanguages[language] ? regionLanguages[language] : Object.values(regionLanguages)[0]; - const timezone = regionTimezones.find(tz => tz.area === timezoneName); + const regionLanguages: RegionLanguages = timezones[countryCode]; + const regionTimezones: RegionTimezones = regionLanguages[language] ? regionLanguages[language] : Object.values(regionLanguages)[0]; + const timezone: RegionTimezone = regionTimezones.find(tz => tz.area === timezoneName); if (person.get('password')) { - const primaryPasswordHash = util.nintendoPasswordHash(person.get('password'), pnid.get('pid')); - const passwordHash = await bcrypt.hash(primaryPasswordHash, 10); + const primaryPasswordHash: string = util.nintendoPasswordHash(person.get('password'), pnid.get('pid')); + const passwordHash: string = await bcrypt.hash(primaryPasswordHash, 10); pnid.password = passwordHash; } pnid.gender = gender; - pnid.region = region; + pnid.region = Number(region); pnid.timezone.name = timezoneName; pnid.timezone.offset = Number(timezone.utc_offset); pnid.timezone.marketing = marketingFlag; @@ -432,8 +451,8 @@ router.put('/@me', async (request, response) => { * Replacement for: https://account.nintendo.net/v1/api/people/@me/emails/ * Description: Gets a list (why?) of PNID emails */ -router.get('/@me/emails', async (request, response) => { - const { pnid } = request; +router.get('/@me/emails', async (request: express.Request, response: express.Response) => { + const pnid: HydratedPNIDDocument = request.pnid; if (!pnid) { response.status(400); @@ -473,9 +492,11 @@ router.get('/@me/emails', async (request, response) => { * Replacement for: https://account.nintendo.net/v1/api/people/@me/emails/@primary * Description: Updates a users email address */ -router.put('/@me/emails/@primary', async (request, response) => { - const { pnid } = request; - const email = request.body.get('email'); +router.put('/@me/emails/@primary', async (request: express.Request, response: express.Response) => { + const pnid: HydratedPNIDDocument = request.pnid; + + // TODO - Make this more strictly typed? + const email: Map = request.body.get('email'); if (!pnid) { response.status(400); diff --git a/src/services/nnid/routes/provider.ts b/src/services/nnid/routes/provider.ts index 804839a..ca1de25 100644 --- a/src/services/nnid/routes/provider.ts +++ b/src/services/nnid/routes/provider.ts @@ -1,24 +1,29 @@ -import { Router } from 'express'; +import express from 'express'; import xmlbuilder from 'xmlbuilder'; import fs from 'fs-extra'; import database from '@/database'; import util from '@/util'; import cache from '@/cache'; import { NEXAccount } from '@/models/nex-account'; +import { CryptoOptions } from '@/types/common/crypto-options'; +import { TokenOptions } from '@/types/common/token-options'; +import { HydratedPNIDDocument } from '@/types/mongoose/pnid'; +import { HydratedServerDocument } from '@/types/mongoose/server'; +import { HydratedNEXAccountDocument } from '@/types/mongoose/nex-account'; -const router = Router(); +const router: express.Router = express.Router(); /** * [GET] * Replacement for: https://account.nintendo.net/v1/api/provider/service_token/@me * Description: Gets a service token */ -router.get('/service_token/@me', async (request, response) => { - const { pnid } = request; +router.get('/service_token/@me', async (request: express.Request, response: express.Response) => { + const pnid: HydratedPNIDDocument = request.pnid; - const titleId = request.headers['x-nintendo-title-id'] as string; - const serverAccessLevel = pnid.get('server_access_level'); - const server = await database.getServerByTitleId(titleId, serverAccessLevel); + const titleId: string = request.headers['x-nintendo-title-id'] as string; + const serverAccessLevel: string = pnid.get('server_access_level'); + const server: HydratedServerDocument = await database.getServerByTitleId(titleId, serverAccessLevel); if (!server) { return response.send(xmlbuilder.create({ @@ -31,9 +36,10 @@ router.get('/service_token/@me', async (request, response) => { }).end()); } - const { service_name, device } = server; + const serverName: string = server.service_name; + const device: number = server.device; - const cryptoPath = `${__dirname}/../../../../certs/service/${service_name}`; + const cryptoPath: string = `${__dirname}/../../../../certs/service/${serverName}`; if (!await fs.pathExists(cryptoPath)) { // Need to generate keys @@ -47,15 +53,15 @@ router.get('/service_token/@me', async (request, response) => { }).end()); } - const publicKey = await cache.getServicePublicKey(service_name); - const secretKey = await cache.getServiceSecretKey(service_name); + const publicKey: Buffer = await cache.getServicePublicKey(serverName); + const secretKey: Buffer = await cache.getServiceSecretKey(serverName); - const cryptoOptions = { + const cryptoOptions: CryptoOptions = { public_key: publicKey, hmac_secret: secretKey }; - const tokenOptions = { + const tokenOptions: TokenOptions = { system_type: device, token_type: 0x4, // service token, pid: pnid.get('pid'), @@ -64,7 +70,7 @@ router.get('/service_token/@me', async (request, response) => { expire_time: BigInt(Date.now() + (3600 * 1000)) }; - let serviceToken = await util.generateToken(cryptoOptions, tokenOptions); + let serviceToken: string = await util.generateToken(cryptoOptions, tokenOptions); if (request.isCemu) { serviceToken = Buffer.from(serviceToken, 'base64').toString('hex'); @@ -82,9 +88,9 @@ router.get('/service_token/@me', async (request, response) => { * Replacement for: https://account.nintendo.net/v1/api/provider/nex_token/@me * Description: Gets a NEX server address and token */ -router.get('/nex_token/@me', async (request, response) => { - const { game_server_id: gameServerID } = request.query; - const { pnid } = request; +router.get('/nex_token/@me', async (request: express.Request, response: express.Response) => { + const pnid: HydratedPNIDDocument = request.pnid; + const gameServerID: string = request.query.game_server_id as string; if (!gameServerID) { return response.send(xmlbuilder.create({ @@ -97,8 +103,8 @@ router.get('/nex_token/@me', async (request, response) => { }).end()); } - const serverAccessLevel = pnid.get('server_access_level'); - const server = await database.getServer(gameServerID, serverAccessLevel); + const serverAccessLevel: string = pnid.get('server_access_level'); + const server: HydratedServerDocument = await database.getServer(gameServerID, serverAccessLevel); if (!server) { return response.send(xmlbuilder.create({ @@ -111,10 +117,13 @@ router.get('/nex_token/@me', async (request, response) => { }).end()); } - const { service_name, ip, port, device } = server; - const titleId = request.headers['x-nintendo-title-id'] as string; + const serverName: string = server.service_name; + const ip: string = server.ip; + const port: number = server.port; + const device: number = server.device; + const titleId: string = request.headers['x-nintendo-title-id'] as string; - const cryptoPath = `${__dirname}/../../../../certs/nex/${service_name}`; + const cryptoPath: string = `${__dirname}/../../../../certs/nex/${serverName}`; if (!await fs.pathExists(cryptoPath)) { // Need to generate keys @@ -128,15 +137,15 @@ router.get('/nex_token/@me', async (request, response) => { }).end()); } - const publicKey = await cache.getNEXPublicKey(service_name); - const secretKey= await cache.getNEXSecretKey(service_name); + const publicKey: Buffer = await cache.getNEXPublicKey(serverName); + const secretKey: Buffer = await cache.getNEXSecretKey(serverName); - const cryptoOptions = { + const cryptoOptions: CryptoOptions = { public_key: publicKey, hmac_secret: secretKey }; - const tokenOptions = { + const tokenOptions: TokenOptions = { system_type: device, token_type: 0x3, // nex token, pid: pnid.get('pid'), @@ -145,7 +154,7 @@ router.get('/nex_token/@me', async (request, response) => { expire_time: BigInt(Date.now() + (3600 * 1000)) }; - const nexUser = await NEXAccount.findOne({ + const nexUser: HydratedNEXAccountDocument = await NEXAccount.findOne({ owning_pid: pnid.get('pid') }); @@ -154,7 +163,7 @@ router.get('/nex_token/@me', async (request, response) => { return response.send('0008Not Found'); } - let nexToken = await util.generateToken(cryptoOptions, tokenOptions); + let nexToken: string = await util.generateToken(cryptoOptions, tokenOptions); if (request.isCemu) { nexToken = Buffer.from(nexToken, 'base64').toString('hex'); diff --git a/src/services/nnid/routes/support.ts b/src/services/nnid/routes/support.ts index b07bcff..edfe201 100644 --- a/src/services/nnid/routes/support.ts +++ b/src/services/nnid/routes/support.ts @@ -1,19 +1,20 @@ import dns from 'node:dns'; -import { Router } from 'express'; +import express from 'express'; import xmlbuilder from 'xmlbuilder'; import moment from 'moment'; import database from '@/database'; import util from '@/util'; +import { HydratedPNIDDocument } from '@/types/mongoose/pnid'; -const router = Router(); +const router: express.Router = express.Router(); /** * [POST] * Replacement for: https://account.nintendo.net/v1/api/support/validate/email * Description: Verifies a provided email address is valid */ -router.post('/validate/email', async (request, response) => { - const { email } = request.body; +router.post('/validate/email', async (request: express.Request, response: express.Response) => { + const email: string = request.body; if (!email) { return response.send(xmlbuilder.create({ @@ -27,9 +28,9 @@ router.post('/validate/email', async (request, response) => { }).end()); } - const domain = email.split('@')[1]; + const domain: string = email.split('@')[1]; - dns.resolveMx(domain, (error) => { + dns.resolveMx(domain, (error: NodeJS.ErrnoException) => { if (error) { return response.send(xmlbuilder.create({ errors: { @@ -51,10 +52,11 @@ router.post('/validate/email', async (request, response) => { * Replacement for: https://account.nintendo.net/v1/api/support/email_confirmation/:pid/:code * Description: Verifies a users email via 6 digit code */ -router.put('/email_confirmation/:pid/:code', async (request, response) => { - const { pid, code } = request.params; +router.put('/email_confirmation/:pid/:code', async (request: express.Request, response: express.Response) => { + const code: string = request.params.code; + const pid: number = Number(request.params.pid); - const pnid = await database.getUserByPID(pid); + const pnid: HydratedPNIDDocument = await database.getUserByPID(pid); if (!pnid) { return response.status(400).send(xmlbuilder.create({ @@ -78,7 +80,7 @@ router.put('/email_confirmation/:pid/:code', async (request, response) => { }).end()); } - const validatedDate = moment().format('YYYY-MM-DDTHH:MM:SS'); + const validatedDate: string = moment().format('YYYY-MM-DDTHH:MM:SS'); pnid.set('email.reachable', true); pnid.set('email.validated', true); @@ -96,10 +98,10 @@ router.put('/email_confirmation/:pid/:code', async (request, response) => { * Replacement for: https://account.nintendo.net/v1/api/support/resend_confirmation * Description: Resends a users confirmation email */ -router.get('/resend_confirmation', async (request, response) => { - const pid = request.headers['x-nintendo-pid']; +router.get('/resend_confirmation', async (request: express.Request, response: express.Response) => { + const pid: number = Number(request.headers['x-nintendo-pid']); - const pnid = await database.getUserByPID(pid); + const pnid: HydratedPNIDDocument = await database.getUserByPID(pid); if (!pnid) { // TODO - Unsure if this is the right error @@ -124,10 +126,10 @@ router.get('/resend_confirmation', async (request, response) => { * Description: Sends the user a password reset email * NOTE: On NN this was a temp password that expired after 24 hours. We do not do that */ -router.get('/forgotten_password/:pid', async (request, response) => { - const { pid } = request.params; +router.get('/forgotten_password/:pid', async (request: express.Request, response: express.Response) => { + const pid: number = Number(request.params.pid); - const pnid = await database.getUserByPID(pid); + const pnid: HydratedPNIDDocument = await database.getUserByPID(pid); if (!pnid) { // TODO - Better errors diff --git a/src/types/common/config.ts b/src/types/common/config.ts index ed3fbb2..4ce668d 100644 --- a/src/types/common/config.ts +++ b/src/types/common/config.ts @@ -37,7 +37,7 @@ export interface Config { base_url: string; }; website_base: string; -}; +} export interface DisabledFeatures { redis: boolean; diff --git a/src/types/common/crypto-options.ts b/src/types/common/crypto-options.ts new file mode 100644 index 0000000..d4a5918 --- /dev/null +++ b/src/types/common/crypto-options.ts @@ -0,0 +1,4 @@ +export interface CryptoOptions { + public_key: Buffer; + hmac_secret: Buffer; +}; \ No newline at end of file diff --git a/src/types/common/mailer-options.ts b/src/types/common/mailer-options.ts new file mode 100644 index 0000000..277cf33 --- /dev/null +++ b/src/types/common/mailer-options.ts @@ -0,0 +1,16 @@ +export interface MailerOptions { + to: string; + subject: string; + username: string; + paragraph?: string; + preview?: string; + text: string; + link?: { + href: string; + text: string; + }; + confirmation?: { + href: string; + code: string; + }; +}; \ No newline at end of file diff --git a/src/types/common/signature-size.ts b/src/types/common/signature-size.ts new file mode 100644 index 0000000..3fe755e --- /dev/null +++ b/src/types/common/signature-size.ts @@ -0,0 +1,4 @@ +export interface SignatureSize { + SIZE: number; + PADDING_SIZE: number; +}; \ No newline at end of file diff --git a/src/types/common/token-options.ts b/src/types/common/token-options.ts new file mode 100644 index 0000000..4a9bb15 --- /dev/null +++ b/src/types/common/token-options.ts @@ -0,0 +1,8 @@ +export interface TokenOptions { + system_type: number; + token_type: number; + pid: number; + access_level?: number; + title_id?: bigint; + expire_time: bigint; +}; \ No newline at end of file diff --git a/src/types/common/token.ts b/src/types/common/token.ts new file mode 100644 index 0000000..84673c2 --- /dev/null +++ b/src/types/common/token.ts @@ -0,0 +1,8 @@ +export interface Token { + system_type: number; + token_type: number; + pid: number; + access_level?: number; + title_id?: bigint; + expire_time: bigint; +}; \ No newline at end of file diff --git a/src/types/common/yes-no-bool-string.ts b/src/types/common/yes-no-bool-string.ts new file mode 100644 index 0000000..474aa6e --- /dev/null +++ b/src/types/common/yes-no-bool-string.ts @@ -0,0 +1 @@ +export type YesNoBoolString = 'Y' | 'N'; \ No newline at end of file diff --git a/src/types/express.d.ts b/src/types/express.d.ts index e6669ab..26882cb 100644 --- a/src/types/express.d.ts +++ b/src/types/express.d.ts @@ -1,13 +1,12 @@ -import { HydratedDocument } from 'mongoose'; import NintendoCertificate from '@/nintendo-certificate'; -import { IPNID, IPNIDMethods } from '@/types/mongoose/pnid'; -import { INEXAccount, INEXAccountMethods } from '@/types/mongoose/nex-account'; +import { HydratedPNIDDocument } from '@/types/mongoose/pnid'; +import { HydratedNEXAccountDocument } from '@/types/mongoose/nex-account'; declare global { namespace Express { interface Request { - pnid?: HydratedDocument; - nexUser?: HydratedDocument; + pnid?: HydratedPNIDDocument; + nexUser?: HydratedNEXAccountDocument; isCemu?: boolean; files?: Record; certificate?: NintendoCertificate; diff --git a/src/types/mongoose/device-attribute.ts b/src/types/mongoose/device-attribute.ts index 2c5a2bf..d77c3d8 100644 --- a/src/types/mongoose/device-attribute.ts +++ b/src/types/mongoose/device-attribute.ts @@ -1,11 +1,13 @@ import { Model } from 'mongoose'; export interface IDeviceAttribute { - created_date: string; + created_date?: string; name: string; value: string; } export interface IDeviceAttributeMethods {} -export interface DeviceAttributeModel extends Model {} \ No newline at end of file +export interface IDeviceAttributeQueryHelpers {} + +export interface DeviceAttributeModel extends Model {} \ No newline at end of file diff --git a/src/types/mongoose/device.ts b/src/types/mongoose/device.ts index 9fe4ba3..d9dcb1f 100644 --- a/src/types/mongoose/device.ts +++ b/src/types/mongoose/device.ts @@ -1,5 +1,5 @@ -import { Model } from 'mongoose'; -import { DeviceAttributeSchema } from '@/models/device'; +import { Model, Types, HydratedDocument } from 'mongoose'; +import { IDeviceAttribute } from '@/types/mongoose/device-attribute'; type MODEL = 'wup' | 'ctr' | 'spr' | 'ftr' | 'ktr' | 'red' | 'jan'; type ACCESS_LEVEL = 0 | 1 | 2 | 3; @@ -11,11 +11,11 @@ export interface IDevice { device_id: number; device_type: number; serial: string; - device_attributes: typeof DeviceAttributeSchema[]; + device_attributes: Types.DocumentArray; soap: { token: string; account_id: number; - }, + }; // * 3DS-specific stuff environment: string; mac_hash: string; @@ -27,4 +27,8 @@ export interface IDevice { export interface IDeviceMethods {} -export interface DeviceModel extends Model {} \ No newline at end of file +export interface IDeviceQueryHelpers {} + +export interface DeviceModel extends Model {} + +export type HydratedDeviceDocument = HydratedDocument \ No newline at end of file diff --git a/src/types/mongoose/nex-account.ts b/src/types/mongoose/nex-account.ts index b109527..fadc1bb 100644 --- a/src/types/mongoose/nex-account.ts +++ b/src/types/mongoose/nex-account.ts @@ -1,4 +1,4 @@ -import { Model } from 'mongoose'; +import { Model, HydratedDocument } from 'mongoose'; type DEVICE = 'wiiu' | '3ds'; type ACCESS_LEVEL = 0 | 1 | 2 | 3; @@ -17,4 +17,8 @@ export interface INEXAccountMethods { generatePassword(): void; } -export interface NEXAccountModel extends Model {} \ No newline at end of file +export interface INEXAccountQueryHelpers {} + +export interface NEXAccountModel extends Model {} + +export type HydratedNEXAccountDocument = HydratedDocument \ No newline at end of file diff --git a/src/types/mongoose/pnid.ts b/src/types/mongoose/pnid.ts index e52f064..8f87458 100644 --- a/src/types/mongoose/pnid.ts +++ b/src/types/mongoose/pnid.ts @@ -1,5 +1,5 @@ -import { Model } from 'mongoose'; -import { DeviceSchema } from '@/models/device'; +import { Model, Types, HydratedDocument } from 'mongoose'; +import { IDevice } from '@/types/mongoose/device'; export interface IPNID { access_level: number; @@ -44,14 +44,14 @@ export interface IPNID { marketing: boolean; off_device: boolean; }; - devices: typeof DeviceSchema[]; + devices: Types.DocumentArray; identification: { // user identification tokens email_code: string; email_token: string; access_token: { value: string; ttl: number; - }, + }; refresh_token: { value: string; ttl: number; @@ -73,4 +73,8 @@ export interface IPNIDMethods { getServerMode(): string; } -export interface PNIDModel extends Model {} \ No newline at end of file +export interface IPNIDQueryHelpers {} + +export interface PNIDModel extends Model {} + +export type HydratedPNIDDocument = HydratedDocument \ No newline at end of file diff --git a/src/types/mongoose/server.ts b/src/types/mongoose/server.ts index f25198e..65865d2 100644 --- a/src/types/mongoose/server.ts +++ b/src/types/mongoose/server.ts @@ -1,8 +1,8 @@ -import { Model } from 'mongoose'; +import { Model, HydratedDocument } from 'mongoose'; export interface IServer { ip: string; // Example: 1.1.1.1 - port: Number; // Example: 60000 + port: number; // Example: 60000 service_name: string; // Example: friends service_type: string; // Example: nex game_server_id: string; // Example: 00003200 @@ -14,4 +14,8 @@ export interface IServer { export interface IServerMethods {} -export interface ServerModel extends Model {} \ No newline at end of file +export interface IServerQueryHelpers {} + +export interface ServerModel extends Model {} + +export type HydratedServerDocument = HydratedDocument \ No newline at end of file diff --git a/src/types/services/api/connection-data.ts b/src/types/services/api/connection-data.ts new file mode 100644 index 0000000..54fe992 --- /dev/null +++ b/src/types/services/api/connection-data.ts @@ -0,0 +1,4 @@ +import { DiscordConnectionData } from '@/types/services/api/discord-connection-data'; + +// TODO - This will be a union of all ConnectionData types when more connections are added +export type ConnectionData = DiscordConnectionData; \ No newline at end of file diff --git a/src/types/services/api/connection-response.ts b/src/types/services/api/connection-response.ts new file mode 100644 index 0000000..6baa9b0 --- /dev/null +++ b/src/types/services/api/connection-response.ts @@ -0,0 +1,5 @@ +export interface ConnectionResponse { + app: string; + status: number; + error?: string; +} \ No newline at end of file diff --git a/src/types/services/api/discord-connection-data.ts b/src/types/services/api/discord-connection-data.ts new file mode 100644 index 0000000..b9ed367 --- /dev/null +++ b/src/types/services/api/discord-connection-data.ts @@ -0,0 +1,3 @@ +export interface DiscordConnectionData { + id: string; +}; \ No newline at end of file diff --git a/src/types/services/api/update-user-request.ts b/src/types/services/api/update-user-request.ts new file mode 100644 index 0000000..a525e98 --- /dev/null +++ b/src/types/services/api/update-user-request.ts @@ -0,0 +1,7 @@ +export interface UpdateUserRequest { + mii?: { + name: string; + primary: string; + data: string; + } +} \ No newline at end of file diff --git a/src/types/services/nasc/request-params.ts b/src/types/services/nasc/request-params.ts new file mode 100644 index 0000000..aafcda6 --- /dev/null +++ b/src/types/services/nasc/request-params.ts @@ -0,0 +1,11 @@ +export interface NASCRequestParams { + action: string; + fcdcert: string; + csnum: string; + macadr: string; + titleid: string; + servertype: string; + userid?: string; + uidhmac?: string; + passwd?: string; +} \ No newline at end of file diff --git a/src/types/services/nnid/person.ts b/src/types/services/nnid/person.ts new file mode 100644 index 0000000..9da3757 --- /dev/null +++ b/src/types/services/nnid/person.ts @@ -0,0 +1,44 @@ +import { YesNoBoolString } from '@/types/common/yes-no-bool-string'; +import { IDeviceAttribute } from '@/types/mongoose/device-attribute'; + +// TODO - Change this Map type to something more strongly typed +export interface Person extends Map { + birth_date: string; + user_id: string; + password: string; + country: string; + language: string; + tz_name: string; + agreement: { + agreement_date: string; + country: string; + location: string; + type: string; + version: string; + }; + email: { + address: string; + owned: YesNoBoolString; + parent: YesNoBoolString; + primary: YesNoBoolString; + validated: YesNoBoolString; + type: string; + }; + mii: { + name: string; + primary: YesNoBoolString; + data: string; + }; + parental_consent: { + scope: string; + consent_date: string; + approval_id: string; + }; + gender: string; + region: string; + marketing_flag: YesNoBoolString; + device_attributes: { + device_attribute: IDeviceAttribute[] + }; + off_device_flag: YesNoBoolString; +} \ No newline at end of file diff --git a/src/types/services/nnid/pnid-profile.ts b/src/types/services/nnid/pnid-profile.ts new file mode 100644 index 0000000..ac95915 --- /dev/null +++ b/src/types/services/nnid/pnid-profile.ts @@ -0,0 +1,53 @@ +import { YesNoBoolString } from '@/types/common/yes-no-bool-string'; + +export interface PNIDProfile { + //accounts: {}; // * We need to figure this out; no idea what these values mean or what they do + active_flag: YesNoBoolString; + birth_date: string; + country: string; + create_date: string; + device_attributes: [{ + device_attribute: { + name: string; + value: string; + created_date: string; + }; + }]; + gender: string; + language: string; + updated: string; + marketing_flag: YesNoBoolString; + off_device_flag: YesNoBoolString; + pid: number; + email: { + address: string; + id: string; + parent: YesNoBoolString; + primary: YesNoBoolString; + reachable: YesNoBoolString; + type: 'DEFAULT'; + updated_by: 'USER'; // * Can also be INTERNAL WS; don't know the difference + validated: YesNoBoolString; + validated_date: string; + }; + mii: { + status: 'COMPLETED'; + data: string; + id: number; + mii_hash: string; + mii_images: { + mii_image: { + cached_url: string; + id: string; + url: string; + type: 'standard'; + } + }; + name: string; + primary: YesNoBoolString; + }; + region: number; + tz_name: string; + user_id: string; + utc_offset: number; +} \ No newline at end of file diff --git a/src/types/services/nnid/region-languages.ts b/src/types/services/nnid/region-languages.ts new file mode 100644 index 0000000..e25307d --- /dev/null +++ b/src/types/services/nnid/region-languages.ts @@ -0,0 +1,5 @@ +import { RegionTimezones } from '@/types/services/nnid/region-timezones'; + +export interface RegionLanguages { + [myKey: string]: RegionTimezones +} \ No newline at end of file diff --git a/src/types/services/nnid/region-timezones.ts b/src/types/services/nnid/region-timezones.ts new file mode 100644 index 0000000..c904aaa --- /dev/null +++ b/src/types/services/nnid/region-timezones.ts @@ -0,0 +1,9 @@ +export interface RegionTimezone { + area: string; + language: string; + name: string; + utc_offset: string; + order: string; +} + +export type RegionTimezones = RegionTimezone[]; \ No newline at end of file diff --git a/src/util.ts b/src/util.ts index e383f93..eaec4c4 100644 --- a/src/util.ts +++ b/src/util.ts @@ -3,11 +3,18 @@ import path from 'node:path'; import NodeRSA from 'node-rsa'; import aws from 'aws-sdk'; import fs from 'fs-extra'; +import express from 'express'; +import mongoose from 'mongoose'; import mailer from '@/mailer'; import cache from '@/cache'; import { config, disabledFeatures } from '@/config-manager'; +import { CryptoOptions } from '@/types/common/crypto-options'; +import { TokenOptions } from '@/types/common/token-options'; +import { Token } from '@/types/common/token'; +import { IPNID, IPNIDMethods } from '@/types/mongoose/pnid'; +import { MailerOptions } from '@/types/common/mailer-options'; -let s3; +let s3: aws.S3; if (!disabledFeatures.s3) { s3 = new aws.S3({ @@ -18,17 +25,16 @@ if (!disabledFeatures.s3) { } function nintendoPasswordHash(password: string, pid: number): string { - const pidBuffer = Buffer.alloc(4); + const pidBuffer: Buffer = Buffer.alloc(4); pidBuffer.writeUInt32LE(pid); - const unpacked = Buffer.concat([ + const unpacked: Buffer = Buffer.concat([ pidBuffer, Buffer.from('\x02\x65\x43\x46'), Buffer.from(password) ]); - const hashed = crypto.createHash('sha256').update(unpacked).digest().toString('hex'); - return hashed; + return crypto.createHash('sha256').update(unpacked).digest().toString('hex'); } function nintendoBase64Decode(encoded: string): Buffer { @@ -37,41 +43,42 @@ function nintendoBase64Decode(encoded: string): Buffer { } function nintendoBase64Encode(decoded: string | Buffer): string { - const encoded = Buffer.from(decoded).toString('base64'); + const encoded: string = Buffer.from(decoded).toString('base64'); return encoded.replaceAll('+', '.').replaceAll('/', '-').replaceAll('=', '*'); } -async function generateToken(cryptoOptions, tokenOptions): Promise { +async function generateToken(cryptoOptions: CryptoOptions | null, tokenOptions: TokenOptions): Promise { // Access and refresh tokens use a different format since they must be much smaller // They take no extra crypto options if (!cryptoOptions) { - const aesKey = await cache.getServiceAESKey('account', 'hex'); + const aesKey: Buffer = await cache.getServiceAESKey('account', 'hex'); - const dataBuffer = Buffer.alloc(1 + 1 + 4 + 8); + const dataBuffer: Buffer = Buffer.alloc(1 + 1 + 4 + 8); dataBuffer.writeUInt8(tokenOptions.system_type, 0x0); dataBuffer.writeUInt8(tokenOptions.token_type, 0x1); dataBuffer.writeUInt32LE(tokenOptions.pid, 0x2); dataBuffer.writeBigUInt64LE(tokenOptions.expire_time, 0x6); - const iv = Buffer.alloc(16); - const cipher = crypto.createCipheriv('aes-128-cbc', aesKey, iv); + const iv: Buffer = Buffer.alloc(16); + const cipher: crypto.Cipher = crypto.createCipheriv('aes-128-cbc', aesKey, iv); - let encryptedBody = cipher.update(dataBuffer); + let encryptedBody: Buffer = cipher.update(dataBuffer); encryptedBody = Buffer.concat([encryptedBody, cipher.final()]); return encryptedBody.toString('base64'); } - const publicKey = new NodeRSA(cryptoOptions.public_key, 'pkcs8-public-pem', { + const publicKey: NodeRSA = new NodeRSA(cryptoOptions.public_key, 'pkcs8-public-pem', { environment: 'browser', encryptionScheme: { - 'hash': 'sha256', + scheme: 'pkcs1_oaep', + hash: 'sha256' } }); // Create the buffer containing the token data - const dataBuffer = Buffer.alloc(1 + 1 + 4 + 1 + 8 + 8); + const dataBuffer: Buffer = Buffer.alloc(1 + 1 + 4 + 1 + 8 + 8); dataBuffer.writeUInt8(tokenOptions.system_type, 0x0); dataBuffer.writeUInt8(tokenOptions.token_type, 0x1); @@ -81,8 +88,8 @@ async function generateToken(cryptoOptions, tokenOptions): Promise { dataBuffer.writeBigUInt64LE(tokenOptions.expire_time, 0xF); // Calculate the signature of the token body - const hmac = crypto.createHmac('sha1', cryptoOptions.hmac_secret).update(dataBuffer); - const signature = hmac.digest(); + const hmac: crypto.Hmac = crypto.createHmac('sha1', cryptoOptions.hmac_secret).update(dataBuffer); + const signature: Buffer = hmac.digest(); // You can thank the 3DS for the shit thats about to happen with the AES IV // The 3DS only allows for strings up to 255 characters in NEX @@ -90,35 +97,37 @@ async function generateToken(cryptoOptions, tokenOptions): Promise { // I am sorry, and have already asked every God I could think of for forgiveness // Generate random AES key - const key = crypto.randomBytes(16); + const key: Buffer = crypto.randomBytes(16); // Encrypt the AES key with RSA public key - const encryptedKey = publicKey.encrypt(key); + const encryptedKey: Buffer = publicKey.encrypt(key); // Take two random points in the RSA encrypted key - const point1 = ~~((encryptedKey.length - 8) * Math.random()); - const point2 = ~~((encryptedKey.length - 8) * Math.random()); + const point1: number = ~~((encryptedKey.length - 8) * Math.random()); + const point2: number = ~~((encryptedKey.length - 8) * Math.random()); // Build an IV from each of the two points - const iv = Buffer.concat([ + const iv: Buffer = Buffer.concat([ Buffer.from(encryptedKey.subarray(point1, point1 + 8)), Buffer.from(encryptedKey.subarray(point2, point2 + 8)) ]); - const cipher = crypto.createCipheriv('aes-128-cbc', key, iv); + const cipher: crypto.Cipher = crypto.createCipheriv('aes-128-cbc', key, iv); // Encrypt the token body with AES - let encryptedBody = cipher.update(dataBuffer); - encryptedBody = Buffer.concat([encryptedBody, cipher.final()]); + const encryptedBody: Buffer = Buffer.concat([ + cipher.update(dataBuffer), + cipher.final() + ]); // Create crypto config token section - const cryptoConfig = Buffer.concat([ + const cryptoConfig: Buffer = Buffer.concat([ encryptedKey, Buffer.from([point1, point2]) ]); // Build the token - const token = Buffer.concat([ + const token: Buffer = Buffer.concat([ cryptoConfig, signature, encryptedBody @@ -127,54 +136,59 @@ async function generateToken(cryptoOptions, tokenOptions): Promise { return token.toString('base64'); // Encode to base64 for transport } -async function decryptToken(token) { +async function decryptToken(token: Buffer): Promise { // Access and refresh tokens use a different format since they must be much smaller // Assume a small length means access or refresh token if (token.length <= 32) { - const aesKey = await cache.getServiceAESKey('account', 'hex'); + const aesKey: Buffer = await cache.getServiceAESKey('account', 'hex'); - const iv = Buffer.alloc(16); + const iv: Buffer = Buffer.alloc(16); - const decipher = crypto.createDecipheriv('aes-128-cbc', aesKey, iv); + const decipher: crypto.Decipher = crypto.createDecipheriv('aes-128-cbc', aesKey, iv); - let decryptedBody = decipher.update(token); - decryptedBody = Buffer.concat([decryptedBody, decipher.final()]); + const decryptedBody: Buffer = Buffer.concat([ + decipher.update(token), + decipher.final() + ]); return decryptedBody; } - const privateKeyBytes = await cache.getServicePrivateKey('account'); - const secretKey = await cache.getServiceSecretKey('account'); + const privateKeyBytes: Buffer = await cache.getServicePrivateKey('account'); + const secretKey: Buffer = await cache.getServiceSecretKey('account'); - const privateKey = new NodeRSA(privateKeyBytes, 'pkcs1-private-pem', { + const privateKey: NodeRSA = new NodeRSA(privateKeyBytes, 'pkcs1-private-pem', { environment: 'browser', encryptionScheme: { - 'hash': 'sha256', + scheme: 'pkcs1_oaep', + hash: 'sha256' } }); - const cryptoConfig = token.subarray(0, 0x82); - const signature = token.subarray(0x82, 0x96); - const encryptedBody = token.subarray(0x96); + const cryptoConfig: Buffer = token.subarray(0, 0x82); + const signature: Buffer = token.subarray(0x82, 0x96); + const encryptedBody: Buffer = token.subarray(0x96); - const encryptedAESKey = cryptoConfig.subarray(0, 128); - const point1 = cryptoConfig.readInt8(0x80); - const point2 = cryptoConfig.readInt8(0x81); + const encryptedAESKey: Buffer = cryptoConfig.subarray(0, 128); + const point1: number = cryptoConfig.readInt8(0x80); + const point2: number = cryptoConfig.readInt8(0x81); - const iv = Buffer.concat([ + const iv: Buffer = Buffer.concat([ Buffer.from(encryptedAESKey.subarray(point1, point1 + 8)), Buffer.from(encryptedAESKey.subarray(point2, point2 + 8)) ]); - const decryptedAESKey = privateKey.decrypt(encryptedAESKey); + const decryptedAESKey: Buffer = privateKey.decrypt(encryptedAESKey); - const decipher = crypto.createDecipheriv('aes-128-cbc', decryptedAESKey, iv); + const decipher: crypto.Decipher = crypto.createDecipheriv('aes-128-cbc', decryptedAESKey, iv); - let decryptedBody = decipher.update(encryptedBody); - decryptedBody = Buffer.concat([decryptedBody, decipher.final()]); + const decryptedBody: Buffer = Buffer.concat([ + decipher.update(encryptedBody), + decipher.final() + ]); - const hmac = crypto.createHmac('sha1', secretKey).update(decryptedBody); - const calculatedSignature = hmac.digest(); + const hmac: crypto.Hmac = crypto.createHmac('sha1', secretKey).update(decryptedBody); + const calculatedSignature: Buffer = hmac.digest(); if (!signature.equals(calculatedSignature)) { console.log('Token signature did not match'); @@ -184,9 +198,9 @@ async function decryptToken(token) { return decryptedBody; } -function unpackToken(token) { +function unpackToken(token: Buffer): Token { if (token.length <= 14) { - return { + return { system_type: token.readUInt8(0x0), token_type: token.readUInt8(0x1), pid: token.readUInt32LE(0x2), @@ -194,7 +208,7 @@ function unpackToken(token) { }; } - return { + return { system_type: token.readUInt8(0x0), token_type: token.readUInt8(0x1), pid: token.readUInt32LE(0x2), @@ -204,49 +218,45 @@ function unpackToken(token) { }; } -function fullUrl(request) { - const protocol = request.protocol; - const host = request.host; - const opath = request.originalUrl; +function fullUrl(request: express.Request): string { + const protocol: string = request.protocol; + const host: string = request.host; + const opath: string = request.originalUrl; return `${protocol}://${host}${opath}`; } -async function uploadCDNAsset(bucket, key, data, acl) { +async function uploadCDNAsset(bucket: string, key: string, data: Buffer, acl: string): Promise { if (disabledFeatures.s3) { await writeLocalCDNFile(key, data); } else { - const awsPutParams = { + await s3.putObject({ Body: data, Key: key, Bucket: bucket, ACL: acl - }; - - await s3.putObject(awsPutParams).promise(); + }).promise(); } } -async function writeLocalCDNFile(key, data) { - const filePath = config.cdn.disk_path; - const folder = path.dirname(filePath); +async function writeLocalCDNFile(key: string, data: Buffer): Promise { + const filePath: string = config.cdn.disk_path; + const folder: string = path.dirname(filePath); await fs.ensureDir(folder); await fs.writeFile(filePath, data); } -function nascError(errorCode) { - const params = new URLSearchParams({ +function nascError(errorCode: string): URLSearchParams { + return new URLSearchParams({ retry: nintendoBase64Encode('1'), returncd: errorCode == 'null' ? errorCode : nintendoBase64Encode(errorCode), datetime: nintendoBase64Encode(Date.now().toString()), }); - - return params; } -async function sendConfirmationEmail(pnid) { - await mailer.sendMail({ +async function sendConfirmationEmail(pnid: mongoose.HydratedDocument): Promise { + const options: MailerOptions = { to: pnid.get('email.address'), subject: '[Pretendo Network] Please confirm your email address', username: pnid.get('username'), @@ -255,30 +265,33 @@ async function sendConfirmationEmail(pnid) { code: pnid.get('identification.email_code') }, text: `Hello ${pnid.get('username')}! \r\n\r\nYour Pretendo Network ID activation is almost complete. Please click the link to confirm your e-mail address and complete the activation process: \r\nhttps://api.pretendo.cc/v1/email/verify?token=${pnid.get('identification.email_token')} \r\n\r\nYou may also enter the following 6-digit code on your console: ${pnid.get('identification.email_code')}` - }); + }; + await mailer.sendMail(options); } -async function sendEmailConfirmedEmail(pnid) { - await mailer.sendMail({ +async function sendEmailConfirmedEmail(pnid: mongoose.HydratedDocument): Promise { + const options: MailerOptions = { to: pnid.get('email.address'), subject: '[Pretendo Network] Email address confirmed', username: pnid.get('username'), paragraph: 'your email address has been confirmed. We hope you have fun on Pretendo Network!', text: `Dear ${pnid.get('username')}, \r\n\r\nYour email address has been confirmed. We hope you have fun on Pretendo Network!` - }); + }; + + await mailer.sendMail(options); } -async function sendForgotPasswordEmail(pnid) { - const publicKey = await cache.getServicePublicKey('account'); - const secretKey = await cache.getServiceSecretKey('account'); +async function sendForgotPasswordEmail(pnid: mongoose.HydratedDocument): Promise { + const publicKey: Buffer = await cache.getServicePublicKey('account'); + const secretKey: Buffer = await cache.getServiceSecretKey('account'); - const cryptoOptions = { + const cryptoOptions: CryptoOptions = { public_key: publicKey, hmac_secret: secretKey }; - const tokenOptions = { + const tokenOptions: TokenOptions = { system_type: 0xF, // API token_type: 0x5, // Password reset pid: pnid.get('pid'), @@ -287,9 +300,9 @@ async function sendForgotPasswordEmail(pnid) { expire_time: BigInt(Date.now() + (24 * 60 * 60 * 1000)) // Only valid for 24 hours }; - const passwordResetToken = await generateToken(cryptoOptions, tokenOptions); + const passwordResetToken: string = await generateToken(cryptoOptions, tokenOptions); - await mailer.sendMail({ + const mailerOptions: MailerOptions = { to: pnid.get('email.address'), subject: '[Pretendo Network] Forgot Password', username: pnid.get('username'), @@ -299,7 +312,9 @@ async function sendForgotPasswordEmail(pnid) { href: `${config.website_base}/account/reset-password?token=${encodeURIComponent(passwordResetToken)}` }, text: `Dear ${pnid.get('username')}, a password reset has been requested from this account. \r\n\r\nIf you did not request the password reset, please ignore this email. \r\nIf you did request this password reset, please click the link to reset your password: ${config.website_base}/account/reset-password?token=${encodeURIComponent(passwordResetToken)}` - }); + }; + + await mailer.sendMail(mailerOptions); } export default { diff --git a/tsconfig.json b/tsconfig.json index 16ad818..e8db98d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -11,7 +11,11 @@ "noEmitOnError": true, "paths": { "@/*": ["./*"] - } + }, + "typeRoots": [ + "node_modules/@types", + "node_modules/@hcaptcha" + ] }, "include": ["src"] } \ No newline at end of file From 7660b9134c76a90b2175b591aef109e59b86e5ad Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Mon, 6 Mar 2023 21:07:35 -0500 Subject: [PATCH 031/219] Removed unused exports --- src/cache.ts | 42 +++++++++++++------------- src/config-manager.ts | 7 +---- src/database.ts | 30 +++++++++--------- src/mailer.ts | 2 +- src/middleware/api.ts | 2 +- src/middleware/cemu.ts | 2 +- src/middleware/client-header.ts | 2 +- src/middleware/device-certificate.ts | 2 +- src/middleware/nasc.ts | 2 +- src/middleware/pnid.ts | 2 +- src/middleware/xml-parser.ts | 2 +- src/models/device.ts | 13 ++------ src/models/nex-account.ts | 9 ++---- src/models/pnid.ts | 9 ++---- src/models/server.ts | 9 ++---- src/nintendo-certificate.ts | 4 +-- src/types/mongoose/device-attribute.ts | 2 +- src/types/mongoose/device.ts | 2 +- src/types/mongoose/nex-account.ts | 2 +- src/types/mongoose/pnid.ts | 2 +- src/types/mongoose/server.ts | 2 +- 21 files changed, 60 insertions(+), 89 deletions(-) diff --git a/src/cache.ts b/src/cache.ts index 7da86d2..f14f2d1 100644 --- a/src/cache.ts +++ b/src/cache.ts @@ -10,7 +10,7 @@ const SERVICE_CERTS_BASE: string = `${__dirname}/../certs/service`; const NEX_CERTS_BASE: string = `${__dirname}/../certs/nex`; const LOCAL_CDN_BASE: string = `${__dirname}/../cdn`; -export async function connect(): Promise { +async function connect(): Promise { if (!disabledFeatures.redis) { client = redis.createClient(config.redis.client); client.on('error', (err) => console.log('Redis Client Error', err)); @@ -19,7 +19,7 @@ export async function connect(): Promise { } } -export async function setCachedFile(fileName: string, value: Buffer): Promise { +async function setCachedFile(fileName: string, value: Buffer): Promise { if (disabledFeatures.redis) { memoryCache[fileName] = value; } else { @@ -27,7 +27,7 @@ export async function setCachedFile(fileName: string, value: Buffer): Promise { +async function getCachedFile(fileName: string, encoding?: BufferEncoding): Promise { let cachedFile: Buffer; if (disabledFeatures.redis) { @@ -44,7 +44,7 @@ export async function getCachedFile(fileName: string, encoding?: BufferEncoding) // * NEX server cache functions -export async function getNEXPublicKey(name: string, encoding?: BufferEncoding): Promise { +async function getNEXPublicKey(name: string, encoding?: BufferEncoding): Promise { let publicKey: Buffer = await getCachedFile(`nex:${name}:public_key`, encoding); if (publicKey === null) { @@ -55,7 +55,7 @@ export async function getNEXPublicKey(name: string, encoding?: BufferEncoding): return publicKey; } -export async function getNEXPrivateKey(name: string, encoding?: BufferEncoding): Promise { +async function getNEXPrivateKey(name: string, encoding?: BufferEncoding): Promise { let privateKey: Buffer = await getCachedFile(`nex:${name}:private_key`, encoding); if (privateKey === null) { @@ -66,7 +66,7 @@ export async function getNEXPrivateKey(name: string, encoding?: BufferEncoding): return privateKey; } -export async function getNEXSecretKey(name: string, encoding?: BufferEncoding): Promise { +async function getNEXSecretKey(name: string, encoding?: BufferEncoding): Promise { let secretKey: Buffer = await getCachedFile(`nex:${name}:secret_key`, encoding); if (secretKey === null) { @@ -78,7 +78,7 @@ export async function getNEXSecretKey(name: string, encoding?: BufferEncoding): return secretKey; } -export async function getNEXAESKey(name: string, encoding?: BufferEncoding): Promise { +async function getNEXAESKey(name: string, encoding?: BufferEncoding): Promise { let aesKey: Buffer = await getCachedFile(`nex:${name}:aes_key`, encoding); if (aesKey === null) { @@ -90,25 +90,25 @@ export async function getNEXAESKey(name: string, encoding?: BufferEncoding): Pro return aesKey; } -export async function setNEXPublicKey(name: string, value: Buffer): Promise { +async function setNEXPublicKey(name: string, value: Buffer): Promise { await setCachedFile(`nex:${name}:public_key`, value); } -export async function setNEXPrivateKey(name: string, value: Buffer): Promise { +async function setNEXPrivateKey(name: string, value: Buffer): Promise { await setCachedFile(`nex:${name}:private_key`, value); } -export async function setNEXSecretKey(name: string, value: Buffer): Promise { +async function setNEXSecretKey(name: string, value: Buffer): Promise { await setCachedFile(`nex:${name}:secret_key`, value); } -export async function setNEXAESKey(name: string, value: Buffer): Promise { +async function setNEXAESKey(name: string, value: Buffer): Promise { await setCachedFile(`nex:${name}:aes_key`, value); } // * 3rd party service cache functions -export async function getServicePublicKey(name: string, encoding?: BufferEncoding): Promise { +async function getServicePublicKey(name: string, encoding?: BufferEncoding): Promise { let publicKey: Buffer = await getCachedFile(`service:${name}:public_key`, encoding); if (publicKey === null) { @@ -119,7 +119,7 @@ export async function getServicePublicKey(name: string, encoding?: BufferEncodin return publicKey; } -export async function getServicePrivateKey(name: string, encoding?: BufferEncoding): Promise { +async function getServicePrivateKey(name: string, encoding?: BufferEncoding): Promise { let privateKey: Buffer = await getCachedFile(`service:${name}:private_key`, encoding); if (privateKey === null) { @@ -130,7 +130,7 @@ export async function getServicePrivateKey(name: string, encoding?: BufferEncodi return privateKey; } -export async function getServiceSecretKey(name: string, encoding?: BufferEncoding): Promise { +async function getServiceSecretKey(name: string, encoding?: BufferEncoding): Promise { let secretKey: Buffer = await getCachedFile(`service:${name}:secret_key`, encoding); if (secretKey === null) { @@ -142,7 +142,7 @@ export async function getServiceSecretKey(name: string, encoding?: BufferEncodin return secretKey; } -export async function getServiceAESKey(name: string, encoding?: BufferEncoding): Promise { +async function getServiceAESKey(name: string, encoding?: BufferEncoding): Promise { let aesKey: Buffer = await getCachedFile(`service:${name}:aes_key`, encoding); if (aesKey === null) { @@ -154,25 +154,25 @@ export async function getServiceAESKey(name: string, encoding?: BufferEncoding): return aesKey; } -export async function setServicePublicKey(name: string, value: Buffer): Promise { +async function setServicePublicKey(name: string, value: Buffer): Promise { await setCachedFile(`service:${name}:public_key`, value); } -export async function setServicePrivateKey(name: string, value: Buffer): Promise { +async function setServicePrivateKey(name: string, value: Buffer): Promise { await setCachedFile(`service:${name}:private_key`, value); } -export async function setServiceSecretKey(name: string, value: Buffer): Promise { +async function setServiceSecretKey(name: string, value: Buffer): Promise { await setCachedFile(`service:${name}:secret_key`, value); } -export async function setServiceAESKey(name: string, value: Buffer): Promise { +async function setServiceAESKey(name: string, value: Buffer): Promise { await setCachedFile(`service:${name}:aes_key`, value); } // * Local CDN cache functions -export async function getLocalCDNFile(name: string, encoding?: BufferEncoding): Promise { +async function getLocalCDNFile(name: string, encoding?: BufferEncoding): Promise { let file: Buffer = await getCachedFile(`local_cdn:${name}`, encoding); if (file === null) { @@ -186,7 +186,7 @@ export async function getLocalCDNFile(name: string, encoding?: BufferEncoding): return file; } -export async function setLocalCDNFile(name: string, value: Buffer): Promise { +async function setLocalCDNFile(name: string, value: Buffer): Promise { await setCachedFile(`local_cdn:${name}`, value); } diff --git a/src/config-manager.ts b/src/config-manager.ts index db16603..ed5631a 100644 --- a/src/config-manager.ts +++ b/src/config-manager.ts @@ -155,9 +155,4 @@ if (disabledFeatures.s3) { if (disabledFeatures.redis) { logger.warn('Both s3 and Redis are disabled. Large CDN files will use the in-memory cache, which may result in high memory use. Please enable s3 if you\'re running a production server.'); } -} - -export default { - config, - disabledFeatures -}; \ No newline at end of file +} \ No newline at end of file diff --git a/src/database.ts b/src/database.ts index 0bd36bb..fa3e10d 100644 --- a/src/database.ts +++ b/src/database.ts @@ -25,14 +25,14 @@ const discordConnectionSchema: joi.ObjectSchema = joi.object({ let _connection: mongoose.Connection; -export async function connect(): Promise { +async function connect(): Promise { await mongoose.connect(connection_string, options); _connection = mongoose.connection; _connection.on('error', console.error.bind(console, 'connection error:')); } -export function connection(): mongoose.Connection { +function connection(): mongoose.Connection { return _connection; } @@ -42,7 +42,7 @@ function verifyConnected(): void { } } -export async function getUserByUsername(username: string): Promise { +async function getUserByUsername(username: string): Promise { verifyConnected(); if (typeof username !== 'string') { @@ -54,7 +54,7 @@ export async function getUserByUsername(username: string): Promise { +async function getUserByPID(pid: number): Promise { verifyConnected(); return await PNID.findOne({ @@ -62,7 +62,7 @@ export async function getUserByPID(pid: number): Promise { }) as HydratedPNIDDocument; } -export async function getUserByEmailAddress(email: string): Promise { +async function getUserByEmailAddress(email: string): Promise { verifyConnected(); return await PNID.findOne({ @@ -70,13 +70,13 @@ export async function getUserByEmailAddress(email: string): Promise { +async function doesUserExist(username: string): Promise { verifyConnected(); return !!await getUserByUsername(username); } -export async function getUserBasic(token: string): Promise { +async function getUserBasic(token: string): Promise { verifyConnected(); // * Wii U sends Basic auth as `username password`, where the password may not have spaces @@ -102,7 +102,7 @@ export async function getUserBasic(token: string): Promise return user; } -export async function getUserBearer(token: string): Promise { +async function getUserBearer(token: string): Promise { verifyConnected(); try { @@ -127,7 +127,7 @@ export async function getUserBearer(token: string): Promise { +async function getUserProfileJSONByPID(pid: number): Promise { verifyConnected(); const user: HydratedPNIDDocument = await getUserByPID(pid); @@ -199,27 +199,27 @@ export async function getUserProfileJSONByPID(pid: number): Promise }; } -export async function getServer(gameServerId: string, accessMode: string): Promise { +async function getServer(gameServerId: string, accessMode: string): Promise { return await Server.findOne({ game_server_id: gameServerId, access_mode: accessMode }); } -export async function getServerByTitleId(titleId: string, accessMode: string): Promise { +async function getServerByTitleId(titleId: string, accessMode: string): Promise { return await Server.findOne({ title_ids: titleId, access_mode: accessMode }); } -export async function addUserConnection(pnid: HydratedPNIDDocument, data: ConnectionData, type: string): Promise { +async function addUserConnection(pnid: HydratedPNIDDocument, data: ConnectionData, type: string): Promise { if (type === 'discord') { return await addUserConnectionDiscord(pnid, data); } } -export async function addUserConnectionDiscord(pnid: HydratedPNIDDocument, data: DiscordConnectionData): Promise { +async function addUserConnectionDiscord(pnid: HydratedPNIDDocument, data: DiscordConnectionData): Promise { const valid: joi.ValidationResult = discordConnectionSchema.validate(data); if (valid.error) { @@ -242,14 +242,14 @@ export async function addUserConnectionDiscord(pnid: HydratedPNIDDocument, data: }; } -export async function removeUserConnection(pnid: HydratedPNIDDocument, type: string): Promise { +async function removeUserConnection(pnid: HydratedPNIDDocument, type: string): Promise { // * Add more connections later? if (type === 'discord') { return await removeUserConnectionDiscord(pnid); } } -export async function removeUserConnectionDiscord(pnid: HydratedPNIDDocument): Promise { +async function removeUserConnectionDiscord(pnid: HydratedPNIDDocument): Promise { await PNID.updateOne({ pid: pnid.get('pid') }, { $set: { 'connections.discord.id': '' diff --git a/src/mailer.ts b/src/mailer.ts index fd39b85..d87ec72 100644 --- a/src/mailer.ts +++ b/src/mailer.ts @@ -13,7 +13,7 @@ if (!disabledFeatures.email) { transporter = nodemailer.createTransport(config.email); } -export async function sendMail(options: MailerOptions): Promise { +async function sendMail(options: MailerOptions): Promise { if (!disabledFeatures.email) { const { to, subject, username, paragraph, preview, text, link, confirmation } = options; diff --git a/src/middleware/api.ts b/src/middleware/api.ts index 67edf9d..dc7c951 100644 --- a/src/middleware/api.ts +++ b/src/middleware/api.ts @@ -2,7 +2,7 @@ import express from 'express'; import database from '@/database'; import { HydratedPNIDDocument } from '@/types/mongoose/pnid'; -export async function APIMiddleware(request: express.Request, _response: express.Response, next: express.NextFunction): Promise { +async function APIMiddleware(request: express.Request, _response: express.Response, next: express.NextFunction): Promise { const authHeader: string = request.headers.authorization; if (!authHeader || !(authHeader.startsWith('Bearer'))) { diff --git a/src/middleware/cemu.ts b/src/middleware/cemu.ts index 029e861..dc44985 100644 --- a/src/middleware/cemu.ts +++ b/src/middleware/cemu.ts @@ -1,6 +1,6 @@ import express from 'express'; -export function CemuMiddleware(request: express.Request, _response: express.Response, next: express.NextFunction): void { +function CemuMiddleware(request: express.Request, _response: express.Response, next: express.NextFunction): void { const subdomain: string = request.subdomains.reverse().join('.'); request.isCemu = subdomain === 'c.account'; diff --git a/src/middleware/client-header.ts b/src/middleware/client-header.ts index 1353c89..18b21fd 100644 --- a/src/middleware/client-header.ts +++ b/src/middleware/client-header.ts @@ -9,7 +9,7 @@ const VALID_CLIENT_ID_SECRET_PAIRS: { [key: string]: string } = { }; -export function nintendoClientHeaderCheck(request: express.Request, response: express.Response, next: express.NextFunction): void { +function nintendoClientHeaderCheck(request: express.Request, response: express.Response, next: express.NextFunction): void { response.set('Content-Type', 'text/xml'); response.set('Server', 'Nintendo 3DS (http)'); response.set('X-Nintendo-Date', new Date().getTime().toString()); diff --git a/src/middleware/device-certificate.ts b/src/middleware/device-certificate.ts index 7cc95a7..e74c11c 100644 --- a/src/middleware/device-certificate.ts +++ b/src/middleware/device-certificate.ts @@ -1,7 +1,7 @@ import express from 'express'; import NintendoCertificate from '@/nintendo-certificate'; -export function deviceCertificateMiddleware(request: express.Request, _response: express.Response, next: express.NextFunction): void { +function deviceCertificateMiddleware(request: express.Request, _response: express.Response, next: express.NextFunction): void { const certificate: string = request.headers['x-nintendo-device-cert'] as string; if (!certificate) { diff --git a/src/middleware/nasc.ts b/src/middleware/nasc.ts index c3e5da6..ebf4dc5 100644 --- a/src/middleware/nasc.ts +++ b/src/middleware/nasc.ts @@ -11,7 +11,7 @@ import { NASCRequestParams } from '@/types/services/nasc/request-params'; import { HydratedNEXAccountDocument } from '@/types/mongoose/nex-account'; import { HydratedDeviceDocument } from '@/types/mongoose/device'; -export async function NASCMiddleware(request: express.Request, response: express.Response, next: express.NextFunction): Promise { +async function NASCMiddleware(request: express.Request, response: express.Response, next: express.NextFunction): Promise { const requestParams: NASCRequestParams = request.body; if (!requestParams.action || diff --git a/src/middleware/pnid.ts b/src/middleware/pnid.ts index 0f8b323..fd242cc 100644 --- a/src/middleware/pnid.ts +++ b/src/middleware/pnid.ts @@ -3,7 +3,7 @@ import xmlbuilder from 'xmlbuilder'; import database from '@/database'; import { HydratedPNIDDocument } from '@/types/mongoose/pnid'; -export async function PNIDMiddleware(request: express.Request, response: express.Response, next: express.NextFunction): Promise { +async function PNIDMiddleware(request: express.Request, response: express.Response, next: express.NextFunction): Promise { const authHeader: string = request.headers.authorization; if (!authHeader || !(authHeader.startsWith('Bearer') || authHeader.startsWith('Basic'))) { diff --git a/src/middleware/xml-parser.ts b/src/middleware/xml-parser.ts index f89b7f5..37d09b2 100644 --- a/src/middleware/xml-parser.ts +++ b/src/middleware/xml-parser.ts @@ -2,7 +2,7 @@ import express from 'express'; import xmlbuilder from 'xmlbuilder'; import { document as xmlParser } from 'xmlbuilder2'; -export function XMLMiddleware(request: express.Request, response: express.Response, next: express.NextFunction): void { +function XMLMiddleware(request: express.Request, response: express.Response, next: express.NextFunction): void { if (request.method == 'POST' || request.method == 'PUT') { const contentType: string = request.headers['content-type']; const contentLength: string = request.headers['content-length']; diff --git a/src/models/device.ts b/src/models/device.ts index d23c120..283dd98 100644 --- a/src/models/device.ts +++ b/src/models/device.ts @@ -2,13 +2,13 @@ import { Schema, model } from 'mongoose'; import { IDeviceAttribute, IDeviceAttributeMethods, DeviceAttributeModel } from '@/types/mongoose/device-attribute'; import { IDevice, IDeviceMethods, DeviceModel } from '@/types/mongoose/device'; -export const DeviceAttributeSchema = new Schema({ +const DeviceAttributeSchema = new Schema({ created_date: String, name: String, value: String }); -export const DeviceAttribute: DeviceAttributeModel = model('DeviceAttribute', DeviceAttributeSchema); +const DeviceAttribute: DeviceAttributeModel = model('DeviceAttribute', DeviceAttributeSchema); export const DeviceSchema = new Schema({ is_emulator: { @@ -50,11 +50,4 @@ export const DeviceSchema = new Schema({ } }); -export const Device: DeviceModel = model('Device', DeviceSchema); - -export default { - DeviceSchema, - Device, - DeviceAttributeSchema, - DeviceAttribute -}; \ No newline at end of file +export const Device: DeviceModel = model('Device', DeviceSchema); \ No newline at end of file diff --git a/src/models/nex-account.ts b/src/models/nex-account.ts index 2683a4f..fe8742e 100644 --- a/src/models/nex-account.ts +++ b/src/models/nex-account.ts @@ -2,7 +2,7 @@ import { Schema, model } from 'mongoose'; import uniqueValidator from 'mongoose-unique-validator'; import { INEXAccount, INEXAccountMethods, NEXAccountModel } from '@/types/mongoose/nex-account'; -export const NEXAccountSchema = new Schema({ +const NEXAccountSchema = new Schema({ device_type: { type: String, enum: [ @@ -69,9 +69,4 @@ NEXAccountSchema.method('generatePassword', function generatePassword(): void { this.set('password', output.join('')); }); -export const NEXAccount: NEXAccountModel = model('NEXAccount', NEXAccountSchema); - -export default { - NEXAccountSchema, - NEXAccount -}; \ No newline at end of file +export const NEXAccount: NEXAccountModel = model('NEXAccount', NEXAccountSchema); \ No newline at end of file diff --git a/src/models/pnid.ts b/src/models/pnid.ts index 3ccdd2d..b2a6888 100644 --- a/src/models/pnid.ts +++ b/src/models/pnid.ts @@ -9,7 +9,7 @@ import { DeviceSchema } from '@/models/device'; import util from '@/util'; import { IPNID, IPNIDMethods, PNIDModel } from '@/types/mongoose/pnid'; -export const PNIDSchema = new Schema({ +const PNIDSchema = new Schema({ access_level: { type: Number, default: 0 // 0: standard, 1: tester, 2: mod?, 3: dev @@ -211,9 +211,4 @@ PNIDSchema.method('getServerMode', function getServerMode(): string { return serverMode; }); -export const PNID: PNIDModel = model('PNID', PNIDSchema); - -export default { - PNIDSchema, - PNID -}; \ No newline at end of file +export const PNID: PNIDModel = model('PNID', PNIDSchema); \ No newline at end of file diff --git a/src/models/server.ts b/src/models/server.ts index 229142c..ab23f6a 100644 --- a/src/models/server.ts +++ b/src/models/server.ts @@ -2,7 +2,7 @@ import { Schema, model } from 'mongoose'; import uniqueValidator from 'mongoose-unique-validator'; import { IServer, IServerMethods, ServerModel } from '@/types/mongoose/server'; -export const ServerSchema = new Schema({ +const ServerSchema = new Schema({ ip: String, // Example: 1.1.1.1 port: Number, // Example: 60000 service_name: String, // Example: friends @@ -16,9 +16,4 @@ export const ServerSchema = new Schema({ ServerSchema.plugin(uniqueValidator, { message: '{PATH} already in use.' }); -export const Server: ServerModel = model('Server', ServerSchema); - -export default { - ServerSchema, - Server, -}; \ No newline at end of file +export const Server: ServerModel = model('Server', ServerSchema); \ No newline at end of file diff --git a/src/nintendo-certificate.ts b/src/nintendo-certificate.ts index 07b93f8..79cfcee 100644 --- a/src/nintendo-certificate.ts +++ b/src/nintendo-certificate.ts @@ -41,8 +41,6 @@ const CTR_LFCS_B_PUB = Buffer.from([ 0xAF, 0x07, 0xEB, 0x9C, 0xBF, 0xA9, 0xC9 ]); - - // Signature options const SIGNATURE_SIZES: { [key: string]: SignatureSize } = { RSA_4096_SHA1: { @@ -71,7 +69,7 @@ const SIGNATURE_SIZES: { [key: string]: SignatureSize } = { } }; -export class NintendoCertificate { +class NintendoCertificate { _certificate: Buffer; _certificateBody: Buffer; signatureType: number; diff --git a/src/types/mongoose/device-attribute.ts b/src/types/mongoose/device-attribute.ts index d77c3d8..e9eb87f 100644 --- a/src/types/mongoose/device-attribute.ts +++ b/src/types/mongoose/device-attribute.ts @@ -8,6 +8,6 @@ export interface IDeviceAttribute { export interface IDeviceAttributeMethods {} -export interface IDeviceAttributeQueryHelpers {} +interface IDeviceAttributeQueryHelpers {} export interface DeviceAttributeModel extends Model {} \ No newline at end of file diff --git a/src/types/mongoose/device.ts b/src/types/mongoose/device.ts index d9dcb1f..a2b2c78 100644 --- a/src/types/mongoose/device.ts +++ b/src/types/mongoose/device.ts @@ -27,7 +27,7 @@ export interface IDevice { export interface IDeviceMethods {} -export interface IDeviceQueryHelpers {} +interface IDeviceQueryHelpers {} export interface DeviceModel extends Model {} diff --git a/src/types/mongoose/nex-account.ts b/src/types/mongoose/nex-account.ts index fadc1bb..0e89b51 100644 --- a/src/types/mongoose/nex-account.ts +++ b/src/types/mongoose/nex-account.ts @@ -17,7 +17,7 @@ export interface INEXAccountMethods { generatePassword(): void; } -export interface INEXAccountQueryHelpers {} +interface INEXAccountQueryHelpers {} export interface NEXAccountModel extends Model {} diff --git a/src/types/mongoose/pnid.ts b/src/types/mongoose/pnid.ts index 8f87458..ee1fda6 100644 --- a/src/types/mongoose/pnid.ts +++ b/src/types/mongoose/pnid.ts @@ -73,7 +73,7 @@ export interface IPNIDMethods { getServerMode(): string; } -export interface IPNIDQueryHelpers {} +interface IPNIDQueryHelpers {} export interface PNIDModel extends Model {} diff --git a/src/types/mongoose/server.ts b/src/types/mongoose/server.ts index 65865d2..a385196 100644 --- a/src/types/mongoose/server.ts +++ b/src/types/mongoose/server.ts @@ -14,7 +14,7 @@ export interface IServer { export interface IServerMethods {} -export interface IServerQueryHelpers {} +interface IServerQueryHelpers {} export interface ServerModel extends Model {} From 19a695af3f71569bb774cf39e256677e58ff941e Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Mon, 6 Mar 2023 21:08:32 -0500 Subject: [PATCH 032/219] DeviceAttribute was defined but not used --- src/models/device.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/models/device.ts b/src/models/device.ts index 283dd98..1866e7f 100644 --- a/src/models/device.ts +++ b/src/models/device.ts @@ -8,8 +8,6 @@ const DeviceAttributeSchema = new Schema('DeviceAttribute', DeviceAttributeSchema); - export const DeviceSchema = new Schema({ is_emulator: { type: Boolean, From b01ef39fb234276a8baf6d60cb4cee4eb5e26bb2 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Mon, 6 Mar 2023 21:16:44 -0500 Subject: [PATCH 033/219] Added missing types --- src/mailer.ts | 8 +++---- src/models/nex-account.ts | 18 +++++++------- src/models/pnid.ts | 48 +++++++++++++++++++------------------ src/nintendo-certificate.ts | 12 +++++----- 4 files changed, 44 insertions(+), 42 deletions(-) diff --git a/src/mailer.ts b/src/mailer.ts index d87ec72..b7f52e9 100644 --- a/src/mailer.ts +++ b/src/mailer.ts @@ -21,14 +21,14 @@ async function sendMail(options: MailerOptions): Promise { html = html.replace(/{{username}}/g, username); html = html.replace(/{{paragraph}}/g, paragraph); - html = html.replace(/{{preview}}/g, (preview || "")); - html = html.replace(/{{confirmation-href}}/g, (confirmation?.href || "")); - html = html.replace(/{{confirmation-code}}/g, (confirmation?.code || "")); + html = html.replace(/{{preview}}/g, (preview || '')); + html = html.replace(/{{confirmation-href}}/g, (confirmation?.href || '')); + html = html.replace(/{{confirmation-code}}/g, (confirmation?.code || '')); if (link) { const { href, text } = link; - const button: string = ` ${text}` + const button: string = ` ${text}`; html = html.replace(//g, button); } diff --git a/src/models/nex-account.ts b/src/models/nex-account.ts index fe8742e..abfa407 100644 --- a/src/models/nex-account.ts +++ b/src/models/nex-account.ts @@ -1,6 +1,6 @@ import { Schema, model } from 'mongoose'; import uniqueValidator from 'mongoose-unique-validator'; -import { INEXAccount, INEXAccountMethods, NEXAccountModel } from '@/types/mongoose/nex-account'; +import { HydratedNEXAccountDocument, INEXAccount, INEXAccountMethods, NEXAccountModel } from '@/types/mongoose/nex-account'; const NEXAccountSchema = new Schema({ device_type: { @@ -38,12 +38,12 @@ NEXAccountSchema.plugin(uniqueValidator, { message: '{PATH} already in use.' }); and the next few accounts counting down seem to be admin, service and internal test accounts */ NEXAccountSchema.method('generatePID', async function generatePID(): Promise { - const min = 1000000000; // The console (WiiU) seems to not accept PIDs smaller than this - const max = 1799999999; + const min: number = 1000000000; // The console (WiiU) seems to not accept PIDs smaller than this + const max: number = 1799999999; - let pid = Math.floor(Math.random() * (max - min + 1) + min); + const pid: number = Math.floor(Math.random() * (max - min + 1) + min); - const inuse = await NEXAccount.findOne({ pid }); + const inuse: HydratedNEXAccountDocument = await NEXAccount.findOne({ pid }); if (inuse) { await this.generatePID(); @@ -53,17 +53,17 @@ NEXAccountSchema.method('generatePID', async function generatePID(): Promise({ access_level: { @@ -116,12 +116,12 @@ PNIDSchema.plugin(uniqueValidator, {message: '{PATH} already in use.'}); and the next few accounts counting down seem to be admin, service and internal test accounts */ PNIDSchema.method('generatePID', async function generatePID(): Promise { - const min = 1000000000; // The console (WiiU) seems to not accept PIDs smaller than this - const max = 1799999999; + const min: number = 1000000000; // The console (WiiU) seems to not accept PIDs smaller than this + const max: number = 1799999999; - let pid = Math.floor(Math.random() * (max - min + 1) + min); + const pid: number = Math.floor(Math.random() * (max - min + 1) + min); - const inuse = await PNID.findOne({ + const inuse: HydratedPNIDDocument = await PNID.findOne({ pid }); @@ -135,15 +135,15 @@ PNIDSchema.method('generatePID', async function generatePID(): Promise { PNIDSchema.method('generateEmailValidationCode', async function generateEmailValidationCode(): Promise { // WiiU passes the PID along with the email code // Does not actually need to be unique to all users - const code = Math.random().toFixed(6).split('.')[1]; // Dirty one-liner to generate numbers of 6 length and padded 0 + const code: string = Math.random().toFixed(6).split('.')[1]; // Dirty one-liner to generate numbers of 6 length and padded 0 this.set('identification.email_code', code); }); PNIDSchema.method('generateEmailValidationToken', async function generateEmailValidationToken(): Promise { - let token = crypto.randomBytes(32).toString('hex'); + const token: string = crypto.randomBytes(32).toString('hex'); - const inuse = await PNID.findOne({ + const inuse: HydratedPNIDDocument = await PNID.findOne({ 'identification.email_token': token }); @@ -168,47 +168,49 @@ PNIDSchema.method('updateMii', async function updateMii({name, primary, data}): }); PNIDSchema.method('generateMiiImages', async function generateMiiImages(): Promise { - const miiData = this.get('mii.data'); - const mii = new Mii(Buffer.from(miiData, 'base64')); - const miiStudioUrl = mii.studioUrl({ + const miiData: string = this.get('mii.data'); + const mii: Mii = new Mii(Buffer.from(miiData, 'base64')); + const miiStudioUrl: string = mii.studioUrl({ type: 'face', width: '128', instanceCount: '1', }); - const miiStudioNormalFaceImageData = await got(miiStudioUrl).buffer(); - const pngData = await imagePixels(miiStudioNormalFaceImageData); - const tga = TGA.createTgaBuffer(pngData.width, pngData.height, pngData.data); + const miiStudioNormalFaceImageData: Buffer = await got(miiStudioUrl).buffer(); + const pngData: { + width: number; + height: number; + data: Buffer; + } = await imagePixels(miiStudioNormalFaceImageData); + const tga: Buffer = TGA.createTgaBuffer(pngData.width, pngData.height, pngData.data); - const userMiiKey = `mii/${this.get('pid')}`; + const userMiiKey: string = `mii/${this.get('pid')}`; await util.uploadCDNAsset('pn-cdn', `${userMiiKey}/standard.tga`, tga, 'public-read'); await util.uploadCDNAsset('pn-cdn', `${userMiiKey}/normal_face.png`, miiStudioNormalFaceImageData, 'public-read'); - const expressions = ['frustrated', 'smile_open_mouth', 'wink_left', 'sorrow', 'surprise_open_mouth']; + const expressions: string[] = ['frustrated', 'smile_open_mouth', 'wink_left', 'sorrow', 'surprise_open_mouth']; for (const expression of expressions) { - const miiStudioExpressionUrl = mii.studioUrl({ + const miiStudioExpressionUrl: string = mii.studioUrl({ type: 'face', expression: expression, width: '128', instanceCount: '1', }); - const miiStudioExpressionImageData = await got(miiStudioExpressionUrl).buffer(); + const miiStudioExpressionImageData: Buffer = await got(miiStudioExpressionUrl).buffer(); await util.uploadCDNAsset('pn-cdn', `${userMiiKey}/${expression}.png`, miiStudioExpressionImageData, 'public-read'); } - const miiStudioBodyUrl = mii.studioUrl({ + const miiStudioBodyUrl: string = mii.studioUrl({ type: 'all_body', width: '270', instanceCount: '1', }); - const miiStudioBodyImageData = await got(miiStudioBodyUrl).buffer(); + const miiStudioBodyImageData: Buffer = await got(miiStudioBodyUrl).buffer(); await util.uploadCDNAsset('pn-cdn', `${userMiiKey}/body.png`, miiStudioBodyImageData, 'public-read'); }); PNIDSchema.method('getServerMode', function getServerMode(): string { - const serverMode = this.get('server_mode') || 'prod'; - - return serverMode; + return this.get('server_mode') || 'prod'; }); export const PNID: PNIDModel = model('PNID', PNIDSchema); \ No newline at end of file diff --git a/src/nintendo-certificate.ts b/src/nintendo-certificate.ts index 79cfcee..0ee9ef1 100644 --- a/src/nintendo-certificate.ts +++ b/src/nintendo-certificate.ts @@ -106,7 +106,7 @@ class NintendoCertificate { } } - _parseCertificateData() { + _parseCertificateData(): void { if (this._certificate.length === 0x110) { // Assume fcdcert (3DS LFCS) this.signature = this._certificate.subarray(0x0, 0x100); @@ -149,7 +149,7 @@ class NintendoCertificate { } } - _verifySignature() { + _verifySignature(): void { switch (this.keyType) { case 0x0: this._verifySignatureRSA4096(); @@ -168,7 +168,7 @@ class NintendoCertificate { } } - _verifySignatureRSA4096() { + _verifySignatureRSA4096(): void { const publicKey: NodeRSA = new NodeRSA(); publicKey.importKey({ @@ -179,7 +179,7 @@ class NintendoCertificate { this.valid = publicKey.verify(this._certificateBody, this.signature); } - _verifySignatureRSA2048() { + _verifySignatureRSA2048(): void { const publicKey: NodeRSA = new NodeRSA(); publicKey.importKey({ @@ -194,7 +194,7 @@ class NintendoCertificate { // with Nodes native crypto module and getting the keys // from bytes to PEM! // https://github.com/Myriachan - _verifySignatureECDSA() { + _verifySignatureECDSA(): void { const pem: string = this.issuer == 'Root-CA00000003-MS00000012' ? WIIU_DEVICE_PUB_PEM : CTR_DEVICE_PUB_PEM; const key: crypto.VerifyPublicKeyInput = { key: pem, @@ -204,7 +204,7 @@ class NintendoCertificate { this.valid = crypto.verify('sha256', this._certificateBody, key, this.signature); } - _verifySignatureLFCS() { + _verifySignatureLFCS(): void { const publicKey: NodeRSA = new NodeRSA(); publicKey.importKey({ From 4f134fed7d68783e3af42a3a04715ec535267397 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Tue, 7 Mar 2023 17:54:24 -0500 Subject: [PATCH 034/219] Removed useless type check in getUserByUsername --- src/database.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/database.ts b/src/database.ts index fa3e10d..aed3385 100644 --- a/src/database.ts +++ b/src/database.ts @@ -45,10 +45,6 @@ function verifyConnected(): void { async function getUserByUsername(username: string): Promise { verifyConnected(); - if (typeof username !== 'string') { - return null; - } - return await PNID.findOne({ usernameLower: username.toLowerCase() }) as HydratedPNIDDocument; From bebae96f9230f86630f25df6f6d28c36a00b6205 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Tue, 7 Mar 2023 17:55:51 -0500 Subject: [PATCH 035/219] Removed HydratedPNIDDocument casting --- src/database.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/database.ts b/src/database.ts index aed3385..e79515c 100644 --- a/src/database.ts +++ b/src/database.ts @@ -45,25 +45,25 @@ function verifyConnected(): void { async function getUserByUsername(username: string): Promise { verifyConnected(); - return await PNID.findOne({ + return await PNID.findOne({ usernameLower: username.toLowerCase() - }) as HydratedPNIDDocument; + }); } async function getUserByPID(pid: number): Promise { verifyConnected(); - return await PNID.findOne({ + return await PNID.findOne({ pid - }) as HydratedPNIDDocument; + }); } async function getUserByEmailAddress(email: string): Promise { verifyConnected(); - return await PNID.findOne({ + return await PNID.findOne({ 'email.address': new RegExp(email, 'i') // * Ignore case - }) as HydratedPNIDDocument; + }); } async function doesUserExist(username: string): Promise { From 928e6f6701e1f966a5ed699fdb5d30902b40f5b7 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Wed, 8 Mar 2023 17:11:17 -0500 Subject: [PATCH 036/219] revert me --- src/database.ts | 66 ++++++++++----------- src/middleware/nasc.ts | 8 +-- src/middleware/pnid.ts | 2 +- src/models/pnid.ts | 6 +- src/services/api/routes/v1/login.ts | 10 ++-- src/services/api/routes/v1/register.ts | 10 ++-- src/services/api/routes/v1/resetPassword.ts | 4 +- src/services/nasc/routes/ac.ts | 4 +- src/services/nnid/routes/oauth.ts | 6 +- src/services/nnid/routes/people.ts | 8 +-- src/types/mongoose/pnid.ts | 4 +- src/types/services/nnid/pnid-profile.ts | 8 +-- src/util.ts | 26 ++++---- 13 files changed, 81 insertions(+), 81 deletions(-) diff --git a/src/database.ts b/src/database.ts index e79515c..a79ecc1 100644 --- a/src/database.ts +++ b/src/database.ts @@ -127,17 +127,17 @@ async function getUserProfileJSONByPID(pid: number): Promise { verifyConnected(); const user: HydratedPNIDDocument = await getUserByPID(pid); - const device: HydratedDeviceDocument = user.get('devices')[0]; // * Just grab the first device - let device_attributes: [{ + const device: HydratedDeviceDocument = user.devices[0]; // * Just grab the first device + let device_attributes: { device_attribute: { name: string; value: string; created_date: string; }; - }]; + }[]; if (device) { - device_attributes = device.get('device_attributes').map(({name, value, created_date}) => ({ + device_attributes = device.device_attributes.map(({name, value, created_date}) => ({ device_attribute: { name, value, @@ -148,50 +148,50 @@ async function getUserProfileJSONByPID(pid: number): Promise { return { //accounts: {}, // * We need to figure this out, no idea what these values mean or what they do - active_flag: user.get('flags.active') ? 'Y' : 'N', - birth_date: user.get('birthdate'), - country: user.get('country'), - create_date: user.get('creation_date'), + active_flag: user.flags.active ? 'Y' : 'N', + birth_date: user.birthdate, + country: user.country, + create_date: user.creation_date, device_attributes: device_attributes, - gender: user.get('gender'), - language: user.get('language'), - updated: user.get('updated'), - marketing_flag: user.get('flags.marketing') ? 'Y' : 'N', - off_device_flag: user.get('flags.off_device') ? 'Y' : 'N', - pid: user.get('pid'), + gender: user.gender, + language: user.language, + updated: user.updated, + marketing_flag: user.flags.marketing ? 'Y' : 'N', + off_device_flag: user.flags.off_device ? 'Y' : 'N', + pid: user.pid, email: { - address: user.get('email.address'), - id: user.get('email.id'), - parent: user.get('email.parent') ? 'Y' : 'N', - primary: user.get('email.primary') ? 'Y' : 'N', - reachable: user.get('email.reachable') ? 'Y' : 'N', + address: user.email.address, + id: user.email.id, + parent: user.email.parent ? 'Y' : 'N', + primary: user.email.primary ? 'Y' : 'N', + reachable: user.email.reachable ? 'Y' : 'N', type: 'DEFAULT', updated_by: 'USER', // * Can also be INTERNAL WS, don't know the difference - validated: user.get('email.validated') ? 'Y' : 'N', - validated_date: user.get('email.validated') ? user.get('email.validated_date') : '' + validated: user.email.validated ? 'Y' : 'N', + validated_date: user.email.validated ? user.email.validated_date : '' }, mii: { status: 'COMPLETED', - data: user.get('mii.data').replace(/(\r\n|\n|\r)/gm, ''), - id: user.get('mii.id'), - mii_hash: user.get('mii.hash'), + data: user.mii.data.replace(/(\r\n|\n|\r)/gm, ''), + id: user.mii.id, + mii_hash: user.mii.hash, mii_images: { mii_image: { // * Images MUST be loaded over HTTPS or console ignores them // * Bunny CDN is the only CDN which seems to support TLS 1.0/1.1 (required) cached_url: `${config.cdn.base_url}/mii/${user.pid}/standard.tga`, - id: user.get('mii.image_id'), + id: user.mii.image_id, url: `${config.cdn.base_url}/mii/${user.pid}/standard.tga`, type: 'standard' } }, - name: user.get('mii.name'), - primary: user.get('mii.primary') ? 'Y' : 'N', + name: user.mii.name, + primary: user.mii.primary ? 'Y' : 'N', }, - region: user.get('region'), - tz_name: user.get('timezone.name'), - user_id: user.get('username'), - utc_offset: user.get('timezone.offset') + region: user.region, + tz_name: user.timezone.name, + user_id: user.username, + utc_offset: user.timezone.offset }; } @@ -226,7 +226,7 @@ async function addUserConnectionDiscord(pnid: HydratedPNIDDocument, data: Discor }; } - await PNID.updateOne({ pid: pnid.get('pid') }, { + await PNID.updateOne({ pid: pnid.pid }, { $set: { 'connections.discord.id': data.id } @@ -246,7 +246,7 @@ async function removeUserConnection(pnid: HydratedPNIDDocument, type: string): P } async function removeUserConnectionDiscord(pnid: HydratedPNIDDocument): Promise { - await PNID.updateOne({ pid: pnid.get('pid') }, { + await PNID.updateOne({ pid: pnid.pid }, { $set: { 'connections.discord.id': '' } diff --git a/src/middleware/nasc.ts b/src/middleware/nasc.ts index ebf4dc5..941e57a 100644 --- a/src/middleware/nasc.ts +++ b/src/middleware/nasc.ts @@ -104,13 +104,13 @@ async function NASCMiddleware(request: express.Request, response: express.Respon }); if (device) { - if (device.get('access_level') < 0) { + if (device.access_level < 0) { response.status(200).send(util.nascError('102')); return; } if (pid) { - const linkedPIDs = device.get('linked_pids'); + const linkedPIDs = device.linked_pids; if (!linkedPIDs.includes(pid)) { response.status(200).send(util.nascError('102')); @@ -137,7 +137,7 @@ async function NASCMiddleware(request: express.Request, response: express.Respon await nexAccount.save({ session }); - pid = Number(nexAccount.get('pid')); + pid = nexAccount.pid; // Set password @@ -176,7 +176,7 @@ async function NASCMiddleware(request: express.Request, response: express.Respon const nexUser: HydratedNEXAccountDocument = await NEXAccount.findOne({ pid }); - if (!nexUser || nexUser.get('access_level') < 0) { + if (!nexUser || nexUser.access_level < 0) { response.status(200).send(util.nascError('102')); return; } diff --git a/src/middleware/pnid.ts b/src/middleware/pnid.ts index fd242cc..ca10d31 100644 --- a/src/middleware/pnid.ts +++ b/src/middleware/pnid.ts @@ -54,7 +54,7 @@ async function PNIDMiddleware(request: express.Request, response: express.Respon return; } - if (user.get('access_level') < 0) { + if (user.access_level < 0) { response.status(400).send(xmlbuilder.create({ errors: { error: { diff --git a/src/models/pnid.ts b/src/models/pnid.ts index c6a3b3f..24109c2 100644 --- a/src/models/pnid.ts +++ b/src/models/pnid.ts @@ -168,7 +168,7 @@ PNIDSchema.method('updateMii', async function updateMii({name, primary, data}): }); PNIDSchema.method('generateMiiImages', async function generateMiiImages(): Promise { - const miiData: string = this.get('mii.data'); + const miiData: string = this.mii.data; const mii: Mii = new Mii(Buffer.from(miiData, 'base64')); const miiStudioUrl: string = mii.studioUrl({ type: 'face', @@ -183,7 +183,7 @@ PNIDSchema.method('generateMiiImages', async function generateMiiImages(): Promi } = await imagePixels(miiStudioNormalFaceImageData); const tga: Buffer = TGA.createTgaBuffer(pngData.width, pngData.height, pngData.data); - const userMiiKey: string = `mii/${this.get('pid')}`; + const userMiiKey: string = `mii/${this.pid}`; await util.uploadCDNAsset('pn-cdn', `${userMiiKey}/standard.tga`, tga, 'public-read'); await util.uploadCDNAsset('pn-cdn', `${userMiiKey}/normal_face.png`, miiStudioNormalFaceImageData, 'public-read'); @@ -210,7 +210,7 @@ PNIDSchema.method('generateMiiImages', async function generateMiiImages(): Promi }); PNIDSchema.method('getServerMode', function getServerMode(): string { - return this.get('server_mode') || 'prod'; + return this.server_mode || 'prod'; }); export const PNID: PNIDModel = model('PNID', PNIDSchema); \ No newline at end of file diff --git a/src/services/api/routes/v1/login.ts b/src/services/api/routes/v1/login.ts index 4aa098a..79aca57 100644 --- a/src/services/api/routes/v1/login.ts +++ b/src/services/api/routes/v1/login.ts @@ -67,7 +67,7 @@ router.post('/', async (request: express.Request, response: express.Response) => }); } - const hashedPassword: string = util.nintendoPasswordHash(password, pnid.get('pid')); + const hashedPassword: string = util.nintendoPasswordHash(password, pnid.pid); if (!pnid || !bcrypt.compareSync(hashedPassword, pnid.password)) { return response.status(400).json({ @@ -110,8 +110,8 @@ router.post('/', async (request: express.Request, response: express.Response) => const accessTokenOptions: TokenOptions = { system_type: 0xF, // API token_type: 0x1, // OAuth Access, - pid: pnid.get('pid'), - access_level: pnid.get('access_level'), + pid: pnid.pid, + access_level: pnid.access_level, title_id: BigInt(0), expire_time: BigInt(Date.now() + (3600 * 1000)) }; @@ -119,8 +119,8 @@ router.post('/', async (request: express.Request, response: express.Response) => const refreshTokenOptions: TokenOptions = { system_type: 0xF, // API token_type: 0x2, // OAuth Refresh, - pid: pnid.get('pid'), - access_level: pnid.get('access_level'), + pid: pnid.pid, + access_level: pnid.access_level, title_id: BigInt(0), expire_time: BigInt(Date.now() + (3600 * 1000)) }; diff --git a/src/services/api/routes/v1/register.ts b/src/services/api/routes/v1/register.ts index 7712d29..8fc5ede 100644 --- a/src/services/api/routes/v1/register.ts +++ b/src/services/api/routes/v1/register.ts @@ -250,15 +250,15 @@ router.post('/', async (request: express.Request, response: express.Response) => // NN with a NNID will always use the NNID PID // even if the provided NEX PID is different // To fix this we make them the same PID - nexAccount.owning_pid = nexAccount.get('pid'); + nexAccount.owning_pid = nexAccount.pid; await nexAccount.save({ session }); - const primaryPasswordHash: string = util.nintendoPasswordHash(password, nexAccount.get('pid')); + const primaryPasswordHash: string = util.nintendoPasswordHash(password, nexAccount.pid); const passwordHash: string = await bcrypt.hash(primaryPasswordHash, 10); pnid = new PNID({ - pid: nexAccount.get('pid'), + pid: nexAccount.pid, creation_date: creationDate, updated: creationDate, username: username, @@ -348,7 +348,7 @@ router.post('/', async (request: express.Request, response: express.Response) => const accessTokenOptions: TokenOptions = { system_type: 0xF, // API token_type: 0x1, // OAuth Access, - pid: pnid.get('pid'), + pid: pnid.pid, access_level: 0, title_id: BigInt(0), expire_time: BigInt(Date.now() + (3600 * 1000)) @@ -357,7 +357,7 @@ router.post('/', async (request: express.Request, response: express.Response) => const refreshTokenOptions: TokenOptions = { system_type: 0xF, // API token_type: 0x2, // OAuth Refresh, - pid: pnid.get('pid'), + pid: pnid.pid, access_level: 0, title_id: BigInt(0), expire_time: BigInt(Date.now() + (3600 * 1000)) diff --git a/src/services/api/routes/v1/resetPassword.ts b/src/services/api/routes/v1/resetPassword.ts index 8f570b5..04d93a0 100644 --- a/src/services/api/routes/v1/resetPassword.ts +++ b/src/services/api/routes/v1/resetPassword.ts @@ -82,7 +82,7 @@ router.post('/', async (request: express.Request, response: express.Response) => }); } - if (password.toLowerCase() === pnid.get('usernameLower')) { + if (password.toLowerCase() === pnid.usernameLower) { return response.status(400).json({ app: 'api', status: 400, @@ -114,7 +114,7 @@ router.post('/', async (request: express.Request, response: express.Response) => }); } - const primaryPasswordHash: string = util.nintendoPasswordHash(password, pnid.get('pid')); + const primaryPasswordHash: string = util.nintendoPasswordHash(password, pnid.pid); const passwordHash: string = await bcrypt.hash(primaryPasswordHash, 10); pnid.password = passwordHash; diff --git a/src/services/nasc/routes/ac.ts b/src/services/nasc/routes/ac.ts index 6e4589a..87dcf17 100644 --- a/src/services/nasc/routes/ac.ts +++ b/src/services/nasc/routes/ac.ts @@ -42,7 +42,7 @@ async function processLoginRequest(request: express.Request): Promise; + devices: Types.DocumentArray; identification: { // user identification tokens email_code: string; email_token: string; diff --git a/src/types/services/nnid/pnid-profile.ts b/src/types/services/nnid/pnid-profile.ts index ac95915..cb02669 100644 --- a/src/types/services/nnid/pnid-profile.ts +++ b/src/types/services/nnid/pnid-profile.ts @@ -6,13 +6,13 @@ export interface PNIDProfile { birth_date: string; country: string; create_date: string; - device_attributes: [{ + device_attributes: { device_attribute: { name: string; value: string; created_date: string; }; - }]; + }[]; gender: string; language: string; updated: string; @@ -21,7 +21,7 @@ export interface PNIDProfile { pid: number; email: { address: string; - id: string; + id: number; parent: YesNoBoolString; primary: YesNoBoolString; reachable: YesNoBoolString; @@ -38,7 +38,7 @@ export interface PNIDProfile { mii_images: { mii_image: { cached_url: string; - id: string; + id: number; url: string; type: 'standard'; } diff --git a/src/util.ts b/src/util.ts index eaec4c4..1f9d1fb 100644 --- a/src/util.ts +++ b/src/util.ts @@ -257,14 +257,14 @@ function nascError(errorCode: string): URLSearchParams { async function sendConfirmationEmail(pnid: mongoose.HydratedDocument): Promise { const options: MailerOptions = { - to: pnid.get('email.address'), + to: pnid.email.address, subject: '[Pretendo Network] Please confirm your email address', - username: pnid.get('username'), + username: pnid.username, confirmation: { - href: `https://api.pretendo.cc/v1/email/verify?token=${pnid.get('identification.email_token')}`, - code: pnid.get('identification.email_code') + href: `https://api.pretendo.cc/v1/email/verify?token=${pnid.identification.email_token}`, + code: pnid.identification.email_code }, - text: `Hello ${pnid.get('username')}! \r\n\r\nYour Pretendo Network ID activation is almost complete. Please click the link to confirm your e-mail address and complete the activation process: \r\nhttps://api.pretendo.cc/v1/email/verify?token=${pnid.get('identification.email_token')} \r\n\r\nYou may also enter the following 6-digit code on your console: ${pnid.get('identification.email_code')}` + text: `Hello ${pnid.username}! \r\n\r\nYour Pretendo Network ID activation is almost complete. Please click the link to confirm your e-mail address and complete the activation process: \r\nhttps://api.pretendo.cc/v1/email/verify?token=${pnid.identification.email_token} \r\n\r\nYou may also enter the following 6-digit code on your console: ${pnid.identification.email_code}` }; await mailer.sendMail(options); @@ -272,11 +272,11 @@ async function sendConfirmationEmail(pnid: mongoose.HydratedDocument): Promise { const options: MailerOptions = { - to: pnid.get('email.address'), + to: pnid.email.address, subject: '[Pretendo Network] Email address confirmed', - username: pnid.get('username'), + username: pnid.username, paragraph: 'your email address has been confirmed. We hope you have fun on Pretendo Network!', - text: `Dear ${pnid.get('username')}, \r\n\r\nYour email address has been confirmed. We hope you have fun on Pretendo Network!` + text: `Dear ${pnid.username}, \r\n\r\nYour email address has been confirmed. We hope you have fun on Pretendo Network!` }; await mailer.sendMail(options); @@ -294,8 +294,8 @@ async function sendForgotPasswordEmail(pnid: mongoose.HydratedDocument Date: Wed, 8 Mar 2023 17:11:26 -0500 Subject: [PATCH 037/219] Revert "revert me" This reverts commit 928e6f6701e1f966a5ed699fdb5d30902b40f5b7. --- src/database.ts | 66 ++++++++++----------- src/middleware/nasc.ts | 8 +-- src/middleware/pnid.ts | 2 +- src/models/pnid.ts | 6 +- src/services/api/routes/v1/login.ts | 10 ++-- src/services/api/routes/v1/register.ts | 10 ++-- src/services/api/routes/v1/resetPassword.ts | 4 +- src/services/nasc/routes/ac.ts | 4 +- src/services/nnid/routes/oauth.ts | 6 +- src/services/nnid/routes/people.ts | 8 +-- src/types/mongoose/pnid.ts | 4 +- src/types/services/nnid/pnid-profile.ts | 8 +-- src/util.ts | 26 ++++---- 13 files changed, 81 insertions(+), 81 deletions(-) diff --git a/src/database.ts b/src/database.ts index a79ecc1..e79515c 100644 --- a/src/database.ts +++ b/src/database.ts @@ -127,17 +127,17 @@ async function getUserProfileJSONByPID(pid: number): Promise { verifyConnected(); const user: HydratedPNIDDocument = await getUserByPID(pid); - const device: HydratedDeviceDocument = user.devices[0]; // * Just grab the first device - let device_attributes: { + const device: HydratedDeviceDocument = user.get('devices')[0]; // * Just grab the first device + let device_attributes: [{ device_attribute: { name: string; value: string; created_date: string; }; - }[]; + }]; if (device) { - device_attributes = device.device_attributes.map(({name, value, created_date}) => ({ + device_attributes = device.get('device_attributes').map(({name, value, created_date}) => ({ device_attribute: { name, value, @@ -148,50 +148,50 @@ async function getUserProfileJSONByPID(pid: number): Promise { return { //accounts: {}, // * We need to figure this out, no idea what these values mean or what they do - active_flag: user.flags.active ? 'Y' : 'N', - birth_date: user.birthdate, - country: user.country, - create_date: user.creation_date, + active_flag: user.get('flags.active') ? 'Y' : 'N', + birth_date: user.get('birthdate'), + country: user.get('country'), + create_date: user.get('creation_date'), device_attributes: device_attributes, - gender: user.gender, - language: user.language, - updated: user.updated, - marketing_flag: user.flags.marketing ? 'Y' : 'N', - off_device_flag: user.flags.off_device ? 'Y' : 'N', - pid: user.pid, + gender: user.get('gender'), + language: user.get('language'), + updated: user.get('updated'), + marketing_flag: user.get('flags.marketing') ? 'Y' : 'N', + off_device_flag: user.get('flags.off_device') ? 'Y' : 'N', + pid: user.get('pid'), email: { - address: user.email.address, - id: user.email.id, - parent: user.email.parent ? 'Y' : 'N', - primary: user.email.primary ? 'Y' : 'N', - reachable: user.email.reachable ? 'Y' : 'N', + address: user.get('email.address'), + id: user.get('email.id'), + parent: user.get('email.parent') ? 'Y' : 'N', + primary: user.get('email.primary') ? 'Y' : 'N', + reachable: user.get('email.reachable') ? 'Y' : 'N', type: 'DEFAULT', updated_by: 'USER', // * Can also be INTERNAL WS, don't know the difference - validated: user.email.validated ? 'Y' : 'N', - validated_date: user.email.validated ? user.email.validated_date : '' + validated: user.get('email.validated') ? 'Y' : 'N', + validated_date: user.get('email.validated') ? user.get('email.validated_date') : '' }, mii: { status: 'COMPLETED', - data: user.mii.data.replace(/(\r\n|\n|\r)/gm, ''), - id: user.mii.id, - mii_hash: user.mii.hash, + data: user.get('mii.data').replace(/(\r\n|\n|\r)/gm, ''), + id: user.get('mii.id'), + mii_hash: user.get('mii.hash'), mii_images: { mii_image: { // * Images MUST be loaded over HTTPS or console ignores them // * Bunny CDN is the only CDN which seems to support TLS 1.0/1.1 (required) cached_url: `${config.cdn.base_url}/mii/${user.pid}/standard.tga`, - id: user.mii.image_id, + id: user.get('mii.image_id'), url: `${config.cdn.base_url}/mii/${user.pid}/standard.tga`, type: 'standard' } }, - name: user.mii.name, - primary: user.mii.primary ? 'Y' : 'N', + name: user.get('mii.name'), + primary: user.get('mii.primary') ? 'Y' : 'N', }, - region: user.region, - tz_name: user.timezone.name, - user_id: user.username, - utc_offset: user.timezone.offset + region: user.get('region'), + tz_name: user.get('timezone.name'), + user_id: user.get('username'), + utc_offset: user.get('timezone.offset') }; } @@ -226,7 +226,7 @@ async function addUserConnectionDiscord(pnid: HydratedPNIDDocument, data: Discor }; } - await PNID.updateOne({ pid: pnid.pid }, { + await PNID.updateOne({ pid: pnid.get('pid') }, { $set: { 'connections.discord.id': data.id } @@ -246,7 +246,7 @@ async function removeUserConnection(pnid: HydratedPNIDDocument, type: string): P } async function removeUserConnectionDiscord(pnid: HydratedPNIDDocument): Promise { - await PNID.updateOne({ pid: pnid.pid }, { + await PNID.updateOne({ pid: pnid.get('pid') }, { $set: { 'connections.discord.id': '' } diff --git a/src/middleware/nasc.ts b/src/middleware/nasc.ts index 941e57a..ebf4dc5 100644 --- a/src/middleware/nasc.ts +++ b/src/middleware/nasc.ts @@ -104,13 +104,13 @@ async function NASCMiddleware(request: express.Request, response: express.Respon }); if (device) { - if (device.access_level < 0) { + if (device.get('access_level') < 0) { response.status(200).send(util.nascError('102')); return; } if (pid) { - const linkedPIDs = device.linked_pids; + const linkedPIDs = device.get('linked_pids'); if (!linkedPIDs.includes(pid)) { response.status(200).send(util.nascError('102')); @@ -137,7 +137,7 @@ async function NASCMiddleware(request: express.Request, response: express.Respon await nexAccount.save({ session }); - pid = nexAccount.pid; + pid = Number(nexAccount.get('pid')); // Set password @@ -176,7 +176,7 @@ async function NASCMiddleware(request: express.Request, response: express.Respon const nexUser: HydratedNEXAccountDocument = await NEXAccount.findOne({ pid }); - if (!nexUser || nexUser.access_level < 0) { + if (!nexUser || nexUser.get('access_level') < 0) { response.status(200).send(util.nascError('102')); return; } diff --git a/src/middleware/pnid.ts b/src/middleware/pnid.ts index ca10d31..fd242cc 100644 --- a/src/middleware/pnid.ts +++ b/src/middleware/pnid.ts @@ -54,7 +54,7 @@ async function PNIDMiddleware(request: express.Request, response: express.Respon return; } - if (user.access_level < 0) { + if (user.get('access_level') < 0) { response.status(400).send(xmlbuilder.create({ errors: { error: { diff --git a/src/models/pnid.ts b/src/models/pnid.ts index 24109c2..c6a3b3f 100644 --- a/src/models/pnid.ts +++ b/src/models/pnid.ts @@ -168,7 +168,7 @@ PNIDSchema.method('updateMii', async function updateMii({name, primary, data}): }); PNIDSchema.method('generateMiiImages', async function generateMiiImages(): Promise { - const miiData: string = this.mii.data; + const miiData: string = this.get('mii.data'); const mii: Mii = new Mii(Buffer.from(miiData, 'base64')); const miiStudioUrl: string = mii.studioUrl({ type: 'face', @@ -183,7 +183,7 @@ PNIDSchema.method('generateMiiImages', async function generateMiiImages(): Promi } = await imagePixels(miiStudioNormalFaceImageData); const tga: Buffer = TGA.createTgaBuffer(pngData.width, pngData.height, pngData.data); - const userMiiKey: string = `mii/${this.pid}`; + const userMiiKey: string = `mii/${this.get('pid')}`; await util.uploadCDNAsset('pn-cdn', `${userMiiKey}/standard.tga`, tga, 'public-read'); await util.uploadCDNAsset('pn-cdn', `${userMiiKey}/normal_face.png`, miiStudioNormalFaceImageData, 'public-read'); @@ -210,7 +210,7 @@ PNIDSchema.method('generateMiiImages', async function generateMiiImages(): Promi }); PNIDSchema.method('getServerMode', function getServerMode(): string { - return this.server_mode || 'prod'; + return this.get('server_mode') || 'prod'; }); export const PNID: PNIDModel = model('PNID', PNIDSchema); \ No newline at end of file diff --git a/src/services/api/routes/v1/login.ts b/src/services/api/routes/v1/login.ts index 79aca57..4aa098a 100644 --- a/src/services/api/routes/v1/login.ts +++ b/src/services/api/routes/v1/login.ts @@ -67,7 +67,7 @@ router.post('/', async (request: express.Request, response: express.Response) => }); } - const hashedPassword: string = util.nintendoPasswordHash(password, pnid.pid); + const hashedPassword: string = util.nintendoPasswordHash(password, pnid.get('pid')); if (!pnid || !bcrypt.compareSync(hashedPassword, pnid.password)) { return response.status(400).json({ @@ -110,8 +110,8 @@ router.post('/', async (request: express.Request, response: express.Response) => const accessTokenOptions: TokenOptions = { system_type: 0xF, // API token_type: 0x1, // OAuth Access, - pid: pnid.pid, - access_level: pnid.access_level, + pid: pnid.get('pid'), + access_level: pnid.get('access_level'), title_id: BigInt(0), expire_time: BigInt(Date.now() + (3600 * 1000)) }; @@ -119,8 +119,8 @@ router.post('/', async (request: express.Request, response: express.Response) => const refreshTokenOptions: TokenOptions = { system_type: 0xF, // API token_type: 0x2, // OAuth Refresh, - pid: pnid.pid, - access_level: pnid.access_level, + pid: pnid.get('pid'), + access_level: pnid.get('access_level'), title_id: BigInt(0), expire_time: BigInt(Date.now() + (3600 * 1000)) }; diff --git a/src/services/api/routes/v1/register.ts b/src/services/api/routes/v1/register.ts index 8fc5ede..7712d29 100644 --- a/src/services/api/routes/v1/register.ts +++ b/src/services/api/routes/v1/register.ts @@ -250,15 +250,15 @@ router.post('/', async (request: express.Request, response: express.Response) => // NN with a NNID will always use the NNID PID // even if the provided NEX PID is different // To fix this we make them the same PID - nexAccount.owning_pid = nexAccount.pid; + nexAccount.owning_pid = nexAccount.get('pid'); await nexAccount.save({ session }); - const primaryPasswordHash: string = util.nintendoPasswordHash(password, nexAccount.pid); + const primaryPasswordHash: string = util.nintendoPasswordHash(password, nexAccount.get('pid')); const passwordHash: string = await bcrypt.hash(primaryPasswordHash, 10); pnid = new PNID({ - pid: nexAccount.pid, + pid: nexAccount.get('pid'), creation_date: creationDate, updated: creationDate, username: username, @@ -348,7 +348,7 @@ router.post('/', async (request: express.Request, response: express.Response) => const accessTokenOptions: TokenOptions = { system_type: 0xF, // API token_type: 0x1, // OAuth Access, - pid: pnid.pid, + pid: pnid.get('pid'), access_level: 0, title_id: BigInt(0), expire_time: BigInt(Date.now() + (3600 * 1000)) @@ -357,7 +357,7 @@ router.post('/', async (request: express.Request, response: express.Response) => const refreshTokenOptions: TokenOptions = { system_type: 0xF, // API token_type: 0x2, // OAuth Refresh, - pid: pnid.pid, + pid: pnid.get('pid'), access_level: 0, title_id: BigInt(0), expire_time: BigInt(Date.now() + (3600 * 1000)) diff --git a/src/services/api/routes/v1/resetPassword.ts b/src/services/api/routes/v1/resetPassword.ts index 04d93a0..8f570b5 100644 --- a/src/services/api/routes/v1/resetPassword.ts +++ b/src/services/api/routes/v1/resetPassword.ts @@ -82,7 +82,7 @@ router.post('/', async (request: express.Request, response: express.Response) => }); } - if (password.toLowerCase() === pnid.usernameLower) { + if (password.toLowerCase() === pnid.get('usernameLower')) { return response.status(400).json({ app: 'api', status: 400, @@ -114,7 +114,7 @@ router.post('/', async (request: express.Request, response: express.Response) => }); } - const primaryPasswordHash: string = util.nintendoPasswordHash(password, pnid.pid); + const primaryPasswordHash: string = util.nintendoPasswordHash(password, pnid.get('pid')); const passwordHash: string = await bcrypt.hash(primaryPasswordHash, 10); pnid.password = passwordHash; diff --git a/src/services/nasc/routes/ac.ts b/src/services/nasc/routes/ac.ts index 87dcf17..6e4589a 100644 --- a/src/services/nasc/routes/ac.ts +++ b/src/services/nasc/routes/ac.ts @@ -42,7 +42,7 @@ async function processLoginRequest(request: express.Request): Promise; + devices: Types.DocumentArray; identification: { // user identification tokens email_code: string; email_token: string; diff --git a/src/types/services/nnid/pnid-profile.ts b/src/types/services/nnid/pnid-profile.ts index cb02669..ac95915 100644 --- a/src/types/services/nnid/pnid-profile.ts +++ b/src/types/services/nnid/pnid-profile.ts @@ -6,13 +6,13 @@ export interface PNIDProfile { birth_date: string; country: string; create_date: string; - device_attributes: { + device_attributes: [{ device_attribute: { name: string; value: string; created_date: string; }; - }[]; + }]; gender: string; language: string; updated: string; @@ -21,7 +21,7 @@ export interface PNIDProfile { pid: number; email: { address: string; - id: number; + id: string; parent: YesNoBoolString; primary: YesNoBoolString; reachable: YesNoBoolString; @@ -38,7 +38,7 @@ export interface PNIDProfile { mii_images: { mii_image: { cached_url: string; - id: number; + id: string; url: string; type: 'standard'; } diff --git a/src/util.ts b/src/util.ts index 1f9d1fb..eaec4c4 100644 --- a/src/util.ts +++ b/src/util.ts @@ -257,14 +257,14 @@ function nascError(errorCode: string): URLSearchParams { async function sendConfirmationEmail(pnid: mongoose.HydratedDocument): Promise { const options: MailerOptions = { - to: pnid.email.address, + to: pnid.get('email.address'), subject: '[Pretendo Network] Please confirm your email address', - username: pnid.username, + username: pnid.get('username'), confirmation: { - href: `https://api.pretendo.cc/v1/email/verify?token=${pnid.identification.email_token}`, - code: pnid.identification.email_code + href: `https://api.pretendo.cc/v1/email/verify?token=${pnid.get('identification.email_token')}`, + code: pnid.get('identification.email_code') }, - text: `Hello ${pnid.username}! \r\n\r\nYour Pretendo Network ID activation is almost complete. Please click the link to confirm your e-mail address and complete the activation process: \r\nhttps://api.pretendo.cc/v1/email/verify?token=${pnid.identification.email_token} \r\n\r\nYou may also enter the following 6-digit code on your console: ${pnid.identification.email_code}` + text: `Hello ${pnid.get('username')}! \r\n\r\nYour Pretendo Network ID activation is almost complete. Please click the link to confirm your e-mail address and complete the activation process: \r\nhttps://api.pretendo.cc/v1/email/verify?token=${pnid.get('identification.email_token')} \r\n\r\nYou may also enter the following 6-digit code on your console: ${pnid.get('identification.email_code')}` }; await mailer.sendMail(options); @@ -272,11 +272,11 @@ async function sendConfirmationEmail(pnid: mongoose.HydratedDocument): Promise { const options: MailerOptions = { - to: pnid.email.address, + to: pnid.get('email.address'), subject: '[Pretendo Network] Email address confirmed', - username: pnid.username, + username: pnid.get('username'), paragraph: 'your email address has been confirmed. We hope you have fun on Pretendo Network!', - text: `Dear ${pnid.username}, \r\n\r\nYour email address has been confirmed. We hope you have fun on Pretendo Network!` + text: `Dear ${pnid.get('username')}, \r\n\r\nYour email address has been confirmed. We hope you have fun on Pretendo Network!` }; await mailer.sendMail(options); @@ -294,8 +294,8 @@ async function sendForgotPasswordEmail(pnid: mongoose.HydratedDocument Date: Fri, 17 Mar 2023 15:02:20 -0400 Subject: [PATCH 038/219] Removed RegExp from getUserByEmailAddress --- src/database.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/database.ts b/src/database.ts index e79515c..4589c58 100644 --- a/src/database.ts +++ b/src/database.ts @@ -61,8 +61,9 @@ async function getUserByPID(pid: number): Promise { async function getUserByEmailAddress(email: string): Promise { verifyConnected(); + // TODO - Update documents to store email normalized return await PNID.findOne({ - 'email.address': new RegExp(email, 'i') // * Ignore case + 'email.address': email }); } From 9f3d5c4a350c99c80ba26ca18443e9739239e4c6 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Fri, 17 Mar 2023 16:16:10 -0400 Subject: [PATCH 039/219] Removed default exports in favor of named exports --- src/cache.ts | 66 +++++++------------- src/config-manager.ts | 44 ++++++------- src/database.ts | 62 +++++++----------- src/logger.ts | 17 ++--- src/mailer.ts | 6 +- src/middleware/api.ts | 4 +- src/middleware/nasc.ts | 46 +++++++------- src/middleware/pnid.ts | 6 +- src/models/pnid.ts | 10 +-- src/server.ts | 30 ++++----- src/services/api/index.ts | 25 ++++---- src/services/api/routes/index.ts | 18 +++--- src/services/api/routes/v1/connections.ts | 6 +- src/services/api/routes/v1/email.ts | 4 +- src/services/api/routes/v1/forgotPassword.ts | 10 +-- src/services/api/routes/v1/login.ts | 20 +++--- src/services/api/routes/v1/register.ts | 26 ++++---- src/services/api/routes/v1/resetPassword.ts | 8 +-- src/services/api/routes/v1/user.ts | 2 +- src/services/assets/index.ts | 6 +- src/services/conntest/index.ts | 8 +-- src/services/datastore/index.ts | 11 ++-- src/services/datastore/routes/index.ts | 5 -- src/services/datastore/routes/upload.ts | 4 +- src/services/local-cdn/index.ts | 13 ++-- src/services/local-cdn/routes/get.ts | 4 +- src/services/local-cdn/routes/index.ts | 5 -- src/services/nasc/index.ts | 13 ++-- src/services/nasc/routes/ac.ts | 42 ++++++------- src/services/nasc/routes/index.ts | 5 -- src/services/nnid/index.ts | 36 ++++++----- src/services/nnid/routes/index.ts | 19 ------ src/services/nnid/routes/oauth.ts | 10 +-- src/services/nnid/routes/people.ts | 30 ++++----- src/services/nnid/routes/provider.ts | 22 +++---- src/services/nnid/routes/support.ts | 16 ++--- src/util.ts | 65 ++++++++----------- 37 files changed, 319 insertions(+), 405 deletions(-) delete mode 100644 src/services/datastore/routes/index.ts delete mode 100644 src/services/local-cdn/routes/index.ts delete mode 100644 src/services/nasc/routes/index.ts delete mode 100644 src/services/nnid/routes/index.ts diff --git a/src/cache.ts b/src/cache.ts index f14f2d1..cc5a6bb 100644 --- a/src/cache.ts +++ b/src/cache.ts @@ -10,7 +10,7 @@ const SERVICE_CERTS_BASE: string = `${__dirname}/../certs/service`; const NEX_CERTS_BASE: string = `${__dirname}/../certs/nex`; const LOCAL_CDN_BASE: string = `${__dirname}/../cdn`; -async function connect(): Promise { +export async function connect(): Promise { if (!disabledFeatures.redis) { client = redis.createClient(config.redis.client); client.on('error', (err) => console.log('Redis Client Error', err)); @@ -19,7 +19,7 @@ async function connect(): Promise { } } -async function setCachedFile(fileName: string, value: Buffer): Promise { +export async function setCachedFile(fileName: string, value: Buffer): Promise { if (disabledFeatures.redis) { memoryCache[fileName] = value; } else { @@ -27,7 +27,7 @@ async function setCachedFile(fileName: string, value: Buffer): Promise { } } -async function getCachedFile(fileName: string, encoding?: BufferEncoding): Promise { +export async function getCachedFile(fileName: string, encoding?: BufferEncoding): Promise { let cachedFile: Buffer; if (disabledFeatures.redis) { @@ -44,7 +44,7 @@ async function getCachedFile(fileName: string, encoding?: BufferEncoding): Promi // * NEX server cache functions -async function getNEXPublicKey(name: string, encoding?: BufferEncoding): Promise { +export async function getNEXPublicKey(name: string, encoding?: BufferEncoding): Promise { let publicKey: Buffer = await getCachedFile(`nex:${name}:public_key`, encoding); if (publicKey === null) { @@ -55,7 +55,7 @@ async function getNEXPublicKey(name: string, encoding?: BufferEncoding): Promise return publicKey; } -async function getNEXPrivateKey(name: string, encoding?: BufferEncoding): Promise { +export async function getNEXPrivateKey(name: string, encoding?: BufferEncoding): Promise { let privateKey: Buffer = await getCachedFile(`nex:${name}:private_key`, encoding); if (privateKey === null) { @@ -66,7 +66,7 @@ async function getNEXPrivateKey(name: string, encoding?: BufferEncoding): Promis return privateKey; } -async function getNEXSecretKey(name: string, encoding?: BufferEncoding): Promise { +export async function getNEXSecretKey(name: string, encoding?: BufferEncoding): Promise { let secretKey: Buffer = await getCachedFile(`nex:${name}:secret_key`, encoding); if (secretKey === null) { @@ -78,7 +78,7 @@ async function getNEXSecretKey(name: string, encoding?: BufferEncoding): Promise return secretKey; } -async function getNEXAESKey(name: string, encoding?: BufferEncoding): Promise { +export async function getNEXAESKey(name: string, encoding?: BufferEncoding): Promise { let aesKey: Buffer = await getCachedFile(`nex:${name}:aes_key`, encoding); if (aesKey === null) { @@ -90,25 +90,25 @@ async function getNEXAESKey(name: string, encoding?: BufferEncoding): Promise { +export async function setNEXPublicKey(name: string, value: Buffer): Promise { await setCachedFile(`nex:${name}:public_key`, value); } -async function setNEXPrivateKey(name: string, value: Buffer): Promise { +export async function setNEXPrivateKey(name: string, value: Buffer): Promise { await setCachedFile(`nex:${name}:private_key`, value); } -async function setNEXSecretKey(name: string, value: Buffer): Promise { +export async function setNEXSecretKey(name: string, value: Buffer): Promise { await setCachedFile(`nex:${name}:secret_key`, value); } -async function setNEXAESKey(name: string, value: Buffer): Promise { +export async function setNEXAESKey(name: string, value: Buffer): Promise { await setCachedFile(`nex:${name}:aes_key`, value); } // * 3rd party service cache functions -async function getServicePublicKey(name: string, encoding?: BufferEncoding): Promise { +export async function getServicePublicKey(name: string, encoding?: BufferEncoding): Promise { let publicKey: Buffer = await getCachedFile(`service:${name}:public_key`, encoding); if (publicKey === null) { @@ -119,7 +119,7 @@ async function getServicePublicKey(name: string, encoding?: BufferEncoding): Pro return publicKey; } -async function getServicePrivateKey(name: string, encoding?: BufferEncoding): Promise { +export async function getServicePrivateKey(name: string, encoding?: BufferEncoding): Promise { let privateKey: Buffer = await getCachedFile(`service:${name}:private_key`, encoding); if (privateKey === null) { @@ -130,7 +130,7 @@ async function getServicePrivateKey(name: string, encoding?: BufferEncoding): Pr return privateKey; } -async function getServiceSecretKey(name: string, encoding?: BufferEncoding): Promise { +export async function getServiceSecretKey(name: string, encoding?: BufferEncoding): Promise { let secretKey: Buffer = await getCachedFile(`service:${name}:secret_key`, encoding); if (secretKey === null) { @@ -142,7 +142,7 @@ async function getServiceSecretKey(name: string, encoding?: BufferEncoding): Pro return secretKey; } -async function getServiceAESKey(name: string, encoding?: BufferEncoding): Promise { +export async function getServiceAESKey(name: string, encoding?: BufferEncoding): Promise { let aesKey: Buffer = await getCachedFile(`service:${name}:aes_key`, encoding); if (aesKey === null) { @@ -154,25 +154,25 @@ async function getServiceAESKey(name: string, encoding?: BufferEncoding): Promis return aesKey; } -async function setServicePublicKey(name: string, value: Buffer): Promise { +export async function setServicePublicKey(name: string, value: Buffer): Promise { await setCachedFile(`service:${name}:public_key`, value); } -async function setServicePrivateKey(name: string, value: Buffer): Promise { +export async function setServicePrivateKey(name: string, value: Buffer): Promise { await setCachedFile(`service:${name}:private_key`, value); } -async function setServiceSecretKey(name: string, value: Buffer): Promise { +export async function setServiceSecretKey(name: string, value: Buffer): Promise { await setCachedFile(`service:${name}:secret_key`, value); } -async function setServiceAESKey(name: string, value: Buffer): Promise { +export async function setServiceAESKey(name: string, value: Buffer): Promise { await setCachedFile(`service:${name}:aes_key`, value); } // * Local CDN cache functions -async function getLocalCDNFile(name: string, encoding?: BufferEncoding): Promise { +export async function getLocalCDNFile(name: string, encoding?: BufferEncoding): Promise { let file: Buffer = await getCachedFile(`local_cdn:${name}`, encoding); if (file === null) { @@ -186,28 +186,6 @@ async function getLocalCDNFile(name: string, encoding?: BufferEncoding): Promise return file; } -async function setLocalCDNFile(name: string, value: Buffer): Promise { +export async function setLocalCDNFile(name: string, value: Buffer): Promise { await setCachedFile(`local_cdn:${name}`, value); -} - -export default { - connect, - getNEXPublicKey, - getNEXPrivateKey, - getNEXSecretKey, - getNEXAESKey, - setNEXPublicKey, - setNEXPrivateKey, - setNEXSecretKey, - setNEXAESKey, - getServicePublicKey, - getServicePrivateKey, - getServiceSecretKey, - getServiceAESKey, - setServicePublicKey, - setServicePrivateKey, - setServiceSecretKey, - setServiceAESKey, - getLocalCDNFile, - setLocalCDNFile -}; \ No newline at end of file +} \ No newline at end of file diff --git a/src/config-manager.ts b/src/config-manager.ts index ed5631a..8b42c75 100644 --- a/src/config-manager.ts +++ b/src/config-manager.ts @@ -1,7 +1,7 @@ import fs from 'fs-extra'; import mongoose from 'mongoose'; import dotenv from 'dotenv'; -import logger from '@/logger'; +import { LOG_INFO, LOG_WARN, LOG_ERROR } from '@/logger'; import { Config, DisabledFeatures } from '@/types/common/config'; dotenv.config(); @@ -13,7 +13,7 @@ export const disabledFeatures: DisabledFeatures = { s3: false }; -logger.info('Loading config'); +LOG_INFO('Loading config'); let mongooseConnectOptions: mongoose.ConnectOptions; @@ -60,99 +60,99 @@ export const config: Config = { website_base: process.env.PN_ACT_CONFIG_WEBSITE_BASE }; -logger.info('Config loaded, checking integrity'); +LOG_INFO('Config loaded, checking integrity'); if (!config.http.port) { - logger.error('Failed to find HTTP port. Set the PN_ACT_CONFIG_HTTP_PORT environment variable'); + LOG_ERROR('Failed to find HTTP port. Set the PN_ACT_CONFIG_HTTP_PORT environment variable'); process.exit(0); } if (!config.mongoose.connection_string) { - logger.error('Failed to find MongoDB connection string. Set the PN_ACT_CONFIG_MONGO_CONNECTION_STRING environment variable'); + LOG_ERROR('Failed to find MongoDB connection string. Set the PN_ACT_CONFIG_MONGO_CONNECTION_STRING environment variable'); process.exit(0); } if (!config.cdn.base_url) { - logger.error('Failed to find asset CDN base URL. Set the PN_ACT_CONFIG_CDN_BASE_URL environment variable'); + LOG_ERROR('Failed to find asset CDN base URL. Set the PN_ACT_CONFIG_CDN_BASE_URL environment variable'); process.exit(0); } if (!config.redis.client.url) { - logger.warn('Failed to find Redis connection url. Disabling feature and using in-memory cache. To enable feature set the PN_ACT_CONFIG_REDIS_URL environment variable'); + LOG_WARN('Failed to find Redis connection url. Disabling feature and using in-memory cache. To enable feature set the PN_ACT_CONFIG_REDIS_URL environment variable'); disabledFeatures.redis = true; } if (!config.email.host) { - logger.warn('Failed to find email SMTP host. Disabling feature. To enable feature set the PN_ACT_CONFIG_EMAIL_HOST environment variable'); + LOG_WARN('Failed to find email SMTP host. Disabling feature. To enable feature set the PN_ACT_CONFIG_EMAIL_HOST environment variable'); disabledFeatures.email = true; } if (!config.email.port) { - logger.warn('Failed to find email SMTP port. Disabling feature. To enable feature set the PN_ACT_CONFIG_EMAIL_PORT environment variable'); + LOG_WARN('Failed to find email SMTP port. Disabling feature. To enable feature set the PN_ACT_CONFIG_EMAIL_PORT environment variable'); disabledFeatures.email = true; } if (config.email.secure === undefined) { - logger.warn('Failed to find email SMTP secure flag. Disabling feature. To enable feature set the PN_ACT_CONFIG_EMAIL_SECURE environment variable'); + LOG_WARN('Failed to find email SMTP secure flag. Disabling feature. To enable feature set the PN_ACT_CONFIG_EMAIL_SECURE environment variable'); disabledFeatures.email = true; } if (!config.email.auth.user) { - logger.warn('Failed to find email account username. Disabling feature. To enable feature set the auth.user environment variable'); + LOG_WARN('Failed to find email account username. Disabling feature. To enable feature set the auth.user environment variable'); disabledFeatures.email = true; } if (!config.email.auth.pass) { - logger.warn('Failed to find email account password. Disabling feature. To enable feature set the PN_ACT_CONFIG_EMAIL_PASSWORD environment variable'); + LOG_WARN('Failed to find email account password. Disabling feature. To enable feature set the PN_ACT_CONFIG_EMAIL_PASSWORD environment variable'); disabledFeatures.email = true; } if (!config.email.from) { - logger.warn('Failed to find email from config. Disabling feature. To enable feature set the PN_ACT_CONFIG_EMAIL_FROM environment variable'); + LOG_WARN('Failed to find email from config. Disabling feature. To enable feature set the PN_ACT_CONFIG_EMAIL_FROM environment variable'); disabledFeatures.email = true; } if (!disabledFeatures.email) { if (!config.website_base) { - logger.error('Email sending is enabled and no website base was configured. Set the PN_ACT_CONFIG_WEBSITE_BASE environment variable'); + LOG_ERROR('Email sending is enabled and no website base was configured. Set the PN_ACT_CONFIG_WEBSITE_BASE environment variable'); process.exit(0); } } if (!config.hcaptcha.secret) { - logger.warn('Failed to find captcha secret config. Disabling feature. To enable feature set the PN_ACT_CONFIG_HCAPTCHA_SECRET environment variable'); + LOG_WARN('Failed to find captcha secret config. Disabling feature. To enable feature set the PN_ACT_CONFIG_HCAPTCHA_SECRET environment variable'); disabledFeatures.captcha = true; } if (!config.s3.endpoint) { - logger.warn('Failed to find s3 endpoint config. Disabling feature. To enable feature set the PN_ACT_CONFIG_S3_ENDPOINT environment variable'); + LOG_WARN('Failed to find s3 endpoint config. Disabling feature. To enable feature set the PN_ACT_CONFIG_S3_ENDPOINT environment variable'); disabledFeatures.s3 = true; } if (!config.s3.key) { - logger.warn('Failed to find s3 access key config. Disabling feature. To enable feature set the PN_ACT_CONFIG_S3_ACCESS_KEY environment variable'); + LOG_WARN('Failed to find s3 access key config. Disabling feature. To enable feature set the PN_ACT_CONFIG_S3_ACCESS_KEY environment variable'); disabledFeatures.s3 = true; } if (!config.s3.secret) { - logger.warn('Failed to find s3 secret key config. Disabling feature. To enable feature set the PN_ACT_CONFIG_S3_ACCESS_SECRET environment variable'); + LOG_WARN('Failed to find s3 secret key config. Disabling feature. To enable feature set the PN_ACT_CONFIG_S3_ACCESS_SECRET environment variable'); disabledFeatures.s3 = true; } if (disabledFeatures.s3) { if (!config.cdn.subdomain) { - logger.error('s3 file storage is disabled and no CDN subdomain was set. Set the PN_ACT_CONFIG_CDN_SUBDOMAIN environment variable'); + LOG_ERROR('s3 file storage is disabled and no CDN subdomain was set. Set the PN_ACT_CONFIG_CDN_SUBDOMAIN environment variable'); process.exit(0); } if (!config.cdn.disk_path) { - logger.error('s3 file storage is disabled and no CDN disk path was set. Set the PN_ACT_CONFIG_CDN_DISK_PATH environment variable'); + LOG_ERROR('s3 file storage is disabled and no CDN disk path was set. Set the PN_ACT_CONFIG_CDN_DISK_PATH environment variable'); process.exit(0); } - logger.warn(`s3 file storage disabled. Using disk-based file storage. Please ensure cdn.base_url config or PN_ACT_CONFIG_CDN_BASE env variable is set to point to this server with the subdomain being ${config.cdn.subdomain}`); + LOG_WARN(`s3 file storage disabled. Using disk-based file storage. Please ensure cdn.base_url config or PN_ACT_CONFIG_CDN_BASE env variable is set to point to this server with the subdomain being ${config.cdn.subdomain}`); if (disabledFeatures.redis) { - logger.warn('Both s3 and Redis are disabled. Large CDN files will use the in-memory cache, which may result in high memory use. Please enable s3 if you\'re running a production server.'); + LOG_WARN('Both s3 and Redis are disabled. Large CDN files will use the in-memory cache, which may result in high memory use. Please enable s3 if you\'re running a production server.'); } } \ No newline at end of file diff --git a/src/database.ts b/src/database.ts index 4589c58..931d181 100644 --- a/src/database.ts +++ b/src/database.ts @@ -1,10 +1,10 @@ import mongoose from 'mongoose'; import bcrypt from 'bcrypt'; import joi from 'joi'; -import util from '@/util'; +import { nintendoPasswordHash, decryptToken, unpackToken } from '@/util'; import { PNID } from '@/models/pnid'; import { Server } from '@/models/server'; -import logger from '@/logger'; +import { LOG_ERROR } from '@/logger'; import { config } from '@/config-manager'; import { HydratedPNIDDocument } from '@/types/mongoose/pnid'; import { HydratedDeviceDocument } from '@/types/mongoose/device'; @@ -25,24 +25,24 @@ const discordConnectionSchema: joi.ObjectSchema = joi.object({ let _connection: mongoose.Connection; -async function connect(): Promise { +export async function connect(): Promise { await mongoose.connect(connection_string, options); _connection = mongoose.connection; _connection.on('error', console.error.bind(console, 'connection error:')); } -function connection(): mongoose.Connection { +export function connection(): mongoose.Connection { return _connection; } -function verifyConnected(): void { +export function verifyConnected(): void { if (!connection()) { throw new Error('Cannot make database requets without being connected'); } } -async function getUserByUsername(username: string): Promise { +export async function getUserByUsername(username: string): Promise { verifyConnected(); return await PNID.findOne({ @@ -50,7 +50,7 @@ async function getUserByUsername(username: string): Promise { +export async function getUserByPID(pid: number): Promise { verifyConnected(); return await PNID.findOne({ @@ -58,7 +58,7 @@ async function getUserByPID(pid: number): Promise { }); } -async function getUserByEmailAddress(email: string): Promise { +export async function getUserByEmailAddress(email: string): Promise { verifyConnected(); // TODO - Update documents to store email normalized @@ -67,13 +67,13 @@ async function getUserByEmailAddress(email: string): Promise { +export async function doesUserExist(username: string): Promise { verifyConnected(); return !!await getUserByUsername(username); } -async function getUserBasic(token: string): Promise { +export async function getUserBasic(token: string): Promise { verifyConnected(); // * Wii U sends Basic auth as `username password`, where the password may not have spaces @@ -90,7 +90,7 @@ async function getUserBasic(token: string): Promise { return null; } - const hashedPassword: string = util.nintendoPasswordHash(password, user.pid); + const hashedPassword: string = nintendoPasswordHash(password, user.pid); if (!bcrypt.compareSync(hashedPassword, user.password)) { return null; @@ -99,12 +99,12 @@ async function getUserBasic(token: string): Promise { return user; } -async function getUserBearer(token: string): Promise { +export async function getUserBearer(token: string): Promise { verifyConnected(); try { - const decryptedToken: Buffer = await util.decryptToken(Buffer.from(token, 'base64')); - const unpackedToken: Token = util.unpackToken(decryptedToken); + const decryptedToken: Buffer = await decryptToken(Buffer.from(token, 'base64')); + const unpackedToken: Token = unpackToken(decryptedToken); const user: HydratedPNIDDocument = await getUserByPID(unpackedToken.pid); @@ -119,12 +119,12 @@ async function getUserBearer(token: string): Promise { return user; } catch (error: any) { // TODO: Handle error - logger.error(error); + LOG_ERROR(error); return null; } } -async function getUserProfileJSONByPID(pid: number): Promise { +export async function getUserProfileJSONByPID(pid: number): Promise { verifyConnected(); const user: HydratedPNIDDocument = await getUserByPID(pid); @@ -196,27 +196,27 @@ async function getUserProfileJSONByPID(pid: number): Promise { }; } -async function getServer(gameServerId: string, accessMode: string): Promise { +export async function getServer(gameServerId: string, accessMode: string): Promise { return await Server.findOne({ game_server_id: gameServerId, access_mode: accessMode }); } -async function getServerByTitleId(titleId: string, accessMode: string): Promise { +export async function getServerByTitleId(titleId: string, accessMode: string): Promise { return await Server.findOne({ title_ids: titleId, access_mode: accessMode }); } -async function addUserConnection(pnid: HydratedPNIDDocument, data: ConnectionData, type: string): Promise { +export async function addUserConnection(pnid: HydratedPNIDDocument, data: ConnectionData, type: string): Promise { if (type === 'discord') { return await addUserConnectionDiscord(pnid, data); } } -async function addUserConnectionDiscord(pnid: HydratedPNIDDocument, data: DiscordConnectionData): Promise { +export async function addUserConnectionDiscord(pnid: HydratedPNIDDocument, data: DiscordConnectionData): Promise { const valid: joi.ValidationResult = discordConnectionSchema.validate(data); if (valid.error) { @@ -239,14 +239,14 @@ async function addUserConnectionDiscord(pnid: HydratedPNIDDocument, data: Discor }; } -async function removeUserConnection(pnid: HydratedPNIDDocument, type: string): Promise { +export async function removeUserConnection(pnid: HydratedPNIDDocument, type: string): Promise { // * Add more connections later? if (type === 'discord') { return await removeUserConnectionDiscord(pnid); } } -async function removeUserConnectionDiscord(pnid: HydratedPNIDDocument): Promise { +export async function removeUserConnectionDiscord(pnid: HydratedPNIDDocument): Promise { await PNID.updateOne({ pid: pnid.get('pid') }, { $set: { 'connections.discord.id': '' @@ -257,20 +257,4 @@ async function removeUserConnectionDiscord(pnid: HydratedPNIDDocument): Promise< app: 'api', status: 200 }; -} - -export default { - connect, - connection, - getUserByUsername, - getUserByPID, - getUserByEmailAddress, - doesUserExist, - getUserBasic, - getUserBearer, - getUserProfileJSONByPID, - getServer, - getServerByTitleId, - addUserConnection, - removeUserConnection -}; \ No newline at end of file +} \ No newline at end of file diff --git a/src/logger.ts b/src/logger.ts index 64435a2..798ef62 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -14,7 +14,7 @@ const streams: { [key: string]: fs.WriteStream } = { info: fs.createWriteStream(`${root}/logs/info.log`) }; -function success(input: string): void { +export function LOG_SUCCESS(input: string): void { const time: Date = new Date(); input = `[${time.getHours()}:${time.getMinutes()}:${time.getSeconds()}] [SUCCESS]: ${input}`; streams.success.write(`${input}\n`); @@ -22,7 +22,7 @@ function success(input: string): void { console.log(`${input}`.green.bold); } -function error(input: string): void { +export function LOG_ERROR(input: string): void { const time: Date = new Date(); input = `[${time.getHours()}:${time.getMinutes()}:${time.getSeconds()}] [ERROR]: ${input}`; streams.error.write(`${input}\n`); @@ -30,7 +30,7 @@ function error(input: string): void { console.log(`${input}`.red.bold); } -function warn(input: string): void { +export function LOG_WARN(input: string): void { const time: Date = new Date(); input = `[${time.getHours()}:${time.getMinutes()}:${time.getSeconds()}] [WARN]: ${input}`; streams.warn.write(`${input}\n`); @@ -38,17 +38,10 @@ function warn(input: string): void { console.log(`${input}`.yellow.bold); } -function info(input: string): void { +export function LOG_INFO(input: string): void { const time: Date = new Date(); input = `[${time.getHours()}:${time.getMinutes()}:${time.getSeconds()}] [INFO]: ${input}`; streams.info.write(`${input}\n`); console.log(`${input}`.cyan.bold); -} - -export default { - success, - error, - warn, - info -}; \ No newline at end of file +} \ No newline at end of file diff --git a/src/mailer.ts b/src/mailer.ts index b7f52e9..a9ec1cc 100644 --- a/src/mailer.ts +++ b/src/mailer.ts @@ -13,7 +13,7 @@ if (!disabledFeatures.email) { transporter = nodemailer.createTransport(config.email); } -async function sendMail(options: MailerOptions): Promise { +export async function sendMail(options: MailerOptions): Promise { if (!disabledFeatures.email) { const { to, subject, username, paragraph, preview, text, link, confirmation } = options; @@ -41,7 +41,3 @@ async function sendMail(options: MailerOptions): Promise { }); } } - -export default { - sendMail -}; diff --git a/src/middleware/api.ts b/src/middleware/api.ts index dc7c951..0f7e876 100644 --- a/src/middleware/api.ts +++ b/src/middleware/api.ts @@ -1,5 +1,5 @@ import express from 'express'; -import database from '@/database'; +import { getUserBearer } from '@/database'; import { HydratedPNIDDocument } from '@/types/mongoose/pnid'; async function APIMiddleware(request: express.Request, _response: express.Response, next: express.NextFunction): Promise { @@ -10,7 +10,7 @@ async function APIMiddleware(request: express.Request, _response: express.Respon } const token: string = authHeader.split(' ')[1]; - const user: HydratedPNIDDocument = await database.getUserBearer(token); + const user: HydratedPNIDDocument = await getUserBearer(token); request.pnid = user; diff --git a/src/middleware/nasc.ts b/src/middleware/nasc.ts index ebf4dc5..5f1e5aa 100644 --- a/src/middleware/nasc.ts +++ b/src/middleware/nasc.ts @@ -3,10 +3,10 @@ import express from 'express'; import mongoose from 'mongoose'; import { Device } from '@/models/device'; import { NEXAccount } from '@/models/nex-account'; -import util from '@/util'; -import database from '@/database'; +import { nascError, nintendoBase64Decode } from '@/util'; +import { connection as databaseConnection } from '@/database'; import NintendoCertificate from '@/nintendo-certificate'; -import logger from '@/logger'; +import { LOG_ERROR } from '@/logger'; import { NASCRequestParams } from '@/types/services/nasc/request-params'; import { HydratedNEXAccountDocument } from '@/types/mongoose/nex-account'; import { HydratedDeviceDocument } from '@/types/mongoose/device'; @@ -21,16 +21,16 @@ async function NASCMiddleware(request: express.Request, response: express.Respon !requestParams.titleid || !requestParams.servertype ) { - response.status(200).send(util.nascError('null')); // This is what Nintendo sends + response.status(200).send(nascError('null')); // This is what Nintendo sends return; } - const action: string = util.nintendoBase64Decode(requestParams.action).toString(); - const fcdcert: Buffer = util.nintendoBase64Decode(requestParams.fcdcert); - const serialNumber: string = util.nintendoBase64Decode(requestParams.csnum).toString(); - const macAddress: string = util.nintendoBase64Decode(requestParams.macadr).toString(); - const titleID: string = util.nintendoBase64Decode(requestParams.titleid).toString(); - const environment: string = util.nintendoBase64Decode(requestParams.servertype).toString(); + const action: string = nintendoBase64Decode(requestParams.action).toString(); + const fcdcert: Buffer = nintendoBase64Decode(requestParams.fcdcert); + const serialNumber: string = nintendoBase64Decode(requestParams.csnum).toString(); + const macAddress: string = nintendoBase64Decode(requestParams.macadr).toString(); + const titleID: string = nintendoBase64Decode(requestParams.titleid).toString(); + const environment: string = nintendoBase64Decode(requestParams.servertype).toString(); const macAddressHash: string = crypto.createHash('sha256').update(macAddress).digest('base64'); const fcdcertHash: string = crypto.createHash('sha256').update(fcdcert).digest('base64'); @@ -40,31 +40,31 @@ async function NASCMiddleware(request: express.Request, response: express.Respon let password: string; if (requestParams.userid) { - pid = Number(util.nintendoBase64Decode(requestParams.userid).toString()); + pid = Number(nintendoBase64Decode(requestParams.userid).toString()); } if (requestParams.uidhmac) { - pidHmac = util.nintendoBase64Decode(requestParams.uidhmac).toString(); + pidHmac = nintendoBase64Decode(requestParams.uidhmac).toString(); } if (requestParams.passwd) { - password = util.nintendoBase64Decode(requestParams.passwd).toString(); + password = nintendoBase64Decode(requestParams.passwd).toString(); } if (action !== 'LOGIN' && action !== 'SVCLOC') { - response.status(200).send(util.nascError('null')); // This is what Nintendo sends + response.status(200).send(nascError('null')); // This is what Nintendo sends return; } const cert: NintendoCertificate = new NintendoCertificate(fcdcert); if (!cert.valid) { - response.status(200).send(util.nascError('121')); + response.status(200).send(nascError('121')); return; } if (!validNintendoMACAddress(macAddress)) { - response.status(200).send(util.nascError('null')); + response.status(200).send(nascError('null')); return; } @@ -91,7 +91,7 @@ async function NASCMiddleware(request: express.Request, response: express.Respon } if (!model) { - response.status(200).send(util.nascError('null')); + response.status(200).send(nascError('null')); return; } @@ -105,7 +105,7 @@ async function NASCMiddleware(request: express.Request, response: express.Respon if (device) { if (device.get('access_level') < 0) { - response.status(200).send(util.nascError('102')); + response.status(200).send(nascError('102')); return; } @@ -113,7 +113,7 @@ async function NASCMiddleware(request: express.Request, response: express.Respon const linkedPIDs = device.get('linked_pids'); if (!linkedPIDs.includes(pid)) { - response.status(200).send(util.nascError('102')); + response.status(200).send(nascError('102')); return; } } @@ -123,7 +123,7 @@ async function NASCMiddleware(request: express.Request, response: express.Respon if (password && !pid && !pidHmac) { // Register new user - const session: mongoose.ClientSession = await database.connection().startSession(); + const session: mongoose.ClientSession = await databaseConnection().startSession(); await session.startTransaction(); try { @@ -159,12 +159,12 @@ async function NASCMiddleware(request: express.Request, response: express.Respon await session.commitTransaction(); } catch (error) { - logger.error('[NASC] REGISTER ACCOUNT: ' + error); + LOG_ERROR('[NASC] REGISTER ACCOUNT: ' + error); await session.abortTransaction(); // 3DS expects 200 even on error - response.status(200).send(util.nascError('102')); + response.status(200).send(nascError('102')); return; } finally { // * This runs regardless of failure @@ -177,7 +177,7 @@ async function NASCMiddleware(request: express.Request, response: express.Respon const nexUser: HydratedNEXAccountDocument = await NEXAccount.findOne({ pid }); if (!nexUser || nexUser.get('access_level') < 0) { - response.status(200).send(util.nascError('102')); + response.status(200).send(nascError('102')); return; } diff --git a/src/middleware/pnid.ts b/src/middleware/pnid.ts index fd242cc..2d52342 100644 --- a/src/middleware/pnid.ts +++ b/src/middleware/pnid.ts @@ -1,6 +1,6 @@ import express from 'express'; import xmlbuilder from 'xmlbuilder'; -import database from '@/database'; +import { getUserBasic, getUserBearer } from '@/database'; import { HydratedPNIDDocument } from '@/types/mongoose/pnid'; async function PNIDMiddleware(request: express.Request, response: express.Response, next: express.NextFunction): Promise { @@ -20,9 +20,9 @@ async function PNIDMiddleware(request: express.Request, response: express.Respon } if (type === 'Basic') { - user = await database.getUserBasic(token); + user = await getUserBasic(token); } else { - user = await database.getUserBearer(token); + user = await getUserBearer(token); } if (!user) { diff --git a/src/models/pnid.ts b/src/models/pnid.ts index c6a3b3f..601b1fe 100644 --- a/src/models/pnid.ts +++ b/src/models/pnid.ts @@ -6,7 +6,7 @@ import TGA from 'tga'; import got from 'got'; import Mii from 'mii-js'; import { DeviceSchema } from '@/models/device'; -import util from '@/util'; +import { uploadCDNAsset } from '@/util'; import { HydratedPNIDDocument, IPNID, IPNIDMethods, PNIDModel } from '@/types/mongoose/pnid'; const PNIDSchema = new Schema({ @@ -185,8 +185,8 @@ PNIDSchema.method('generateMiiImages', async function generateMiiImages(): Promi const userMiiKey: string = `mii/${this.get('pid')}`; - await util.uploadCDNAsset('pn-cdn', `${userMiiKey}/standard.tga`, tga, 'public-read'); - await util.uploadCDNAsset('pn-cdn', `${userMiiKey}/normal_face.png`, miiStudioNormalFaceImageData, 'public-read'); + await uploadCDNAsset('pn-cdn', `${userMiiKey}/standard.tga`, tga, 'public-read'); + await uploadCDNAsset('pn-cdn', `${userMiiKey}/normal_face.png`, miiStudioNormalFaceImageData, 'public-read'); const expressions: string[] = ['frustrated', 'smile_open_mouth', 'wink_left', 'sorrow', 'surprise_open_mouth']; for (const expression of expressions) { @@ -197,7 +197,7 @@ PNIDSchema.method('generateMiiImages', async function generateMiiImages(): Promi instanceCount: '1', }); const miiStudioExpressionImageData: Buffer = await got(miiStudioExpressionUrl).buffer(); - await util.uploadCDNAsset('pn-cdn', `${userMiiKey}/${expression}.png`, miiStudioExpressionImageData, 'public-read'); + await uploadCDNAsset('pn-cdn', `${userMiiKey}/${expression}.png`, miiStudioExpressionImageData, 'public-read'); } const miiStudioBodyUrl: string = mii.studioUrl({ @@ -206,7 +206,7 @@ PNIDSchema.method('generateMiiImages', async function generateMiiImages(): Promi instanceCount: '1', }); const miiStudioBodyImageData: Buffer = await got(miiStudioBodyUrl).buffer(); - await util.uploadCDNAsset('pn-cdn', `${userMiiKey}/body.png`, miiStudioBodyImageData, 'public-read'); + await uploadCDNAsset('pn-cdn', `${userMiiKey}/body.png`, miiStudioBodyImageData, 'public-read'); }); PNIDSchema.method('getServerMode', function getServerMode(): string { diff --git a/src/server.ts b/src/server.ts index 1d3710f..52fe75a 100644 --- a/src/server.ts +++ b/src/server.ts @@ -7,10 +7,10 @@ process.on('uncaughtException', (err, origin) => { import express from 'express'; import morgan from 'morgan'; import xmlparser from '@/middleware/xml-parser'; -import cache from '@/cache'; -import database from '@/database'; -import util from '@/util'; -import logger from '@/logger'; +import { connect as connectCache } from '@/cache'; +import { connect as connectDatabase } from '@/database'; +import { fullUrl } from '@/util'; +import { LOG_INFO, LOG_SUCCESS, LOG_WARN } from '@/logger'; import conntest from '@/services/conntest'; import nnid from '@/services/nnid'; @@ -28,7 +28,7 @@ const app = express(); // START APPLICATION // Create router -logger.info('Setting up Middleware'); +LOG_INFO('Setting up Middleware'); app.use(morgan('dev')); app.use(express.json()); app.use(express.urlencoded({ @@ -46,12 +46,12 @@ app.use(localcdn); app.use(assets); // 404 handler -logger.info('Creating 404 status handler'); +LOG_INFO('Creating 404 status handler'); app.use((request: express.Request, response: express.Response) => { - const fullUrl: string = util.fullUrl(request); + const url: string = fullUrl(request); const deviceId: string = request.headers['X-Nintendo-Device-ID'] as string || 'Unknown'; - logger.warn(`HTTP 404 at ${fullUrl} from ${deviceId}`); + LOG_WARN(`HTTP 404 at ${url} from ${deviceId}`); response.set('Content-Type', 'text/xml'); response.set('Server', 'Nintendo 3DS (http)'); @@ -62,13 +62,13 @@ app.use((request: express.Request, response: express.Response) => { }); // non-404 error handler -logger.info('Creating non-404 status handler'); +LOG_INFO('Creating non-404 status handler'); app.use((error: any, request: express.Request, response: express.Response, _next: express.NextFunction) => { const status: number = error.status || 500; - const fullUrl: string = util.fullUrl(request); + const url: string = fullUrl(request); const deviceId: string = request.headers['X-Nintendo-Device-ID'] as string || 'Unknown'; - logger.warn(`HTTP ${status} at ${fullUrl} from ${deviceId}: ${error.message}`); + LOG_WARN(`HTTP ${status} at ${url} from ${deviceId}: ${error.message}`); response.status(status); response.json({ @@ -80,13 +80,13 @@ app.use((error: any, request: express.Request, response: express.Response, _next async function main(): Promise { // Starts the server - logger.info('Starting server'); + LOG_INFO('Starting server'); - await database.connect(); - await cache.connect(); + await connectDatabase(); + await connectCache(); app.listen(port, () => { - logger.success(`Server started on port ${port}`); + LOG_SUCCESS(`Server started on port ${port}`); }); } diff --git a/src/services/api/index.ts b/src/services/api/index.ts index df383af..18b7cde 100644 --- a/src/services/api/index.ts +++ b/src/services/api/index.ts @@ -4,33 +4,34 @@ import express from 'express'; import subdomain from 'express-subdomain'; import cors from 'cors'; import APIMiddleware from '@/middleware/api'; -import routes from '@/services/api/routes'; -import logger from '@/logger'; +import { LOG_INFO } from '@/logger'; + +import { V1 } from '@/services/api/routes'; // Router to handle the subdomain restriction const api: express.Router = express.Router(); -logger.info('[USER API] Importing middleware'); +LOG_INFO('[USER API] Importing middleware'); api.use(APIMiddleware); api.use(cors()); api.options('*', cors()); // Setup routes -logger.info('[USER API] Applying imported routes'); -api.use('/v1/connections', routes.V1.CONNECTIONS); -api.use('/v1/email', routes.V1.EMAIL); -api.use('/v1/forgot-password', routes.V1.FORGOT_PASSWORD); -api.use('/v1/login', routes.V1.LOGIN); -api.use('/v1/register', routes.V1.REGISTER); -api.use('/v1/reset-password', routes.V1.RESET_PASSWORD); -api.use('/v1/user', routes.V1.USER); +LOG_INFO('[USER API] Applying imported routes'); +api.use('/v1/connections', V1.CONNECTIONS); +api.use('/v1/email', V1.EMAIL); +api.use('/v1/forgot-password', V1.FORGOT_PASSWORD); +api.use('/v1/login', V1.LOGIN); +api.use('/v1/register', V1.REGISTER); +api.use('/v1/reset-password', V1.RESET_PASSWORD); +api.use('/v1/user', V1.USER); // Main router for endpoints const router: express.Router = express.Router(); // Create subdomains -logger.info('[USER API] Creating \'api\' subdomain'); +LOG_INFO('[USER API] Creating \'api\' subdomain'); router.use(subdomain('api', api)); export default router; \ No newline at end of file diff --git a/src/services/api/routes/index.ts b/src/services/api/routes/index.ts index 61691f2..6f25c45 100644 --- a/src/services/api/routes/index.ts +++ b/src/services/api/routes/index.ts @@ -6,14 +6,12 @@ import register_v1 from '@/services/api/routes/v1/register'; import resetPassword_v1 from '@/services/api/routes/v1/resetPassword'; import user_v1 from '@/services/api/routes/v1/user'; -export default { - V1: { - CONNECTIONS: connections_v1, - EMAIL: email_v1, - FORGOT_PASSWORD: forgotPassword_v1, - LOGIN: login_v1, - REGISTER: register_v1, - RESET_PASSWORD: resetPassword_v1, - USER: user_v1 - } +export const V1 = { + CONNECTIONS: connections_v1, + EMAIL: email_v1, + FORGOT_PASSWORD: forgotPassword_v1, + LOGIN: login_v1, + REGISTER: register_v1, + RESET_PASSWORD: resetPassword_v1, + USER: user_v1 }; \ No newline at end of file diff --git a/src/services/api/routes/v1/connections.ts b/src/services/api/routes/v1/connections.ts index 1a307e6..6ddfd31 100644 --- a/src/services/api/routes/v1/connections.ts +++ b/src/services/api/routes/v1/connections.ts @@ -1,5 +1,5 @@ import express from 'express'; -import database from '@/database'; +import { addUserConnection, removeUserConnection } from '@/database'; import { ConnectionData } from '@/types/services/api/connection-data'; import { ConnectionResponse } from '@/types/services/api/connection-response'; import { HydratedPNIDDocument } from '@/types/mongoose/pnid'; @@ -44,7 +44,7 @@ router.post('/add/:type', async (request: express.Request, response: express.Res }); } - const result: ConnectionResponse = await database.addUserConnection(pnid, data, type); + const result: ConnectionResponse = await addUserConnection(pnid, data, type); response.status(result.status).json(result); }); @@ -74,7 +74,7 @@ router.delete('/remove/:type', async (request: express.Request, response: expres }); } - const result: ConnectionResponse = await database.removeUserConnection(pnid, type); + const result: ConnectionResponse = await removeUserConnection(pnid, type); response.status(result.status).json(result); }); diff --git a/src/services/api/routes/v1/email.ts b/src/services/api/routes/v1/email.ts index 308bc84..2b12d60 100644 --- a/src/services/api/routes/v1/email.ts +++ b/src/services/api/routes/v1/email.ts @@ -1,7 +1,7 @@ import express from 'express'; import moment from 'moment'; import { PNID } from '@/models/pnid'; -import util from '@/util'; +import { sendEmailConfirmedEmail } from '@/util'; import { HydratedPNIDDocument } from '@/types/mongoose/pnid'; const router: express.Router = express.Router(); @@ -37,7 +37,7 @@ router.get('/verify', async (request: express.Request, response: express.Respons await pnid.save(); - await util.sendEmailConfirmedEmail(pnid); + await sendEmailConfirmedEmail(pnid); response.status(200).send('Email validated. You may close this window'); }); diff --git a/src/services/api/routes/v1/forgotPassword.ts b/src/services/api/routes/v1/forgotPassword.ts index 24148ba..74e7cdc 100644 --- a/src/services/api/routes/v1/forgotPassword.ts +++ b/src/services/api/routes/v1/forgotPassword.ts @@ -1,7 +1,7 @@ import express from 'express'; import validator from 'validator'; -import database from '@/database'; -import util from '@/util'; +import { getUserByEmailAddress, getUserByUsername } from '@/database'; +import { sendForgotPasswordEmail } from '@/util'; import { HydratedPNIDDocument } from '@/types/mongoose/pnid'; const router: express.Router = express.Router(); @@ -20,13 +20,13 @@ router.post('/', async (request: express.Request, response: express.Response) => let pnid: HydratedPNIDDocument; if (validator.isEmail(input)) { - pnid = await database.getUserByEmailAddress(input); + pnid = await getUserByEmailAddress(input); } else { - pnid = await database.getUserByUsername(input); + pnid = await getUserByUsername(input); } if (pnid) { - await util.sendForgotPasswordEmail(pnid); + await sendForgotPasswordEmail(pnid); } response.json({ diff --git a/src/services/api/routes/v1/login.ts b/src/services/api/routes/v1/login.ts index 4aa098a..c6bfaa6 100644 --- a/src/services/api/routes/v1/login.ts +++ b/src/services/api/routes/v1/login.ts @@ -1,9 +1,9 @@ import express from 'express'; import bcrypt from 'bcrypt'; import fs from 'fs-extra'; -import database from '@/database'; -import cache from '@/cache'; -import util from '@/util'; +import { getUserByUsername, getUserBearer } from '@/database'; +import { getServicePublicKey, getServiceSecretKey } from '@/cache'; +import { nintendoPasswordHash, generateToken} from '@/util'; import { CryptoOptions } from '@/types/common/crypto-options'; import { TokenOptions } from '@/types/common/token-options'; import { HydratedPNIDDocument } from '@/types/mongoose/pnid'; @@ -57,7 +57,7 @@ router.post('/', async (request: express.Request, response: express.Response) => let pnid: HydratedPNIDDocument; if (grantType === 'password') { - pnid = await database.getUserByUsername(username); + pnid = await getUserByUsername(username); if (!pnid) { return response.status(400).json({ @@ -67,7 +67,7 @@ router.post('/', async (request: express.Request, response: express.Response) => }); } - const hashedPassword: string = util.nintendoPasswordHash(password, pnid.get('pid')); + const hashedPassword: string = nintendoPasswordHash(password, pnid.get('pid')); if (!pnid || !bcrypt.compareSync(hashedPassword, pnid.password)) { return response.status(400).json({ @@ -77,7 +77,7 @@ router.post('/', async (request: express.Request, response: express.Response) => }); } } else { - pnid = await database.getUserBearer(refreshToken); + pnid = await getUserBearer(refreshToken); if (!pnid) { return response.status(400).json({ @@ -99,8 +99,8 @@ router.post('/', async (request: express.Request, response: express.Response) => }); } - const publicKey: Buffer = await cache.getServicePublicKey('account'); - const secretKey: Buffer = await cache.getServiceSecretKey('account'); + const publicKey: Buffer = await getServicePublicKey('account'); + const secretKey: Buffer = await getServiceSecretKey('account'); const cryptoOptions: CryptoOptions = { public_key: publicKey, @@ -125,8 +125,8 @@ router.post('/', async (request: express.Request, response: express.Response) => expire_time: BigInt(Date.now() + (3600 * 1000)) }; - const accessToken: string = await util.generateToken(cryptoOptions, accessTokenOptions); - const newRefreshToken: string = await util.generateToken(cryptoOptions, refreshTokenOptions); + const accessToken: string = await generateToken(cryptoOptions, accessTokenOptions); + const newRefreshToken: string = await generateToken(cryptoOptions, refreshTokenOptions); response.json({ access_token: accessToken, diff --git a/src/services/api/routes/v1/register.ts b/src/services/api/routes/v1/register.ts index 7712d29..3b6d315 100644 --- a/src/services/api/routes/v1/register.ts +++ b/src/services/api/routes/v1/register.ts @@ -8,10 +8,10 @@ import moment from 'moment'; import hcaptcha from 'hcaptcha'; import Mii from 'mii-js'; import mongoose from 'mongoose'; -import database from '@/database'; -import cache from '@/cache'; -import util from '@/util'; -import logger from '@/logger'; +import { doesUserExist, connection as databaseConnection } from '@/database'; +import { getServicePublicKey, getServiceSecretKey } from '@/cache'; +import { nintendoPasswordHash, sendConfirmationEmail, generateToken } from '@/util'; +import { LOG_ERROR } from '@/logger'; import { PNID } from '@/models/pnid'; import { NEXAccount } from '@/models/nex-account'; import { config, disabledFeatures } from '@/config-manager'; @@ -141,7 +141,7 @@ router.post('/', async (request: express.Request, response: express.Response) => }); } - const userExists: boolean = await database.doesUserExist(username); + const userExists: boolean = await doesUserExist(username); if (userExists) { return response.status(400).json({ @@ -232,7 +232,7 @@ router.post('/', async (request: express.Request, response: express.Response) => let pnid: HydratedPNIDDocument; let nexAccount: HydratedNEXAccountDocument; - const session: mongoose.ClientSession = await database.connection().startSession(); + const session: mongoose.ClientSession = await databaseConnection().startSession(); await session.startTransaction(); try { @@ -254,7 +254,7 @@ router.post('/', async (request: express.Request, response: express.Response) => await nexAccount.save({ session }); - const primaryPasswordHash: string = util.nintendoPasswordHash(password, nexAccount.get('pid')); + const primaryPasswordHash: string = nintendoPasswordHash(password, nexAccount.get('pid')); const passwordHash: string = await bcrypt.hash(primaryPasswordHash, 10); pnid = new PNID({ @@ -309,7 +309,7 @@ router.post('/', async (request: express.Request, response: express.Response) => await session.commitTransaction(); } catch (error) { - logger.error('[POST] /v1/register: ' + error); + LOG_ERROR('[POST] /v1/register: ' + error); await session.abortTransaction(); @@ -324,7 +324,7 @@ router.post('/', async (request: express.Request, response: express.Response) => await session.endSession(); } - await util.sendConfirmationEmail(pnid); + await sendConfirmationEmail(pnid); const cryptoPath: string = `${__dirname}/../../../../../certs/service/account`; @@ -337,8 +337,8 @@ router.post('/', async (request: express.Request, response: express.Response) => }); } - const publicKey: Buffer = await cache.getServicePublicKey('account'); - const secretKey: Buffer = await cache.getServiceSecretKey('account'); + const publicKey: Buffer = await getServicePublicKey('account'); + const secretKey: Buffer = await getServiceSecretKey('account'); const cryptoOptions: CryptoOptions = { public_key: publicKey, @@ -363,8 +363,8 @@ router.post('/', async (request: express.Request, response: express.Response) => expire_time: BigInt(Date.now() + (3600 * 1000)) }; - const accessToken: string = await util.generateToken(cryptoOptions, accessTokenOptions); - const refreshToken: string = await util.generateToken(cryptoOptions, refreshTokenOptions); + const accessToken: string = await generateToken(cryptoOptions, accessTokenOptions); + const refreshToken: string = await generateToken(cryptoOptions, refreshTokenOptions); response.json({ access_token: accessToken, diff --git a/src/services/api/routes/v1/resetPassword.ts b/src/services/api/routes/v1/resetPassword.ts index 8f570b5..fe7f3c8 100644 --- a/src/services/api/routes/v1/resetPassword.ts +++ b/src/services/api/routes/v1/resetPassword.ts @@ -1,7 +1,7 @@ import express from 'express'; import bcrypt from 'bcrypt'; import { PNID } from '@/models/pnid'; -import util from '@/util'; +import { decryptToken, unpackToken, nintendoPasswordHash } from '@/util'; import { Token } from '@/types/common/token'; import { HydratedPNIDDocument } from '@/types/mongoose/pnid'; @@ -28,8 +28,8 @@ router.post('/', async (request: express.Request, response: express.Response) => let unpackedToken: Token; try { - const decryptedToken: Buffer = await util.decryptToken(Buffer.from(token, 'base64')); - unpackedToken = util.unpackToken(decryptedToken); + const decryptedToken: Buffer = await decryptToken(Buffer.from(token, 'base64')); + unpackedToken = unpackToken(decryptedToken); } catch (error) { console.log(error); return response.status(400).json({ @@ -114,7 +114,7 @@ router.post('/', async (request: express.Request, response: express.Response) => }); } - const primaryPasswordHash: string = util.nintendoPasswordHash(password, pnid.get('pid')); + const primaryPasswordHash: string = nintendoPasswordHash(password, pnid.get('pid')); const passwordHash: string = await bcrypt.hash(primaryPasswordHash, 10); pnid.password = passwordHash; diff --git a/src/services/api/routes/v1/user.ts b/src/services/api/routes/v1/user.ts index ef03c25..15fb167 100644 --- a/src/services/api/routes/v1/user.ts +++ b/src/services/api/routes/v1/user.ts @@ -1,6 +1,6 @@ import express from 'express'; import joi from 'joi'; -import { PNID } from '@/models/pnid'; +//import { PNID } from '@/models/pnid'; import { config } from '@/config-manager'; import { HydratedPNIDDocument } from '@/types/mongoose/pnid'; import { UpdateUserRequest } from '@/types/services/api/update-user-request'; diff --git a/src/services/assets/index.ts b/src/services/assets/index.ts index f40cd56..03faa93 100644 --- a/src/services/assets/index.ts +++ b/src/services/assets/index.ts @@ -3,20 +3,20 @@ import path from 'node:path'; import express from 'express'; import subdomain from 'express-subdomain'; -import logger from '@/logger'; +import { LOG_INFO } from '@/logger'; // Router to handle the subdomain restriction const assets: express.Router = express.Router(); // Setup public folder -logger.info('[assets] Setting up public folder'); +LOG_INFO('[assets] Setting up public folder'); assets.use(express.static(path.join(__dirname, '../../assets'))); // Main router for endpoints const router: express.Router = express.Router(); // Create subdomains -logger.info('[conntest] Creating \'assets\' subdomain'); +LOG_INFO('[conntest] Creating \'assets\' subdomain'); router.use(subdomain('assets', assets)); export default router; \ No newline at end of file diff --git a/src/services/conntest/index.ts b/src/services/conntest/index.ts index 610cdda..c016eca 100644 --- a/src/services/conntest/index.ts +++ b/src/services/conntest/index.ts @@ -2,13 +2,13 @@ import express from 'express'; import subdomain from 'express-subdomain'; -import logger from '@/logger'; +import { LOG_INFO } from '@/logger'; // Router to handle the subdomain restriction const conntest: express.Router = express.Router(); // Setup route -logger.info('[conntest] Applying imported routes'); +LOG_INFO('[conntest] Applying imported routes'); conntest.get('/', async (request: express.Request, response: express.Response) => { response.set('Content-Type', 'text/html'); response.set('X-Organization', 'Nintendo'); @@ -23,14 +23,14 @@ conntest.get('/', async (request: express.Request, response: express.Response) = This is test.html page -`) +`); }); // Main router for endpoints const router: express.Router = express.Router(); // Create subdomains -logger.info('[conntest] Creating \'conntest\' subdomain'); +LOG_INFO('[conntest] Creating \'conntest\' subdomain'); router.use(subdomain('conntest', conntest)); export default router; \ No newline at end of file diff --git a/src/services/datastore/index.ts b/src/services/datastore/index.ts index a06640d..3d2e4f7 100644 --- a/src/services/datastore/index.ts +++ b/src/services/datastore/index.ts @@ -1,20 +1,21 @@ import express from 'express'; import subdomain from 'express-subdomain'; -import routes from '@/services/datastore/routes'; -import logger from '@/logger'; +import { LOG_INFO } from '@/logger'; + +import upload from '@/services/datastore/routes/upload'; // Router to handle the subdomain const datastore: express.Router = express.Router(); // Setup routes -logger.info('[DATASTORE] Applying imported routes'); -datastore.use(routes.UPLOAD); +LOG_INFO('[DATASTORE] Applying imported routes'); +datastore.use(upload); // Main router for endpoints const router: express.Router = express.Router(); // Create subdomains -logger.info('[DATASTORE] Creating \'datastore\' subdomain'); +LOG_INFO('[DATASTORE] Creating \'datastore\' subdomain'); router.use(subdomain('datastore', datastore)); export default router; \ No newline at end of file diff --git a/src/services/datastore/routes/index.ts b/src/services/datastore/routes/index.ts deleted file mode 100644 index 5e00989..0000000 --- a/src/services/datastore/routes/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -import upload from '@/services/datastore/routes/upload'; - -export default { - UPLOAD: upload -}; \ No newline at end of file diff --git a/src/services/datastore/routes/upload.ts b/src/services/datastore/routes/upload.ts index 5b47857..c8c9ddf 100644 --- a/src/services/datastore/routes/upload.ts +++ b/src/services/datastore/routes/upload.ts @@ -2,7 +2,7 @@ import fs from 'node:fs'; import crypto from 'node:crypto'; import express from 'express'; import Dicer from 'dicer'; -import util from '@/util'; +import { uploadCDNAsset } from '@/util'; const router: express.Router = express.Router(); @@ -72,7 +72,7 @@ router.post('/upload', multipartParser, async (request: express.Request, respons return response.sendStatus(400); } - await util.uploadCDNAsset(bucket, key, file, acl); + await uploadCDNAsset(bucket, key, file, acl); response.sendStatus(200); }); diff --git a/src/services/local-cdn/index.ts b/src/services/local-cdn/index.ts index 9f9fe3f..c1b2963 100644 --- a/src/services/local-cdn/index.ts +++ b/src/services/local-cdn/index.ts @@ -1,8 +1,9 @@ import express from 'express'; import subdomain from 'express-subdomain'; -import routes from '@/services/local-cdn/routes'; import { config, disabledFeatures } from '@/config-manager'; -import logger from '@/logger'; +import { LOG_INFO } from '@/logger'; + +import get from '@/services/local-cdn/routes/get'; const router: express.Router = express.Router(); @@ -13,14 +14,14 @@ if (disabledFeatures.s3) { const localcdn: express.Router = express.Router(); // * Setup routes - logger.info('[LOCAL-CDN] Applying imported routes'); - localcdn.use(routes.GET); + LOG_INFO('[LOCAL-CDN] Applying imported routes'); + localcdn.use(get); // * Create subdomains - logger.info(`[LOCAL-CDN] Creating '${config.cdn.subdomain}' subdomain`); + LOG_INFO(`[LOCAL-CDN] Creating '${config.cdn.subdomain}' subdomain`); router.use(subdomain(config.cdn.subdomain, localcdn)); } else { - logger.info('[LOCAL-CDN] s3 enabled, skipping local CDN'); + LOG_INFO('[LOCAL-CDN] s3 enabled, skipping local CDN'); } export default router; \ No newline at end of file diff --git a/src/services/local-cdn/routes/get.ts b/src/services/local-cdn/routes/get.ts index 1492c13..ff18990 100644 --- a/src/services/local-cdn/routes/get.ts +++ b/src/services/local-cdn/routes/get.ts @@ -1,12 +1,12 @@ import express from 'express'; -import cache from '@/cache'; +import { getLocalCDNFile } from '@/cache'; const router: express.Router = express.Router(); router.get('/*', async (request: express.Request, response: express.Response) => { const filePath: string = request.params[0] as string; - const file: Buffer = await cache.getLocalCDNFile(filePath); + const file: Buffer = await getLocalCDNFile(filePath); if (file) { response.send(file); diff --git a/src/services/local-cdn/routes/index.ts b/src/services/local-cdn/routes/index.ts deleted file mode 100644 index 5038ab4..0000000 --- a/src/services/local-cdn/routes/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -import get from '@/services/local-cdn/routes/get'; - -export default { - GET: get, -}; \ No newline at end of file diff --git a/src/services/nasc/index.ts b/src/services/nasc/index.ts index 38f1d60..29d1148 100644 --- a/src/services/nasc/index.ts +++ b/src/services/nasc/index.ts @@ -3,24 +3,25 @@ import express from 'express'; import subdomain from 'express-subdomain'; import NASCMiddleware from '@/middleware/nasc'; -import routes from '@/services/nasc/routes'; -import logger from '@/logger'; +import { LOG_INFO } from '@/logger'; + +import ac from '@/services/nasc/routes/ac'; // Router to handle the subdomain restriction const nasc: express.Router = express.Router(); -logger.info('[NASC] Importing middleware'); +LOG_INFO('[NASC] Importing middleware'); nasc.use(NASCMiddleware); // Setup routes -logger.info('[NASC] Applying imported routes'); -nasc.use('/ac', routes.AC); +LOG_INFO('[NASC] Applying imported routes'); +nasc.use('/ac', ac); // Main router for endpoints const router: express.Router = express.Router(); // Create subdomains -logger.info('[NASC] Creating \'nasc\' subdomain'); +LOG_INFO('[NASC] Creating \'nasc\' subdomain'); router.use(subdomain('nasc', nasc)); export default router; \ No newline at end of file diff --git a/src/services/nasc/routes/ac.ts b/src/services/nasc/routes/ac.ts index 6e4589a..001de88 100644 --- a/src/services/nasc/routes/ac.ts +++ b/src/services/nasc/routes/ac.ts @@ -1,7 +1,7 @@ import express from 'express'; -import util from '@/util'; -import database from '@/database'; -import cache from '@/cache'; +import { nintendoBase64Encode, nintendoBase64Decode, nascError, generateToken } from '@/util'; +import { getServerByTitleId } from '@/database'; +import { getNEXPublicKey, getNEXSecretKey } from '@/cache'; import { CryptoOptions } from '@/types/common/crypto-options'; import { TokenOptions } from '@/types/common/token-options'; import { NASCRequestParams } from '@/types/services/nasc/request-params'; @@ -17,7 +17,7 @@ const router: express.Router = express.Router(); */ router.post('/', async (request: express.Request, response: express.Response) => { const requestParams: NASCRequestParams = request.body; - const action: string = util.nintendoBase64Decode(requestParams.action).toString(); + const action: string = nintendoBase64Decode(requestParams.action).toString(); let responseData: URLSearchParams; switch (action) { @@ -34,7 +34,7 @@ router.post('/', async (request: express.Request, response: express.Response) => async function processLoginRequest(request: express.Request): Promise { const requestParams: NASCRequestParams = request.body; - const titleID: string = util.nintendoBase64Decode(requestParams.titleid).toString(); + const titleID: string = nintendoBase64Decode(requestParams.titleid).toString(); const nexUser: HydratedNEXAccountDocument = request.nexUser; // TODO: REMOVE AFTER PUBLIC LAUNCH @@ -45,18 +45,18 @@ async function processLoginRequest(request: express.Request): Promise { return new URLSearchParams({ - retry: util.nintendoBase64Encode('0'), - returncd: util.nintendoBase64Encode('007'), - servicetoken: util.nintendoBase64Encode(Buffer.alloc(64).toString()), // hard coded for now - statusdata: util.nintendoBase64Encode('Y'), - svchost: util.nintendoBase64Encode('n/a'), - datetime: util.nintendoBase64Encode(Date.now().toString()), + retry: nintendoBase64Encode('0'), + returncd: nintendoBase64Encode('007'), + servicetoken: nintendoBase64Encode(Buffer.alloc(64).toString()), // hard coded for now + statusdata: nintendoBase64Encode('Y'), + svchost: nintendoBase64Encode('n/a'), + datetime: nintendoBase64Encode(Date.now().toString()), }); } diff --git a/src/services/nasc/routes/index.ts b/src/services/nasc/routes/index.ts deleted file mode 100644 index e8dfff6..0000000 --- a/src/services/nasc/routes/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -import ac from '@/services/nasc/routes/ac'; - -export default { - AC: ac -}; \ No newline at end of file diff --git a/src/services/nnid/index.ts b/src/services/nnid/index.ts index 9c9cdd3..8fbcf6b 100644 --- a/src/services/nnid/index.ts +++ b/src/services/nnid/index.ts @@ -5,36 +5,44 @@ import subdomain from 'express-subdomain'; import clientHeaderCheck from '@/middleware/client-header'; import cemuMiddleware from '@/middleware/cemu'; import pnidMiddleware from '@/middleware/pnid'; -import routes from '@/services/nnid/routes'; -import logger from '@/logger'; +import { LOG_INFO } from '@/logger'; + +import admin from '@/services/nnid/routes/admin'; +import content from '@/services/nnid/routes/content'; +import devices from '@/services/nnid/routes/devices'; +import miis from '@/services/nnid/routes/miis'; +import oauth from '@/services/nnid/routes/oauth'; +import people from '@/services/nnid/routes/people'; +import provider from '@/services/nnid/routes/provider'; +import support from '@/services/nnid/routes/support'; // Router to handle the subdomain restriction const nnid = express.Router(); -logger.info('[NNID] Importing middleware'); +LOG_INFO('[NNID] Importing middleware'); nnid.use(clientHeaderCheck); nnid.use(cemuMiddleware); nnid.use(pnidMiddleware); // Setup routes -logger.info('[NNID] Applying imported routes'); -nnid.use('/v1/api/admin', routes.ADMIN); -nnid.use('/v1/api/content', routes.CONTENT); -nnid.use('/v1/api/devices', routes.DEVICES); -nnid.use('/v1/api/miis', routes.MIIS); -nnid.use('/v1/api/oauth20', routes.OAUTH); -nnid.use('/v1/api/people', routes.PEOPLE); -nnid.use('/v1/api/provider', routes.PROVIDER); -nnid.use('/v1/api/support', routes.SUPPORT); +LOG_INFO('[NNID] Applying imported routes'); +nnid.use('/v1/api/admin', admin); +nnid.use('/v1/api/content', content); +nnid.use('/v1/api/devices', devices); +nnid.use('/v1/api/miis', miis); +nnid.use('/v1/api/oauth20', oauth); +nnid.use('/v1/api/people', people); +nnid.use('/v1/api/provider', provider); +nnid.use('/v1/api/support', support); // Main router for endpoints const router = express.Router(); // Create subdomains -logger.info('[NNID] Creating \'account\' subdomain'); +LOG_INFO('[NNID] Creating \'account\' subdomain'); router.use(subdomain('account', nnid)); -logger.info('[NNID] Creating \'c.account\' subdomain'); +LOG_INFO('[NNID] Creating \'c.account\' subdomain'); router.use(subdomain('c.account', nnid)); export default router; \ No newline at end of file diff --git a/src/services/nnid/routes/index.ts b/src/services/nnid/routes/index.ts deleted file mode 100644 index 7510b1f..0000000 --- a/src/services/nnid/routes/index.ts +++ /dev/null @@ -1,19 +0,0 @@ -import admin from '@/services/nnid/routes/admin'; -import content from '@/services/nnid/routes/content'; -import devices from '@/services/nnid/routes/devices'; -import miis from '@/services/nnid/routes/miis'; -import oauth from '@/services/nnid/routes/oauth'; -import people from '@/services/nnid/routes/people'; -import provider from '@/services/nnid/routes/provider'; -import support from '@/services/nnid/routes/support'; - -export default { - ADMIN: admin, - CONTENT: content, - DEVICES: devices, - MIIS: miis, - OAUTH: oauth, - PEOPLE: people, - PROVIDER: provider, - SUPPORT: support, -}; \ No newline at end of file diff --git a/src/services/nnid/routes/oauth.ts b/src/services/nnid/routes/oauth.ts index 800cad5..7d19804 100644 --- a/src/services/nnid/routes/oauth.ts +++ b/src/services/nnid/routes/oauth.ts @@ -2,8 +2,8 @@ import express from 'express'; import xmlbuilder from 'xmlbuilder'; import bcrypt from 'bcrypt'; import fs from 'fs-extra'; -import database from '@/database'; -import util from '@/util'; +import { getUserByUsername } from '@/database'; +import { generateToken } from '@/util'; import { TokenOptions } from '@/types/common/token-options'; import { HydratedPNIDDocument } from '@/types/mongoose/pnid'; @@ -52,7 +52,7 @@ router.post('/access_token/generate', async (request: express.Request, response: }).end()); } - const pnid: HydratedPNIDDocument = await database.getUserByUsername(username); + const pnid: HydratedPNIDDocument = await getUserByUsername(username); if (!pnid || !await bcrypt.compare(password, pnid.password)) { response.status(400); @@ -103,8 +103,8 @@ router.post('/access_token/generate', async (request: express.Request, response: expire_time: BigInt(Date.now() + (3600 * 1000)) }; - let accessToken: string = await util.generateToken(null, accessTokenOptions); - let refreshToken: string = await util.generateToken(null, refreshTokenOptions); + let accessToken: string = await generateToken(null, accessTokenOptions); + let refreshToken: string = await generateToken(null, refreshTokenOptions); if (request.isCemu) { accessToken = Buffer.from(accessToken, 'base64').toString('hex'); diff --git a/src/services/nnid/routes/people.ts b/src/services/nnid/routes/people.ts index 9c508f2..b838ffc 100644 --- a/src/services/nnid/routes/people.ts +++ b/src/services/nnid/routes/people.ts @@ -6,12 +6,14 @@ import moment from 'moment'; import mongoose from 'mongoose'; import deviceCertificateMiddleware from '@/middleware/device-certificate'; import ratelimit from '@/middleware/ratelimit'; -import database from '@/database'; -import util from '@/util'; +import { connection as databaseConnection, doesUserExist, getUserProfileJSONByPID } from '@/database'; +import { nintendoPasswordHash, sendConfirmationEmail } from '@/util'; import { PNID } from '@/models/pnid'; import { NEXAccount } from '@/models/nex-account'; -import logger from '@/logger'; +import { LOG_ERROR } from '@/logger'; + import timezones from '@/services/nnid/timezones.json'; + import { HydratedPNIDDocument } from '@/types/mongoose/pnid'; import { HydratedNEXAccountDocument } from '@/types/mongoose/nex-account'; import { RegionLanguages } from '@/types/services/nnid/region-languages'; @@ -29,7 +31,7 @@ const router: express.Router = express.Router(); router.get('/:username', async (request: express.Request, response: express.Response) => { const username: string = request.params.username; - const userExists: boolean = await database.doesUserExist(username); + const userExists: boolean = await doesUserExist(username); if (userExists) { response.status(400); @@ -70,7 +72,7 @@ router.post('/', ratelimit, deviceCertificateMiddleware, async (request: express // * request.body is a Map, as is request.body.person const person: Person = request.body.get('person'); - const userExists: boolean = await database.doesUserExist(person.get('user_id')); + const userExists: boolean = await doesUserExist(person.get('user_id')); if (userExists) { response.status(400); @@ -89,7 +91,7 @@ router.post('/', ratelimit, deviceCertificateMiddleware, async (request: express let pnid: HydratedPNIDDocument; let nexAccount: HydratedNEXAccountDocument; - const session: mongoose.ClientSession = await database.connection().startSession(); + const session: mongoose.ClientSession = await databaseConnection().startSession(); await session.startTransaction(); try { @@ -109,7 +111,7 @@ router.post('/', ratelimit, deviceCertificateMiddleware, async (request: express await nexAccount.save({ session }); - const primaryPasswordHash: string = util.nintendoPasswordHash(person.get('password'), nexAccount.get('pid')); + const primaryPasswordHash: string = nintendoPasswordHash(person.get('password'), nexAccount.get('pid')); const passwordHash: string = await bcrypt.hash(primaryPasswordHash, 10); const countryCode: string = person.get('country'); @@ -172,7 +174,7 @@ router.post('/', ratelimit, deviceCertificateMiddleware, async (request: express await session.commitTransaction(); } catch (error) { - logger.error('[POST] /v1/api/people: ' + error); + LOG_ERROR('[POST] /v1/api/people: ' + error); await session.abortTransaction(); @@ -191,7 +193,7 @@ router.post('/', ratelimit, deviceCertificateMiddleware, async (request: express await session.endSession(); } - await util.sendConfirmationEmail(pnid); + await sendConfirmationEmail(pnid); response.send(xmlbuilder.create({ person: { @@ -212,7 +214,7 @@ router.get('/@me/profile', async (request: express.Request, response: express.Re const pnid: HydratedPNIDDocument = request.pnid; - const person: PNIDProfile = await database.getUserProfileJSONByPID(pnid.get('pid')); + const person: PNIDProfile = await getUserProfileJSONByPID(pnid.get('pid')); response.send(xmlbuilder.create({ person @@ -237,7 +239,7 @@ router.post('/@me/devices', async (request: express.Request, response: express.R const pnid: HydratedPNIDDocument = request.pnid; - const person: PNIDProfile = await database.getUserProfileJSONByPID(pnid.pid); + const person: PNIDProfile = await getUserProfileJSONByPID(pnid.pid); response.send(xmlbuilder.create({ person @@ -295,7 +297,7 @@ router.get('/@me/devices/owner', async (request: express.Request, response: expr const pnid: HydratedPNIDDocument = request.pnid; - const person: PNIDProfile = await database.getUserProfileJSONByPID(pnid.get('pid')); + const person: PNIDProfile = await getUserProfileJSONByPID(pnid.get('pid')); response.send(xmlbuilder.create({ person @@ -428,7 +430,7 @@ router.put('/@me', async (request: express.Request, response: express.Response) const timezone: RegionTimezone = regionTimezones.find(tz => tz.area === timezoneName); if (person.get('password')) { - const primaryPasswordHash: string = util.nintendoPasswordHash(person.get('password'), pnid.get('pid')); + const primaryPasswordHash: string = nintendoPasswordHash(person.get('password'), pnid.get('pid')); const passwordHash: string = await bcrypt.hash(primaryPasswordHash, 10); pnid.password = passwordHash; @@ -523,7 +525,7 @@ router.put('/@me/emails/@primary', async (request: express.Request, response: ex await pnid.save(); - await util.sendConfirmationEmail(pnid); + await sendConfirmationEmail(pnid); response.send(''); }); diff --git a/src/services/nnid/routes/provider.ts b/src/services/nnid/routes/provider.ts index ca1de25..2f8dd70 100644 --- a/src/services/nnid/routes/provider.ts +++ b/src/services/nnid/routes/provider.ts @@ -1,9 +1,9 @@ import express from 'express'; import xmlbuilder from 'xmlbuilder'; import fs from 'fs-extra'; -import database from '@/database'; -import util from '@/util'; -import cache from '@/cache'; +import { getServerByTitleId, getServer } from '@/database'; +import { generateToken } from '@/util'; +import { getServicePublicKey, getServiceSecretKey, getNEXPublicKey, getNEXSecretKey } from '@/cache'; import { NEXAccount } from '@/models/nex-account'; import { CryptoOptions } from '@/types/common/crypto-options'; import { TokenOptions } from '@/types/common/token-options'; @@ -23,7 +23,7 @@ router.get('/service_token/@me', async (request: express.Request, response: expr const titleId: string = request.headers['x-nintendo-title-id'] as string; const serverAccessLevel: string = pnid.get('server_access_level'); - const server: HydratedServerDocument = await database.getServerByTitleId(titleId, serverAccessLevel); + const server: HydratedServerDocument = await getServerByTitleId(titleId, serverAccessLevel); if (!server) { return response.send(xmlbuilder.create({ @@ -53,8 +53,8 @@ router.get('/service_token/@me', async (request: express.Request, response: expr }).end()); } - const publicKey: Buffer = await cache.getServicePublicKey(serverName); - const secretKey: Buffer = await cache.getServiceSecretKey(serverName); + const publicKey: Buffer = await getServicePublicKey(serverName); + const secretKey: Buffer = await getServiceSecretKey(serverName); const cryptoOptions: CryptoOptions = { public_key: publicKey, @@ -70,7 +70,7 @@ router.get('/service_token/@me', async (request: express.Request, response: expr expire_time: BigInt(Date.now() + (3600 * 1000)) }; - let serviceToken: string = await util.generateToken(cryptoOptions, tokenOptions); + let serviceToken: string = await generateToken(cryptoOptions, tokenOptions); if (request.isCemu) { serviceToken = Buffer.from(serviceToken, 'base64').toString('hex'); @@ -104,7 +104,7 @@ router.get('/nex_token/@me', async (request: express.Request, response: express. } const serverAccessLevel: string = pnid.get('server_access_level'); - const server: HydratedServerDocument = await database.getServer(gameServerID, serverAccessLevel); + const server: HydratedServerDocument = await getServer(gameServerID, serverAccessLevel); if (!server) { return response.send(xmlbuilder.create({ @@ -137,8 +137,8 @@ router.get('/nex_token/@me', async (request: express.Request, response: express. }).end()); } - const publicKey: Buffer = await cache.getNEXPublicKey(serverName); - const secretKey: Buffer = await cache.getNEXSecretKey(serverName); + const publicKey: Buffer = await getNEXPublicKey(serverName); + const secretKey: Buffer = await getNEXSecretKey(serverName); const cryptoOptions: CryptoOptions = { public_key: publicKey, @@ -163,7 +163,7 @@ router.get('/nex_token/@me', async (request: express.Request, response: express. return response.send('0008Not Found'); } - let nexToken: string = await util.generateToken(cryptoOptions, tokenOptions); + let nexToken: string = await generateToken(cryptoOptions, tokenOptions); if (request.isCemu) { nexToken = Buffer.from(nexToken, 'base64').toString('hex'); diff --git a/src/services/nnid/routes/support.ts b/src/services/nnid/routes/support.ts index edfe201..6ecb0c9 100644 --- a/src/services/nnid/routes/support.ts +++ b/src/services/nnid/routes/support.ts @@ -2,8 +2,8 @@ import dns from 'node:dns'; import express from 'express'; import xmlbuilder from 'xmlbuilder'; import moment from 'moment'; -import database from '@/database'; -import util from '@/util'; +import { getUserByPID } from '@/database'; +import { sendEmailConfirmedEmail, sendConfirmationEmail, sendForgotPasswordEmail } from '@/util'; import { HydratedPNIDDocument } from '@/types/mongoose/pnid'; const router: express.Router = express.Router(); @@ -56,7 +56,7 @@ router.put('/email_confirmation/:pid/:code', async (request: express.Request, re const code: string = request.params.code; const pid: number = Number(request.params.pid); - const pnid: HydratedPNIDDocument = await database.getUserByPID(pid); + const pnid: HydratedPNIDDocument = await getUserByPID(pid); if (!pnid) { return response.status(400).send(xmlbuilder.create({ @@ -88,7 +88,7 @@ router.put('/email_confirmation/:pid/:code', async (request: express.Request, re await pnid.save(); - await util.sendEmailConfirmedEmail(pnid); + await sendEmailConfirmedEmail(pnid); response.status(200).send(''); }); @@ -101,7 +101,7 @@ router.put('/email_confirmation/:pid/:code', async (request: express.Request, re router.get('/resend_confirmation', async (request: express.Request, response: express.Response) => { const pid: number = Number(request.headers['x-nintendo-pid']); - const pnid: HydratedPNIDDocument = await database.getUserByPID(pid); + const pnid: HydratedPNIDDocument = await getUserByPID(pid); if (!pnid) { // TODO - Unsure if this is the right error @@ -115,7 +115,7 @@ router.get('/resend_confirmation', async (request: express.Request, response: ex }).end()); } - await util.sendConfirmationEmail(pnid); + await sendConfirmationEmail(pnid); response.status(200).send(''); }); @@ -129,7 +129,7 @@ router.get('/resend_confirmation', async (request: express.Request, response: ex router.get('/forgotten_password/:pid', async (request: express.Request, response: express.Response) => { const pid: number = Number(request.params.pid); - const pnid: HydratedPNIDDocument = await database.getUserByPID(pid); + const pnid: HydratedPNIDDocument = await getUserByPID(pid); if (!pnid) { // TODO - Better errors @@ -144,7 +144,7 @@ router.get('/forgotten_password/:pid', async (request: express.Request, response }).end()); } - await util.sendForgotPasswordEmail(pnid); + await sendForgotPasswordEmail(pnid); response.status(200).send(''); }); diff --git a/src/util.ts b/src/util.ts index eaec4c4..f2ca7d6 100644 --- a/src/util.ts +++ b/src/util.ts @@ -5,8 +5,8 @@ import aws from 'aws-sdk'; import fs from 'fs-extra'; import express from 'express'; import mongoose from 'mongoose'; -import mailer from '@/mailer'; -import cache from '@/cache'; +import { sendMail } from '@/mailer'; +import { getServiceAESKey, getServicePrivateKey, getServiceSecretKey, getServicePublicKey } from '@/cache'; import { config, disabledFeatures } from '@/config-manager'; import { CryptoOptions } from '@/types/common/crypto-options'; import { TokenOptions } from '@/types/common/token-options'; @@ -24,7 +24,7 @@ if (!disabledFeatures.s3) { }); } -function nintendoPasswordHash(password: string, pid: number): string { +export function nintendoPasswordHash(password: string, pid: number): string { const pidBuffer: Buffer = Buffer.alloc(4); pidBuffer.writeUInt32LE(pid); @@ -37,21 +37,21 @@ function nintendoPasswordHash(password: string, pid: number): string { return crypto.createHash('sha256').update(unpacked).digest().toString('hex'); } -function nintendoBase64Decode(encoded: string): Buffer { +export function nintendoBase64Decode(encoded: string): Buffer { encoded = encoded.replaceAll('.', '+').replaceAll('-', '/').replaceAll('*', '='); return Buffer.from(encoded, 'base64'); } -function nintendoBase64Encode(decoded: string | Buffer): string { +export function nintendoBase64Encode(decoded: string | Buffer): string { const encoded: string = Buffer.from(decoded).toString('base64'); return encoded.replaceAll('+', '.').replaceAll('/', '-').replaceAll('=', '*'); } -async function generateToken(cryptoOptions: CryptoOptions | null, tokenOptions: TokenOptions): Promise { +export async function generateToken(cryptoOptions: CryptoOptions | null, tokenOptions: TokenOptions): Promise { // Access and refresh tokens use a different format since they must be much smaller // They take no extra crypto options if (!cryptoOptions) { - const aesKey: Buffer = await cache.getServiceAESKey('account', 'hex'); + const aesKey: Buffer = await getServiceAESKey('account', 'hex'); const dataBuffer: Buffer = Buffer.alloc(1 + 1 + 4 + 8); @@ -136,11 +136,11 @@ async function generateToken(cryptoOptions: CryptoOptions | null, tokenOptions: return token.toString('base64'); // Encode to base64 for transport } -async function decryptToken(token: Buffer): Promise { +export async function decryptToken(token: Buffer): Promise { // Access and refresh tokens use a different format since they must be much smaller // Assume a small length means access or refresh token if (token.length <= 32) { - const aesKey: Buffer = await cache.getServiceAESKey('account', 'hex'); + const aesKey: Buffer = await getServiceAESKey('account', 'hex'); const iv: Buffer = Buffer.alloc(16); @@ -154,8 +154,8 @@ async function decryptToken(token: Buffer): Promise { return decryptedBody; } - const privateKeyBytes: Buffer = await cache.getServicePrivateKey('account'); - const secretKey: Buffer = await cache.getServiceSecretKey('account'); + const privateKeyBytes: Buffer = await getServicePrivateKey('account'); + const secretKey: Buffer = await getServiceSecretKey('account'); const privateKey: NodeRSA = new NodeRSA(privateKeyBytes, 'pkcs1-private-pem', { environment: 'browser', @@ -198,7 +198,7 @@ async function decryptToken(token: Buffer): Promise { return decryptedBody; } -function unpackToken(token: Buffer): Token { +export function unpackToken(token: Buffer): Token { if (token.length <= 14) { return { system_type: token.readUInt8(0x0), @@ -218,7 +218,7 @@ function unpackToken(token: Buffer): Token { }; } -function fullUrl(request: express.Request): string { +export function fullUrl(request: express.Request): string { const protocol: string = request.protocol; const host: string = request.host; const opath: string = request.originalUrl; @@ -226,7 +226,7 @@ function fullUrl(request: express.Request): string { return `${protocol}://${host}${opath}`; } -async function uploadCDNAsset(bucket: string, key: string, data: Buffer, acl: string): Promise { +export async function uploadCDNAsset(bucket: string, key: string, data: Buffer, acl: string): Promise { if (disabledFeatures.s3) { await writeLocalCDNFile(key, data); } else { @@ -239,7 +239,7 @@ async function uploadCDNAsset(bucket: string, key: string, data: Buffer, acl: st } } -async function writeLocalCDNFile(key: string, data: Buffer): Promise { +export async function writeLocalCDNFile(key: string, data: Buffer): Promise { const filePath: string = config.cdn.disk_path; const folder: string = path.dirname(filePath); @@ -247,7 +247,7 @@ async function writeLocalCDNFile(key: string, data: Buffer): Promise { await fs.writeFile(filePath, data); } -function nascError(errorCode: string): URLSearchParams { +export function nascError(errorCode: string): URLSearchParams { return new URLSearchParams({ retry: nintendoBase64Encode('1'), returncd: errorCode == 'null' ? errorCode : nintendoBase64Encode(errorCode), @@ -255,7 +255,7 @@ function nascError(errorCode: string): URLSearchParams { }); } -async function sendConfirmationEmail(pnid: mongoose.HydratedDocument): Promise { +export async function sendConfirmationEmail(pnid: mongoose.HydratedDocument): Promise { const options: MailerOptions = { to: pnid.get('email.address'), subject: '[Pretendo Network] Please confirm your email address', @@ -267,10 +267,10 @@ async function sendConfirmationEmail(pnid: mongoose.HydratedDocument): Promise { +export async function sendEmailConfirmedEmail(pnid: mongoose.HydratedDocument): Promise { const options: MailerOptions = { to: pnid.get('email.address'), subject: '[Pretendo Network] Email address confirmed', @@ -279,12 +279,12 @@ async function sendEmailConfirmedEmail(pnid: mongoose.HydratedDocument): Promise { - const publicKey: Buffer = await cache.getServicePublicKey('account'); - const secretKey: Buffer = await cache.getServiceSecretKey('account'); +export async function sendForgotPasswordEmail(pnid: mongoose.HydratedDocument): Promise { + const publicKey: Buffer = await getServicePublicKey('account'); + const secretKey: Buffer = await getServiceSecretKey('account'); const cryptoOptions: CryptoOptions = { public_key: publicKey, @@ -314,20 +314,5 @@ async function sendForgotPasswordEmail(pnid: mongoose.HydratedDocument Date: Fri, 17 Mar 2023 16:18:18 -0400 Subject: [PATCH 040/219] Removed unneccesary casts --- src/database.ts | 8 ++++---- src/util.ts | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/database.ts b/src/database.ts index 931d181..d5b8c8c 100644 --- a/src/database.ts +++ b/src/database.ts @@ -147,7 +147,7 @@ export async function getUserProfileJSONByPID(pid: number): Promise })); } - return { + return { //accounts: {}, // * We need to figure this out, no idea what these values mean or what they do active_flag: user.get('flags.active') ? 'Y' : 'N', birth_date: user.get('birthdate'), @@ -220,7 +220,7 @@ export async function addUserConnectionDiscord(pnid: HydratedPNIDDocument, data: const valid: joi.ValidationResult = discordConnectionSchema.validate(data); if (valid.error) { - return { + return { app: 'api', status: 400, error: 'Invalid or missing connection data' @@ -233,7 +233,7 @@ export async function addUserConnectionDiscord(pnid: HydratedPNIDDocument, data: } }); - return { + return { app: 'api', status: 200 }; @@ -253,7 +253,7 @@ export async function removeUserConnectionDiscord(pnid: HydratedPNIDDocument): P } }); - return { + return { app: 'api', status: 200 }; diff --git a/src/util.ts b/src/util.ts index f2ca7d6..5269666 100644 --- a/src/util.ts +++ b/src/util.ts @@ -200,7 +200,7 @@ export async function decryptToken(token: Buffer): Promise { export function unpackToken(token: Buffer): Token { if (token.length <= 14) { - return { + return { system_type: token.readUInt8(0x0), token_type: token.readUInt8(0x1), pid: token.readUInt32LE(0x2), @@ -208,7 +208,7 @@ export function unpackToken(token: Buffer): Token { }; } - return { + return { system_type: token.readUInt8(0x0), token_type: token.readUInt8(0x1), pid: token.readUInt32LE(0x2), From dd952954fde5262c4f2d0e03ad425e79310e42aa Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Fri, 17 Mar 2023 17:39:46 -0400 Subject: [PATCH 041/219] Enable strict mode in TS and fix most issues with it --- .eslintignore | 3 +- package-lock.json | 3083 +++++++++++++++++- package.json | 6 +- src/cache.ts | 6 +- src/config-manager.ts | 36 +- src/database.ts | 55 +- src/mailer.ts | 8 +- src/middleware/api.ts | 4 +- src/middleware/nasc.ts | 12 +- src/middleware/pnid.ts | 4 +- src/middleware/xml-parser.ts | 4 +- src/models/pnid.ts | 4 +- src/services/api/routes/v1/connections.ts | 26 +- src/services/api/routes/v1/email.ts | 4 +- src/services/api/routes/v1/forgotPassword.ts | 2 +- src/services/api/routes/v1/login.ts | 8 +- src/services/api/routes/v1/register.ts | 6 +- src/services/api/routes/v1/resetPassword.ts | 2 +- src/services/api/routes/v1/user.ts | 4 +- src/services/datastore/routes/upload.ts | 20 +- src/services/nasc/routes/ac.ts | 17 +- src/services/nnid/routes/admin.ts | 3 +- src/services/nnid/routes/oauth.ts | 12 +- src/services/nnid/routes/people.ts | 87 +- src/services/nnid/routes/provider.ts | 51 +- src/services/nnid/routes/support.ts | 8 +- src/types/express.d.ts | 4 +- src/types/services/nnid/pnid-profile.ts | 4 +- src/util.ts | 15 +- tsconfig.json | 1 + 30 files changed, 3358 insertions(+), 141 deletions(-) diff --git a/.eslintignore b/.eslintignore index 53c37a1..7c5c224 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1 +1,2 @@ -dist \ No newline at end of file +dist +*.js \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 612c092..78ae38f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,7 +17,7 @@ "dotenv": "^16.0.3", "email-validator": "^2.0.4", "express": "^4.17.1", - "express-rate-limit": "^5.3.0", + "express-rate-limit": "^6.7.0", "express-subdomain": "^1.0.5", "fs-extra": "^8.1.0", "got": "^11.8.2", @@ -39,13 +39,17 @@ }, "devDependencies": { "@hcaptcha/types": "^1.0.3", + "@types/bcrypt": "^5.0.0", + "@types/cors": "^2.8.13", "@types/dicer": "^0.2.2", "@types/express": "^4.17.17", "@types/fs-extra": "^11.0.1", + "@types/mongoose-unique-validator": "^1.0.7", "@types/morgan": "^1.9.4", "@types/node": "^18.14.4", "@types/node-rsa": "^1.1.1", "@types/nodemailer": "^6.4.7", + "@types/validator": "^13.7.14", "@typescript-eslint/eslint-plugin": "^5.54.1", "@typescript-eslint/parser": "^5.54.1", "eslint": "^8.35.0", @@ -54,6 +58,1404 @@ "yesno": "^0.4.0" } }, + "node_modules/@aws-crypto/ie11-detection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/ie11-detection/-/ie11-detection-3.0.0.tgz", + "integrity": "sha512-341lBBkiY1DfDNKai/wXM3aujNBkXR7tq1URPQDL9wi3AUbI80NR74uF1TXHMm7po1AcnFk8iu2S2IeU/+/A+Q==", + "optional": true, + "dependencies": { + "tslib": "^1.11.1" + } + }, + "node_modules/@aws-crypto/sha256-browser": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-3.0.0.tgz", + "integrity": "sha512-8VLmW2B+gjFbU5uMeqtQM6Nj0/F1bro80xQXCW6CQBWgosFWXTx77aeOF5CAIAmbOK64SdMBJdNr6J41yP5mvQ==", + "optional": true, + "dependencies": { + "@aws-crypto/ie11-detection": "^3.0.0", + "@aws-crypto/sha256-js": "^3.0.0", + "@aws-crypto/supports-web-crypto": "^3.0.0", + "@aws-crypto/util": "^3.0.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@aws-sdk/util-utf8-browser": "^3.0.0", + "tslib": "^1.11.1" + } + }, + "node_modules/@aws-crypto/sha256-js": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-3.0.0.tgz", + "integrity": "sha512-PnNN7os0+yd1XvXAy23CFOmTbMaDxgxXtTKHybrJ39Y8kGzBATgBFibWJKH6BhytLI/Zyszs87xCOBNyBig6vQ==", + "optional": true, + "dependencies": { + "@aws-crypto/util": "^3.0.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^1.11.1" + } + }, + "node_modules/@aws-crypto/supports-web-crypto": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-3.0.0.tgz", + "integrity": "sha512-06hBdMwUAb2WFTuGG73LSC0wfPu93xWwo5vL2et9eymgmu3Id5vFAHBbajVWiGhPO37qcsdCap/FqXvJGJWPIg==", + "optional": true, + "dependencies": { + "tslib": "^1.11.1" + } + }, + "node_modules/@aws-crypto/util": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-3.0.0.tgz", + "integrity": "sha512-2OJlpeJpCR48CC8r+uKVChzs9Iungj9wkZrl8Z041DWEWvyIHILYKCPNzJghKsivj+S3mLo6BVc7mBNzdxA46w==", + "optional": true, + "dependencies": { + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-utf8-browser": "^3.0.0", + "tslib": "^1.11.1" + } + }, + "node_modules/@aws-sdk/abort-controller": { + "version": "3.292.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/abort-controller/-/abort-controller-3.292.0.tgz", + "integrity": "sha512-lf+OPptL01kvryIJy7+dvFux5KbJ6OTwLPPEekVKZ2AfEvwcVtOZWFUhyw3PJCBTVncjKB1Kjl3V/eTS3YuPXQ==", + "optional": true, + "dependencies": { + "@aws-sdk/types": "3.292.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/abort-controller/node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + }, + "node_modules/@aws-sdk/client-cognito-identity": { + "version": "3.294.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-cognito-identity/-/client-cognito-identity-3.294.0.tgz", + "integrity": "sha512-QMk/QratNvAvmnJ77pu9KDjpfUf/5LOJplHcLKcY962ewJGBtFFY4XZVnmUgQMSG0phRODtex7pyH//FueGKLQ==", + "optional": true, + "dependencies": { + "@aws-crypto/sha256-browser": "3.0.0", + "@aws-crypto/sha256-js": "3.0.0", + "@aws-sdk/client-sts": "3.294.0", + "@aws-sdk/config-resolver": "3.292.0", + "@aws-sdk/credential-provider-node": "3.294.0", + "@aws-sdk/fetch-http-handler": "3.292.0", + "@aws-sdk/hash-node": "3.292.0", + "@aws-sdk/invalid-dependency": "3.292.0", + "@aws-sdk/middleware-content-length": "3.292.0", + "@aws-sdk/middleware-endpoint": "3.292.0", + "@aws-sdk/middleware-host-header": "3.292.0", + "@aws-sdk/middleware-logger": "3.292.0", + "@aws-sdk/middleware-recursion-detection": "3.292.0", + "@aws-sdk/middleware-retry": "3.293.0", + "@aws-sdk/middleware-serde": "3.292.0", + "@aws-sdk/middleware-signing": "3.292.0", + "@aws-sdk/middleware-stack": "3.292.0", + "@aws-sdk/middleware-user-agent": "3.293.0", + "@aws-sdk/node-config-provider": "3.292.0", + "@aws-sdk/node-http-handler": "3.292.0", + "@aws-sdk/protocol-http": "3.292.0", + "@aws-sdk/smithy-client": "3.292.0", + "@aws-sdk/types": "3.292.0", + "@aws-sdk/url-parser": "3.292.0", + "@aws-sdk/util-base64": "3.292.0", + "@aws-sdk/util-body-length-browser": "3.292.0", + "@aws-sdk/util-body-length-node": "3.292.0", + "@aws-sdk/util-defaults-mode-browser": "3.292.0", + "@aws-sdk/util-defaults-mode-node": "3.292.0", + "@aws-sdk/util-endpoints": "3.293.0", + "@aws-sdk/util-retry": "3.292.0", + "@aws-sdk/util-user-agent-browser": "3.292.0", + "@aws-sdk/util-user-agent-node": "3.292.0", + "@aws-sdk/util-utf8": "3.292.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + }, + "node_modules/@aws-sdk/client-sso": { + "version": "3.294.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.294.0.tgz", + "integrity": "sha512-+FuxQTi5WvnaXM5JbNLkBIzQ3An4gA0ox61N1u+3xled+nywKb1yQ7WmRpyMG5bLbkmnj3aqoo5/uskFc4c4EA==", + "optional": true, + "dependencies": { + "@aws-crypto/sha256-browser": "3.0.0", + "@aws-crypto/sha256-js": "3.0.0", + "@aws-sdk/config-resolver": "3.292.0", + "@aws-sdk/fetch-http-handler": "3.292.0", + "@aws-sdk/hash-node": "3.292.0", + "@aws-sdk/invalid-dependency": "3.292.0", + "@aws-sdk/middleware-content-length": "3.292.0", + "@aws-sdk/middleware-endpoint": "3.292.0", + "@aws-sdk/middleware-host-header": "3.292.0", + "@aws-sdk/middleware-logger": "3.292.0", + "@aws-sdk/middleware-recursion-detection": "3.292.0", + "@aws-sdk/middleware-retry": "3.293.0", + "@aws-sdk/middleware-serde": "3.292.0", + "@aws-sdk/middleware-stack": "3.292.0", + "@aws-sdk/middleware-user-agent": "3.293.0", + "@aws-sdk/node-config-provider": "3.292.0", + "@aws-sdk/node-http-handler": "3.292.0", + "@aws-sdk/protocol-http": "3.292.0", + "@aws-sdk/smithy-client": "3.292.0", + "@aws-sdk/types": "3.292.0", + "@aws-sdk/url-parser": "3.292.0", + "@aws-sdk/util-base64": "3.292.0", + "@aws-sdk/util-body-length-browser": "3.292.0", + "@aws-sdk/util-body-length-node": "3.292.0", + "@aws-sdk/util-defaults-mode-browser": "3.292.0", + "@aws-sdk/util-defaults-mode-node": "3.292.0", + "@aws-sdk/util-endpoints": "3.293.0", + "@aws-sdk/util-retry": "3.292.0", + "@aws-sdk/util-user-agent-browser": "3.292.0", + "@aws-sdk/util-user-agent-node": "3.292.0", + "@aws-sdk/util-utf8": "3.292.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-sso-oidc": { + "version": "3.294.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.294.0.tgz", + "integrity": "sha512-/ZfDud76MdSPJ/TxjV2xLE30XbBQDZwKQ32axwoK1eziPvrAIUBYVgpBwj+m0quhoiQhBKkg3aFl6j39AF2thw==", + "optional": true, + "dependencies": { + "@aws-crypto/sha256-browser": "3.0.0", + "@aws-crypto/sha256-js": "3.0.0", + "@aws-sdk/config-resolver": "3.292.0", + "@aws-sdk/fetch-http-handler": "3.292.0", + "@aws-sdk/hash-node": "3.292.0", + "@aws-sdk/invalid-dependency": "3.292.0", + "@aws-sdk/middleware-content-length": "3.292.0", + "@aws-sdk/middleware-endpoint": "3.292.0", + "@aws-sdk/middleware-host-header": "3.292.0", + "@aws-sdk/middleware-logger": "3.292.0", + "@aws-sdk/middleware-recursion-detection": "3.292.0", + "@aws-sdk/middleware-retry": "3.293.0", + "@aws-sdk/middleware-serde": "3.292.0", + "@aws-sdk/middleware-stack": "3.292.0", + "@aws-sdk/middleware-user-agent": "3.293.0", + "@aws-sdk/node-config-provider": "3.292.0", + "@aws-sdk/node-http-handler": "3.292.0", + "@aws-sdk/protocol-http": "3.292.0", + "@aws-sdk/smithy-client": "3.292.0", + "@aws-sdk/types": "3.292.0", + "@aws-sdk/url-parser": "3.292.0", + "@aws-sdk/util-base64": "3.292.0", + "@aws-sdk/util-body-length-browser": "3.292.0", + "@aws-sdk/util-body-length-node": "3.292.0", + "@aws-sdk/util-defaults-mode-browser": "3.292.0", + "@aws-sdk/util-defaults-mode-node": "3.292.0", + "@aws-sdk/util-endpoints": "3.293.0", + "@aws-sdk/util-retry": "3.292.0", + "@aws-sdk/util-user-agent-browser": "3.292.0", + "@aws-sdk/util-user-agent-node": "3.292.0", + "@aws-sdk/util-utf8": "3.292.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-sso-oidc/node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + }, + "node_modules/@aws-sdk/client-sso/node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + }, + "node_modules/@aws-sdk/client-sts": { + "version": "3.294.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.294.0.tgz", + "integrity": "sha512-AefqwhFjTDzelZuSYhriJbiI+GQwf2yKiKAnCt0gRj6rswewStM63Gtlhfb01sFPp+ZiqPcyQ47LqUaHp1mz/g==", + "optional": true, + "dependencies": { + "@aws-crypto/sha256-browser": "3.0.0", + "@aws-crypto/sha256-js": "3.0.0", + "@aws-sdk/config-resolver": "3.292.0", + "@aws-sdk/credential-provider-node": "3.294.0", + "@aws-sdk/fetch-http-handler": "3.292.0", + "@aws-sdk/hash-node": "3.292.0", + "@aws-sdk/invalid-dependency": "3.292.0", + "@aws-sdk/middleware-content-length": "3.292.0", + "@aws-sdk/middleware-endpoint": "3.292.0", + "@aws-sdk/middleware-host-header": "3.292.0", + "@aws-sdk/middleware-logger": "3.292.0", + "@aws-sdk/middleware-recursion-detection": "3.292.0", + "@aws-sdk/middleware-retry": "3.293.0", + "@aws-sdk/middleware-sdk-sts": "3.292.0", + "@aws-sdk/middleware-serde": "3.292.0", + "@aws-sdk/middleware-signing": "3.292.0", + "@aws-sdk/middleware-stack": "3.292.0", + "@aws-sdk/middleware-user-agent": "3.293.0", + "@aws-sdk/node-config-provider": "3.292.0", + "@aws-sdk/node-http-handler": "3.292.0", + "@aws-sdk/protocol-http": "3.292.0", + "@aws-sdk/smithy-client": "3.292.0", + "@aws-sdk/types": "3.292.0", + "@aws-sdk/url-parser": "3.292.0", + "@aws-sdk/util-base64": "3.292.0", + "@aws-sdk/util-body-length-browser": "3.292.0", + "@aws-sdk/util-body-length-node": "3.292.0", + "@aws-sdk/util-defaults-mode-browser": "3.292.0", + "@aws-sdk/util-defaults-mode-node": "3.292.0", + "@aws-sdk/util-endpoints": "3.293.0", + "@aws-sdk/util-retry": "3.292.0", + "@aws-sdk/util-user-agent-browser": "3.292.0", + "@aws-sdk/util-user-agent-node": "3.292.0", + "@aws-sdk/util-utf8": "3.292.0", + "fast-xml-parser": "4.1.2", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-sts/node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + }, + "node_modules/@aws-sdk/config-resolver": { + "version": "3.292.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/config-resolver/-/config-resolver-3.292.0.tgz", + "integrity": "sha512-cB3twnNR7vYvlt2jvw8VlA1+iv/tVzl+/S39MKqw2tepU+AbJAM0EHwb/dkf1OKSmlrnANXhshx80MHF9zL4mA==", + "optional": true, + "dependencies": { + "@aws-sdk/signature-v4": "3.292.0", + "@aws-sdk/types": "3.292.0", + "@aws-sdk/util-config-provider": "3.292.0", + "@aws-sdk/util-middleware": "3.292.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/config-resolver/node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + }, + "node_modules/@aws-sdk/credential-provider-cognito-identity": { + "version": "3.294.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-cognito-identity/-/credential-provider-cognito-identity-3.294.0.tgz", + "integrity": "sha512-YSPqHEbLC0dnbFF5LdlMH0B50sMSN/CyG/sHkPYUwL/hkbUk9URnVW7ZJlt6lRftT7X4C7muzLqUP8sJAaiJEA==", + "optional": true, + "dependencies": { + "@aws-sdk/client-cognito-identity": "3.294.0", + "@aws-sdk/property-provider": "3.292.0", + "@aws-sdk/types": "3.292.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + }, + "node_modules/@aws-sdk/credential-provider-env": { + "version": "3.292.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.292.0.tgz", + "integrity": "sha512-YbafSG0ZEKE2969CJWVtUhh3hfOeLPecFVoXOtegCyAJgY5Ghtu4TsVhL4DgiGAgOC30ojAmUVQEXzd7xJF5xA==", + "optional": true, + "dependencies": { + "@aws-sdk/property-provider": "3.292.0", + "@aws-sdk/types": "3.292.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-env/node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + }, + "node_modules/@aws-sdk/credential-provider-imds": { + "version": "3.292.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-imds/-/credential-provider-imds-3.292.0.tgz", + "integrity": "sha512-W/peOgDSRYulgzFpUhvgi1pCm6piBz6xrVN17N4QOy+3NHBXRVMVzYk6ct2qpLPgJUSEZkcpP+Gds+bBm8ed1A==", + "optional": true, + "dependencies": { + "@aws-sdk/node-config-provider": "3.292.0", + "@aws-sdk/property-provider": "3.292.0", + "@aws-sdk/types": "3.292.0", + "@aws-sdk/url-parser": "3.292.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-imds/node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + }, + "node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.294.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.294.0.tgz", + "integrity": "sha512-pdTPbaAb5bWA+DnuKoL2TpXeNDp6Ejpv/OYt+bw2gdzl9w5r/ZCtUTTbW+Vvejr4WL5s3c1bY96kwdqCn7iLqA==", + "optional": true, + "dependencies": { + "@aws-sdk/credential-provider-env": "3.292.0", + "@aws-sdk/credential-provider-imds": "3.292.0", + "@aws-sdk/credential-provider-process": "3.292.0", + "@aws-sdk/credential-provider-sso": "3.294.0", + "@aws-sdk/credential-provider-web-identity": "3.292.0", + "@aws-sdk/property-provider": "3.292.0", + "@aws-sdk/shared-ini-file-loader": "3.292.0", + "@aws-sdk/types": "3.292.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-ini/node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + }, + "node_modules/@aws-sdk/credential-provider-node": { + "version": "3.294.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.294.0.tgz", + "integrity": "sha512-zUL1Qhb4BsQIZCs/TPpG4oIYH/9YsGiS+Se1tasSGjTOLfBy7jhOZ0QIdpEeyAx/EP8blOBredM9xWfEXgiHVA==", + "optional": true, + "dependencies": { + "@aws-sdk/credential-provider-env": "3.292.0", + "@aws-sdk/credential-provider-imds": "3.292.0", + "@aws-sdk/credential-provider-ini": "3.294.0", + "@aws-sdk/credential-provider-process": "3.292.0", + "@aws-sdk/credential-provider-sso": "3.294.0", + "@aws-sdk/credential-provider-web-identity": "3.292.0", + "@aws-sdk/property-provider": "3.292.0", + "@aws-sdk/shared-ini-file-loader": "3.292.0", + "@aws-sdk/types": "3.292.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-node/node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + }, + "node_modules/@aws-sdk/credential-provider-process": { + "version": "3.292.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.292.0.tgz", + "integrity": "sha512-CFVXuMuUvg/a4tknzRikEDwZBnKlHs1LZCpTXIGjBdUTdosoi4WNzDLzGp93ZRTtcgFz+4wirz2f7P3lC0NrQw==", + "optional": true, + "dependencies": { + "@aws-sdk/property-provider": "3.292.0", + "@aws-sdk/shared-ini-file-loader": "3.292.0", + "@aws-sdk/types": "3.292.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-process/node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + }, + "node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.294.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.294.0.tgz", + "integrity": "sha512-UxrcAA/0l7j9+3tolYcG5M61D/IE1Bjd/9H87H1i2A2BrwUUBhW1Dp/vvROEDrrywlMDG3CDF3T/7ADtTak+sg==", + "optional": true, + "dependencies": { + "@aws-sdk/client-sso": "3.294.0", + "@aws-sdk/property-provider": "3.292.0", + "@aws-sdk/shared-ini-file-loader": "3.292.0", + "@aws-sdk/token-providers": "3.294.0", + "@aws-sdk/types": "3.292.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-sso/node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + }, + "node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.292.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.292.0.tgz", + "integrity": "sha512-4DbtIEM9gGVfqYlMdYXg3XY+vBhemjB1zXIequottW8loLYM8Vuz4/uGxxKNze6evVVzowsA0wKrYclE1aj/Rg==", + "optional": true, + "dependencies": { + "@aws-sdk/property-provider": "3.292.0", + "@aws-sdk/types": "3.292.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-web-identity/node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + }, + "node_modules/@aws-sdk/credential-providers": { + "version": "3.294.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-providers/-/credential-providers-3.294.0.tgz", + "integrity": "sha512-CRGwCs6F31mQzjYi5YF2Z6c1T+UrFKtJSa6Ff/Q1rrPnROexyhBVnpP8WzpkODx/pZxKtTX50IX7ehIxbFDIyQ==", + "optional": true, + "dependencies": { + "@aws-sdk/client-cognito-identity": "3.294.0", + "@aws-sdk/client-sso": "3.294.0", + "@aws-sdk/client-sts": "3.294.0", + "@aws-sdk/credential-provider-cognito-identity": "3.294.0", + "@aws-sdk/credential-provider-env": "3.292.0", + "@aws-sdk/credential-provider-imds": "3.292.0", + "@aws-sdk/credential-provider-ini": "3.294.0", + "@aws-sdk/credential-provider-node": "3.294.0", + "@aws-sdk/credential-provider-process": "3.292.0", + "@aws-sdk/credential-provider-sso": "3.294.0", + "@aws-sdk/credential-provider-web-identity": "3.292.0", + "@aws-sdk/property-provider": "3.292.0", + "@aws-sdk/shared-ini-file-loader": "3.292.0", + "@aws-sdk/types": "3.292.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/credential-providers/node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + }, + "node_modules/@aws-sdk/fetch-http-handler": { + "version": "3.292.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/fetch-http-handler/-/fetch-http-handler-3.292.0.tgz", + "integrity": "sha512-zh3bhUJbL8RSa39ZKDcy+AghtUkIP8LwcNlwRIoxMQh3Row4D1s4fCq0KZCx98NJBEXoiTLyTQlZxxI//BOb1Q==", + "optional": true, + "dependencies": { + "@aws-sdk/protocol-http": "3.292.0", + "@aws-sdk/querystring-builder": "3.292.0", + "@aws-sdk/types": "3.292.0", + "@aws-sdk/util-base64": "3.292.0", + "tslib": "^2.3.1" + } + }, + "node_modules/@aws-sdk/fetch-http-handler/node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + }, + "node_modules/@aws-sdk/hash-node": { + "version": "3.292.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/hash-node/-/hash-node-3.292.0.tgz", + "integrity": "sha512-1yLxmIsvE+eK36JXEgEIouTITdykQLVhsA5Oai//Lar6Ddgu1sFpLDbdkMtKbrh4I0jLN9RacNCkeVQjZPTCCQ==", + "optional": true, + "dependencies": { + "@aws-sdk/types": "3.292.0", + "@aws-sdk/util-buffer-from": "3.292.0", + "@aws-sdk/util-utf8": "3.292.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/hash-node/node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + }, + "node_modules/@aws-sdk/invalid-dependency": { + "version": "3.292.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/invalid-dependency/-/invalid-dependency-3.292.0.tgz", + "integrity": "sha512-39OUV78CD3TmEbjhpt+V+Fk4wAGWhixqHxDSN8+4WL0uB4Fl7k5m3Z9hNY78AttHQSl2twR7WtLztnXPAFsriw==", + "optional": true, + "dependencies": { + "@aws-sdk/types": "3.292.0", + "tslib": "^2.3.1" + } + }, + "node_modules/@aws-sdk/invalid-dependency/node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + }, + "node_modules/@aws-sdk/is-array-buffer": { + "version": "3.292.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/is-array-buffer/-/is-array-buffer-3.292.0.tgz", + "integrity": "sha512-kW/G5T/fzI0sJH5foZG6XJiNCevXqKLxV50qIT4B1pMuw7regd4ALIy0HwSqj1nnn9mSbRWBfmby0jWCJsMcwg==", + "optional": true, + "dependencies": { + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/is-array-buffer/node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + }, + "node_modules/@aws-sdk/middleware-content-length": { + "version": "3.292.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-content-length/-/middleware-content-length-3.292.0.tgz", + "integrity": "sha512-2gMWzQus5mj14menolpPDbYBeaOYcj7KNFZOjTjjI3iQ0KqyetG6XasirNrcJ/8QX1BRmpTol8Xjp2Ue3Gbzwg==", + "optional": true, + "dependencies": { + "@aws-sdk/protocol-http": "3.292.0", + "@aws-sdk/types": "3.292.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/middleware-content-length/node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + }, + "node_modules/@aws-sdk/middleware-endpoint": { + "version": "3.292.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-endpoint/-/middleware-endpoint-3.292.0.tgz", + "integrity": "sha512-cPMkiSxpZGG6tYlW4OS+ucS6r43f9ddX9kcUoemJCY10MOuogdPjulCAjE0HTs2PLKSOrrG4CTP4Q4wWDrH4Bw==", + "optional": true, + "dependencies": { + "@aws-sdk/middleware-serde": "3.292.0", + "@aws-sdk/protocol-http": "3.292.0", + "@aws-sdk/signature-v4": "3.292.0", + "@aws-sdk/types": "3.292.0", + "@aws-sdk/url-parser": "3.292.0", + "@aws-sdk/util-config-provider": "3.292.0", + "@aws-sdk/util-middleware": "3.292.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/middleware-endpoint/node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + }, + "node_modules/@aws-sdk/middleware-host-header": { + "version": "3.292.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.292.0.tgz", + "integrity": "sha512-mHuCWe3Yg2S5YZ7mB7sKU6C97XspfqrimWjMW9pfV2usAvLA3R0HrB03jpR5vpZ3P4q7HB6wK3S6CjYMGGRNag==", + "optional": true, + "dependencies": { + "@aws-sdk/protocol-http": "3.292.0", + "@aws-sdk/types": "3.292.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/middleware-host-header/node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + }, + "node_modules/@aws-sdk/middleware-logger": { + "version": "3.292.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.292.0.tgz", + "integrity": "sha512-yZNY1XYmG3NG+uonET7jzKXNiwu61xm/ZZ6i/l51SusuaYN+qQtTAhOFsieQqTehF9kP4FzbsWgPDwD8ZZX9lw==", + "optional": true, + "dependencies": { + "@aws-sdk/types": "3.292.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/middleware-logger/node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + }, + "node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.292.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.292.0.tgz", + "integrity": "sha512-kA3VZpPko0Zqd7CYPTKAxhjEv0HJqFu2054L04dde1JLr43ro+2MTdX7vsHzeAFUVRphqatFFofCumvXmU6Mig==", + "optional": true, + "dependencies": { + "@aws-sdk/protocol-http": "3.292.0", + "@aws-sdk/types": "3.292.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/middleware-recursion-detection/node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + }, + "node_modules/@aws-sdk/middleware-retry": { + "version": "3.293.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-retry/-/middleware-retry-3.293.0.tgz", + "integrity": "sha512-7tiaz2GzRecNHaZ6YnF+Nrtk3au8qF6oiipf11R7MJiqJ0fkMLnz/iRrlakDziS9qF/a9v+3yxb4W4NHK3f4Tw==", + "optional": true, + "dependencies": { + "@aws-sdk/protocol-http": "3.292.0", + "@aws-sdk/service-error-classification": "3.292.0", + "@aws-sdk/types": "3.292.0", + "@aws-sdk/util-middleware": "3.292.0", + "@aws-sdk/util-retry": "3.292.0", + "tslib": "^2.3.1", + "uuid": "^8.3.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/middleware-retry/node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + }, + "node_modules/@aws-sdk/middleware-retry/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "optional": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/@aws-sdk/middleware-sdk-sts": { + "version": "3.292.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-sts/-/middleware-sdk-sts-3.292.0.tgz", + "integrity": "sha512-GN5ZHEqXZqDi+HkVbaXRX9HaW/vA5rikYpWKYsmxTUZ7fB7ijvEO3co3lleJv2C+iGYRtUIHC4wYNB5xgoTCxg==", + "optional": true, + "dependencies": { + "@aws-sdk/middleware-signing": "3.292.0", + "@aws-sdk/property-provider": "3.292.0", + "@aws-sdk/protocol-http": "3.292.0", + "@aws-sdk/signature-v4": "3.292.0", + "@aws-sdk/types": "3.292.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/middleware-sdk-sts/node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + }, + "node_modules/@aws-sdk/middleware-serde": { + "version": "3.292.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-serde/-/middleware-serde-3.292.0.tgz", + "integrity": "sha512-6hN9mTQwSvV8EcGvtXbS/MpK7WMCokUku5Wu7X24UwCNMVkoRHLIkYcxHcvBTwttuOU0d8hph1/lIX4dkLwkQw==", + "optional": true, + "dependencies": { + "@aws-sdk/types": "3.292.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/middleware-serde/node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + }, + "node_modules/@aws-sdk/middleware-signing": { + "version": "3.292.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-signing/-/middleware-signing-3.292.0.tgz", + "integrity": "sha512-GVfoSjDjEQ4TaO6x9MffyP3uRV+2KcS5FtexLCYOM9pJcnE9tqq9FJOrZ1xl1g+YjUVKxo4x8lu3tpEtIb17qg==", + "optional": true, + "dependencies": { + "@aws-sdk/property-provider": "3.292.0", + "@aws-sdk/protocol-http": "3.292.0", + "@aws-sdk/signature-v4": "3.292.0", + "@aws-sdk/types": "3.292.0", + "@aws-sdk/util-middleware": "3.292.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/middleware-signing/node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + }, + "node_modules/@aws-sdk/middleware-stack": { + "version": "3.292.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-stack/-/middleware-stack-3.292.0.tgz", + "integrity": "sha512-WdQpRkuMysrEwrkByCM1qCn2PPpFGGQ2iXqaFha5RzCdZDlxJni9cVNb6HzWUcgjLEYVTXCmOR9Wxm3CNW44Qg==", + "optional": true, + "dependencies": { + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/middleware-stack/node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + }, + "node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.293.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.293.0.tgz", + "integrity": "sha512-gZ7/e6XwpKk9mvgA78q4Ffc796jTn02TUKx2qMDnkLVbeJXBNN2jnvYEKq8v70+o7fd/ALRudg8gBDmkkhM/Hw==", + "optional": true, + "dependencies": { + "@aws-sdk/protocol-http": "3.292.0", + "@aws-sdk/types": "3.292.0", + "@aws-sdk/util-endpoints": "3.293.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/middleware-user-agent/node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + }, + "node_modules/@aws-sdk/node-config-provider": { + "version": "3.292.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/node-config-provider/-/node-config-provider-3.292.0.tgz", + "integrity": "sha512-S3NnC9dQ5GIbJYSDIldZb4zdpCOEua1tM7bjYL3VS5uqCEM93kIi/o/UkIUveMp/eqTS2LJa5HjNIz5Te6je0A==", + "optional": true, + "dependencies": { + "@aws-sdk/property-provider": "3.292.0", + "@aws-sdk/shared-ini-file-loader": "3.292.0", + "@aws-sdk/types": "3.292.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/node-config-provider/node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + }, + "node_modules/@aws-sdk/node-http-handler": { + "version": "3.292.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/node-http-handler/-/node-http-handler-3.292.0.tgz", + "integrity": "sha512-L/E3UDSwXLXjt1XWWh0RBD55F+aZI1AEdPwdES9i1PjnZLyuxuDhEDptVibNN56+I9/4Q3SbmuVRVlOD0uzBag==", + "optional": true, + "dependencies": { + "@aws-sdk/abort-controller": "3.292.0", + "@aws-sdk/protocol-http": "3.292.0", + "@aws-sdk/querystring-builder": "3.292.0", + "@aws-sdk/types": "3.292.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/node-http-handler/node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + }, + "node_modules/@aws-sdk/property-provider": { + "version": "3.292.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/property-provider/-/property-provider-3.292.0.tgz", + "integrity": "sha512-dHArSvsiqhno/g55N815gXmAMrmN8DP7OeFNqJ4wJG42xsF2PFN3DAsjIuHuXMwu+7A3R1LHqIpvv0hA9KeoJQ==", + "optional": true, + "dependencies": { + "@aws-sdk/types": "3.292.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/property-provider/node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + }, + "node_modules/@aws-sdk/protocol-http": { + "version": "3.292.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/protocol-http/-/protocol-http-3.292.0.tgz", + "integrity": "sha512-NLi4fq3k41aXIh1I97yX0JTy+3p6aW1NdwFwdMa674z86QNfb4SfRQRZBQe9wEnAZ/eWHVnlKIuII+U1URk/Kg==", + "optional": true, + "dependencies": { + "@aws-sdk/types": "3.292.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/protocol-http/node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + }, + "node_modules/@aws-sdk/querystring-builder": { + "version": "3.292.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/querystring-builder/-/querystring-builder-3.292.0.tgz", + "integrity": "sha512-XElIFJaReIm24eEvBtV2dOtZvcm3gXsGu/ftG8MLJKbKXFKpAP1q+K6En0Bs7/T88voKghKdKpKT+eZUWgTqlg==", + "optional": true, + "dependencies": { + "@aws-sdk/types": "3.292.0", + "@aws-sdk/util-uri-escape": "3.292.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/querystring-builder/node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + }, + "node_modules/@aws-sdk/querystring-parser": { + "version": "3.292.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/querystring-parser/-/querystring-parser-3.292.0.tgz", + "integrity": "sha512-iTYpYo7a8X9RxiPbjjewIpm6XQPx2EOcF1dWCPRII9EFlmZ4bwnX+PDI36fIo9oVs8TIKXmwNGODU9nsg7CSAw==", + "optional": true, + "dependencies": { + "@aws-sdk/types": "3.292.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/querystring-parser/node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + }, + "node_modules/@aws-sdk/service-error-classification": { + "version": "3.292.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/service-error-classification/-/service-error-classification-3.292.0.tgz", + "integrity": "sha512-X1k3sixCeC45XSNHBe+kRBQBwPDyTFtFITb8O5Qw4dS9XWGhrUJT4CX0qE5aj8qP3F9U5nRizs9c2mBVVP0Caw==", + "optional": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/shared-ini-file-loader": { + "version": "3.292.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/shared-ini-file-loader/-/shared-ini-file-loader-3.292.0.tgz", + "integrity": "sha512-Av2TTYg1Jig2kbkD56ybiqZJB6vVrYjv1W5UQwY/q3nA/T2mcrgQ20ByCOt5Bv9VvY7FSgC+znj+L4a7RLGmBg==", + "optional": true, + "dependencies": { + "@aws-sdk/types": "3.292.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/shared-ini-file-loader/node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + }, + "node_modules/@aws-sdk/signature-v4": { + "version": "3.292.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4/-/signature-v4-3.292.0.tgz", + "integrity": "sha512-+rw47VY5mvBecn13tDQTl1ipGWg5tE63faWgmZe68HoBL87ZiDzsd7bUKOvjfW21iMgWlwAppkaNNQayYRb2zg==", + "optional": true, + "dependencies": { + "@aws-sdk/is-array-buffer": "3.292.0", + "@aws-sdk/types": "3.292.0", + "@aws-sdk/util-hex-encoding": "3.292.0", + "@aws-sdk/util-middleware": "3.292.0", + "@aws-sdk/util-uri-escape": "3.292.0", + "@aws-sdk/util-utf8": "3.292.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/signature-v4/node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + }, + "node_modules/@aws-sdk/smithy-client": { + "version": "3.292.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/smithy-client/-/smithy-client-3.292.0.tgz", + "integrity": "sha512-S8PKzjPkZ6SXYZuZiU787dMsvQ0d/LFEhw2OI4Oe2An9Fc2IwJ2FYukyHoQJOV2tV0DiuMebPo7eMyQyjKElvA==", + "optional": true, + "dependencies": { + "@aws-sdk/middleware-stack": "3.292.0", + "@aws-sdk/types": "3.292.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/smithy-client/node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + }, + "node_modules/@aws-sdk/token-providers": { + "version": "3.294.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.294.0.tgz", + "integrity": "sha512-6nwO04LtC5f4AsUvGZXyjaswuEK4Rr2VsuANpMKrPCgunRfI58a8YXLniudOSXN6e7CFJ6M3uo/h5YXqtnzGug==", + "optional": true, + "dependencies": { + "@aws-sdk/client-sso-oidc": "3.294.0", + "@aws-sdk/property-provider": "3.292.0", + "@aws-sdk/shared-ini-file-loader": "3.292.0", + "@aws-sdk/types": "3.292.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/token-providers/node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + }, + "node_modules/@aws-sdk/types": { + "version": "3.292.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.292.0.tgz", + "integrity": "sha512-1teYAY2M73UXZxMAxqZxVS2qwXjQh0OWtt7qyLfha0TtIk/fZ1hRwFgxbDCHUFcdNBSOSbKH/ESor90KROXLCQ==", + "optional": true, + "dependencies": { + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/types/node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + }, + "node_modules/@aws-sdk/url-parser": { + "version": "3.292.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/url-parser/-/url-parser-3.292.0.tgz", + "integrity": "sha512-NZeAuZCk1x6TIiWuRfbOU6wHPBhf0ly2qOHzWut4BCH+b4RrDmFF8EmXcH1auEfGhE7yRyR6XqIN0t3S+hYACA==", + "optional": true, + "dependencies": { + "@aws-sdk/querystring-parser": "3.292.0", + "@aws-sdk/types": "3.292.0", + "tslib": "^2.3.1" + } + }, + "node_modules/@aws-sdk/url-parser/node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + }, + "node_modules/@aws-sdk/util-base64": { + "version": "3.292.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-base64/-/util-base64-3.292.0.tgz", + "integrity": "sha512-zjNCwNdy617yFvEjZorepNWXB2sQCVfsShCwFy/kIQ5iW5tT2jQKaqc0K77diU9atkooxw9p1W9m9sOgrkOFNw==", + "optional": true, + "dependencies": { + "@aws-sdk/util-buffer-from": "3.292.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/util-base64/node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + }, + "node_modules/@aws-sdk/util-body-length-browser": { + "version": "3.292.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-body-length-browser/-/util-body-length-browser-3.292.0.tgz", + "integrity": "sha512-Wd/BM+JsMiKvKs/bN3z6TredVEHh2pKudGfg3CSjTRpqFpOG903KDfyHBD42yg5PuCHoHoewJvTPKwgn7/vhaw==", + "optional": true, + "dependencies": { + "tslib": "^2.3.1" + } + }, + "node_modules/@aws-sdk/util-body-length-browser/node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + }, + "node_modules/@aws-sdk/util-body-length-node": { + "version": "3.292.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-body-length-node/-/util-body-length-node-3.292.0.tgz", + "integrity": "sha512-BBgipZ2P6RhogWE/qj0oqpdlyd3iSBYmb+aD/TBXwB2lA/X8A99GxweBd/kp06AmcJRoMS9WIXgbWkiiBlRlSA==", + "optional": true, + "dependencies": { + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/util-body-length-node/node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + }, + "node_modules/@aws-sdk/util-buffer-from": { + "version": "3.292.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-buffer-from/-/util-buffer-from-3.292.0.tgz", + "integrity": "sha512-RxNZjLoXNxHconH9TYsk5RaEBjSgTtozHeyIdacaHPj5vlQKi4hgL2hIfKeeNiAfQEVjaUFF29lv81xpNMzVMQ==", + "optional": true, + "dependencies": { + "@aws-sdk/is-array-buffer": "3.292.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/util-buffer-from/node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + }, + "node_modules/@aws-sdk/util-config-provider": { + "version": "3.292.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-config-provider/-/util-config-provider-3.292.0.tgz", + "integrity": "sha512-t3noYll6bPRSxeeNNEkC5czVjAiTPcsq00OwfJ2xyUqmquhLEfLwoJKmrT1uP7DjIEXdUtfoIQ2jWiIVm/oO5A==", + "optional": true, + "dependencies": { + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/util-config-provider/node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + }, + "node_modules/@aws-sdk/util-defaults-mode-browser": { + "version": "3.292.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-defaults-mode-browser/-/util-defaults-mode-browser-3.292.0.tgz", + "integrity": "sha512-7+zVUlMGfa8/KT++9humHo6IDxTnxMCmWUj5jVNlkpk6h7Ecmppf7aXotviyVIA43lhtz0p2AErs0N0ekEUK+w==", + "optional": true, + "dependencies": { + "@aws-sdk/property-provider": "3.292.0", + "@aws-sdk/types": "3.292.0", + "bowser": "^2.11.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@aws-sdk/util-defaults-mode-browser/node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + }, + "node_modules/@aws-sdk/util-defaults-mode-node": { + "version": "3.292.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-defaults-mode-node/-/util-defaults-mode-node-3.292.0.tgz", + "integrity": "sha512-SSIw85eF4BVs0fOJRyshT+R3b/UmBPhiVKCUZm2rq6+lIGkDPiSwQU3d/80AhXtiL5SFT/IzAKKgQd8qMa7q3A==", + "optional": true, + "dependencies": { + "@aws-sdk/config-resolver": "3.292.0", + "@aws-sdk/credential-provider-imds": "3.292.0", + "@aws-sdk/node-config-provider": "3.292.0", + "@aws-sdk/property-provider": "3.292.0", + "@aws-sdk/types": "3.292.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@aws-sdk/util-defaults-mode-node/node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + }, + "node_modules/@aws-sdk/util-endpoints": { + "version": "3.293.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.293.0.tgz", + "integrity": "sha512-R/99aNV49Refpv5guiUjEUrZYlvnfaNBniB+/ZtMO3ixxUopapssCrUivuJrmhccmrYaTCZw7dRzIWjU1jJhKg==", + "optional": true, + "dependencies": { + "@aws-sdk/types": "3.292.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/util-endpoints/node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + }, + "node_modules/@aws-sdk/util-hex-encoding": { + "version": "3.292.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-hex-encoding/-/util-hex-encoding-3.292.0.tgz", + "integrity": "sha512-qBd5KFIUywQ3qSSbj814S2srk0vfv8A6QMI+Obs1y2LHZFdQN5zViptI4UhXhKOHe+NnrHWxSuLC/LMH6q3SmA==", + "optional": true, + "dependencies": { + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/util-hex-encoding/node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + }, + "node_modules/@aws-sdk/util-locate-window": { + "version": "3.292.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.292.0.tgz", + "integrity": "sha512-6xnFJXZI9pKw5lQCDvuWA5PnOaUtNRKWwdxvGkkLx5orboFaoVMS6zowjSQxwVNRjW82u6dYNkhmj9mZ8VSjWg==", + "optional": true, + "dependencies": { + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/util-locate-window/node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + }, + "node_modules/@aws-sdk/util-middleware": { + "version": "3.292.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-middleware/-/util-middleware-3.292.0.tgz", + "integrity": "sha512-KjhS7flfoBKDxbiBZjLjMvEizXgjfQb7GQEItgzGoI9rfGCmZtvqCcqQQoIlxb8bIzGRggAUHtBGWnlLbpb+GQ==", + "optional": true, + "dependencies": { + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/util-middleware/node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + }, + "node_modules/@aws-sdk/util-retry": { + "version": "3.292.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-retry/-/util-retry-3.292.0.tgz", + "integrity": "sha512-JEHyF7MpVeRF5uR4LDYgpOKcFpOPiAj8TqN46SVOQQcL1K+V7cSr7O7N7J6MwJaN9XOzAcBadeIupMm7/BFbgw==", + "optional": true, + "dependencies": { + "@aws-sdk/service-error-classification": "3.292.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@aws-sdk/util-retry/node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + }, + "node_modules/@aws-sdk/util-uri-escape": { + "version": "3.292.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-uri-escape/-/util-uri-escape-3.292.0.tgz", + "integrity": "sha512-hOQtUMQ4VcQ9iwKz50AoCp1XBD5gJ9nly/gJZccAM7zSA5mOO8RRKkbdonqquVHxrO0CnYgiFeCh3V35GFecUw==", + "optional": true, + "dependencies": { + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/util-uri-escape/node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + }, + "node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.292.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.292.0.tgz", + "integrity": "sha512-dld+lpC3QdmTQHdBWJ0WFDkXDSrJgfz03q6mQ8+7H+BC12ZhT0I0g9iuvUjolqy7QR00OxOy47Y9FVhq8EC0Gg==", + "optional": true, + "dependencies": { + "@aws-sdk/types": "3.292.0", + "bowser": "^2.11.0", + "tslib": "^2.3.1" + } + }, + "node_modules/@aws-sdk/util-user-agent-browser/node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + }, + "node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.292.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.292.0.tgz", + "integrity": "sha512-f+NfIMal5E61MDc5WGhUEoicr7b1eNNhA+GgVdSB/Hg5fYhEZvFK9RZizH5rrtsLjjgcr9nPYSR7/nDKCJLumw==", + "optional": true, + "dependencies": { + "@aws-sdk/node-config-provider": "3.292.0", + "@aws-sdk/types": "3.292.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } + } + }, + "node_modules/@aws-sdk/util-user-agent-node/node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + }, + "node_modules/@aws-sdk/util-utf8": { + "version": "3.292.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-utf8/-/util-utf8-3.292.0.tgz", + "integrity": "sha512-FPkj+Z59/DQWvoVu2wFaRncc3KVwe/pgK3MfVb0Lx+Ibey5KUx+sNpJmYcVYHUAe/Nv/JeIpOtYuC96IXOnI6w==", + "optional": true, + "dependencies": { + "@aws-sdk/util-buffer-from": "3.292.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/util-utf8-browser": { + "version": "3.259.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-utf8-browser/-/util-utf8-browser-3.259.0.tgz", + "integrity": "sha512-UvFa/vR+e19XookZF8RzFZBrw2EUkQWxiBW0yYQAhvk3C+QVGl0H3ouca8LDBlBfQKXwmW3huo/59H8rwb1wJw==", + "optional": true, + "dependencies": { + "tslib": "^2.3.1" + } + }, + "node_modules/@aws-sdk/util-utf8-browser/node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + }, + "node_modules/@aws-sdk/util-utf8/node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + }, "node_modules/@colors/colors": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", @@ -456,6 +1858,15 @@ "node": ">=10" } }, + "node_modules/@types/bcrypt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/bcrypt/-/bcrypt-5.0.0.tgz", + "integrity": "sha512-agtcFKaruL8TmcvqbndlqHPSJgsolhf/qPWchFlgnW1gECTN/nKbFcoFnvKAQRFfKbh+BO6A3SWdJu9t+xF3Lw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/body-parser": { "version": "1.19.2", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", @@ -486,6 +1897,15 @@ "@types/node": "*" } }, + "node_modules/@types/cors": { + "version": "2.8.13", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.13.tgz", + "integrity": "sha512-RG8AStHlUiV5ysZQKq97copd2UmVYw3/pRMLefISZ3S1hK104Cwm7iLQ3fTKx+lsUH2CE8FlLaYeEA2LSeqYUA==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/dicer": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/@types/dicer/-/dicer-0.2.2.tgz", @@ -562,6 +1982,132 @@ "integrity": "sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==", "dev": true }, + "node_modules/@types/mongoose-unique-validator": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@types/mongoose-unique-validator/-/mongoose-unique-validator-1.0.7.tgz", + "integrity": "sha512-wgPWN7x9mHdy8x1jcluDF7EDWFb/s8LD7skLr012C8i2Q3tUhdTlp3JxRhFbEM3TD7J1INaoTrWaSaguhuqilQ==", + "dev": true, + "dependencies": { + "mongoose": "^6.3.0" + } + }, + "node_modules/@types/mongoose-unique-validator/node_modules/bson": { + "version": "4.7.2", + "resolved": "https://registry.npmjs.org/bson/-/bson-4.7.2.tgz", + "integrity": "sha512-Ry9wCtIZ5kGqkJoi6aD8KjxFZEx78guTQDnpXWiNthsxzrxAK/i8E6pCHAIZTbaEFWcOCvbecMukfK7XUvyLpQ==", + "dev": true, + "dependencies": { + "buffer": "^5.6.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@types/mongoose-unique-validator/node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/@types/mongoose-unique-validator/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@types/mongoose-unique-validator/node_modules/debug/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/@types/mongoose-unique-validator/node_modules/mongodb": { + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.14.0.tgz", + "integrity": "sha512-coGKkWXIBczZPr284tYKFLg+KbGPPLlSbdgfKAb6QqCFt5bo5VFZ50O3FFzsw4rnkqjwT6D8Qcoo9nshYKM7Mg==", + "dev": true, + "dependencies": { + "bson": "^4.7.0", + "mongodb-connection-string-url": "^2.5.4", + "socks": "^2.7.1" + }, + "engines": { + "node": ">=12.9.0" + }, + "optionalDependencies": { + "@aws-sdk/credential-providers": "^3.186.0", + "saslprep": "^1.0.3" + } + }, + "node_modules/@types/mongoose-unique-validator/node_modules/mongoose": { + "version": "6.10.3", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-6.10.3.tgz", + "integrity": "sha512-fZ3pIlQn7lM632r1l4qiU58lKrJ+FufKVG8TNeRXSChAeu9alCl5KoQ9bLw4jnQNYevSq9o+sqZmFDHP+EVW3g==", + "dev": true, + "dependencies": { + "bson": "^4.7.0", + "kareem": "2.5.1", + "mongodb": "4.14.0", + "mpath": "0.9.0", + "mquery": "4.0.3", + "ms": "2.1.3", + "sift": "16.0.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mongoose" + } + }, + "node_modules/@types/mongoose-unique-validator/node_modules/mquery": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/mquery/-/mquery-4.0.3.tgz", + "integrity": "sha512-J5heI+P08I6VJ2Ky3+33IpCdAvlYGTSUjwTPxkAr8i8EoduPMBX2OY/wa3IKZIQl7MU4SbFk8ndgSKyB/cl1zA==", + "dev": true, + "dependencies": { + "debug": "4.x" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@types/mongoose-unique-validator/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, "node_modules/@types/morgan": { "version": "1.9.4", "resolved": "https://registry.npmjs.org/@types/morgan/-/morgan-1.9.4.tgz", @@ -630,6 +2176,12 @@ "@types/node": "*" } }, + "node_modules/@types/validator": { + "version": "13.7.14", + "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.7.14.tgz", + "integrity": "sha512-J6OAed6rhN6zyqL9Of6ZMamhlsOEU/poBVvbHr/dKOYKTeuYYMlDkMv+b6UUV0o2i0tw73cgyv/97WTWaUl0/g==", + "dev": true + }, "node_modules/@types/webidl-conversions": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.0.tgz", @@ -1308,6 +2860,12 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/bowser": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.11.0.tgz", + "integrity": "sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==", + "optional": true + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -2379,9 +3937,15 @@ } }, "node_modules/express-rate-limit": { - "version": "5.5.1", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-5.5.1.tgz", - "integrity": "sha512-MTjE2eIbHv5DyfuFz4zLYWxpqVhEhkTiwFGuB74Q9CSou2WHO52nlE5y3Zlg6SIsiYUIPj6ifFxnkPz6O3sIUg==" + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-6.7.0.tgz", + "integrity": "sha512-vhwIdRoqcYB/72TK3tRZI+0ttS8Ytrk24GfmsxDXK9o9IhHNO5bXRiXQSExPQ4GbaE5tvIS7j1SGrxsuWs+sGA==", + "engines": { + "node": ">= 12.9.0" + }, + "peerDependencies": { + "express": "^4 || ^5" + } }, "node_modules/express-subdomain": { "version": "1.0.5", @@ -2466,6 +4030,22 @@ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==" }, + "node_modules/fast-xml-parser": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.1.2.tgz", + "integrity": "sha512-CDYeykkle1LiA/uqQyNwYpFbyF6Axec6YapmpUP+/RHWIoR1zKjocdvNaTsxCxZzQ6v9MLXaSYm9Qq0thv0DHg==", + "optional": true, + "dependencies": { + "strnum": "^1.0.5" + }, + "bin": { + "fxparser": "src/cli/cli.js" + }, + "funding": { + "type": "paypal", + "url": "https://paypal.me/naturalintelligence" + } + }, "node_modules/fastq": { "version": "1.15.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", @@ -4943,6 +6523,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/strnum": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz", + "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==", + "optional": true + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -5093,7 +6679,7 @@ "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true + "devOptional": true }, "node_modules/tsutils": { "version": "3.21.0", @@ -5470,6 +7056,1349 @@ } }, "dependencies": { + "@aws-crypto/ie11-detection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/ie11-detection/-/ie11-detection-3.0.0.tgz", + "integrity": "sha512-341lBBkiY1DfDNKai/wXM3aujNBkXR7tq1URPQDL9wi3AUbI80NR74uF1TXHMm7po1AcnFk8iu2S2IeU/+/A+Q==", + "optional": true, + "requires": { + "tslib": "^1.11.1" + } + }, + "@aws-crypto/sha256-browser": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-3.0.0.tgz", + "integrity": "sha512-8VLmW2B+gjFbU5uMeqtQM6Nj0/F1bro80xQXCW6CQBWgosFWXTx77aeOF5CAIAmbOK64SdMBJdNr6J41yP5mvQ==", + "optional": true, + "requires": { + "@aws-crypto/ie11-detection": "^3.0.0", + "@aws-crypto/sha256-js": "^3.0.0", + "@aws-crypto/supports-web-crypto": "^3.0.0", + "@aws-crypto/util": "^3.0.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@aws-sdk/util-utf8-browser": "^3.0.0", + "tslib": "^1.11.1" + } + }, + "@aws-crypto/sha256-js": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-3.0.0.tgz", + "integrity": "sha512-PnNN7os0+yd1XvXAy23CFOmTbMaDxgxXtTKHybrJ39Y8kGzBATgBFibWJKH6BhytLI/Zyszs87xCOBNyBig6vQ==", + "optional": true, + "requires": { + "@aws-crypto/util": "^3.0.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^1.11.1" + } + }, + "@aws-crypto/supports-web-crypto": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-3.0.0.tgz", + "integrity": "sha512-06hBdMwUAb2WFTuGG73LSC0wfPu93xWwo5vL2et9eymgmu3Id5vFAHBbajVWiGhPO37qcsdCap/FqXvJGJWPIg==", + "optional": true, + "requires": { + "tslib": "^1.11.1" + } + }, + "@aws-crypto/util": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-3.0.0.tgz", + "integrity": "sha512-2OJlpeJpCR48CC8r+uKVChzs9Iungj9wkZrl8Z041DWEWvyIHILYKCPNzJghKsivj+S3mLo6BVc7mBNzdxA46w==", + "optional": true, + "requires": { + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-utf8-browser": "^3.0.0", + "tslib": "^1.11.1" + } + }, + "@aws-sdk/abort-controller": { + "version": "3.292.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/abort-controller/-/abort-controller-3.292.0.tgz", + "integrity": "sha512-lf+OPptL01kvryIJy7+dvFux5KbJ6OTwLPPEekVKZ2AfEvwcVtOZWFUhyw3PJCBTVncjKB1Kjl3V/eTS3YuPXQ==", + "optional": true, + "requires": { + "@aws-sdk/types": "3.292.0", + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + } + } + }, + "@aws-sdk/client-cognito-identity": { + "version": "3.294.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-cognito-identity/-/client-cognito-identity-3.294.0.tgz", + "integrity": "sha512-QMk/QratNvAvmnJ77pu9KDjpfUf/5LOJplHcLKcY962ewJGBtFFY4XZVnmUgQMSG0phRODtex7pyH//FueGKLQ==", + "optional": true, + "requires": { + "@aws-crypto/sha256-browser": "3.0.0", + "@aws-crypto/sha256-js": "3.0.0", + "@aws-sdk/client-sts": "3.294.0", + "@aws-sdk/config-resolver": "3.292.0", + "@aws-sdk/credential-provider-node": "3.294.0", + "@aws-sdk/fetch-http-handler": "3.292.0", + "@aws-sdk/hash-node": "3.292.0", + "@aws-sdk/invalid-dependency": "3.292.0", + "@aws-sdk/middleware-content-length": "3.292.0", + "@aws-sdk/middleware-endpoint": "3.292.0", + "@aws-sdk/middleware-host-header": "3.292.0", + "@aws-sdk/middleware-logger": "3.292.0", + "@aws-sdk/middleware-recursion-detection": "3.292.0", + "@aws-sdk/middleware-retry": "3.293.0", + "@aws-sdk/middleware-serde": "3.292.0", + "@aws-sdk/middleware-signing": "3.292.0", + "@aws-sdk/middleware-stack": "3.292.0", + "@aws-sdk/middleware-user-agent": "3.293.0", + "@aws-sdk/node-config-provider": "3.292.0", + "@aws-sdk/node-http-handler": "3.292.0", + "@aws-sdk/protocol-http": "3.292.0", + "@aws-sdk/smithy-client": "3.292.0", + "@aws-sdk/types": "3.292.0", + "@aws-sdk/url-parser": "3.292.0", + "@aws-sdk/util-base64": "3.292.0", + "@aws-sdk/util-body-length-browser": "3.292.0", + "@aws-sdk/util-body-length-node": "3.292.0", + "@aws-sdk/util-defaults-mode-browser": "3.292.0", + "@aws-sdk/util-defaults-mode-node": "3.292.0", + "@aws-sdk/util-endpoints": "3.293.0", + "@aws-sdk/util-retry": "3.292.0", + "@aws-sdk/util-user-agent-browser": "3.292.0", + "@aws-sdk/util-user-agent-node": "3.292.0", + "@aws-sdk/util-utf8": "3.292.0", + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + } + } + }, + "@aws-sdk/client-sso": { + "version": "3.294.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.294.0.tgz", + "integrity": "sha512-+FuxQTi5WvnaXM5JbNLkBIzQ3An4gA0ox61N1u+3xled+nywKb1yQ7WmRpyMG5bLbkmnj3aqoo5/uskFc4c4EA==", + "optional": true, + "requires": { + "@aws-crypto/sha256-browser": "3.0.0", + "@aws-crypto/sha256-js": "3.0.0", + "@aws-sdk/config-resolver": "3.292.0", + "@aws-sdk/fetch-http-handler": "3.292.0", + "@aws-sdk/hash-node": "3.292.0", + "@aws-sdk/invalid-dependency": "3.292.0", + "@aws-sdk/middleware-content-length": "3.292.0", + "@aws-sdk/middleware-endpoint": "3.292.0", + "@aws-sdk/middleware-host-header": "3.292.0", + "@aws-sdk/middleware-logger": "3.292.0", + "@aws-sdk/middleware-recursion-detection": "3.292.0", + "@aws-sdk/middleware-retry": "3.293.0", + "@aws-sdk/middleware-serde": "3.292.0", + "@aws-sdk/middleware-stack": "3.292.0", + "@aws-sdk/middleware-user-agent": "3.293.0", + "@aws-sdk/node-config-provider": "3.292.0", + "@aws-sdk/node-http-handler": "3.292.0", + "@aws-sdk/protocol-http": "3.292.0", + "@aws-sdk/smithy-client": "3.292.0", + "@aws-sdk/types": "3.292.0", + "@aws-sdk/url-parser": "3.292.0", + "@aws-sdk/util-base64": "3.292.0", + "@aws-sdk/util-body-length-browser": "3.292.0", + "@aws-sdk/util-body-length-node": "3.292.0", + "@aws-sdk/util-defaults-mode-browser": "3.292.0", + "@aws-sdk/util-defaults-mode-node": "3.292.0", + "@aws-sdk/util-endpoints": "3.293.0", + "@aws-sdk/util-retry": "3.292.0", + "@aws-sdk/util-user-agent-browser": "3.292.0", + "@aws-sdk/util-user-agent-node": "3.292.0", + "@aws-sdk/util-utf8": "3.292.0", + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + } + } + }, + "@aws-sdk/client-sso-oidc": { + "version": "3.294.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.294.0.tgz", + "integrity": "sha512-/ZfDud76MdSPJ/TxjV2xLE30XbBQDZwKQ32axwoK1eziPvrAIUBYVgpBwj+m0quhoiQhBKkg3aFl6j39AF2thw==", + "optional": true, + "requires": { + "@aws-crypto/sha256-browser": "3.0.0", + "@aws-crypto/sha256-js": "3.0.0", + "@aws-sdk/config-resolver": "3.292.0", + "@aws-sdk/fetch-http-handler": "3.292.0", + "@aws-sdk/hash-node": "3.292.0", + "@aws-sdk/invalid-dependency": "3.292.0", + "@aws-sdk/middleware-content-length": "3.292.0", + "@aws-sdk/middleware-endpoint": "3.292.0", + "@aws-sdk/middleware-host-header": "3.292.0", + "@aws-sdk/middleware-logger": "3.292.0", + "@aws-sdk/middleware-recursion-detection": "3.292.0", + "@aws-sdk/middleware-retry": "3.293.0", + "@aws-sdk/middleware-serde": "3.292.0", + "@aws-sdk/middleware-stack": "3.292.0", + "@aws-sdk/middleware-user-agent": "3.293.0", + "@aws-sdk/node-config-provider": "3.292.0", + "@aws-sdk/node-http-handler": "3.292.0", + "@aws-sdk/protocol-http": "3.292.0", + "@aws-sdk/smithy-client": "3.292.0", + "@aws-sdk/types": "3.292.0", + "@aws-sdk/url-parser": "3.292.0", + "@aws-sdk/util-base64": "3.292.0", + "@aws-sdk/util-body-length-browser": "3.292.0", + "@aws-sdk/util-body-length-node": "3.292.0", + "@aws-sdk/util-defaults-mode-browser": "3.292.0", + "@aws-sdk/util-defaults-mode-node": "3.292.0", + "@aws-sdk/util-endpoints": "3.293.0", + "@aws-sdk/util-retry": "3.292.0", + "@aws-sdk/util-user-agent-browser": "3.292.0", + "@aws-sdk/util-user-agent-node": "3.292.0", + "@aws-sdk/util-utf8": "3.292.0", + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + } + } + }, + "@aws-sdk/client-sts": { + "version": "3.294.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.294.0.tgz", + "integrity": "sha512-AefqwhFjTDzelZuSYhriJbiI+GQwf2yKiKAnCt0gRj6rswewStM63Gtlhfb01sFPp+ZiqPcyQ47LqUaHp1mz/g==", + "optional": true, + "requires": { + "@aws-crypto/sha256-browser": "3.0.0", + "@aws-crypto/sha256-js": "3.0.0", + "@aws-sdk/config-resolver": "3.292.0", + "@aws-sdk/credential-provider-node": "3.294.0", + "@aws-sdk/fetch-http-handler": "3.292.0", + "@aws-sdk/hash-node": "3.292.0", + "@aws-sdk/invalid-dependency": "3.292.0", + "@aws-sdk/middleware-content-length": "3.292.0", + "@aws-sdk/middleware-endpoint": "3.292.0", + "@aws-sdk/middleware-host-header": "3.292.0", + "@aws-sdk/middleware-logger": "3.292.0", + "@aws-sdk/middleware-recursion-detection": "3.292.0", + "@aws-sdk/middleware-retry": "3.293.0", + "@aws-sdk/middleware-sdk-sts": "3.292.0", + "@aws-sdk/middleware-serde": "3.292.0", + "@aws-sdk/middleware-signing": "3.292.0", + "@aws-sdk/middleware-stack": "3.292.0", + "@aws-sdk/middleware-user-agent": "3.293.0", + "@aws-sdk/node-config-provider": "3.292.0", + "@aws-sdk/node-http-handler": "3.292.0", + "@aws-sdk/protocol-http": "3.292.0", + "@aws-sdk/smithy-client": "3.292.0", + "@aws-sdk/types": "3.292.0", + "@aws-sdk/url-parser": "3.292.0", + "@aws-sdk/util-base64": "3.292.0", + "@aws-sdk/util-body-length-browser": "3.292.0", + "@aws-sdk/util-body-length-node": "3.292.0", + "@aws-sdk/util-defaults-mode-browser": "3.292.0", + "@aws-sdk/util-defaults-mode-node": "3.292.0", + "@aws-sdk/util-endpoints": "3.293.0", + "@aws-sdk/util-retry": "3.292.0", + "@aws-sdk/util-user-agent-browser": "3.292.0", + "@aws-sdk/util-user-agent-node": "3.292.0", + "@aws-sdk/util-utf8": "3.292.0", + "fast-xml-parser": "4.1.2", + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + } + } + }, + "@aws-sdk/config-resolver": { + "version": "3.292.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/config-resolver/-/config-resolver-3.292.0.tgz", + "integrity": "sha512-cB3twnNR7vYvlt2jvw8VlA1+iv/tVzl+/S39MKqw2tepU+AbJAM0EHwb/dkf1OKSmlrnANXhshx80MHF9zL4mA==", + "optional": true, + "requires": { + "@aws-sdk/signature-v4": "3.292.0", + "@aws-sdk/types": "3.292.0", + "@aws-sdk/util-config-provider": "3.292.0", + "@aws-sdk/util-middleware": "3.292.0", + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + } + } + }, + "@aws-sdk/credential-provider-cognito-identity": { + "version": "3.294.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-cognito-identity/-/credential-provider-cognito-identity-3.294.0.tgz", + "integrity": "sha512-YSPqHEbLC0dnbFF5LdlMH0B50sMSN/CyG/sHkPYUwL/hkbUk9URnVW7ZJlt6lRftT7X4C7muzLqUP8sJAaiJEA==", + "optional": true, + "requires": { + "@aws-sdk/client-cognito-identity": "3.294.0", + "@aws-sdk/property-provider": "3.292.0", + "@aws-sdk/types": "3.292.0", + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + } + } + }, + "@aws-sdk/credential-provider-env": { + "version": "3.292.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.292.0.tgz", + "integrity": "sha512-YbafSG0ZEKE2969CJWVtUhh3hfOeLPecFVoXOtegCyAJgY5Ghtu4TsVhL4DgiGAgOC30ojAmUVQEXzd7xJF5xA==", + "optional": true, + "requires": { + "@aws-sdk/property-provider": "3.292.0", + "@aws-sdk/types": "3.292.0", + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + } + } + }, + "@aws-sdk/credential-provider-imds": { + "version": "3.292.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-imds/-/credential-provider-imds-3.292.0.tgz", + "integrity": "sha512-W/peOgDSRYulgzFpUhvgi1pCm6piBz6xrVN17N4QOy+3NHBXRVMVzYk6ct2qpLPgJUSEZkcpP+Gds+bBm8ed1A==", + "optional": true, + "requires": { + "@aws-sdk/node-config-provider": "3.292.0", + "@aws-sdk/property-provider": "3.292.0", + "@aws-sdk/types": "3.292.0", + "@aws-sdk/url-parser": "3.292.0", + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + } + } + }, + "@aws-sdk/credential-provider-ini": { + "version": "3.294.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.294.0.tgz", + "integrity": "sha512-pdTPbaAb5bWA+DnuKoL2TpXeNDp6Ejpv/OYt+bw2gdzl9w5r/ZCtUTTbW+Vvejr4WL5s3c1bY96kwdqCn7iLqA==", + "optional": true, + "requires": { + "@aws-sdk/credential-provider-env": "3.292.0", + "@aws-sdk/credential-provider-imds": "3.292.0", + "@aws-sdk/credential-provider-process": "3.292.0", + "@aws-sdk/credential-provider-sso": "3.294.0", + "@aws-sdk/credential-provider-web-identity": "3.292.0", + "@aws-sdk/property-provider": "3.292.0", + "@aws-sdk/shared-ini-file-loader": "3.292.0", + "@aws-sdk/types": "3.292.0", + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + } + } + }, + "@aws-sdk/credential-provider-node": { + "version": "3.294.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.294.0.tgz", + "integrity": "sha512-zUL1Qhb4BsQIZCs/TPpG4oIYH/9YsGiS+Se1tasSGjTOLfBy7jhOZ0QIdpEeyAx/EP8blOBredM9xWfEXgiHVA==", + "optional": true, + "requires": { + "@aws-sdk/credential-provider-env": "3.292.0", + "@aws-sdk/credential-provider-imds": "3.292.0", + "@aws-sdk/credential-provider-ini": "3.294.0", + "@aws-sdk/credential-provider-process": "3.292.0", + "@aws-sdk/credential-provider-sso": "3.294.0", + "@aws-sdk/credential-provider-web-identity": "3.292.0", + "@aws-sdk/property-provider": "3.292.0", + "@aws-sdk/shared-ini-file-loader": "3.292.0", + "@aws-sdk/types": "3.292.0", + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + } + } + }, + "@aws-sdk/credential-provider-process": { + "version": "3.292.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.292.0.tgz", + "integrity": "sha512-CFVXuMuUvg/a4tknzRikEDwZBnKlHs1LZCpTXIGjBdUTdosoi4WNzDLzGp93ZRTtcgFz+4wirz2f7P3lC0NrQw==", + "optional": true, + "requires": { + "@aws-sdk/property-provider": "3.292.0", + "@aws-sdk/shared-ini-file-loader": "3.292.0", + "@aws-sdk/types": "3.292.0", + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + } + } + }, + "@aws-sdk/credential-provider-sso": { + "version": "3.294.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.294.0.tgz", + "integrity": "sha512-UxrcAA/0l7j9+3tolYcG5M61D/IE1Bjd/9H87H1i2A2BrwUUBhW1Dp/vvROEDrrywlMDG3CDF3T/7ADtTak+sg==", + "optional": true, + "requires": { + "@aws-sdk/client-sso": "3.294.0", + "@aws-sdk/property-provider": "3.292.0", + "@aws-sdk/shared-ini-file-loader": "3.292.0", + "@aws-sdk/token-providers": "3.294.0", + "@aws-sdk/types": "3.292.0", + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + } + } + }, + "@aws-sdk/credential-provider-web-identity": { + "version": "3.292.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.292.0.tgz", + "integrity": "sha512-4DbtIEM9gGVfqYlMdYXg3XY+vBhemjB1zXIequottW8loLYM8Vuz4/uGxxKNze6evVVzowsA0wKrYclE1aj/Rg==", + "optional": true, + "requires": { + "@aws-sdk/property-provider": "3.292.0", + "@aws-sdk/types": "3.292.0", + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + } + } + }, + "@aws-sdk/credential-providers": { + "version": "3.294.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-providers/-/credential-providers-3.294.0.tgz", + "integrity": "sha512-CRGwCs6F31mQzjYi5YF2Z6c1T+UrFKtJSa6Ff/Q1rrPnROexyhBVnpP8WzpkODx/pZxKtTX50IX7ehIxbFDIyQ==", + "optional": true, + "requires": { + "@aws-sdk/client-cognito-identity": "3.294.0", + "@aws-sdk/client-sso": "3.294.0", + "@aws-sdk/client-sts": "3.294.0", + "@aws-sdk/credential-provider-cognito-identity": "3.294.0", + "@aws-sdk/credential-provider-env": "3.292.0", + "@aws-sdk/credential-provider-imds": "3.292.0", + "@aws-sdk/credential-provider-ini": "3.294.0", + "@aws-sdk/credential-provider-node": "3.294.0", + "@aws-sdk/credential-provider-process": "3.292.0", + "@aws-sdk/credential-provider-sso": "3.294.0", + "@aws-sdk/credential-provider-web-identity": "3.292.0", + "@aws-sdk/property-provider": "3.292.0", + "@aws-sdk/shared-ini-file-loader": "3.292.0", + "@aws-sdk/types": "3.292.0", + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + } + } + }, + "@aws-sdk/fetch-http-handler": { + "version": "3.292.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/fetch-http-handler/-/fetch-http-handler-3.292.0.tgz", + "integrity": "sha512-zh3bhUJbL8RSa39ZKDcy+AghtUkIP8LwcNlwRIoxMQh3Row4D1s4fCq0KZCx98NJBEXoiTLyTQlZxxI//BOb1Q==", + "optional": true, + "requires": { + "@aws-sdk/protocol-http": "3.292.0", + "@aws-sdk/querystring-builder": "3.292.0", + "@aws-sdk/types": "3.292.0", + "@aws-sdk/util-base64": "3.292.0", + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + } + } + }, + "@aws-sdk/hash-node": { + "version": "3.292.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/hash-node/-/hash-node-3.292.0.tgz", + "integrity": "sha512-1yLxmIsvE+eK36JXEgEIouTITdykQLVhsA5Oai//Lar6Ddgu1sFpLDbdkMtKbrh4I0jLN9RacNCkeVQjZPTCCQ==", + "optional": true, + "requires": { + "@aws-sdk/types": "3.292.0", + "@aws-sdk/util-buffer-from": "3.292.0", + "@aws-sdk/util-utf8": "3.292.0", + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + } + } + }, + "@aws-sdk/invalid-dependency": { + "version": "3.292.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/invalid-dependency/-/invalid-dependency-3.292.0.tgz", + "integrity": "sha512-39OUV78CD3TmEbjhpt+V+Fk4wAGWhixqHxDSN8+4WL0uB4Fl7k5m3Z9hNY78AttHQSl2twR7WtLztnXPAFsriw==", + "optional": true, + "requires": { + "@aws-sdk/types": "3.292.0", + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + } + } + }, + "@aws-sdk/is-array-buffer": { + "version": "3.292.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/is-array-buffer/-/is-array-buffer-3.292.0.tgz", + "integrity": "sha512-kW/G5T/fzI0sJH5foZG6XJiNCevXqKLxV50qIT4B1pMuw7regd4ALIy0HwSqj1nnn9mSbRWBfmby0jWCJsMcwg==", + "optional": true, + "requires": { + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + } + } + }, + "@aws-sdk/middleware-content-length": { + "version": "3.292.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-content-length/-/middleware-content-length-3.292.0.tgz", + "integrity": "sha512-2gMWzQus5mj14menolpPDbYBeaOYcj7KNFZOjTjjI3iQ0KqyetG6XasirNrcJ/8QX1BRmpTol8Xjp2Ue3Gbzwg==", + "optional": true, + "requires": { + "@aws-sdk/protocol-http": "3.292.0", + "@aws-sdk/types": "3.292.0", + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + } + } + }, + "@aws-sdk/middleware-endpoint": { + "version": "3.292.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-endpoint/-/middleware-endpoint-3.292.0.tgz", + "integrity": "sha512-cPMkiSxpZGG6tYlW4OS+ucS6r43f9ddX9kcUoemJCY10MOuogdPjulCAjE0HTs2PLKSOrrG4CTP4Q4wWDrH4Bw==", + "optional": true, + "requires": { + "@aws-sdk/middleware-serde": "3.292.0", + "@aws-sdk/protocol-http": "3.292.0", + "@aws-sdk/signature-v4": "3.292.0", + "@aws-sdk/types": "3.292.0", + "@aws-sdk/url-parser": "3.292.0", + "@aws-sdk/util-config-provider": "3.292.0", + "@aws-sdk/util-middleware": "3.292.0", + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + } + } + }, + "@aws-sdk/middleware-host-header": { + "version": "3.292.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.292.0.tgz", + "integrity": "sha512-mHuCWe3Yg2S5YZ7mB7sKU6C97XspfqrimWjMW9pfV2usAvLA3R0HrB03jpR5vpZ3P4q7HB6wK3S6CjYMGGRNag==", + "optional": true, + "requires": { + "@aws-sdk/protocol-http": "3.292.0", + "@aws-sdk/types": "3.292.0", + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + } + } + }, + "@aws-sdk/middleware-logger": { + "version": "3.292.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.292.0.tgz", + "integrity": "sha512-yZNY1XYmG3NG+uonET7jzKXNiwu61xm/ZZ6i/l51SusuaYN+qQtTAhOFsieQqTehF9kP4FzbsWgPDwD8ZZX9lw==", + "optional": true, + "requires": { + "@aws-sdk/types": "3.292.0", + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + } + } + }, + "@aws-sdk/middleware-recursion-detection": { + "version": "3.292.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.292.0.tgz", + "integrity": "sha512-kA3VZpPko0Zqd7CYPTKAxhjEv0HJqFu2054L04dde1JLr43ro+2MTdX7vsHzeAFUVRphqatFFofCumvXmU6Mig==", + "optional": true, + "requires": { + "@aws-sdk/protocol-http": "3.292.0", + "@aws-sdk/types": "3.292.0", + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + } + } + }, + "@aws-sdk/middleware-retry": { + "version": "3.293.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-retry/-/middleware-retry-3.293.0.tgz", + "integrity": "sha512-7tiaz2GzRecNHaZ6YnF+Nrtk3au8qF6oiipf11R7MJiqJ0fkMLnz/iRrlakDziS9qF/a9v+3yxb4W4NHK3f4Tw==", + "optional": true, + "requires": { + "@aws-sdk/protocol-http": "3.292.0", + "@aws-sdk/service-error-classification": "3.292.0", + "@aws-sdk/types": "3.292.0", + "@aws-sdk/util-middleware": "3.292.0", + "@aws-sdk/util-retry": "3.292.0", + "tslib": "^2.3.1", + "uuid": "^8.3.2" + }, + "dependencies": { + "tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + }, + "uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "optional": true + } + } + }, + "@aws-sdk/middleware-sdk-sts": { + "version": "3.292.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-sts/-/middleware-sdk-sts-3.292.0.tgz", + "integrity": "sha512-GN5ZHEqXZqDi+HkVbaXRX9HaW/vA5rikYpWKYsmxTUZ7fB7ijvEO3co3lleJv2C+iGYRtUIHC4wYNB5xgoTCxg==", + "optional": true, + "requires": { + "@aws-sdk/middleware-signing": "3.292.0", + "@aws-sdk/property-provider": "3.292.0", + "@aws-sdk/protocol-http": "3.292.0", + "@aws-sdk/signature-v4": "3.292.0", + "@aws-sdk/types": "3.292.0", + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + } + } + }, + "@aws-sdk/middleware-serde": { + "version": "3.292.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-serde/-/middleware-serde-3.292.0.tgz", + "integrity": "sha512-6hN9mTQwSvV8EcGvtXbS/MpK7WMCokUku5Wu7X24UwCNMVkoRHLIkYcxHcvBTwttuOU0d8hph1/lIX4dkLwkQw==", + "optional": true, + "requires": { + "@aws-sdk/types": "3.292.0", + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + } + } + }, + "@aws-sdk/middleware-signing": { + "version": "3.292.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-signing/-/middleware-signing-3.292.0.tgz", + "integrity": "sha512-GVfoSjDjEQ4TaO6x9MffyP3uRV+2KcS5FtexLCYOM9pJcnE9tqq9FJOrZ1xl1g+YjUVKxo4x8lu3tpEtIb17qg==", + "optional": true, + "requires": { + "@aws-sdk/property-provider": "3.292.0", + "@aws-sdk/protocol-http": "3.292.0", + "@aws-sdk/signature-v4": "3.292.0", + "@aws-sdk/types": "3.292.0", + "@aws-sdk/util-middleware": "3.292.0", + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + } + } + }, + "@aws-sdk/middleware-stack": { + "version": "3.292.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-stack/-/middleware-stack-3.292.0.tgz", + "integrity": "sha512-WdQpRkuMysrEwrkByCM1qCn2PPpFGGQ2iXqaFha5RzCdZDlxJni9cVNb6HzWUcgjLEYVTXCmOR9Wxm3CNW44Qg==", + "optional": true, + "requires": { + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + } + } + }, + "@aws-sdk/middleware-user-agent": { + "version": "3.293.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.293.0.tgz", + "integrity": "sha512-gZ7/e6XwpKk9mvgA78q4Ffc796jTn02TUKx2qMDnkLVbeJXBNN2jnvYEKq8v70+o7fd/ALRudg8gBDmkkhM/Hw==", + "optional": true, + "requires": { + "@aws-sdk/protocol-http": "3.292.0", + "@aws-sdk/types": "3.292.0", + "@aws-sdk/util-endpoints": "3.293.0", + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + } + } + }, + "@aws-sdk/node-config-provider": { + "version": "3.292.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/node-config-provider/-/node-config-provider-3.292.0.tgz", + "integrity": "sha512-S3NnC9dQ5GIbJYSDIldZb4zdpCOEua1tM7bjYL3VS5uqCEM93kIi/o/UkIUveMp/eqTS2LJa5HjNIz5Te6je0A==", + "optional": true, + "requires": { + "@aws-sdk/property-provider": "3.292.0", + "@aws-sdk/shared-ini-file-loader": "3.292.0", + "@aws-sdk/types": "3.292.0", + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + } + } + }, + "@aws-sdk/node-http-handler": { + "version": "3.292.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/node-http-handler/-/node-http-handler-3.292.0.tgz", + "integrity": "sha512-L/E3UDSwXLXjt1XWWh0RBD55F+aZI1AEdPwdES9i1PjnZLyuxuDhEDptVibNN56+I9/4Q3SbmuVRVlOD0uzBag==", + "optional": true, + "requires": { + "@aws-sdk/abort-controller": "3.292.0", + "@aws-sdk/protocol-http": "3.292.0", + "@aws-sdk/querystring-builder": "3.292.0", + "@aws-sdk/types": "3.292.0", + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + } + } + }, + "@aws-sdk/property-provider": { + "version": "3.292.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/property-provider/-/property-provider-3.292.0.tgz", + "integrity": "sha512-dHArSvsiqhno/g55N815gXmAMrmN8DP7OeFNqJ4wJG42xsF2PFN3DAsjIuHuXMwu+7A3R1LHqIpvv0hA9KeoJQ==", + "optional": true, + "requires": { + "@aws-sdk/types": "3.292.0", + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + } + } + }, + "@aws-sdk/protocol-http": { + "version": "3.292.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/protocol-http/-/protocol-http-3.292.0.tgz", + "integrity": "sha512-NLi4fq3k41aXIh1I97yX0JTy+3p6aW1NdwFwdMa674z86QNfb4SfRQRZBQe9wEnAZ/eWHVnlKIuII+U1URk/Kg==", + "optional": true, + "requires": { + "@aws-sdk/types": "3.292.0", + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + } + } + }, + "@aws-sdk/querystring-builder": { + "version": "3.292.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/querystring-builder/-/querystring-builder-3.292.0.tgz", + "integrity": "sha512-XElIFJaReIm24eEvBtV2dOtZvcm3gXsGu/ftG8MLJKbKXFKpAP1q+K6En0Bs7/T88voKghKdKpKT+eZUWgTqlg==", + "optional": true, + "requires": { + "@aws-sdk/types": "3.292.0", + "@aws-sdk/util-uri-escape": "3.292.0", + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + } + } + }, + "@aws-sdk/querystring-parser": { + "version": "3.292.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/querystring-parser/-/querystring-parser-3.292.0.tgz", + "integrity": "sha512-iTYpYo7a8X9RxiPbjjewIpm6XQPx2EOcF1dWCPRII9EFlmZ4bwnX+PDI36fIo9oVs8TIKXmwNGODU9nsg7CSAw==", + "optional": true, + "requires": { + "@aws-sdk/types": "3.292.0", + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + } + } + }, + "@aws-sdk/service-error-classification": { + "version": "3.292.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/service-error-classification/-/service-error-classification-3.292.0.tgz", + "integrity": "sha512-X1k3sixCeC45XSNHBe+kRBQBwPDyTFtFITb8O5Qw4dS9XWGhrUJT4CX0qE5aj8qP3F9U5nRizs9c2mBVVP0Caw==", + "optional": true + }, + "@aws-sdk/shared-ini-file-loader": { + "version": "3.292.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/shared-ini-file-loader/-/shared-ini-file-loader-3.292.0.tgz", + "integrity": "sha512-Av2TTYg1Jig2kbkD56ybiqZJB6vVrYjv1W5UQwY/q3nA/T2mcrgQ20ByCOt5Bv9VvY7FSgC+znj+L4a7RLGmBg==", + "optional": true, + "requires": { + "@aws-sdk/types": "3.292.0", + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + } + } + }, + "@aws-sdk/signature-v4": { + "version": "3.292.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4/-/signature-v4-3.292.0.tgz", + "integrity": "sha512-+rw47VY5mvBecn13tDQTl1ipGWg5tE63faWgmZe68HoBL87ZiDzsd7bUKOvjfW21iMgWlwAppkaNNQayYRb2zg==", + "optional": true, + "requires": { + "@aws-sdk/is-array-buffer": "3.292.0", + "@aws-sdk/types": "3.292.0", + "@aws-sdk/util-hex-encoding": "3.292.0", + "@aws-sdk/util-middleware": "3.292.0", + "@aws-sdk/util-uri-escape": "3.292.0", + "@aws-sdk/util-utf8": "3.292.0", + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + } + } + }, + "@aws-sdk/smithy-client": { + "version": "3.292.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/smithy-client/-/smithy-client-3.292.0.tgz", + "integrity": "sha512-S8PKzjPkZ6SXYZuZiU787dMsvQ0d/LFEhw2OI4Oe2An9Fc2IwJ2FYukyHoQJOV2tV0DiuMebPo7eMyQyjKElvA==", + "optional": true, + "requires": { + "@aws-sdk/middleware-stack": "3.292.0", + "@aws-sdk/types": "3.292.0", + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + } + } + }, + "@aws-sdk/token-providers": { + "version": "3.294.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.294.0.tgz", + "integrity": "sha512-6nwO04LtC5f4AsUvGZXyjaswuEK4Rr2VsuANpMKrPCgunRfI58a8YXLniudOSXN6e7CFJ6M3uo/h5YXqtnzGug==", + "optional": true, + "requires": { + "@aws-sdk/client-sso-oidc": "3.294.0", + "@aws-sdk/property-provider": "3.292.0", + "@aws-sdk/shared-ini-file-loader": "3.292.0", + "@aws-sdk/types": "3.292.0", + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + } + } + }, + "@aws-sdk/types": { + "version": "3.292.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.292.0.tgz", + "integrity": "sha512-1teYAY2M73UXZxMAxqZxVS2qwXjQh0OWtt7qyLfha0TtIk/fZ1hRwFgxbDCHUFcdNBSOSbKH/ESor90KROXLCQ==", + "optional": true, + "requires": { + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + } + } + }, + "@aws-sdk/url-parser": { + "version": "3.292.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/url-parser/-/url-parser-3.292.0.tgz", + "integrity": "sha512-NZeAuZCk1x6TIiWuRfbOU6wHPBhf0ly2qOHzWut4BCH+b4RrDmFF8EmXcH1auEfGhE7yRyR6XqIN0t3S+hYACA==", + "optional": true, + "requires": { + "@aws-sdk/querystring-parser": "3.292.0", + "@aws-sdk/types": "3.292.0", + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + } + } + }, + "@aws-sdk/util-base64": { + "version": "3.292.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-base64/-/util-base64-3.292.0.tgz", + "integrity": "sha512-zjNCwNdy617yFvEjZorepNWXB2sQCVfsShCwFy/kIQ5iW5tT2jQKaqc0K77diU9atkooxw9p1W9m9sOgrkOFNw==", + "optional": true, + "requires": { + "@aws-sdk/util-buffer-from": "3.292.0", + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + } + } + }, + "@aws-sdk/util-body-length-browser": { + "version": "3.292.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-body-length-browser/-/util-body-length-browser-3.292.0.tgz", + "integrity": "sha512-Wd/BM+JsMiKvKs/bN3z6TredVEHh2pKudGfg3CSjTRpqFpOG903KDfyHBD42yg5PuCHoHoewJvTPKwgn7/vhaw==", + "optional": true, + "requires": { + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + } + } + }, + "@aws-sdk/util-body-length-node": { + "version": "3.292.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-body-length-node/-/util-body-length-node-3.292.0.tgz", + "integrity": "sha512-BBgipZ2P6RhogWE/qj0oqpdlyd3iSBYmb+aD/TBXwB2lA/X8A99GxweBd/kp06AmcJRoMS9WIXgbWkiiBlRlSA==", + "optional": true, + "requires": { + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + } + } + }, + "@aws-sdk/util-buffer-from": { + "version": "3.292.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-buffer-from/-/util-buffer-from-3.292.0.tgz", + "integrity": "sha512-RxNZjLoXNxHconH9TYsk5RaEBjSgTtozHeyIdacaHPj5vlQKi4hgL2hIfKeeNiAfQEVjaUFF29lv81xpNMzVMQ==", + "optional": true, + "requires": { + "@aws-sdk/is-array-buffer": "3.292.0", + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + } + } + }, + "@aws-sdk/util-config-provider": { + "version": "3.292.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-config-provider/-/util-config-provider-3.292.0.tgz", + "integrity": "sha512-t3noYll6bPRSxeeNNEkC5czVjAiTPcsq00OwfJ2xyUqmquhLEfLwoJKmrT1uP7DjIEXdUtfoIQ2jWiIVm/oO5A==", + "optional": true, + "requires": { + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + } + } + }, + "@aws-sdk/util-defaults-mode-browser": { + "version": "3.292.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-defaults-mode-browser/-/util-defaults-mode-browser-3.292.0.tgz", + "integrity": "sha512-7+zVUlMGfa8/KT++9humHo6IDxTnxMCmWUj5jVNlkpk6h7Ecmppf7aXotviyVIA43lhtz0p2AErs0N0ekEUK+w==", + "optional": true, + "requires": { + "@aws-sdk/property-provider": "3.292.0", + "@aws-sdk/types": "3.292.0", + "bowser": "^2.11.0", + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + } + } + }, + "@aws-sdk/util-defaults-mode-node": { + "version": "3.292.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-defaults-mode-node/-/util-defaults-mode-node-3.292.0.tgz", + "integrity": "sha512-SSIw85eF4BVs0fOJRyshT+R3b/UmBPhiVKCUZm2rq6+lIGkDPiSwQU3d/80AhXtiL5SFT/IzAKKgQd8qMa7q3A==", + "optional": true, + "requires": { + "@aws-sdk/config-resolver": "3.292.0", + "@aws-sdk/credential-provider-imds": "3.292.0", + "@aws-sdk/node-config-provider": "3.292.0", + "@aws-sdk/property-provider": "3.292.0", + "@aws-sdk/types": "3.292.0", + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + } + } + }, + "@aws-sdk/util-endpoints": { + "version": "3.293.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.293.0.tgz", + "integrity": "sha512-R/99aNV49Refpv5guiUjEUrZYlvnfaNBniB+/ZtMO3ixxUopapssCrUivuJrmhccmrYaTCZw7dRzIWjU1jJhKg==", + "optional": true, + "requires": { + "@aws-sdk/types": "3.292.0", + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + } + } + }, + "@aws-sdk/util-hex-encoding": { + "version": "3.292.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-hex-encoding/-/util-hex-encoding-3.292.0.tgz", + "integrity": "sha512-qBd5KFIUywQ3qSSbj814S2srk0vfv8A6QMI+Obs1y2LHZFdQN5zViptI4UhXhKOHe+NnrHWxSuLC/LMH6q3SmA==", + "optional": true, + "requires": { + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + } + } + }, + "@aws-sdk/util-locate-window": { + "version": "3.292.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.292.0.tgz", + "integrity": "sha512-6xnFJXZI9pKw5lQCDvuWA5PnOaUtNRKWwdxvGkkLx5orboFaoVMS6zowjSQxwVNRjW82u6dYNkhmj9mZ8VSjWg==", + "optional": true, + "requires": { + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + } + } + }, + "@aws-sdk/util-middleware": { + "version": "3.292.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-middleware/-/util-middleware-3.292.0.tgz", + "integrity": "sha512-KjhS7flfoBKDxbiBZjLjMvEizXgjfQb7GQEItgzGoI9rfGCmZtvqCcqQQoIlxb8bIzGRggAUHtBGWnlLbpb+GQ==", + "optional": true, + "requires": { + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + } + } + }, + "@aws-sdk/util-retry": { + "version": "3.292.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-retry/-/util-retry-3.292.0.tgz", + "integrity": "sha512-JEHyF7MpVeRF5uR4LDYgpOKcFpOPiAj8TqN46SVOQQcL1K+V7cSr7O7N7J6MwJaN9XOzAcBadeIupMm7/BFbgw==", + "optional": true, + "requires": { + "@aws-sdk/service-error-classification": "3.292.0", + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + } + } + }, + "@aws-sdk/util-uri-escape": { + "version": "3.292.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-uri-escape/-/util-uri-escape-3.292.0.tgz", + "integrity": "sha512-hOQtUMQ4VcQ9iwKz50AoCp1XBD5gJ9nly/gJZccAM7zSA5mOO8RRKkbdonqquVHxrO0CnYgiFeCh3V35GFecUw==", + "optional": true, + "requires": { + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + } + } + }, + "@aws-sdk/util-user-agent-browser": { + "version": "3.292.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.292.0.tgz", + "integrity": "sha512-dld+lpC3QdmTQHdBWJ0WFDkXDSrJgfz03q6mQ8+7H+BC12ZhT0I0g9iuvUjolqy7QR00OxOy47Y9FVhq8EC0Gg==", + "optional": true, + "requires": { + "@aws-sdk/types": "3.292.0", + "bowser": "^2.11.0", + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + } + } + }, + "@aws-sdk/util-user-agent-node": { + "version": "3.292.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.292.0.tgz", + "integrity": "sha512-f+NfIMal5E61MDc5WGhUEoicr7b1eNNhA+GgVdSB/Hg5fYhEZvFK9RZizH5rrtsLjjgcr9nPYSR7/nDKCJLumw==", + "optional": true, + "requires": { + "@aws-sdk/node-config-provider": "3.292.0", + "@aws-sdk/types": "3.292.0", + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + } + } + }, + "@aws-sdk/util-utf8": { + "version": "3.292.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-utf8/-/util-utf8-3.292.0.tgz", + "integrity": "sha512-FPkj+Z59/DQWvoVu2wFaRncc3KVwe/pgK3MfVb0Lx+Ibey5KUx+sNpJmYcVYHUAe/Nv/JeIpOtYuC96IXOnI6w==", + "optional": true, + "requires": { + "@aws-sdk/util-buffer-from": "3.292.0", + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + } + } + }, + "@aws-sdk/util-utf8-browser": { + "version": "3.259.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-utf8-browser/-/util-utf8-browser-3.259.0.tgz", + "integrity": "sha512-UvFa/vR+e19XookZF8RzFZBrw2EUkQWxiBW0yYQAhvk3C+QVGl0H3ouca8LDBlBfQKXwmW3huo/59H8rwb1wJw==", + "optional": true, + "requires": { + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "optional": true + } + } + }, "@colors/colors": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", @@ -5778,6 +8707,15 @@ "defer-to-connect": "^2.0.0" } }, + "@types/bcrypt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/bcrypt/-/bcrypt-5.0.0.tgz", + "integrity": "sha512-agtcFKaruL8TmcvqbndlqHPSJgsolhf/qPWchFlgnW1gECTN/nKbFcoFnvKAQRFfKbh+BO6A3SWdJu9t+xF3Lw==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/body-parser": { "version": "1.19.2", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", @@ -5808,6 +8746,15 @@ "@types/node": "*" } }, + "@types/cors": { + "version": "2.8.13", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.13.tgz", + "integrity": "sha512-RG8AStHlUiV5ysZQKq97copd2UmVYw3/pRMLefISZ3S1hK104Cwm7iLQ3fTKx+lsUH2CE8FlLaYeEA2LSeqYUA==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/dicer": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/@types/dicer/-/dicer-0.2.2.tgz", @@ -5884,6 +8831,96 @@ "integrity": "sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==", "dev": true }, + "@types/mongoose-unique-validator": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@types/mongoose-unique-validator/-/mongoose-unique-validator-1.0.7.tgz", + "integrity": "sha512-wgPWN7x9mHdy8x1jcluDF7EDWFb/s8LD7skLr012C8i2Q3tUhdTlp3JxRhFbEM3TD7J1INaoTrWaSaguhuqilQ==", + "dev": true, + "requires": { + "mongoose": "^6.3.0" + }, + "dependencies": { + "bson": { + "version": "4.7.2", + "resolved": "https://registry.npmjs.org/bson/-/bson-4.7.2.tgz", + "integrity": "sha512-Ry9wCtIZ5kGqkJoi6aD8KjxFZEx78guTQDnpXWiNthsxzrxAK/i8E6pCHAIZTbaEFWcOCvbecMukfK7XUvyLpQ==", + "dev": true, + "requires": { + "buffer": "^5.6.0" + } + }, + "buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + }, + "dependencies": { + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "mongodb": { + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.14.0.tgz", + "integrity": "sha512-coGKkWXIBczZPr284tYKFLg+KbGPPLlSbdgfKAb6QqCFt5bo5VFZ50O3FFzsw4rnkqjwT6D8Qcoo9nshYKM7Mg==", + "dev": true, + "requires": { + "@aws-sdk/credential-providers": "^3.186.0", + "bson": "^4.7.0", + "mongodb-connection-string-url": "^2.5.4", + "saslprep": "^1.0.3", + "socks": "^2.7.1" + } + }, + "mongoose": { + "version": "6.10.3", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-6.10.3.tgz", + "integrity": "sha512-fZ3pIlQn7lM632r1l4qiU58lKrJ+FufKVG8TNeRXSChAeu9alCl5KoQ9bLw4jnQNYevSq9o+sqZmFDHP+EVW3g==", + "dev": true, + "requires": { + "bson": "^4.7.0", + "kareem": "2.5.1", + "mongodb": "4.14.0", + "mpath": "0.9.0", + "mquery": "4.0.3", + "ms": "2.1.3", + "sift": "16.0.1" + } + }, + "mquery": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/mquery/-/mquery-4.0.3.tgz", + "integrity": "sha512-J5heI+P08I6VJ2Ky3+33IpCdAvlYGTSUjwTPxkAr8i8EoduPMBX2OY/wa3IKZIQl7MU4SbFk8ndgSKyB/cl1zA==", + "dev": true, + "requires": { + "debug": "4.x" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + } + } + }, "@types/morgan": { "version": "1.9.4", "resolved": "https://registry.npmjs.org/@types/morgan/-/morgan-1.9.4.tgz", @@ -5952,6 +8989,12 @@ "@types/node": "*" } }, + "@types/validator": { + "version": "13.7.14", + "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.7.14.tgz", + "integrity": "sha512-J6OAed6rhN6zyqL9Of6ZMamhlsOEU/poBVvbHr/dKOYKTeuYYMlDkMv+b6UUV0o2i0tw73cgyv/97WTWaUl0/g==", + "dev": true + }, "@types/webidl-conversions": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.0.tgz", @@ -6432,6 +9475,12 @@ "unpipe": "1.0.0" } }, + "bowser": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.11.0.tgz", + "integrity": "sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==", + "optional": true + }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -7278,9 +10327,10 @@ } }, "express-rate-limit": { - "version": "5.5.1", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-5.5.1.tgz", - "integrity": "sha512-MTjE2eIbHv5DyfuFz4zLYWxpqVhEhkTiwFGuB74Q9CSou2WHO52nlE5y3Zlg6SIsiYUIPj6ifFxnkPz6O3sIUg==" + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-6.7.0.tgz", + "integrity": "sha512-vhwIdRoqcYB/72TK3tRZI+0ttS8Ytrk24GfmsxDXK9o9IhHNO5bXRiXQSExPQ4GbaE5tvIS7j1SGrxsuWs+sGA==", + "requires": {} }, "express-subdomain": { "version": "1.0.5", @@ -7357,6 +10407,15 @@ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==" }, + "fast-xml-parser": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.1.2.tgz", + "integrity": "sha512-CDYeykkle1LiA/uqQyNwYpFbyF6Axec6YapmpUP+/RHWIoR1zKjocdvNaTsxCxZzQ6v9MLXaSYm9Qq0thv0DHg==", + "optional": true, + "requires": { + "strnum": "^1.0.5" + } + }, "fastq": { "version": "1.15.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", @@ -9212,6 +12271,12 @@ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true }, + "strnum": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz", + "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==", + "optional": true + }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -9337,7 +12402,7 @@ "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true + "devOptional": true }, "tsutils": { "version": "3.21.0", diff --git a/package.json b/package.json index f97be5c..88063fd 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "dotenv": "^16.0.3", "email-validator": "^2.0.4", "express": "^4.17.1", - "express-rate-limit": "^5.3.0", + "express-rate-limit": "^6.7.0", "express-subdomain": "^1.0.5", "fs-extra": "^8.1.0", "got": "^11.8.2", @@ -55,13 +55,17 @@ }, "devDependencies": { "@hcaptcha/types": "^1.0.3", + "@types/bcrypt": "^5.0.0", + "@types/cors": "^2.8.13", "@types/dicer": "^0.2.2", "@types/express": "^4.17.17", "@types/fs-extra": "^11.0.1", + "@types/mongoose-unique-validator": "^1.0.7", "@types/morgan": "^1.9.4", "@types/node": "^18.14.4", "@types/node-rsa": "^1.1.1", "@types/nodemailer": "^6.4.7", + "@types/validator": "^13.7.14", "@typescript-eslint/eslint-plugin": "^5.54.1", "@typescript-eslint/parser": "^5.54.1", "eslint": "^8.35.0", diff --git a/src/cache.ts b/src/cache.ts index cc5a6bb..912f78c 100644 --- a/src/cache.ts +++ b/src/cache.ts @@ -28,12 +28,12 @@ export async function setCachedFile(fileName: string, value: Buffer): Promise { - let cachedFile: Buffer; + let cachedFile: Buffer = Buffer.alloc(0); if (disabledFeatures.redis) { cachedFile = memoryCache[fileName] || null; } else { - const redisValue: string = await client.get(fileName); + const redisValue: string | null = await client.get(fileName); if (redisValue) { cachedFile = Buffer.from(redisValue, encoding); } @@ -177,7 +177,7 @@ export async function getLocalCDNFile(name: string, encoding?: BufferEncoding): if (file === null) { if (await fs.pathExists(`${LOCAL_CDN_BASE}/${name}`)) { - const fileBuffer: string = await fs.readFile(`${LOCAL_CDN_BASE}/${name}`, { encoding }); + const fileBuffer: string | Buffer = await fs.readFile(`${LOCAL_CDN_BASE}/${name}`, { encoding }); file = Buffer.from(fileBuffer); await setLocalCDNFile(name, file); } diff --git a/src/config-manager.ts b/src/config-manager.ts index 8b42c75..9f98cc8 100644 --- a/src/config-manager.ts +++ b/src/config-manager.ts @@ -15,7 +15,7 @@ export const disabledFeatures: DisabledFeatures = { LOG_INFO('Loading config'); -let mongooseConnectOptions: mongoose.ConnectOptions; +let mongooseConnectOptions: mongoose.ConnectOptions = {}; if (process.env.PN_ACT_CONFIG_MONGOOSE_CONNECT_OPTIONS_PATH) { mongooseConnectOptions = fs.readJSONSync(process.env.PN_ACT_CONFIG_MONGOOSE_CONNECT_OPTIONS_PATH); @@ -23,41 +23,41 @@ if (process.env.PN_ACT_CONFIG_MONGOOSE_CONNECT_OPTIONS_PATH) { export const config: Config = { http: { - port: Number(process.env.PN_ACT_CONFIG_HTTP_PORT) + port: Number(process.env.PN_ACT_CONFIG_HTTP_PORT || '') }, mongoose: { - connection_string: process.env.PN_ACT_CONFIG_MONGO_CONNECTION_STRING, + connection_string: process.env.PN_ACT_CONFIG_MONGO_CONNECTION_STRING || '', options: mongooseConnectOptions }, redis: { client: { - url: process.env.PN_ACT_CONFIG_REDIS_URL + url: process.env.PN_ACT_CONFIG_REDIS_URL || '' } }, email: { - host: process.env.PN_ACT_CONFIG_EMAIL_HOST, - port: Number(process.env.PN_ACT_CONFIG_EMAIL_PORT), - secure: Boolean(process.env.PN_ACT_CONFIG_EMAIL_SECURE), + host: process.env.PN_ACT_CONFIG_EMAIL_HOST || '', + port: Number(process.env.PN_ACT_CONFIG_EMAIL_PORT || ''), + secure: Boolean(process.env.PN_ACT_CONFIG_EMAIL_SECURE || ''), auth: { - user: process.env.PN_ACT_CONFIG_EMAIL_USERNAME, - pass: process.env.PN_ACT_CONFIG_EMAIL_PASSWORD + user: process.env.PN_ACT_CONFIG_EMAIL_USERNAME || '', + pass: process.env.PN_ACT_CONFIG_EMAIL_PASSWORD || '' }, - from: process.env.PN_ACT_CONFIG_EMAIL_FROM + from: process.env.PN_ACT_CONFIG_EMAIL_FROM || '' }, s3: { - endpoint: process.env.PN_ACT_CONFIG_S3_ENDPOINT, - key: process.env.PN_ACT_CONFIG_S3_ACCESS_KEY, - secret: process.env.PN_ACT_CONFIG_S3_ACCESS_SECRET + endpoint: process.env.PN_ACT_CONFIG_S3_ENDPOINT || '', + key: process.env.PN_ACT_CONFIG_S3_ACCESS_KEY || '', + secret: process.env.PN_ACT_CONFIG_S3_ACCESS_SECRET || '' }, hcaptcha: { - secret: process.env.PN_ACT_CONFIG_HCAPTCHA_SECRET + secret: process.env.PN_ACT_CONFIG_HCAPTCHA_SECRET || '' }, cdn: { - subdomain: process.env.PN_ACT_CONFIG_CDN_SUBDOMAIN, - disk_path: process.env.PN_ACT_CONFIG_CDN_DISK_PATH, - base_url: process.env.PN_ACT_CONFIG_CDN_BASE_URL + subdomain: process.env.PN_ACT_CONFIG_CDN_SUBDOMAIN || '', + disk_path: process.env.PN_ACT_CONFIG_CDN_DISK_PATH || '', + base_url: process.env.PN_ACT_CONFIG_CDN_BASE_URL || '' }, - website_base: process.env.PN_ACT_CONFIG_WEBSITE_BASE + website_base: process.env.PN_ACT_CONFIG_WEBSITE_BASE || '' }; LOG_INFO('Config loaded, checking integrity'); diff --git a/src/database.ts b/src/database.ts index d5b8c8c..3406aab 100644 --- a/src/database.ts +++ b/src/database.ts @@ -42,7 +42,7 @@ export function verifyConnected(): void { } } -export async function getUserByUsername(username: string): Promise { +export async function getUserByUsername(username: string): Promise { verifyConnected(); return await PNID.findOne({ @@ -50,7 +50,7 @@ export async function getUserByUsername(username: string): Promise { +export async function getUserByPID(pid: number): Promise { verifyConnected(); return await PNID.findOne({ @@ -58,7 +58,7 @@ export async function getUserByPID(pid: number): Promise { }); } -export async function getUserByEmailAddress(email: string): Promise { +export async function getUserByEmailAddress(email: string): Promise { verifyConnected(); // TODO - Update documents to store email normalized @@ -73,7 +73,7 @@ export async function doesUserExist(username: string): Promise { return !!await getUserByUsername(username); } -export async function getUserBasic(token: string): Promise { +export async function getUserBasic(token: string): Promise { verifyConnected(); // * Wii U sends Basic auth as `username password`, where the password may not have spaces @@ -84,7 +84,7 @@ export async function getUserBasic(token: string): Promise const username: string = parts[0]; const password: string = parts[1]; - const user: HydratedPNIDDocument = await getUserByUsername(username); + const user: HydratedPNIDDocument | null = await getUserByUsername(username); if (!user) { return null; @@ -99,14 +99,14 @@ export async function getUserBasic(token: string): Promise return user; } -export async function getUserBearer(token: string): Promise { +export async function getUserBearer(token: string): Promise { verifyConnected(); try { const decryptedToken: Buffer = await decryptToken(Buffer.from(token, 'base64')); const unpackedToken: Token = unpackToken(decryptedToken); - const user: HydratedPNIDDocument = await getUserByPID(unpackedToken.pid); + const user: HydratedPNIDDocument | null = await getUserByPID(unpackedToken.pid); if (user) { const expireTime: number = Math.floor((Number(unpackedToken.expire_time) / 1000)); @@ -124,27 +124,38 @@ export async function getUserBearer(token: string): Promise { +export async function getUserProfileJSONByPID(pid: number): Promise { verifyConnected(); - const user: HydratedPNIDDocument = await getUserByPID(pid); + const user: HydratedPNIDDocument | null = await getUserByPID(pid); + + if (!user) { + return null; + } + const device: HydratedDeviceDocument = user.get('devices')[0]; // * Just grab the first device - let device_attributes: [{ + let device_attributes: { device_attribute: { name: string; value: string; created_date: string; }; - }]; + }[] = []; if (device) { - device_attributes = device.get('device_attributes').map(({name, value, created_date}) => ({ - device_attribute: { - name, - value, - created_date: created_date ? created_date : '' - } - })); + device_attributes = device.get('device_attributes').map((attribute: { name: string; value: string; created_date: string; }) => { + const name: string = attribute.name; + const value: string = attribute.value; + const created_date: string = attribute.created_date; + + return { + device_attribute: { + name, + value, + created_date: created_date ? created_date : '' + } + }; + }); } return { @@ -196,21 +207,21 @@ export async function getUserProfileJSONByPID(pid: number): Promise }; } -export async function getServer(gameServerId: string, accessMode: string): Promise { +export async function getServer(gameServerId: string, accessMode: string): Promise { return await Server.findOne({ game_server_id: gameServerId, access_mode: accessMode }); } -export async function getServerByTitleId(titleId: string, accessMode: string): Promise { +export async function getServerByTitleId(titleId: string, accessMode: string): Promise { return await Server.findOne({ title_ids: titleId, access_mode: accessMode }); } -export async function addUserConnection(pnid: HydratedPNIDDocument, data: ConnectionData, type: string): Promise { +export async function addUserConnection(pnid: HydratedPNIDDocument, data: ConnectionData, type: string): Promise { if (type === 'discord') { return await addUserConnectionDiscord(pnid, data); } @@ -239,7 +250,7 @@ export async function addUserConnectionDiscord(pnid: HydratedPNIDDocument, data: }; } -export async function removeUserConnection(pnid: HydratedPNIDDocument, type: string): Promise { +export async function removeUserConnection(pnid: HydratedPNIDDocument, type: string): Promise { // * Add more connections later? if (type === 'discord') { return await removeUserConnectionDiscord(pnid); diff --git a/src/mailer.ts b/src/mailer.ts index a9ec1cc..230d412 100644 --- a/src/mailer.ts +++ b/src/mailer.ts @@ -20,10 +20,10 @@ export async function sendMail(options: MailerOptions): Promise { let html: string = confirmation ? confirmationEmailTemplate : genericEmailTemplate; html = html.replace(/{{username}}/g, username); - html = html.replace(/{{paragraph}}/g, paragraph); - html = html.replace(/{{preview}}/g, (preview || '')); - html = html.replace(/{{confirmation-href}}/g, (confirmation?.href || '')); - html = html.replace(/{{confirmation-code}}/g, (confirmation?.code || '')); + html = html.replace(/{{paragraph}}/g, paragraph || ''); + html = html.replace(/{{preview}}/g, preview || ''); + html = html.replace(/{{confirmation-href}}/g, confirmation?.href || ''); + html = html.replace(/{{confirmation-code}}/g, confirmation?.code || ''); if (link) { const { href, text } = link; diff --git a/src/middleware/api.ts b/src/middleware/api.ts index 0f7e876..e4fa82a 100644 --- a/src/middleware/api.ts +++ b/src/middleware/api.ts @@ -3,14 +3,14 @@ import { getUserBearer } from '@/database'; import { HydratedPNIDDocument } from '@/types/mongoose/pnid'; async function APIMiddleware(request: express.Request, _response: express.Response, next: express.NextFunction): Promise { - const authHeader: string = request.headers.authorization; + const authHeader: string | undefined = request.headers.authorization; if (!authHeader || !(authHeader.startsWith('Bearer'))) { return next(); } const token: string = authHeader.split(' ')[1]; - const user: HydratedPNIDDocument = await getUserBearer(token); + const user: HydratedPNIDDocument | null = await getUserBearer(token); request.pnid = user; diff --git a/src/middleware/nasc.ts b/src/middleware/nasc.ts index 5f1e5aa..9308bb9 100644 --- a/src/middleware/nasc.ts +++ b/src/middleware/nasc.ts @@ -35,9 +35,9 @@ async function NASCMiddleware(request: express.Request, response: express.Respon const macAddressHash: string = crypto.createHash('sha256').update(macAddress).digest('base64'); const fcdcertHash: string = crypto.createHash('sha256').update(fcdcert).digest('base64'); - let pid: number; - let pidHmac: string; - let password: string; + let pid: number = 0; // * Real PIDs are always positive and non-zero + let pidHmac: string = ''; + let password: string = ''; if (requestParams.userid) { pid = Number(nintendoBase64Decode(requestParams.userid).toString()); @@ -68,7 +68,7 @@ async function NASCMiddleware(request: express.Request, response: express.Respon return; } - let model: string; + let model: string = ''; switch (serialNumber[0]) { case 'C': model = 'ctr'; @@ -95,7 +95,7 @@ async function NASCMiddleware(request: express.Request, response: express.Respon return; } - let device: HydratedDeviceDocument = await Device.findOne({ + let device: HydratedDeviceDocument | null = await Device.findOne({ model, serial: serialNumber, environment, @@ -174,7 +174,7 @@ async function NASCMiddleware(request: express.Request, response: express.Respon } } - const nexUser: HydratedNEXAccountDocument = await NEXAccount.findOne({ pid }); + const nexUser: HydratedNEXAccountDocument | null = await NEXAccount.findOne({ pid }); if (!nexUser || nexUser.get('access_level') < 0) { response.status(200).send(nascError('102')); diff --git a/src/middleware/pnid.ts b/src/middleware/pnid.ts index 2d52342..96f58cd 100644 --- a/src/middleware/pnid.ts +++ b/src/middleware/pnid.ts @@ -4,7 +4,7 @@ import { getUserBasic, getUserBearer } from '@/database'; import { HydratedPNIDDocument } from '@/types/mongoose/pnid'; async function PNIDMiddleware(request: express.Request, response: express.Response, next: express.NextFunction): Promise { - const authHeader: string = request.headers.authorization; + const authHeader: string | undefined = request.headers.authorization; if (!authHeader || !(authHeader.startsWith('Bearer') || authHeader.startsWith('Basic'))) { return next(); @@ -13,7 +13,7 @@ async function PNIDMiddleware(request: express.Request, response: express.Respon const parts: string[] = authHeader.split(' '); const type: string = parts[0]; let token: string = parts[1]; - let user: HydratedPNIDDocument; + let user: HydratedPNIDDocument | null; if (request.isCemu) { token = Buffer.from(token, 'hex').toString('base64'); diff --git a/src/middleware/xml-parser.ts b/src/middleware/xml-parser.ts index 37d09b2..cf1335e 100644 --- a/src/middleware/xml-parser.ts +++ b/src/middleware/xml-parser.ts @@ -4,8 +4,8 @@ import { document as xmlParser } from 'xmlbuilder2'; function XMLMiddleware(request: express.Request, response: express.Response, next: express.NextFunction): void { if (request.method == 'POST' || request.method == 'PUT') { - const contentType: string = request.headers['content-type']; - const contentLength: string = request.headers['content-length']; + const contentType: string | undefined = request.headers['content-type']; + const contentLength: string | undefined = request.headers['content-length']; let body: string = ''; if ( diff --git a/src/models/pnid.ts b/src/models/pnid.ts index 601b1fe..8849822 100644 --- a/src/models/pnid.ts +++ b/src/models/pnid.ts @@ -121,7 +121,7 @@ PNIDSchema.method('generatePID', async function generatePID(): Promise { const pid: number = Math.floor(Math.random() * (max - min + 1) + min); - const inuse: HydratedPNIDDocument = await PNID.findOne({ + const inuse: HydratedPNIDDocument | null = await PNID.findOne({ pid }); @@ -143,7 +143,7 @@ PNIDSchema.method('generateEmailValidationCode', async function generateEmailVal PNIDSchema.method('generateEmailValidationToken', async function generateEmailValidationToken(): Promise { const token: string = crypto.randomBytes(32).toString('hex'); - const inuse: HydratedPNIDDocument = await PNID.findOne({ + const inuse: HydratedPNIDDocument | null = await PNID.findOne({ 'identification.email_token': token }); diff --git a/src/services/api/routes/v1/connections.ts b/src/services/api/routes/v1/connections.ts index 6ddfd31..a828c2d 100644 --- a/src/services/api/routes/v1/connections.ts +++ b/src/services/api/routes/v1/connections.ts @@ -17,7 +17,7 @@ const VALID_CONNECTION_TYPES: string[] = [ */ router.post('/add/:type', async (request: express.Request, response: express.Response) => { const data: ConnectionData = request.body?.data; - const pnid: HydratedPNIDDocument = request.pnid; + const pnid: HydratedPNIDDocument | null = request.pnid; const type: string = request.params.type; if (!pnid) { @@ -44,9 +44,17 @@ router.post('/add/:type', async (request: express.Request, response: express.Res }); } - const result: ConnectionResponse = await addUserConnection(pnid, data, type); + let result: ConnectionResponse | undefined = await addUserConnection(pnid, data, type); - response.status(result.status).json(result); + if (!result) { + result = { + app: 'api', + status: 500, + error: 'Unknown server error' + }; + } + + response.status(result.status || 500).json(result); }); /** @@ -55,7 +63,7 @@ router.post('/add/:type', async (request: express.Request, response: express.Res * Description: Removes an account connection from the users PNID */ router.delete('/remove/:type', async (request: express.Request, response: express.Response) => { - const pnid: HydratedPNIDDocument = request.pnid; + const pnid: HydratedPNIDDocument | null = request.pnid; const type: string = request.params.type; if (!pnid) { @@ -74,7 +82,15 @@ router.delete('/remove/:type', async (request: express.Request, response: expres }); } - const result: ConnectionResponse = await removeUserConnection(pnid, type); + let result: ConnectionResponse | undefined = await removeUserConnection(pnid, type); + + if (!result) { + result = { + app: 'api', + status: 500, + error: 'Unknown server error' + }; + } response.status(result.status).json(result); }); diff --git a/src/services/api/routes/v1/email.ts b/src/services/api/routes/v1/email.ts index 2b12d60..a7c6eb7 100644 --- a/src/services/api/routes/v1/email.ts +++ b/src/services/api/routes/v1/email.ts @@ -7,7 +7,7 @@ import { HydratedPNIDDocument } from '@/types/mongoose/pnid'; const router: express.Router = express.Router(); router.get('/verify', async (request: express.Request, response: express.Response) => { - let token: string = request.query.token as string; + const token: string = request.query.token as string; if (!token || token.trim() == '') { return response.status(400).json({ @@ -17,7 +17,7 @@ router.get('/verify', async (request: express.Request, response: express.Respons }); } - const pnid: HydratedPNIDDocument = await PNID.findOne({ + const pnid: HydratedPNIDDocument | null = await PNID.findOne({ 'identification.email_token': token }); diff --git a/src/services/api/routes/v1/forgotPassword.ts b/src/services/api/routes/v1/forgotPassword.ts index 74e7cdc..bfc6d61 100644 --- a/src/services/api/routes/v1/forgotPassword.ts +++ b/src/services/api/routes/v1/forgotPassword.ts @@ -17,7 +17,7 @@ router.post('/', async (request: express.Request, response: express.Response) => }); } - let pnid: HydratedPNIDDocument; + let pnid: HydratedPNIDDocument | null; if (validator.isEmail(input)) { pnid = await getUserByEmailAddress(input); diff --git a/src/services/api/routes/v1/login.ts b/src/services/api/routes/v1/login.ts index c6bfaa6..692f13d 100644 --- a/src/services/api/routes/v1/login.ts +++ b/src/services/api/routes/v1/login.ts @@ -54,7 +54,7 @@ router.post('/', async (request: express.Request, response: express.Response) => }); } - let pnid: HydratedPNIDDocument; + let pnid: HydratedPNIDDocument | null; if (grantType === 'password') { pnid = await getUserByUsername(username); @@ -125,8 +125,10 @@ router.post('/', async (request: express.Request, response: express.Response) => expire_time: BigInt(Date.now() + (3600 * 1000)) }; - const accessToken: string = await generateToken(cryptoOptions, accessTokenOptions); - const newRefreshToken: string = await generateToken(cryptoOptions, refreshTokenOptions); + const accessToken: string | null = await generateToken(cryptoOptions, accessTokenOptions); + const newRefreshToken: string | null = await generateToken(cryptoOptions, refreshTokenOptions); + + // TODO - Handle null tokens response.json({ access_token: accessToken, diff --git a/src/services/api/routes/v1/register.ts b/src/services/api/routes/v1/register.ts index 3b6d315..b1e37b1 100644 --- a/src/services/api/routes/v1/register.ts +++ b/src/services/api/routes/v1/register.ts @@ -363,8 +363,10 @@ router.post('/', async (request: express.Request, response: express.Response) => expire_time: BigInt(Date.now() + (3600 * 1000)) }; - const accessToken: string = await generateToken(cryptoOptions, accessTokenOptions); - const refreshToken: string = await generateToken(cryptoOptions, refreshTokenOptions); + const accessToken: string | null = await generateToken(cryptoOptions, accessTokenOptions); + const refreshToken: string | null = await generateToken(cryptoOptions, refreshTokenOptions); + + // TODO - Handle null tokens response.json({ access_token: accessToken, diff --git a/src/services/api/routes/v1/resetPassword.ts b/src/services/api/routes/v1/resetPassword.ts index fe7f3c8..95266f7 100644 --- a/src/services/api/routes/v1/resetPassword.ts +++ b/src/services/api/routes/v1/resetPassword.ts @@ -47,7 +47,7 @@ router.post('/', async (request: express.Request, response: express.Response) => }); } - const pnid: HydratedPNIDDocument = await PNID.findOne({ pid: unpackedToken.pid }); + const pnid: HydratedPNIDDocument | null = await PNID.findOne({ pid: unpackedToken.pid }); if (!pnid) { return response.status(400).json({ diff --git a/src/services/api/routes/v1/user.ts b/src/services/api/routes/v1/user.ts index 15fb167..28eac4e 100644 --- a/src/services/api/routes/v1/user.ts +++ b/src/services/api/routes/v1/user.ts @@ -22,7 +22,7 @@ const userSchema: joi.ObjectSchema = joi.object({ * Description: Gets PNID details about the current user */ router.get('/', async (request: express.Request, response: express.Response) => { - const pnid: HydratedPNIDDocument = request.pnid; + const pnid: HydratedPNIDDocument | null = request.pnid; if (!pnid) { return response.status(400).json({ @@ -70,7 +70,7 @@ router.get('/', async (request: express.Request, response: express.Response) => * Description: Updates PNID certain details about the current user */ router.post('/', async (request: express.Request, response: express.Response) => { - const pnid: HydratedPNIDDocument = request.pnid; + const pnid: HydratedPNIDDocument | null = request.pnid; const updateUserRequest: UpdateUserRequest = request.body; if (!pnid) { diff --git a/src/services/datastore/routes/upload.ts b/src/services/datastore/routes/upload.ts index c8c9ddf..88130b1 100644 --- a/src/services/datastore/routes/upload.ts +++ b/src/services/datastore/routes/upload.ts @@ -8,11 +8,22 @@ const router: express.Router = express.Router(); const signatureSecret: Buffer = fs.readFileSync(`${__dirname}/../../../../certs/nex/datastore/secret.key`); -function multipartParser(request: express.Request, response: express.Response, next: express.NextFunction) { +function multipartParser(request: express.Request, response: express.Response, next: express.NextFunction): void { const RE_BOUNDARY: RegExp = /^multipart\/.+?(?:; boundary=(?:(?:"(.+)")|(?:([^\s]+))))$/i; const RE_FILE_NAME: RegExp = /name="(.*)"/; - const boundary: RegExpExecArray = RE_BOUNDARY.exec(request.header('content-type')); + const contentType: string | undefined = request.header('content-type'); + + if (!contentType) { + return next(); + } + + const boundary: RegExpExecArray | null = RE_BOUNDARY.exec(contentType); + + if (!boundary) { + return next(); + } + const dicer: Dicer = new Dicer({ boundary: boundary[1] || boundary[2] }); const files: { [key: string]: Buffer } = {}; @@ -21,6 +32,7 @@ function multipartParser(request: express.Request, response: express.Response, n let fileName: string = ''; part.on('header', header => { + // TODO - strict mode yells here fileName = RE_FILE_NAME.exec(header['content-disposition'][0])[1]; }); @@ -46,6 +58,10 @@ function multipartParser(request: express.Request, response: express.Response, n } router.post('/upload', multipartParser, async (request: express.Request, response: express.Response) => { + if (!request.files) { + return response.sendStatus(500); + } + const bucket: string = request.files.bucket.toString(); const key: string = request.files.key.toString(); const file: Buffer = request.files.file; diff --git a/src/services/nasc/routes/ac.ts b/src/services/nasc/routes/ac.ts index 001de88..a46fc04 100644 --- a/src/services/nasc/routes/ac.ts +++ b/src/services/nasc/routes/ac.ts @@ -18,7 +18,7 @@ const router: express.Router = express.Router(); router.post('/', async (request: express.Request, response: express.Response) => { const requestParams: NASCRequestParams = request.body; const action: string = nintendoBase64Decode(requestParams.action).toString(); - let responseData: URLSearchParams; + let responseData: URLSearchParams = nascError('null'); switch (action) { case 'LOGIN': @@ -35,7 +35,12 @@ router.post('/', async (request: express.Request, response: express.Response) => async function processLoginRequest(request: express.Request): Promise { const requestParams: NASCRequestParams = request.body; const titleID: string = nintendoBase64Decode(requestParams.titleid).toString(); - const nexUser: HydratedNEXAccountDocument = request.nexUser; + const nexUser: HydratedNEXAccountDocument | null = request.nexUser; + + if (!nexUser) { + // TODO - Research this error more + return nascError('null'); + } // TODO: REMOVE AFTER PUBLIC LAUNCH // LET EVERYONE IN THE `test` FRIENDS SERVER @@ -45,7 +50,7 @@ async function processLoginRequest(request: express.Request): Promise0008Not Found'); + } + + const person: PNIDProfile | null = await getUserProfileJSONByPID(pnid.get('pid')); + + if (!person) { + // TODO - Research this error more + response.status(404); + return response.send('0008Not Found'); + } response.send(xmlbuilder.create({ person @@ -237,9 +249,21 @@ router.post('/@me/devices', async (request: express.Request, response: express.R // TODO - CHANGE THIS. WE NEED TO SAVE CONSOLE DETAILS !!! - const pnid: HydratedPNIDDocument = request.pnid; + const pnid: HydratedPNIDDocument | null = request.pnid; - const person: PNIDProfile = await getUserProfileJSONByPID(pnid.pid); + if (!pnid) { + // TODO - Research this error more + response.status(404); + return response.send('0008Not Found'); + } + + const person: PNIDProfile | null = await getUserProfileJSONByPID(pnid.get('pid')); + + if (!person) { + // TODO - Research this error more + response.status(404); + return response.send('0008Not Found'); + } response.send(xmlbuilder.create({ person @@ -256,7 +280,7 @@ router.get('/@me/devices', async (request: express.Request, response: express.Re response.set('Server', 'Nintendo 3DS (http)'); response.set('X-Nintendo-Date', new Date().getTime().toString()); - const pnid: HydratedPNIDDocument = request.pnid; + const pnid: HydratedPNIDDocument | null = request.pnid; const deviceId: string = request.headers['x-nintendo-device-id'] as string; const acceptLanguage: string = request.headers['accept-language'] as string; const platformId: string = request.headers['x-nintendo-platform-id'] as string; @@ -264,6 +288,12 @@ router.get('/@me/devices', async (request: express.Request, response: express.Re const serialNumber: string = request.headers['x-nintendo-serial-number'] as string; const systemVersion: string = request.headers['x-nintendo-system-version'] as string; + if (!pnid) { + // TODO - Research this error more + response.status(404); + return response.send('0008Not Found'); + } + response.send(xmlbuilder.create({ devices: [ { @@ -295,9 +325,21 @@ router.get('/@me/devices/owner', async (request: express.Request, response: expr response.set('Server', 'Nintendo 3DS (http)'); response.set('X-Nintendo-Date', moment().add(5, 'h').toString()); - const pnid: HydratedPNIDDocument = request.pnid; + const pnid: HydratedPNIDDocument | null = request.pnid; - const person: PNIDProfile = await getUserProfileJSONByPID(pnid.get('pid')); + if (!pnid) { + // TODO - Research this error more + response.status(404); + return response.send('0008Not Found'); + } + + const person: PNIDProfile | null = await getUserProfileJSONByPID(pnid.get('pid')); + + if (!person) { + // TODO - Research this error more + response.status(404); + return response.send('0008Not Found'); + } response.send(xmlbuilder.create({ person @@ -326,14 +368,22 @@ router.get('/@me/devices/status', async (_request: express.Request, response: ex * Description: Updates a users Mii */ router.put('/@me/miis/@primary', async (request: express.Request, response: express.Response) => { - const pnid: HydratedPNIDDocument = request.pnid; + const pnid: HydratedPNIDDocument | null = request.pnid; + + if (!pnid) { + // TODO - Research this error more + response.status(404); + return response.send('0008Not Found'); + } // TODO - Make this more strictly typed? const mii: Map = request.body.get('mii'); - const name: string = mii.get('name'); - const primary: string = mii.get('primary'); - const data: string = mii.get('data'); + // TODO - Better checks + + const name: string | undefined = mii.get('name') || ''; + const primary: string | undefined = mii.get('primary') || ''; + const data: string | undefined = mii.get('data') || ''; await pnid.updateMii({ name, primary, data }); @@ -349,7 +399,7 @@ router.put('/@me/devices/@current/inactivate', async (request: express.Request, response.set('Server', 'Nintendo 3DS (http)'); response.set('X-Nintendo-Date', new Date().getTime().toString()); - const pnid: HydratedPNIDDocument = request.pnid; + const pnid: HydratedPNIDDocument | null = request.pnid; if (!pnid) { response.status(400); @@ -375,7 +425,7 @@ router.put('/@me/devices/@current/inactivate', async (request: express.Request, * Description: Deletes a NNID */ router.put('/@me/deletion', async (request: express.Request, response: express.Response) => { - const pnid: HydratedPNIDDocument = request.pnid; + const pnid: HydratedPNIDDocument | null = request.pnid; if (!pnid) { response.status(400); @@ -400,7 +450,7 @@ router.put('/@me/deletion', async (request: express.Request, response: express.R * Description: Updates a PNIDs account details */ router.put('/@me', async (request: express.Request, response: express.Response) => { - const pnid: HydratedPNIDDocument = request.pnid; + const pnid: HydratedPNIDDocument | null = request.pnid; const person: Person = request.body.get('person'); if (!pnid) { @@ -454,7 +504,7 @@ router.put('/@me', async (request: express.Request, response: express.Response) * Description: Gets a list (why?) of PNID emails */ router.get('/@me/emails', async (request: express.Request, response: express.Response) => { - const pnid: HydratedPNIDDocument = request.pnid; + const pnid: HydratedPNIDDocument | null = request.pnid; if (!pnid) { response.status(400); @@ -495,12 +545,12 @@ router.get('/@me/emails', async (request: express.Request, response: express.Res * Description: Updates a users email address */ router.put('/@me/emails/@primary', async (request: express.Request, response: express.Response) => { - const pnid: HydratedPNIDDocument = request.pnid; + const pnid: HydratedPNIDDocument | null = request.pnid; // TODO - Make this more strictly typed? const email: Map = request.body.get('email'); - if (!pnid) { + if (!pnid || !email) { response.status(400); return response.end(xmlbuilder.create({ @@ -514,7 +564,8 @@ router.put('/@me/emails/@primary', async (request: express.Request, response: ex }).end()); } - pnid.set('email.address', email.get('address').toLowerCase()); + // TODO - Better email check + pnid.set('email.address', (email.get('address') || '').toLowerCase()); pnid.set('email.reachable', false); pnid.set('email.validated', false); pnid.set('email.validated_date', ''); diff --git a/src/services/nnid/routes/provider.ts b/src/services/nnid/routes/provider.ts index 2f8dd70..e2cfa4d 100644 --- a/src/services/nnid/routes/provider.ts +++ b/src/services/nnid/routes/provider.ts @@ -19,11 +19,25 @@ const router: express.Router = express.Router(); * Description: Gets a service token */ router.get('/service_token/@me', async (request: express.Request, response: express.Response) => { - const pnid: HydratedPNIDDocument = request.pnid; + const pnid: HydratedPNIDDocument | null = request.pnid; + + if (!pnid) { + response.status(400); + + return response.end(xmlbuilder.create({ + errors: { + error: { + cause: 'access_token', + code: '0002', + message: 'Invalid access token' + } + } + }).end()); + } const titleId: string = request.headers['x-nintendo-title-id'] as string; const serverAccessLevel: string = pnid.get('server_access_level'); - const server: HydratedServerDocument = await getServerByTitleId(titleId, serverAccessLevel); + const server: HydratedServerDocument | null = await getServerByTitleId(titleId, serverAccessLevel); if (!server) { return response.send(xmlbuilder.create({ @@ -70,10 +84,12 @@ router.get('/service_token/@me', async (request: express.Request, response: expr expire_time: BigInt(Date.now() + (3600 * 1000)) }; - let serviceToken: string = await generateToken(cryptoOptions, tokenOptions); + let serviceToken: string | null = await generateToken(cryptoOptions, tokenOptions); + + // TODO - Handle null tokens if (request.isCemu) { - serviceToken = Buffer.from(serviceToken, 'base64').toString('hex'); + serviceToken = Buffer.from(serviceToken || '', 'base64').toString('hex'); } response.send(xmlbuilder.create({ @@ -89,7 +105,22 @@ router.get('/service_token/@me', async (request: express.Request, response: expr * Description: Gets a NEX server address and token */ router.get('/nex_token/@me', async (request: express.Request, response: express.Response) => { - const pnid: HydratedPNIDDocument = request.pnid; + const pnid: HydratedPNIDDocument | null = request.pnid; + + if (!pnid) { + response.status(400); + + return response.end(xmlbuilder.create({ + errors: { + error: { + cause: 'access_token', + code: '0002', + message: 'Invalid access token' + } + } + }).end()); + } + const gameServerID: string = request.query.game_server_id as string; if (!gameServerID) { @@ -104,7 +135,7 @@ router.get('/nex_token/@me', async (request: express.Request, response: express. } const serverAccessLevel: string = pnid.get('server_access_level'); - const server: HydratedServerDocument = await getServer(gameServerID, serverAccessLevel); + const server: HydratedServerDocument | null = await getServer(gameServerID, serverAccessLevel); if (!server) { return response.send(xmlbuilder.create({ @@ -154,7 +185,7 @@ router.get('/nex_token/@me', async (request: express.Request, response: express. expire_time: BigInt(Date.now() + (3600 * 1000)) }; - const nexUser: HydratedNEXAccountDocument = await NEXAccount.findOne({ + const nexUser: HydratedNEXAccountDocument | null = await NEXAccount.findOne({ owning_pid: pnid.get('pid') }); @@ -163,10 +194,12 @@ router.get('/nex_token/@me', async (request: express.Request, response: express. return response.send('0008Not Found'); } - let nexToken: string = await generateToken(cryptoOptions, tokenOptions); + let nexToken: string | null = await generateToken(cryptoOptions, tokenOptions); + + // TODO = Handle null tokens if (request.isCemu) { - nexToken = Buffer.from(nexToken, 'base64').toString('hex'); + nexToken = Buffer.from(nexToken || '', 'base64').toString('hex'); } response.send(xmlbuilder.create({ diff --git a/src/services/nnid/routes/support.ts b/src/services/nnid/routes/support.ts index 6ecb0c9..5e4aab0 100644 --- a/src/services/nnid/routes/support.ts +++ b/src/services/nnid/routes/support.ts @@ -30,7 +30,7 @@ router.post('/validate/email', async (request: express.Request, response: expres const domain: string = email.split('@')[1]; - dns.resolveMx(domain, (error: NodeJS.ErrnoException) => { + dns.resolveMx(domain, (error: NodeJS.ErrnoException | null) => { if (error) { return response.send(xmlbuilder.create({ errors: { @@ -56,7 +56,7 @@ router.put('/email_confirmation/:pid/:code', async (request: express.Request, re const code: string = request.params.code; const pid: number = Number(request.params.pid); - const pnid: HydratedPNIDDocument = await getUserByPID(pid); + const pnid: HydratedPNIDDocument | null = await getUserByPID(pid); if (!pnid) { return response.status(400).send(xmlbuilder.create({ @@ -101,7 +101,7 @@ router.put('/email_confirmation/:pid/:code', async (request: express.Request, re router.get('/resend_confirmation', async (request: express.Request, response: express.Response) => { const pid: number = Number(request.headers['x-nintendo-pid']); - const pnid: HydratedPNIDDocument = await getUserByPID(pid); + const pnid: HydratedPNIDDocument | null = await getUserByPID(pid); if (!pnid) { // TODO - Unsure if this is the right error @@ -129,7 +129,7 @@ router.get('/resend_confirmation', async (request: express.Request, response: ex router.get('/forgotten_password/:pid', async (request: express.Request, response: express.Response) => { const pid: number = Number(request.params.pid); - const pnid: HydratedPNIDDocument = await getUserByPID(pid); + const pnid: HydratedPNIDDocument | null = await getUserByPID(pid); if (!pnid) { // TODO - Better errors diff --git a/src/types/express.d.ts b/src/types/express.d.ts index 26882cb..0e614bc 100644 --- a/src/types/express.d.ts +++ b/src/types/express.d.ts @@ -5,8 +5,8 @@ import { HydratedNEXAccountDocument } from '@/types/mongoose/nex-account'; declare global { namespace Express { interface Request { - pnid?: HydratedPNIDDocument; - nexUser?: HydratedNEXAccountDocument; + pnid: HydratedPNIDDocument | null; + nexUser: HydratedNEXAccountDocument | null; isCemu?: boolean; files?: Record; certificate?: NintendoCertificate; diff --git a/src/types/services/nnid/pnid-profile.ts b/src/types/services/nnid/pnid-profile.ts index ac95915..c0fb21a 100644 --- a/src/types/services/nnid/pnid-profile.ts +++ b/src/types/services/nnid/pnid-profile.ts @@ -6,13 +6,13 @@ export interface PNIDProfile { birth_date: string; country: string; create_date: string; - device_attributes: [{ + device_attributes: { device_attribute: { name: string; value: string; created_date: string; }; - }]; + }[]; gender: string; language: string; updated: string; diff --git a/src/util.ts b/src/util.ts index 5269666..5a79d1f 100644 --- a/src/util.ts +++ b/src/util.ts @@ -47,7 +47,7 @@ export function nintendoBase64Encode(decoded: string | Buffer): string { return encoded.replaceAll('+', '.').replaceAll('/', '-').replaceAll('=', '*'); } -export async function generateToken(cryptoOptions: CryptoOptions | null, tokenOptions: TokenOptions): Promise { +export async function generateToken(cryptoOptions: CryptoOptions | null, tokenOptions: TokenOptions): Promise { // Access and refresh tokens use a different format since they must be much smaller // They take no extra crypto options if (!cryptoOptions) { @@ -67,6 +67,8 @@ export async function generateToken(cryptoOptions: CryptoOptions | null, tokenOp encryptedBody = Buffer.concat([encryptedBody, cipher.final()]); return encryptedBody.toString('base64'); + } else if (!tokenOptions.access_level || !tokenOptions.title_id) { + return null; } const publicKey: NodeRSA = new NodeRSA(cryptoOptions.public_key, 'pkcs8-public-pem', { @@ -191,8 +193,9 @@ export async function decryptToken(token: Buffer): Promise { const calculatedSignature: Buffer = hmac.digest(); if (!signature.equals(calculatedSignature)) { + // TODO - FIX THIS. ONLY DONE SO STRICT MODE DOESN'T YELL console.log('Token signature did not match'); - return null; + return Buffer.alloc(0); } return decryptedBody; @@ -300,7 +303,9 @@ export async function sendForgotPasswordEmail(pnid: mongoose.HydratedDocument Date: Sat, 18 Mar 2023 10:12:37 -0400 Subject: [PATCH 042/219] Stubbed missing types from 3rd party modules --- package-lock.json | 721 ++++++++++++----------- package.json | 1 - src/models/pnid.ts | 12 +- src/types/express-subdomain.d.ts | 3 + src/types/image-pixels.d.ts | 3 + src/types/mii-js.d.ts | 98 +++ src/types/mongoose-unique-validator.d.ts | 1 + src/types/tga.d.ts | 3 + 8 files changed, 494 insertions(+), 348 deletions(-) create mode 100644 src/types/express-subdomain.d.ts create mode 100644 src/types/image-pixels.d.ts create mode 100644 src/types/mii-js.d.ts create mode 100644 src/types/mongoose-unique-validator.d.ts create mode 100644 src/types/tga.d.ts diff --git a/package-lock.json b/package-lock.json index 78ae38f..dc8a274 100644 --- a/package-lock.json +++ b/package-lock.json @@ -44,7 +44,6 @@ "@types/dicer": "^0.2.2", "@types/express": "^4.17.17", "@types/fs-extra": "^11.0.1", - "@types/mongoose-unique-validator": "^1.0.7", "@types/morgan": "^1.9.4", "@types/node": "^18.14.4", "@types/node-rsa": "^1.1.1", @@ -63,6 +62,7 @@ "resolved": "https://registry.npmjs.org/@aws-crypto/ie11-detection/-/ie11-detection-3.0.0.tgz", "integrity": "sha512-341lBBkiY1DfDNKai/wXM3aujNBkXR7tq1URPQDL9wi3AUbI80NR74uF1TXHMm7po1AcnFk8iu2S2IeU/+/A+Q==", "optional": true, + "peer": true, "dependencies": { "tslib": "^1.11.1" } @@ -72,6 +72,7 @@ "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-3.0.0.tgz", "integrity": "sha512-8VLmW2B+gjFbU5uMeqtQM6Nj0/F1bro80xQXCW6CQBWgosFWXTx77aeOF5CAIAmbOK64SdMBJdNr6J41yP5mvQ==", "optional": true, + "peer": true, "dependencies": { "@aws-crypto/ie11-detection": "^3.0.0", "@aws-crypto/sha256-js": "^3.0.0", @@ -88,6 +89,7 @@ "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-3.0.0.tgz", "integrity": "sha512-PnNN7os0+yd1XvXAy23CFOmTbMaDxgxXtTKHybrJ39Y8kGzBATgBFibWJKH6BhytLI/Zyszs87xCOBNyBig6vQ==", "optional": true, + "peer": true, "dependencies": { "@aws-crypto/util": "^3.0.0", "@aws-sdk/types": "^3.222.0", @@ -99,6 +101,7 @@ "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-3.0.0.tgz", "integrity": "sha512-06hBdMwUAb2WFTuGG73LSC0wfPu93xWwo5vL2et9eymgmu3Id5vFAHBbajVWiGhPO37qcsdCap/FqXvJGJWPIg==", "optional": true, + "peer": true, "dependencies": { "tslib": "^1.11.1" } @@ -108,6 +111,7 @@ "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-3.0.0.tgz", "integrity": "sha512-2OJlpeJpCR48CC8r+uKVChzs9Iungj9wkZrl8Z041DWEWvyIHILYKCPNzJghKsivj+S3mLo6BVc7mBNzdxA46w==", "optional": true, + "peer": true, "dependencies": { "@aws-sdk/types": "^3.222.0", "@aws-sdk/util-utf8-browser": "^3.0.0", @@ -119,6 +123,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/abort-controller/-/abort-controller-3.292.0.tgz", "integrity": "sha512-lf+OPptL01kvryIJy7+dvFux5KbJ6OTwLPPEekVKZ2AfEvwcVtOZWFUhyw3PJCBTVncjKB1Kjl3V/eTS3YuPXQ==", "optional": true, + "peer": true, "dependencies": { "@aws-sdk/types": "3.292.0", "tslib": "^2.3.1" @@ -131,13 +136,15 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true }, "node_modules/@aws-sdk/client-cognito-identity": { "version": "3.294.0", "resolved": "https://registry.npmjs.org/@aws-sdk/client-cognito-identity/-/client-cognito-identity-3.294.0.tgz", "integrity": "sha512-QMk/QratNvAvmnJ77pu9KDjpfUf/5LOJplHcLKcY962ewJGBtFFY4XZVnmUgQMSG0phRODtex7pyH//FueGKLQ==", "optional": true, + "peer": true, "dependencies": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", @@ -183,13 +190,15 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true }, "node_modules/@aws-sdk/client-sso": { "version": "3.294.0", "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.294.0.tgz", "integrity": "sha512-+FuxQTi5WvnaXM5JbNLkBIzQ3An4gA0ox61N1u+3xled+nywKb1yQ7WmRpyMG5bLbkmnj3aqoo5/uskFc4c4EA==", "optional": true, + "peer": true, "dependencies": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", @@ -233,6 +242,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.294.0.tgz", "integrity": "sha512-/ZfDud76MdSPJ/TxjV2xLE30XbBQDZwKQ32axwoK1eziPvrAIUBYVgpBwj+m0quhoiQhBKkg3aFl6j39AF2thw==", "optional": true, + "peer": true, "dependencies": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", @@ -275,19 +285,22 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true }, "node_modules/@aws-sdk/client-sso/node_modules/tslib": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true }, "node_modules/@aws-sdk/client-sts": { "version": "3.294.0", "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.294.0.tgz", "integrity": "sha512-AefqwhFjTDzelZuSYhriJbiI+GQwf2yKiKAnCt0gRj6rswewStM63Gtlhfb01sFPp+ZiqPcyQ47LqUaHp1mz/g==", "optional": true, + "peer": true, "dependencies": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", @@ -334,13 +347,15 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true }, "node_modules/@aws-sdk/config-resolver": { "version": "3.292.0", "resolved": "https://registry.npmjs.org/@aws-sdk/config-resolver/-/config-resolver-3.292.0.tgz", "integrity": "sha512-cB3twnNR7vYvlt2jvw8VlA1+iv/tVzl+/S39MKqw2tepU+AbJAM0EHwb/dkf1OKSmlrnANXhshx80MHF9zL4mA==", "optional": true, + "peer": true, "dependencies": { "@aws-sdk/signature-v4": "3.292.0", "@aws-sdk/types": "3.292.0", @@ -356,13 +371,15 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true }, "node_modules/@aws-sdk/credential-provider-cognito-identity": { "version": "3.294.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-cognito-identity/-/credential-provider-cognito-identity-3.294.0.tgz", "integrity": "sha512-YSPqHEbLC0dnbFF5LdlMH0B50sMSN/CyG/sHkPYUwL/hkbUk9URnVW7ZJlt6lRftT7X4C7muzLqUP8sJAaiJEA==", "optional": true, + "peer": true, "dependencies": { "@aws-sdk/client-cognito-identity": "3.294.0", "@aws-sdk/property-provider": "3.292.0", @@ -377,13 +394,15 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true }, "node_modules/@aws-sdk/credential-provider-env": { "version": "3.292.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.292.0.tgz", "integrity": "sha512-YbafSG0ZEKE2969CJWVtUhh3hfOeLPecFVoXOtegCyAJgY5Ghtu4TsVhL4DgiGAgOC30ojAmUVQEXzd7xJF5xA==", "optional": true, + "peer": true, "dependencies": { "@aws-sdk/property-provider": "3.292.0", "@aws-sdk/types": "3.292.0", @@ -397,13 +416,15 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true }, "node_modules/@aws-sdk/credential-provider-imds": { "version": "3.292.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-imds/-/credential-provider-imds-3.292.0.tgz", "integrity": "sha512-W/peOgDSRYulgzFpUhvgi1pCm6piBz6xrVN17N4QOy+3NHBXRVMVzYk6ct2qpLPgJUSEZkcpP+Gds+bBm8ed1A==", "optional": true, + "peer": true, "dependencies": { "@aws-sdk/node-config-provider": "3.292.0", "@aws-sdk/property-provider": "3.292.0", @@ -419,13 +440,15 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true }, "node_modules/@aws-sdk/credential-provider-ini": { "version": "3.294.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.294.0.tgz", "integrity": "sha512-pdTPbaAb5bWA+DnuKoL2TpXeNDp6Ejpv/OYt+bw2gdzl9w5r/ZCtUTTbW+Vvejr4WL5s3c1bY96kwdqCn7iLqA==", "optional": true, + "peer": true, "dependencies": { "@aws-sdk/credential-provider-env": "3.292.0", "@aws-sdk/credential-provider-imds": "3.292.0", @@ -445,13 +468,15 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true }, "node_modules/@aws-sdk/credential-provider-node": { "version": "3.294.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.294.0.tgz", "integrity": "sha512-zUL1Qhb4BsQIZCs/TPpG4oIYH/9YsGiS+Se1tasSGjTOLfBy7jhOZ0QIdpEeyAx/EP8blOBredM9xWfEXgiHVA==", "optional": true, + "peer": true, "dependencies": { "@aws-sdk/credential-provider-env": "3.292.0", "@aws-sdk/credential-provider-imds": "3.292.0", @@ -472,13 +497,15 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true }, "node_modules/@aws-sdk/credential-provider-process": { "version": "3.292.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.292.0.tgz", "integrity": "sha512-CFVXuMuUvg/a4tknzRikEDwZBnKlHs1LZCpTXIGjBdUTdosoi4WNzDLzGp93ZRTtcgFz+4wirz2f7P3lC0NrQw==", "optional": true, + "peer": true, "dependencies": { "@aws-sdk/property-provider": "3.292.0", "@aws-sdk/shared-ini-file-loader": "3.292.0", @@ -493,13 +520,15 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true }, "node_modules/@aws-sdk/credential-provider-sso": { "version": "3.294.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.294.0.tgz", "integrity": "sha512-UxrcAA/0l7j9+3tolYcG5M61D/IE1Bjd/9H87H1i2A2BrwUUBhW1Dp/vvROEDrrywlMDG3CDF3T/7ADtTak+sg==", "optional": true, + "peer": true, "dependencies": { "@aws-sdk/client-sso": "3.294.0", "@aws-sdk/property-provider": "3.292.0", @@ -516,13 +545,15 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true }, "node_modules/@aws-sdk/credential-provider-web-identity": { "version": "3.292.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.292.0.tgz", "integrity": "sha512-4DbtIEM9gGVfqYlMdYXg3XY+vBhemjB1zXIequottW8loLYM8Vuz4/uGxxKNze6evVVzowsA0wKrYclE1aj/Rg==", "optional": true, + "peer": true, "dependencies": { "@aws-sdk/property-provider": "3.292.0", "@aws-sdk/types": "3.292.0", @@ -536,13 +567,15 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true }, "node_modules/@aws-sdk/credential-providers": { "version": "3.294.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-providers/-/credential-providers-3.294.0.tgz", "integrity": "sha512-CRGwCs6F31mQzjYi5YF2Z6c1T+UrFKtJSa6Ff/Q1rrPnROexyhBVnpP8WzpkODx/pZxKtTX50IX7ehIxbFDIyQ==", "optional": true, + "peer": true, "dependencies": { "@aws-sdk/client-cognito-identity": "3.294.0", "@aws-sdk/client-sso": "3.294.0", @@ -568,13 +601,15 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true }, "node_modules/@aws-sdk/fetch-http-handler": { "version": "3.292.0", "resolved": "https://registry.npmjs.org/@aws-sdk/fetch-http-handler/-/fetch-http-handler-3.292.0.tgz", "integrity": "sha512-zh3bhUJbL8RSa39ZKDcy+AghtUkIP8LwcNlwRIoxMQh3Row4D1s4fCq0KZCx98NJBEXoiTLyTQlZxxI//BOb1Q==", "optional": true, + "peer": true, "dependencies": { "@aws-sdk/protocol-http": "3.292.0", "@aws-sdk/querystring-builder": "3.292.0", @@ -587,13 +622,15 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true }, "node_modules/@aws-sdk/hash-node": { "version": "3.292.0", "resolved": "https://registry.npmjs.org/@aws-sdk/hash-node/-/hash-node-3.292.0.tgz", "integrity": "sha512-1yLxmIsvE+eK36JXEgEIouTITdykQLVhsA5Oai//Lar6Ddgu1sFpLDbdkMtKbrh4I0jLN9RacNCkeVQjZPTCCQ==", "optional": true, + "peer": true, "dependencies": { "@aws-sdk/types": "3.292.0", "@aws-sdk/util-buffer-from": "3.292.0", @@ -608,13 +645,15 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true }, "node_modules/@aws-sdk/invalid-dependency": { "version": "3.292.0", "resolved": "https://registry.npmjs.org/@aws-sdk/invalid-dependency/-/invalid-dependency-3.292.0.tgz", "integrity": "sha512-39OUV78CD3TmEbjhpt+V+Fk4wAGWhixqHxDSN8+4WL0uB4Fl7k5m3Z9hNY78AttHQSl2twR7WtLztnXPAFsriw==", "optional": true, + "peer": true, "dependencies": { "@aws-sdk/types": "3.292.0", "tslib": "^2.3.1" @@ -624,13 +663,15 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true }, "node_modules/@aws-sdk/is-array-buffer": { "version": "3.292.0", "resolved": "https://registry.npmjs.org/@aws-sdk/is-array-buffer/-/is-array-buffer-3.292.0.tgz", "integrity": "sha512-kW/G5T/fzI0sJH5foZG6XJiNCevXqKLxV50qIT4B1pMuw7regd4ALIy0HwSqj1nnn9mSbRWBfmby0jWCJsMcwg==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.3.1" }, @@ -642,13 +683,15 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true }, "node_modules/@aws-sdk/middleware-content-length": { "version": "3.292.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-content-length/-/middleware-content-length-3.292.0.tgz", "integrity": "sha512-2gMWzQus5mj14menolpPDbYBeaOYcj7KNFZOjTjjI3iQ0KqyetG6XasirNrcJ/8QX1BRmpTol8Xjp2Ue3Gbzwg==", "optional": true, + "peer": true, "dependencies": { "@aws-sdk/protocol-http": "3.292.0", "@aws-sdk/types": "3.292.0", @@ -662,13 +705,15 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true }, "node_modules/@aws-sdk/middleware-endpoint": { "version": "3.292.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-endpoint/-/middleware-endpoint-3.292.0.tgz", "integrity": "sha512-cPMkiSxpZGG6tYlW4OS+ucS6r43f9ddX9kcUoemJCY10MOuogdPjulCAjE0HTs2PLKSOrrG4CTP4Q4wWDrH4Bw==", "optional": true, + "peer": true, "dependencies": { "@aws-sdk/middleware-serde": "3.292.0", "@aws-sdk/protocol-http": "3.292.0", @@ -687,13 +732,15 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true }, "node_modules/@aws-sdk/middleware-host-header": { "version": "3.292.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.292.0.tgz", "integrity": "sha512-mHuCWe3Yg2S5YZ7mB7sKU6C97XspfqrimWjMW9pfV2usAvLA3R0HrB03jpR5vpZ3P4q7HB6wK3S6CjYMGGRNag==", "optional": true, + "peer": true, "dependencies": { "@aws-sdk/protocol-http": "3.292.0", "@aws-sdk/types": "3.292.0", @@ -707,13 +754,15 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true }, "node_modules/@aws-sdk/middleware-logger": { "version": "3.292.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.292.0.tgz", "integrity": "sha512-yZNY1XYmG3NG+uonET7jzKXNiwu61xm/ZZ6i/l51SusuaYN+qQtTAhOFsieQqTehF9kP4FzbsWgPDwD8ZZX9lw==", "optional": true, + "peer": true, "dependencies": { "@aws-sdk/types": "3.292.0", "tslib": "^2.3.1" @@ -726,13 +775,15 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true }, "node_modules/@aws-sdk/middleware-recursion-detection": { "version": "3.292.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.292.0.tgz", "integrity": "sha512-kA3VZpPko0Zqd7CYPTKAxhjEv0HJqFu2054L04dde1JLr43ro+2MTdX7vsHzeAFUVRphqatFFofCumvXmU6Mig==", "optional": true, + "peer": true, "dependencies": { "@aws-sdk/protocol-http": "3.292.0", "@aws-sdk/types": "3.292.0", @@ -746,13 +797,15 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true }, "node_modules/@aws-sdk/middleware-retry": { "version": "3.293.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-retry/-/middleware-retry-3.293.0.tgz", "integrity": "sha512-7tiaz2GzRecNHaZ6YnF+Nrtk3au8qF6oiipf11R7MJiqJ0fkMLnz/iRrlakDziS9qF/a9v+3yxb4W4NHK3f4Tw==", "optional": true, + "peer": true, "dependencies": { "@aws-sdk/protocol-http": "3.292.0", "@aws-sdk/service-error-classification": "3.292.0", @@ -770,13 +823,15 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true }, "node_modules/@aws-sdk/middleware-retry/node_modules/uuid": { "version": "8.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", "optional": true, + "peer": true, "bin": { "uuid": "dist/bin/uuid" } @@ -786,6 +841,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-sts/-/middleware-sdk-sts-3.292.0.tgz", "integrity": "sha512-GN5ZHEqXZqDi+HkVbaXRX9HaW/vA5rikYpWKYsmxTUZ7fB7ijvEO3co3lleJv2C+iGYRtUIHC4wYNB5xgoTCxg==", "optional": true, + "peer": true, "dependencies": { "@aws-sdk/middleware-signing": "3.292.0", "@aws-sdk/property-provider": "3.292.0", @@ -802,13 +858,15 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true }, "node_modules/@aws-sdk/middleware-serde": { "version": "3.292.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-serde/-/middleware-serde-3.292.0.tgz", "integrity": "sha512-6hN9mTQwSvV8EcGvtXbS/MpK7WMCokUku5Wu7X24UwCNMVkoRHLIkYcxHcvBTwttuOU0d8hph1/lIX4dkLwkQw==", "optional": true, + "peer": true, "dependencies": { "@aws-sdk/types": "3.292.0", "tslib": "^2.3.1" @@ -821,13 +879,15 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true }, "node_modules/@aws-sdk/middleware-signing": { "version": "3.292.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-signing/-/middleware-signing-3.292.0.tgz", "integrity": "sha512-GVfoSjDjEQ4TaO6x9MffyP3uRV+2KcS5FtexLCYOM9pJcnE9tqq9FJOrZ1xl1g+YjUVKxo4x8lu3tpEtIb17qg==", "optional": true, + "peer": true, "dependencies": { "@aws-sdk/property-provider": "3.292.0", "@aws-sdk/protocol-http": "3.292.0", @@ -844,13 +904,15 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true }, "node_modules/@aws-sdk/middleware-stack": { "version": "3.292.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-stack/-/middleware-stack-3.292.0.tgz", "integrity": "sha512-WdQpRkuMysrEwrkByCM1qCn2PPpFGGQ2iXqaFha5RzCdZDlxJni9cVNb6HzWUcgjLEYVTXCmOR9Wxm3CNW44Qg==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.3.1" }, @@ -862,13 +924,15 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true }, "node_modules/@aws-sdk/middleware-user-agent": { "version": "3.293.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.293.0.tgz", "integrity": "sha512-gZ7/e6XwpKk9mvgA78q4Ffc796jTn02TUKx2qMDnkLVbeJXBNN2jnvYEKq8v70+o7fd/ALRudg8gBDmkkhM/Hw==", "optional": true, + "peer": true, "dependencies": { "@aws-sdk/protocol-http": "3.292.0", "@aws-sdk/types": "3.292.0", @@ -883,13 +947,15 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true }, "node_modules/@aws-sdk/node-config-provider": { "version": "3.292.0", "resolved": "https://registry.npmjs.org/@aws-sdk/node-config-provider/-/node-config-provider-3.292.0.tgz", "integrity": "sha512-S3NnC9dQ5GIbJYSDIldZb4zdpCOEua1tM7bjYL3VS5uqCEM93kIi/o/UkIUveMp/eqTS2LJa5HjNIz5Te6je0A==", "optional": true, + "peer": true, "dependencies": { "@aws-sdk/property-provider": "3.292.0", "@aws-sdk/shared-ini-file-loader": "3.292.0", @@ -904,13 +970,15 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true }, "node_modules/@aws-sdk/node-http-handler": { "version": "3.292.0", "resolved": "https://registry.npmjs.org/@aws-sdk/node-http-handler/-/node-http-handler-3.292.0.tgz", "integrity": "sha512-L/E3UDSwXLXjt1XWWh0RBD55F+aZI1AEdPwdES9i1PjnZLyuxuDhEDptVibNN56+I9/4Q3SbmuVRVlOD0uzBag==", "optional": true, + "peer": true, "dependencies": { "@aws-sdk/abort-controller": "3.292.0", "@aws-sdk/protocol-http": "3.292.0", @@ -926,13 +994,15 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true }, "node_modules/@aws-sdk/property-provider": { "version": "3.292.0", "resolved": "https://registry.npmjs.org/@aws-sdk/property-provider/-/property-provider-3.292.0.tgz", "integrity": "sha512-dHArSvsiqhno/g55N815gXmAMrmN8DP7OeFNqJ4wJG42xsF2PFN3DAsjIuHuXMwu+7A3R1LHqIpvv0hA9KeoJQ==", "optional": true, + "peer": true, "dependencies": { "@aws-sdk/types": "3.292.0", "tslib": "^2.3.1" @@ -945,13 +1015,15 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true }, "node_modules/@aws-sdk/protocol-http": { "version": "3.292.0", "resolved": "https://registry.npmjs.org/@aws-sdk/protocol-http/-/protocol-http-3.292.0.tgz", "integrity": "sha512-NLi4fq3k41aXIh1I97yX0JTy+3p6aW1NdwFwdMa674z86QNfb4SfRQRZBQe9wEnAZ/eWHVnlKIuII+U1URk/Kg==", "optional": true, + "peer": true, "dependencies": { "@aws-sdk/types": "3.292.0", "tslib": "^2.3.1" @@ -964,13 +1036,15 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true }, "node_modules/@aws-sdk/querystring-builder": { "version": "3.292.0", "resolved": "https://registry.npmjs.org/@aws-sdk/querystring-builder/-/querystring-builder-3.292.0.tgz", "integrity": "sha512-XElIFJaReIm24eEvBtV2dOtZvcm3gXsGu/ftG8MLJKbKXFKpAP1q+K6En0Bs7/T88voKghKdKpKT+eZUWgTqlg==", "optional": true, + "peer": true, "dependencies": { "@aws-sdk/types": "3.292.0", "@aws-sdk/util-uri-escape": "3.292.0", @@ -984,13 +1058,15 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true }, "node_modules/@aws-sdk/querystring-parser": { "version": "3.292.0", "resolved": "https://registry.npmjs.org/@aws-sdk/querystring-parser/-/querystring-parser-3.292.0.tgz", "integrity": "sha512-iTYpYo7a8X9RxiPbjjewIpm6XQPx2EOcF1dWCPRII9EFlmZ4bwnX+PDI36fIo9oVs8TIKXmwNGODU9nsg7CSAw==", "optional": true, + "peer": true, "dependencies": { "@aws-sdk/types": "3.292.0", "tslib": "^2.3.1" @@ -1003,13 +1079,15 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true }, "node_modules/@aws-sdk/service-error-classification": { "version": "3.292.0", "resolved": "https://registry.npmjs.org/@aws-sdk/service-error-classification/-/service-error-classification-3.292.0.tgz", "integrity": "sha512-X1k3sixCeC45XSNHBe+kRBQBwPDyTFtFITb8O5Qw4dS9XWGhrUJT4CX0qE5aj8qP3F9U5nRizs9c2mBVVP0Caw==", "optional": true, + "peer": true, "engines": { "node": ">=14.0.0" } @@ -1019,6 +1097,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/shared-ini-file-loader/-/shared-ini-file-loader-3.292.0.tgz", "integrity": "sha512-Av2TTYg1Jig2kbkD56ybiqZJB6vVrYjv1W5UQwY/q3nA/T2mcrgQ20ByCOt5Bv9VvY7FSgC+znj+L4a7RLGmBg==", "optional": true, + "peer": true, "dependencies": { "@aws-sdk/types": "3.292.0", "tslib": "^2.3.1" @@ -1031,13 +1110,15 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true }, "node_modules/@aws-sdk/signature-v4": { "version": "3.292.0", "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4/-/signature-v4-3.292.0.tgz", "integrity": "sha512-+rw47VY5mvBecn13tDQTl1ipGWg5tE63faWgmZe68HoBL87ZiDzsd7bUKOvjfW21iMgWlwAppkaNNQayYRb2zg==", "optional": true, + "peer": true, "dependencies": { "@aws-sdk/is-array-buffer": "3.292.0", "@aws-sdk/types": "3.292.0", @@ -1055,13 +1136,15 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true }, "node_modules/@aws-sdk/smithy-client": { "version": "3.292.0", "resolved": "https://registry.npmjs.org/@aws-sdk/smithy-client/-/smithy-client-3.292.0.tgz", "integrity": "sha512-S8PKzjPkZ6SXYZuZiU787dMsvQ0d/LFEhw2OI4Oe2An9Fc2IwJ2FYukyHoQJOV2tV0DiuMebPo7eMyQyjKElvA==", "optional": true, + "peer": true, "dependencies": { "@aws-sdk/middleware-stack": "3.292.0", "@aws-sdk/types": "3.292.0", @@ -1075,13 +1158,15 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true }, "node_modules/@aws-sdk/token-providers": { "version": "3.294.0", "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.294.0.tgz", "integrity": "sha512-6nwO04LtC5f4AsUvGZXyjaswuEK4Rr2VsuANpMKrPCgunRfI58a8YXLniudOSXN6e7CFJ6M3uo/h5YXqtnzGug==", "optional": true, + "peer": true, "dependencies": { "@aws-sdk/client-sso-oidc": "3.294.0", "@aws-sdk/property-provider": "3.292.0", @@ -1097,13 +1182,15 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true }, "node_modules/@aws-sdk/types": { "version": "3.292.0", "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.292.0.tgz", "integrity": "sha512-1teYAY2M73UXZxMAxqZxVS2qwXjQh0OWtt7qyLfha0TtIk/fZ1hRwFgxbDCHUFcdNBSOSbKH/ESor90KROXLCQ==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.3.1" }, @@ -1115,13 +1202,15 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true }, "node_modules/@aws-sdk/url-parser": { "version": "3.292.0", "resolved": "https://registry.npmjs.org/@aws-sdk/url-parser/-/url-parser-3.292.0.tgz", "integrity": "sha512-NZeAuZCk1x6TIiWuRfbOU6wHPBhf0ly2qOHzWut4BCH+b4RrDmFF8EmXcH1auEfGhE7yRyR6XqIN0t3S+hYACA==", "optional": true, + "peer": true, "dependencies": { "@aws-sdk/querystring-parser": "3.292.0", "@aws-sdk/types": "3.292.0", @@ -1132,13 +1221,15 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true }, "node_modules/@aws-sdk/util-base64": { "version": "3.292.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-base64/-/util-base64-3.292.0.tgz", "integrity": "sha512-zjNCwNdy617yFvEjZorepNWXB2sQCVfsShCwFy/kIQ5iW5tT2jQKaqc0K77diU9atkooxw9p1W9m9sOgrkOFNw==", "optional": true, + "peer": true, "dependencies": { "@aws-sdk/util-buffer-from": "3.292.0", "tslib": "^2.3.1" @@ -1151,13 +1242,15 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true }, "node_modules/@aws-sdk/util-body-length-browser": { "version": "3.292.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-body-length-browser/-/util-body-length-browser-3.292.0.tgz", "integrity": "sha512-Wd/BM+JsMiKvKs/bN3z6TredVEHh2pKudGfg3CSjTRpqFpOG903KDfyHBD42yg5PuCHoHoewJvTPKwgn7/vhaw==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.3.1" } @@ -1166,13 +1259,15 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true }, "node_modules/@aws-sdk/util-body-length-node": { "version": "3.292.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-body-length-node/-/util-body-length-node-3.292.0.tgz", "integrity": "sha512-BBgipZ2P6RhogWE/qj0oqpdlyd3iSBYmb+aD/TBXwB2lA/X8A99GxweBd/kp06AmcJRoMS9WIXgbWkiiBlRlSA==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.3.1" }, @@ -1184,13 +1279,15 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true }, "node_modules/@aws-sdk/util-buffer-from": { "version": "3.292.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-buffer-from/-/util-buffer-from-3.292.0.tgz", "integrity": "sha512-RxNZjLoXNxHconH9TYsk5RaEBjSgTtozHeyIdacaHPj5vlQKi4hgL2hIfKeeNiAfQEVjaUFF29lv81xpNMzVMQ==", "optional": true, + "peer": true, "dependencies": { "@aws-sdk/is-array-buffer": "3.292.0", "tslib": "^2.3.1" @@ -1203,13 +1300,15 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true }, "node_modules/@aws-sdk/util-config-provider": { "version": "3.292.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-config-provider/-/util-config-provider-3.292.0.tgz", "integrity": "sha512-t3noYll6bPRSxeeNNEkC5czVjAiTPcsq00OwfJ2xyUqmquhLEfLwoJKmrT1uP7DjIEXdUtfoIQ2jWiIVm/oO5A==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.3.1" }, @@ -1221,13 +1320,15 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true }, "node_modules/@aws-sdk/util-defaults-mode-browser": { "version": "3.292.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-defaults-mode-browser/-/util-defaults-mode-browser-3.292.0.tgz", "integrity": "sha512-7+zVUlMGfa8/KT++9humHo6IDxTnxMCmWUj5jVNlkpk6h7Ecmppf7aXotviyVIA43lhtz0p2AErs0N0ekEUK+w==", "optional": true, + "peer": true, "dependencies": { "@aws-sdk/property-provider": "3.292.0", "@aws-sdk/types": "3.292.0", @@ -1242,13 +1343,15 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true }, "node_modules/@aws-sdk/util-defaults-mode-node": { "version": "3.292.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-defaults-mode-node/-/util-defaults-mode-node-3.292.0.tgz", "integrity": "sha512-SSIw85eF4BVs0fOJRyshT+R3b/UmBPhiVKCUZm2rq6+lIGkDPiSwQU3d/80AhXtiL5SFT/IzAKKgQd8qMa7q3A==", "optional": true, + "peer": true, "dependencies": { "@aws-sdk/config-resolver": "3.292.0", "@aws-sdk/credential-provider-imds": "3.292.0", @@ -1265,13 +1368,15 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true }, "node_modules/@aws-sdk/util-endpoints": { "version": "3.293.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.293.0.tgz", "integrity": "sha512-R/99aNV49Refpv5guiUjEUrZYlvnfaNBniB+/ZtMO3ixxUopapssCrUivuJrmhccmrYaTCZw7dRzIWjU1jJhKg==", "optional": true, + "peer": true, "dependencies": { "@aws-sdk/types": "3.292.0", "tslib": "^2.3.1" @@ -1284,13 +1389,15 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true }, "node_modules/@aws-sdk/util-hex-encoding": { "version": "3.292.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-hex-encoding/-/util-hex-encoding-3.292.0.tgz", "integrity": "sha512-qBd5KFIUywQ3qSSbj814S2srk0vfv8A6QMI+Obs1y2LHZFdQN5zViptI4UhXhKOHe+NnrHWxSuLC/LMH6q3SmA==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.3.1" }, @@ -1302,13 +1409,15 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true }, "node_modules/@aws-sdk/util-locate-window": { "version": "3.292.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.292.0.tgz", "integrity": "sha512-6xnFJXZI9pKw5lQCDvuWA5PnOaUtNRKWwdxvGkkLx5orboFaoVMS6zowjSQxwVNRjW82u6dYNkhmj9mZ8VSjWg==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.3.1" }, @@ -1320,13 +1429,15 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true }, "node_modules/@aws-sdk/util-middleware": { "version": "3.292.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-middleware/-/util-middleware-3.292.0.tgz", "integrity": "sha512-KjhS7flfoBKDxbiBZjLjMvEizXgjfQb7GQEItgzGoI9rfGCmZtvqCcqQQoIlxb8bIzGRggAUHtBGWnlLbpb+GQ==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.3.1" }, @@ -1338,13 +1449,15 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true }, "node_modules/@aws-sdk/util-retry": { "version": "3.292.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-retry/-/util-retry-3.292.0.tgz", "integrity": "sha512-JEHyF7MpVeRF5uR4LDYgpOKcFpOPiAj8TqN46SVOQQcL1K+V7cSr7O7N7J6MwJaN9XOzAcBadeIupMm7/BFbgw==", "optional": true, + "peer": true, "dependencies": { "@aws-sdk/service-error-classification": "3.292.0", "tslib": "^2.3.1" @@ -1357,13 +1470,15 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true }, "node_modules/@aws-sdk/util-uri-escape": { "version": "3.292.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-uri-escape/-/util-uri-escape-3.292.0.tgz", "integrity": "sha512-hOQtUMQ4VcQ9iwKz50AoCp1XBD5gJ9nly/gJZccAM7zSA5mOO8RRKkbdonqquVHxrO0CnYgiFeCh3V35GFecUw==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.3.1" }, @@ -1375,13 +1490,15 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true }, "node_modules/@aws-sdk/util-user-agent-browser": { "version": "3.292.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.292.0.tgz", "integrity": "sha512-dld+lpC3QdmTQHdBWJ0WFDkXDSrJgfz03q6mQ8+7H+BC12ZhT0I0g9iuvUjolqy7QR00OxOy47Y9FVhq8EC0Gg==", "optional": true, + "peer": true, "dependencies": { "@aws-sdk/types": "3.292.0", "bowser": "^2.11.0", @@ -1392,13 +1509,15 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true }, "node_modules/@aws-sdk/util-user-agent-node": { "version": "3.292.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.292.0.tgz", "integrity": "sha512-f+NfIMal5E61MDc5WGhUEoicr7b1eNNhA+GgVdSB/Hg5fYhEZvFK9RZizH5rrtsLjjgcr9nPYSR7/nDKCJLumw==", "optional": true, + "peer": true, "dependencies": { "@aws-sdk/node-config-provider": "3.292.0", "@aws-sdk/types": "3.292.0", @@ -1420,13 +1539,15 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true }, "node_modules/@aws-sdk/util-utf8": { "version": "3.292.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-utf8/-/util-utf8-3.292.0.tgz", "integrity": "sha512-FPkj+Z59/DQWvoVu2wFaRncc3KVwe/pgK3MfVb0Lx+Ibey5KUx+sNpJmYcVYHUAe/Nv/JeIpOtYuC96IXOnI6w==", "optional": true, + "peer": true, "dependencies": { "@aws-sdk/util-buffer-from": "3.292.0", "tslib": "^2.3.1" @@ -1440,6 +1561,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/util-utf8-browser/-/util-utf8-browser-3.259.0.tgz", "integrity": "sha512-UvFa/vR+e19XookZF8RzFZBrw2EUkQWxiBW0yYQAhvk3C+QVGl0H3ouca8LDBlBfQKXwmW3huo/59H8rwb1wJw==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.3.1" } @@ -1448,13 +1570,15 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true }, "node_modules/@aws-sdk/util-utf8/node_modules/tslib": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true }, "node_modules/@colors/colors": { "version": "1.5.0", @@ -1982,132 +2106,6 @@ "integrity": "sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==", "dev": true }, - "node_modules/@types/mongoose-unique-validator": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/@types/mongoose-unique-validator/-/mongoose-unique-validator-1.0.7.tgz", - "integrity": "sha512-wgPWN7x9mHdy8x1jcluDF7EDWFb/s8LD7skLr012C8i2Q3tUhdTlp3JxRhFbEM3TD7J1INaoTrWaSaguhuqilQ==", - "dev": true, - "dependencies": { - "mongoose": "^6.3.0" - } - }, - "node_modules/@types/mongoose-unique-validator/node_modules/bson": { - "version": "4.7.2", - "resolved": "https://registry.npmjs.org/bson/-/bson-4.7.2.tgz", - "integrity": "sha512-Ry9wCtIZ5kGqkJoi6aD8KjxFZEx78guTQDnpXWiNthsxzrxAK/i8E6pCHAIZTbaEFWcOCvbecMukfK7XUvyLpQ==", - "dev": true, - "dependencies": { - "buffer": "^5.6.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@types/mongoose-unique-validator/node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, - "node_modules/@types/mongoose-unique-validator/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@types/mongoose-unique-validator/node_modules/debug/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/@types/mongoose-unique-validator/node_modules/mongodb": { - "version": "4.14.0", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.14.0.tgz", - "integrity": "sha512-coGKkWXIBczZPr284tYKFLg+KbGPPLlSbdgfKAb6QqCFt5bo5VFZ50O3FFzsw4rnkqjwT6D8Qcoo9nshYKM7Mg==", - "dev": true, - "dependencies": { - "bson": "^4.7.0", - "mongodb-connection-string-url": "^2.5.4", - "socks": "^2.7.1" - }, - "engines": { - "node": ">=12.9.0" - }, - "optionalDependencies": { - "@aws-sdk/credential-providers": "^3.186.0", - "saslprep": "^1.0.3" - } - }, - "node_modules/@types/mongoose-unique-validator/node_modules/mongoose": { - "version": "6.10.3", - "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-6.10.3.tgz", - "integrity": "sha512-fZ3pIlQn7lM632r1l4qiU58lKrJ+FufKVG8TNeRXSChAeu9alCl5KoQ9bLw4jnQNYevSq9o+sqZmFDHP+EVW3g==", - "dev": true, - "dependencies": { - "bson": "^4.7.0", - "kareem": "2.5.1", - "mongodb": "4.14.0", - "mpath": "0.9.0", - "mquery": "4.0.3", - "ms": "2.1.3", - "sift": "16.0.1" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mongoose" - } - }, - "node_modules/@types/mongoose-unique-validator/node_modules/mquery": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/mquery/-/mquery-4.0.3.tgz", - "integrity": "sha512-J5heI+P08I6VJ2Ky3+33IpCdAvlYGTSUjwTPxkAr8i8EoduPMBX2OY/wa3IKZIQl7MU4SbFk8ndgSKyB/cl1zA==", - "dev": true, - "dependencies": { - "debug": "4.x" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/@types/mongoose-unique-validator/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true - }, "node_modules/@types/morgan": { "version": "1.9.4", "resolved": "https://registry.npmjs.org/@types/morgan/-/morgan-1.9.4.tgz", @@ -2864,7 +2862,8 @@ "version": "2.11.0", "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.11.0.tgz", "integrity": "sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==", - "optional": true + "optional": true, + "peer": true }, "node_modules/brace-expansion": { "version": "1.1.11", @@ -4035,6 +4034,7 @@ "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.1.2.tgz", "integrity": "sha512-CDYeykkle1LiA/uqQyNwYpFbyF6Axec6YapmpUP+/RHWIoR1zKjocdvNaTsxCxZzQ6v9MLXaSYm9Qq0thv0DHg==", "optional": true, + "peer": true, "dependencies": { "strnum": "^1.0.5" }, @@ -6527,7 +6527,8 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz", "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==", - "optional": true + "optional": true, + "peer": true }, "node_modules/supports-color": { "version": "7.2.0", @@ -7061,6 +7062,7 @@ "resolved": "https://registry.npmjs.org/@aws-crypto/ie11-detection/-/ie11-detection-3.0.0.tgz", "integrity": "sha512-341lBBkiY1DfDNKai/wXM3aujNBkXR7tq1URPQDL9wi3AUbI80NR74uF1TXHMm7po1AcnFk8iu2S2IeU/+/A+Q==", "optional": true, + "peer": true, "requires": { "tslib": "^1.11.1" } @@ -7070,6 +7072,7 @@ "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-3.0.0.tgz", "integrity": "sha512-8VLmW2B+gjFbU5uMeqtQM6Nj0/F1bro80xQXCW6CQBWgosFWXTx77aeOF5CAIAmbOK64SdMBJdNr6J41yP5mvQ==", "optional": true, + "peer": true, "requires": { "@aws-crypto/ie11-detection": "^3.0.0", "@aws-crypto/sha256-js": "^3.0.0", @@ -7086,6 +7089,7 @@ "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-3.0.0.tgz", "integrity": "sha512-PnNN7os0+yd1XvXAy23CFOmTbMaDxgxXtTKHybrJ39Y8kGzBATgBFibWJKH6BhytLI/Zyszs87xCOBNyBig6vQ==", "optional": true, + "peer": true, "requires": { "@aws-crypto/util": "^3.0.0", "@aws-sdk/types": "^3.222.0", @@ -7097,6 +7101,7 @@ "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-3.0.0.tgz", "integrity": "sha512-06hBdMwUAb2WFTuGG73LSC0wfPu93xWwo5vL2et9eymgmu3Id5vFAHBbajVWiGhPO37qcsdCap/FqXvJGJWPIg==", "optional": true, + "peer": true, "requires": { "tslib": "^1.11.1" } @@ -7106,6 +7111,7 @@ "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-3.0.0.tgz", "integrity": "sha512-2OJlpeJpCR48CC8r+uKVChzs9Iungj9wkZrl8Z041DWEWvyIHILYKCPNzJghKsivj+S3mLo6BVc7mBNzdxA46w==", "optional": true, + "peer": true, "requires": { "@aws-sdk/types": "^3.222.0", "@aws-sdk/util-utf8-browser": "^3.0.0", @@ -7117,6 +7123,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/abort-controller/-/abort-controller-3.292.0.tgz", "integrity": "sha512-lf+OPptL01kvryIJy7+dvFux5KbJ6OTwLPPEekVKZ2AfEvwcVtOZWFUhyw3PJCBTVncjKB1Kjl3V/eTS3YuPXQ==", "optional": true, + "peer": true, "requires": { "@aws-sdk/types": "3.292.0", "tslib": "^2.3.1" @@ -7126,7 +7133,8 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true } } }, @@ -7135,6 +7143,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/client-cognito-identity/-/client-cognito-identity-3.294.0.tgz", "integrity": "sha512-QMk/QratNvAvmnJ77pu9KDjpfUf/5LOJplHcLKcY962ewJGBtFFY4XZVnmUgQMSG0phRODtex7pyH//FueGKLQ==", "optional": true, + "peer": true, "requires": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", @@ -7177,7 +7186,8 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true } } }, @@ -7186,6 +7196,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.294.0.tgz", "integrity": "sha512-+FuxQTi5WvnaXM5JbNLkBIzQ3An4gA0ox61N1u+3xled+nywKb1yQ7WmRpyMG5bLbkmnj3aqoo5/uskFc4c4EA==", "optional": true, + "peer": true, "requires": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", @@ -7225,7 +7236,8 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true } } }, @@ -7234,6 +7246,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.294.0.tgz", "integrity": "sha512-/ZfDud76MdSPJ/TxjV2xLE30XbBQDZwKQ32axwoK1eziPvrAIUBYVgpBwj+m0quhoiQhBKkg3aFl6j39AF2thw==", "optional": true, + "peer": true, "requires": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", @@ -7273,7 +7286,8 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true } } }, @@ -7282,6 +7296,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.294.0.tgz", "integrity": "sha512-AefqwhFjTDzelZuSYhriJbiI+GQwf2yKiKAnCt0gRj6rswewStM63Gtlhfb01sFPp+ZiqPcyQ47LqUaHp1mz/g==", "optional": true, + "peer": true, "requires": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", @@ -7325,7 +7340,8 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true } } }, @@ -7334,6 +7350,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/config-resolver/-/config-resolver-3.292.0.tgz", "integrity": "sha512-cB3twnNR7vYvlt2jvw8VlA1+iv/tVzl+/S39MKqw2tepU+AbJAM0EHwb/dkf1OKSmlrnANXhshx80MHF9zL4mA==", "optional": true, + "peer": true, "requires": { "@aws-sdk/signature-v4": "3.292.0", "@aws-sdk/types": "3.292.0", @@ -7346,7 +7363,8 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true } } }, @@ -7355,6 +7373,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-cognito-identity/-/credential-provider-cognito-identity-3.294.0.tgz", "integrity": "sha512-YSPqHEbLC0dnbFF5LdlMH0B50sMSN/CyG/sHkPYUwL/hkbUk9URnVW7ZJlt6lRftT7X4C7muzLqUP8sJAaiJEA==", "optional": true, + "peer": true, "requires": { "@aws-sdk/client-cognito-identity": "3.294.0", "@aws-sdk/property-provider": "3.292.0", @@ -7366,7 +7385,8 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true } } }, @@ -7375,6 +7395,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.292.0.tgz", "integrity": "sha512-YbafSG0ZEKE2969CJWVtUhh3hfOeLPecFVoXOtegCyAJgY5Ghtu4TsVhL4DgiGAgOC30ojAmUVQEXzd7xJF5xA==", "optional": true, + "peer": true, "requires": { "@aws-sdk/property-provider": "3.292.0", "@aws-sdk/types": "3.292.0", @@ -7385,7 +7406,8 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true } } }, @@ -7394,6 +7416,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-imds/-/credential-provider-imds-3.292.0.tgz", "integrity": "sha512-W/peOgDSRYulgzFpUhvgi1pCm6piBz6xrVN17N4QOy+3NHBXRVMVzYk6ct2qpLPgJUSEZkcpP+Gds+bBm8ed1A==", "optional": true, + "peer": true, "requires": { "@aws-sdk/node-config-provider": "3.292.0", "@aws-sdk/property-provider": "3.292.0", @@ -7406,7 +7429,8 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true } } }, @@ -7415,6 +7439,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.294.0.tgz", "integrity": "sha512-pdTPbaAb5bWA+DnuKoL2TpXeNDp6Ejpv/OYt+bw2gdzl9w5r/ZCtUTTbW+Vvejr4WL5s3c1bY96kwdqCn7iLqA==", "optional": true, + "peer": true, "requires": { "@aws-sdk/credential-provider-env": "3.292.0", "@aws-sdk/credential-provider-imds": "3.292.0", @@ -7431,7 +7456,8 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true } } }, @@ -7440,6 +7466,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.294.0.tgz", "integrity": "sha512-zUL1Qhb4BsQIZCs/TPpG4oIYH/9YsGiS+Se1tasSGjTOLfBy7jhOZ0QIdpEeyAx/EP8blOBredM9xWfEXgiHVA==", "optional": true, + "peer": true, "requires": { "@aws-sdk/credential-provider-env": "3.292.0", "@aws-sdk/credential-provider-imds": "3.292.0", @@ -7457,7 +7484,8 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true } } }, @@ -7466,6 +7494,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.292.0.tgz", "integrity": "sha512-CFVXuMuUvg/a4tknzRikEDwZBnKlHs1LZCpTXIGjBdUTdosoi4WNzDLzGp93ZRTtcgFz+4wirz2f7P3lC0NrQw==", "optional": true, + "peer": true, "requires": { "@aws-sdk/property-provider": "3.292.0", "@aws-sdk/shared-ini-file-loader": "3.292.0", @@ -7477,7 +7506,8 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true } } }, @@ -7486,6 +7516,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.294.0.tgz", "integrity": "sha512-UxrcAA/0l7j9+3tolYcG5M61D/IE1Bjd/9H87H1i2A2BrwUUBhW1Dp/vvROEDrrywlMDG3CDF3T/7ADtTak+sg==", "optional": true, + "peer": true, "requires": { "@aws-sdk/client-sso": "3.294.0", "@aws-sdk/property-provider": "3.292.0", @@ -7499,7 +7530,8 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true } } }, @@ -7508,6 +7540,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.292.0.tgz", "integrity": "sha512-4DbtIEM9gGVfqYlMdYXg3XY+vBhemjB1zXIequottW8loLYM8Vuz4/uGxxKNze6evVVzowsA0wKrYclE1aj/Rg==", "optional": true, + "peer": true, "requires": { "@aws-sdk/property-provider": "3.292.0", "@aws-sdk/types": "3.292.0", @@ -7518,7 +7551,8 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true } } }, @@ -7527,6 +7561,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-providers/-/credential-providers-3.294.0.tgz", "integrity": "sha512-CRGwCs6F31mQzjYi5YF2Z6c1T+UrFKtJSa6Ff/Q1rrPnROexyhBVnpP8WzpkODx/pZxKtTX50IX7ehIxbFDIyQ==", "optional": true, + "peer": true, "requires": { "@aws-sdk/client-cognito-identity": "3.294.0", "@aws-sdk/client-sso": "3.294.0", @@ -7549,7 +7584,8 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true } } }, @@ -7558,6 +7594,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/fetch-http-handler/-/fetch-http-handler-3.292.0.tgz", "integrity": "sha512-zh3bhUJbL8RSa39ZKDcy+AghtUkIP8LwcNlwRIoxMQh3Row4D1s4fCq0KZCx98NJBEXoiTLyTQlZxxI//BOb1Q==", "optional": true, + "peer": true, "requires": { "@aws-sdk/protocol-http": "3.292.0", "@aws-sdk/querystring-builder": "3.292.0", @@ -7570,7 +7607,8 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true } } }, @@ -7579,6 +7617,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/hash-node/-/hash-node-3.292.0.tgz", "integrity": "sha512-1yLxmIsvE+eK36JXEgEIouTITdykQLVhsA5Oai//Lar6Ddgu1sFpLDbdkMtKbrh4I0jLN9RacNCkeVQjZPTCCQ==", "optional": true, + "peer": true, "requires": { "@aws-sdk/types": "3.292.0", "@aws-sdk/util-buffer-from": "3.292.0", @@ -7590,7 +7629,8 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true } } }, @@ -7599,6 +7639,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/invalid-dependency/-/invalid-dependency-3.292.0.tgz", "integrity": "sha512-39OUV78CD3TmEbjhpt+V+Fk4wAGWhixqHxDSN8+4WL0uB4Fl7k5m3Z9hNY78AttHQSl2twR7WtLztnXPAFsriw==", "optional": true, + "peer": true, "requires": { "@aws-sdk/types": "3.292.0", "tslib": "^2.3.1" @@ -7608,7 +7649,8 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true } } }, @@ -7617,6 +7659,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/is-array-buffer/-/is-array-buffer-3.292.0.tgz", "integrity": "sha512-kW/G5T/fzI0sJH5foZG6XJiNCevXqKLxV50qIT4B1pMuw7regd4ALIy0HwSqj1nnn9mSbRWBfmby0jWCJsMcwg==", "optional": true, + "peer": true, "requires": { "tslib": "^2.3.1" }, @@ -7625,7 +7668,8 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true } } }, @@ -7634,6 +7678,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-content-length/-/middleware-content-length-3.292.0.tgz", "integrity": "sha512-2gMWzQus5mj14menolpPDbYBeaOYcj7KNFZOjTjjI3iQ0KqyetG6XasirNrcJ/8QX1BRmpTol8Xjp2Ue3Gbzwg==", "optional": true, + "peer": true, "requires": { "@aws-sdk/protocol-http": "3.292.0", "@aws-sdk/types": "3.292.0", @@ -7644,7 +7689,8 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true } } }, @@ -7653,6 +7699,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-endpoint/-/middleware-endpoint-3.292.0.tgz", "integrity": "sha512-cPMkiSxpZGG6tYlW4OS+ucS6r43f9ddX9kcUoemJCY10MOuogdPjulCAjE0HTs2PLKSOrrG4CTP4Q4wWDrH4Bw==", "optional": true, + "peer": true, "requires": { "@aws-sdk/middleware-serde": "3.292.0", "@aws-sdk/protocol-http": "3.292.0", @@ -7668,7 +7715,8 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true } } }, @@ -7677,6 +7725,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.292.0.tgz", "integrity": "sha512-mHuCWe3Yg2S5YZ7mB7sKU6C97XspfqrimWjMW9pfV2usAvLA3R0HrB03jpR5vpZ3P4q7HB6wK3S6CjYMGGRNag==", "optional": true, + "peer": true, "requires": { "@aws-sdk/protocol-http": "3.292.0", "@aws-sdk/types": "3.292.0", @@ -7687,7 +7736,8 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true } } }, @@ -7696,6 +7746,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.292.0.tgz", "integrity": "sha512-yZNY1XYmG3NG+uonET7jzKXNiwu61xm/ZZ6i/l51SusuaYN+qQtTAhOFsieQqTehF9kP4FzbsWgPDwD8ZZX9lw==", "optional": true, + "peer": true, "requires": { "@aws-sdk/types": "3.292.0", "tslib": "^2.3.1" @@ -7705,7 +7756,8 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true } } }, @@ -7714,6 +7766,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.292.0.tgz", "integrity": "sha512-kA3VZpPko0Zqd7CYPTKAxhjEv0HJqFu2054L04dde1JLr43ro+2MTdX7vsHzeAFUVRphqatFFofCumvXmU6Mig==", "optional": true, + "peer": true, "requires": { "@aws-sdk/protocol-http": "3.292.0", "@aws-sdk/types": "3.292.0", @@ -7724,7 +7777,8 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true } } }, @@ -7733,6 +7787,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-retry/-/middleware-retry-3.293.0.tgz", "integrity": "sha512-7tiaz2GzRecNHaZ6YnF+Nrtk3au8qF6oiipf11R7MJiqJ0fkMLnz/iRrlakDziS9qF/a9v+3yxb4W4NHK3f4Tw==", "optional": true, + "peer": true, "requires": { "@aws-sdk/protocol-http": "3.292.0", "@aws-sdk/service-error-classification": "3.292.0", @@ -7747,13 +7802,15 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true }, "uuid": { "version": "8.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "optional": true + "optional": true, + "peer": true } } }, @@ -7762,6 +7819,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-sts/-/middleware-sdk-sts-3.292.0.tgz", "integrity": "sha512-GN5ZHEqXZqDi+HkVbaXRX9HaW/vA5rikYpWKYsmxTUZ7fB7ijvEO3co3lleJv2C+iGYRtUIHC4wYNB5xgoTCxg==", "optional": true, + "peer": true, "requires": { "@aws-sdk/middleware-signing": "3.292.0", "@aws-sdk/property-provider": "3.292.0", @@ -7775,7 +7833,8 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true } } }, @@ -7784,6 +7843,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-serde/-/middleware-serde-3.292.0.tgz", "integrity": "sha512-6hN9mTQwSvV8EcGvtXbS/MpK7WMCokUku5Wu7X24UwCNMVkoRHLIkYcxHcvBTwttuOU0d8hph1/lIX4dkLwkQw==", "optional": true, + "peer": true, "requires": { "@aws-sdk/types": "3.292.0", "tslib": "^2.3.1" @@ -7793,7 +7853,8 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true } } }, @@ -7802,6 +7863,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-signing/-/middleware-signing-3.292.0.tgz", "integrity": "sha512-GVfoSjDjEQ4TaO6x9MffyP3uRV+2KcS5FtexLCYOM9pJcnE9tqq9FJOrZ1xl1g+YjUVKxo4x8lu3tpEtIb17qg==", "optional": true, + "peer": true, "requires": { "@aws-sdk/property-provider": "3.292.0", "@aws-sdk/protocol-http": "3.292.0", @@ -7815,7 +7877,8 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true } } }, @@ -7824,6 +7887,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-stack/-/middleware-stack-3.292.0.tgz", "integrity": "sha512-WdQpRkuMysrEwrkByCM1qCn2PPpFGGQ2iXqaFha5RzCdZDlxJni9cVNb6HzWUcgjLEYVTXCmOR9Wxm3CNW44Qg==", "optional": true, + "peer": true, "requires": { "tslib": "^2.3.1" }, @@ -7832,7 +7896,8 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true } } }, @@ -7841,6 +7906,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.293.0.tgz", "integrity": "sha512-gZ7/e6XwpKk9mvgA78q4Ffc796jTn02TUKx2qMDnkLVbeJXBNN2jnvYEKq8v70+o7fd/ALRudg8gBDmkkhM/Hw==", "optional": true, + "peer": true, "requires": { "@aws-sdk/protocol-http": "3.292.0", "@aws-sdk/types": "3.292.0", @@ -7852,7 +7918,8 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true } } }, @@ -7861,6 +7928,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/node-config-provider/-/node-config-provider-3.292.0.tgz", "integrity": "sha512-S3NnC9dQ5GIbJYSDIldZb4zdpCOEua1tM7bjYL3VS5uqCEM93kIi/o/UkIUveMp/eqTS2LJa5HjNIz5Te6je0A==", "optional": true, + "peer": true, "requires": { "@aws-sdk/property-provider": "3.292.0", "@aws-sdk/shared-ini-file-loader": "3.292.0", @@ -7872,7 +7940,8 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true } } }, @@ -7881,6 +7950,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/node-http-handler/-/node-http-handler-3.292.0.tgz", "integrity": "sha512-L/E3UDSwXLXjt1XWWh0RBD55F+aZI1AEdPwdES9i1PjnZLyuxuDhEDptVibNN56+I9/4Q3SbmuVRVlOD0uzBag==", "optional": true, + "peer": true, "requires": { "@aws-sdk/abort-controller": "3.292.0", "@aws-sdk/protocol-http": "3.292.0", @@ -7893,7 +7963,8 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true } } }, @@ -7902,6 +7973,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/property-provider/-/property-provider-3.292.0.tgz", "integrity": "sha512-dHArSvsiqhno/g55N815gXmAMrmN8DP7OeFNqJ4wJG42xsF2PFN3DAsjIuHuXMwu+7A3R1LHqIpvv0hA9KeoJQ==", "optional": true, + "peer": true, "requires": { "@aws-sdk/types": "3.292.0", "tslib": "^2.3.1" @@ -7911,7 +7983,8 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true } } }, @@ -7920,6 +7993,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/protocol-http/-/protocol-http-3.292.0.tgz", "integrity": "sha512-NLi4fq3k41aXIh1I97yX0JTy+3p6aW1NdwFwdMa674z86QNfb4SfRQRZBQe9wEnAZ/eWHVnlKIuII+U1URk/Kg==", "optional": true, + "peer": true, "requires": { "@aws-sdk/types": "3.292.0", "tslib": "^2.3.1" @@ -7929,7 +8003,8 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true } } }, @@ -7938,6 +8013,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/querystring-builder/-/querystring-builder-3.292.0.tgz", "integrity": "sha512-XElIFJaReIm24eEvBtV2dOtZvcm3gXsGu/ftG8MLJKbKXFKpAP1q+K6En0Bs7/T88voKghKdKpKT+eZUWgTqlg==", "optional": true, + "peer": true, "requires": { "@aws-sdk/types": "3.292.0", "@aws-sdk/util-uri-escape": "3.292.0", @@ -7948,7 +8024,8 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true } } }, @@ -7957,6 +8034,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/querystring-parser/-/querystring-parser-3.292.0.tgz", "integrity": "sha512-iTYpYo7a8X9RxiPbjjewIpm6XQPx2EOcF1dWCPRII9EFlmZ4bwnX+PDI36fIo9oVs8TIKXmwNGODU9nsg7CSAw==", "optional": true, + "peer": true, "requires": { "@aws-sdk/types": "3.292.0", "tslib": "^2.3.1" @@ -7966,7 +8044,8 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true } } }, @@ -7974,13 +8053,15 @@ "version": "3.292.0", "resolved": "https://registry.npmjs.org/@aws-sdk/service-error-classification/-/service-error-classification-3.292.0.tgz", "integrity": "sha512-X1k3sixCeC45XSNHBe+kRBQBwPDyTFtFITb8O5Qw4dS9XWGhrUJT4CX0qE5aj8qP3F9U5nRizs9c2mBVVP0Caw==", - "optional": true + "optional": true, + "peer": true }, "@aws-sdk/shared-ini-file-loader": { "version": "3.292.0", "resolved": "https://registry.npmjs.org/@aws-sdk/shared-ini-file-loader/-/shared-ini-file-loader-3.292.0.tgz", "integrity": "sha512-Av2TTYg1Jig2kbkD56ybiqZJB6vVrYjv1W5UQwY/q3nA/T2mcrgQ20ByCOt5Bv9VvY7FSgC+znj+L4a7RLGmBg==", "optional": true, + "peer": true, "requires": { "@aws-sdk/types": "3.292.0", "tslib": "^2.3.1" @@ -7990,7 +8071,8 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true } } }, @@ -7999,6 +8081,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4/-/signature-v4-3.292.0.tgz", "integrity": "sha512-+rw47VY5mvBecn13tDQTl1ipGWg5tE63faWgmZe68HoBL87ZiDzsd7bUKOvjfW21iMgWlwAppkaNNQayYRb2zg==", "optional": true, + "peer": true, "requires": { "@aws-sdk/is-array-buffer": "3.292.0", "@aws-sdk/types": "3.292.0", @@ -8013,7 +8096,8 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true } } }, @@ -8022,6 +8106,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/smithy-client/-/smithy-client-3.292.0.tgz", "integrity": "sha512-S8PKzjPkZ6SXYZuZiU787dMsvQ0d/LFEhw2OI4Oe2An9Fc2IwJ2FYukyHoQJOV2tV0DiuMebPo7eMyQyjKElvA==", "optional": true, + "peer": true, "requires": { "@aws-sdk/middleware-stack": "3.292.0", "@aws-sdk/types": "3.292.0", @@ -8032,7 +8117,8 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true } } }, @@ -8041,6 +8127,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.294.0.tgz", "integrity": "sha512-6nwO04LtC5f4AsUvGZXyjaswuEK4Rr2VsuANpMKrPCgunRfI58a8YXLniudOSXN6e7CFJ6M3uo/h5YXqtnzGug==", "optional": true, + "peer": true, "requires": { "@aws-sdk/client-sso-oidc": "3.294.0", "@aws-sdk/property-provider": "3.292.0", @@ -8053,7 +8140,8 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true } } }, @@ -8062,6 +8150,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.292.0.tgz", "integrity": "sha512-1teYAY2M73UXZxMAxqZxVS2qwXjQh0OWtt7qyLfha0TtIk/fZ1hRwFgxbDCHUFcdNBSOSbKH/ESor90KROXLCQ==", "optional": true, + "peer": true, "requires": { "tslib": "^2.3.1" }, @@ -8070,7 +8159,8 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true } } }, @@ -8079,6 +8169,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/url-parser/-/url-parser-3.292.0.tgz", "integrity": "sha512-NZeAuZCk1x6TIiWuRfbOU6wHPBhf0ly2qOHzWut4BCH+b4RrDmFF8EmXcH1auEfGhE7yRyR6XqIN0t3S+hYACA==", "optional": true, + "peer": true, "requires": { "@aws-sdk/querystring-parser": "3.292.0", "@aws-sdk/types": "3.292.0", @@ -8089,7 +8180,8 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true } } }, @@ -8098,6 +8190,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/util-base64/-/util-base64-3.292.0.tgz", "integrity": "sha512-zjNCwNdy617yFvEjZorepNWXB2sQCVfsShCwFy/kIQ5iW5tT2jQKaqc0K77diU9atkooxw9p1W9m9sOgrkOFNw==", "optional": true, + "peer": true, "requires": { "@aws-sdk/util-buffer-from": "3.292.0", "tslib": "^2.3.1" @@ -8107,7 +8200,8 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true } } }, @@ -8116,6 +8210,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/util-body-length-browser/-/util-body-length-browser-3.292.0.tgz", "integrity": "sha512-Wd/BM+JsMiKvKs/bN3z6TredVEHh2pKudGfg3CSjTRpqFpOG903KDfyHBD42yg5PuCHoHoewJvTPKwgn7/vhaw==", "optional": true, + "peer": true, "requires": { "tslib": "^2.3.1" }, @@ -8124,7 +8219,8 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true } } }, @@ -8133,6 +8229,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/util-body-length-node/-/util-body-length-node-3.292.0.tgz", "integrity": "sha512-BBgipZ2P6RhogWE/qj0oqpdlyd3iSBYmb+aD/TBXwB2lA/X8A99GxweBd/kp06AmcJRoMS9WIXgbWkiiBlRlSA==", "optional": true, + "peer": true, "requires": { "tslib": "^2.3.1" }, @@ -8141,7 +8238,8 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true } } }, @@ -8150,6 +8248,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/util-buffer-from/-/util-buffer-from-3.292.0.tgz", "integrity": "sha512-RxNZjLoXNxHconH9TYsk5RaEBjSgTtozHeyIdacaHPj5vlQKi4hgL2hIfKeeNiAfQEVjaUFF29lv81xpNMzVMQ==", "optional": true, + "peer": true, "requires": { "@aws-sdk/is-array-buffer": "3.292.0", "tslib": "^2.3.1" @@ -8159,7 +8258,8 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true } } }, @@ -8168,6 +8268,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/util-config-provider/-/util-config-provider-3.292.0.tgz", "integrity": "sha512-t3noYll6bPRSxeeNNEkC5czVjAiTPcsq00OwfJ2xyUqmquhLEfLwoJKmrT1uP7DjIEXdUtfoIQ2jWiIVm/oO5A==", "optional": true, + "peer": true, "requires": { "tslib": "^2.3.1" }, @@ -8176,7 +8277,8 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true } } }, @@ -8185,6 +8287,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/util-defaults-mode-browser/-/util-defaults-mode-browser-3.292.0.tgz", "integrity": "sha512-7+zVUlMGfa8/KT++9humHo6IDxTnxMCmWUj5jVNlkpk6h7Ecmppf7aXotviyVIA43lhtz0p2AErs0N0ekEUK+w==", "optional": true, + "peer": true, "requires": { "@aws-sdk/property-provider": "3.292.0", "@aws-sdk/types": "3.292.0", @@ -8196,7 +8299,8 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true } } }, @@ -8205,6 +8309,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/util-defaults-mode-node/-/util-defaults-mode-node-3.292.0.tgz", "integrity": "sha512-SSIw85eF4BVs0fOJRyshT+R3b/UmBPhiVKCUZm2rq6+lIGkDPiSwQU3d/80AhXtiL5SFT/IzAKKgQd8qMa7q3A==", "optional": true, + "peer": true, "requires": { "@aws-sdk/config-resolver": "3.292.0", "@aws-sdk/credential-provider-imds": "3.292.0", @@ -8218,7 +8323,8 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true } } }, @@ -8227,6 +8333,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.293.0.tgz", "integrity": "sha512-R/99aNV49Refpv5guiUjEUrZYlvnfaNBniB+/ZtMO3ixxUopapssCrUivuJrmhccmrYaTCZw7dRzIWjU1jJhKg==", "optional": true, + "peer": true, "requires": { "@aws-sdk/types": "3.292.0", "tslib": "^2.3.1" @@ -8236,7 +8343,8 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true } } }, @@ -8245,6 +8353,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/util-hex-encoding/-/util-hex-encoding-3.292.0.tgz", "integrity": "sha512-qBd5KFIUywQ3qSSbj814S2srk0vfv8A6QMI+Obs1y2LHZFdQN5zViptI4UhXhKOHe+NnrHWxSuLC/LMH6q3SmA==", "optional": true, + "peer": true, "requires": { "tslib": "^2.3.1" }, @@ -8253,7 +8362,8 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true } } }, @@ -8262,6 +8372,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.292.0.tgz", "integrity": "sha512-6xnFJXZI9pKw5lQCDvuWA5PnOaUtNRKWwdxvGkkLx5orboFaoVMS6zowjSQxwVNRjW82u6dYNkhmj9mZ8VSjWg==", "optional": true, + "peer": true, "requires": { "tslib": "^2.3.1" }, @@ -8270,7 +8381,8 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true } } }, @@ -8279,6 +8391,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/util-middleware/-/util-middleware-3.292.0.tgz", "integrity": "sha512-KjhS7flfoBKDxbiBZjLjMvEizXgjfQb7GQEItgzGoI9rfGCmZtvqCcqQQoIlxb8bIzGRggAUHtBGWnlLbpb+GQ==", "optional": true, + "peer": true, "requires": { "tslib": "^2.3.1" }, @@ -8287,7 +8400,8 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true } } }, @@ -8296,6 +8410,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/util-retry/-/util-retry-3.292.0.tgz", "integrity": "sha512-JEHyF7MpVeRF5uR4LDYgpOKcFpOPiAj8TqN46SVOQQcL1K+V7cSr7O7N7J6MwJaN9XOzAcBadeIupMm7/BFbgw==", "optional": true, + "peer": true, "requires": { "@aws-sdk/service-error-classification": "3.292.0", "tslib": "^2.3.1" @@ -8305,7 +8420,8 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true } } }, @@ -8314,6 +8430,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/util-uri-escape/-/util-uri-escape-3.292.0.tgz", "integrity": "sha512-hOQtUMQ4VcQ9iwKz50AoCp1XBD5gJ9nly/gJZccAM7zSA5mOO8RRKkbdonqquVHxrO0CnYgiFeCh3V35GFecUw==", "optional": true, + "peer": true, "requires": { "tslib": "^2.3.1" }, @@ -8322,7 +8439,8 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true } } }, @@ -8331,6 +8449,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.292.0.tgz", "integrity": "sha512-dld+lpC3QdmTQHdBWJ0WFDkXDSrJgfz03q6mQ8+7H+BC12ZhT0I0g9iuvUjolqy7QR00OxOy47Y9FVhq8EC0Gg==", "optional": true, + "peer": true, "requires": { "@aws-sdk/types": "3.292.0", "bowser": "^2.11.0", @@ -8341,7 +8460,8 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true } } }, @@ -8350,6 +8470,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.292.0.tgz", "integrity": "sha512-f+NfIMal5E61MDc5WGhUEoicr7b1eNNhA+GgVdSB/Hg5fYhEZvFK9RZizH5rrtsLjjgcr9nPYSR7/nDKCJLumw==", "optional": true, + "peer": true, "requires": { "@aws-sdk/node-config-provider": "3.292.0", "@aws-sdk/types": "3.292.0", @@ -8360,7 +8481,8 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true } } }, @@ -8369,6 +8491,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/util-utf8/-/util-utf8-3.292.0.tgz", "integrity": "sha512-FPkj+Z59/DQWvoVu2wFaRncc3KVwe/pgK3MfVb0Lx+Ibey5KUx+sNpJmYcVYHUAe/Nv/JeIpOtYuC96IXOnI6w==", "optional": true, + "peer": true, "requires": { "@aws-sdk/util-buffer-from": "3.292.0", "tslib": "^2.3.1" @@ -8378,7 +8501,8 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true } } }, @@ -8387,6 +8511,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/util-utf8-browser/-/util-utf8-browser-3.259.0.tgz", "integrity": "sha512-UvFa/vR+e19XookZF8RzFZBrw2EUkQWxiBW0yYQAhvk3C+QVGl0H3ouca8LDBlBfQKXwmW3huo/59H8rwb1wJw==", "optional": true, + "peer": true, "requires": { "tslib": "^2.3.1" }, @@ -8395,7 +8520,8 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true + "optional": true, + "peer": true } } }, @@ -8831,96 +8957,6 @@ "integrity": "sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==", "dev": true }, - "@types/mongoose-unique-validator": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/@types/mongoose-unique-validator/-/mongoose-unique-validator-1.0.7.tgz", - "integrity": "sha512-wgPWN7x9mHdy8x1jcluDF7EDWFb/s8LD7skLr012C8i2Q3tUhdTlp3JxRhFbEM3TD7J1INaoTrWaSaguhuqilQ==", - "dev": true, - "requires": { - "mongoose": "^6.3.0" - }, - "dependencies": { - "bson": { - "version": "4.7.2", - "resolved": "https://registry.npmjs.org/bson/-/bson-4.7.2.tgz", - "integrity": "sha512-Ry9wCtIZ5kGqkJoi6aD8KjxFZEx78guTQDnpXWiNthsxzrxAK/i8E6pCHAIZTbaEFWcOCvbecMukfK7XUvyLpQ==", - "dev": true, - "requires": { - "buffer": "^5.6.0" - } - }, - "buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "dev": true, - "requires": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, - "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "requires": { - "ms": "2.1.2" - }, - "dependencies": { - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - } - } - }, - "mongodb": { - "version": "4.14.0", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.14.0.tgz", - "integrity": "sha512-coGKkWXIBczZPr284tYKFLg+KbGPPLlSbdgfKAb6QqCFt5bo5VFZ50O3FFzsw4rnkqjwT6D8Qcoo9nshYKM7Mg==", - "dev": true, - "requires": { - "@aws-sdk/credential-providers": "^3.186.0", - "bson": "^4.7.0", - "mongodb-connection-string-url": "^2.5.4", - "saslprep": "^1.0.3", - "socks": "^2.7.1" - } - }, - "mongoose": { - "version": "6.10.3", - "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-6.10.3.tgz", - "integrity": "sha512-fZ3pIlQn7lM632r1l4qiU58lKrJ+FufKVG8TNeRXSChAeu9alCl5KoQ9bLw4jnQNYevSq9o+sqZmFDHP+EVW3g==", - "dev": true, - "requires": { - "bson": "^4.7.0", - "kareem": "2.5.1", - "mongodb": "4.14.0", - "mpath": "0.9.0", - "mquery": "4.0.3", - "ms": "2.1.3", - "sift": "16.0.1" - } - }, - "mquery": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/mquery/-/mquery-4.0.3.tgz", - "integrity": "sha512-J5heI+P08I6VJ2Ky3+33IpCdAvlYGTSUjwTPxkAr8i8EoduPMBX2OY/wa3IKZIQl7MU4SbFk8ndgSKyB/cl1zA==", - "dev": true, - "requires": { - "debug": "4.x" - } - }, - "ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true - } - } - }, "@types/morgan": { "version": "1.9.4", "resolved": "https://registry.npmjs.org/@types/morgan/-/morgan-1.9.4.tgz", @@ -9479,7 +9515,8 @@ "version": "2.11.0", "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.11.0.tgz", "integrity": "sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==", - "optional": true + "optional": true, + "peer": true }, "brace-expansion": { "version": "1.1.11", @@ -10412,6 +10449,7 @@ "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.1.2.tgz", "integrity": "sha512-CDYeykkle1LiA/uqQyNwYpFbyF6Axec6YapmpUP+/RHWIoR1zKjocdvNaTsxCxZzQ6v9MLXaSYm9Qq0thv0DHg==", "optional": true, + "peer": true, "requires": { "strnum": "^1.0.5" } @@ -12275,7 +12313,8 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz", "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==", - "optional": true + "optional": true, + "peer": true }, "supports-color": { "version": "7.2.0", diff --git a/package.json b/package.json index 88063fd..5ca8b49 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,6 @@ "@types/dicer": "^0.2.2", "@types/express": "^4.17.17", "@types/fs-extra": "^11.0.1", - "@types/mongoose-unique-validator": "^1.0.7", "@types/morgan": "^1.9.4", "@types/node": "^18.14.4", "@types/node-rsa": "^1.1.1", diff --git a/src/models/pnid.ts b/src/models/pnid.ts index 8849822..dd09b8f 100644 --- a/src/models/pnid.ts +++ b/src/models/pnid.ts @@ -172,8 +172,8 @@ PNIDSchema.method('generateMiiImages', async function generateMiiImages(): Promi const mii: Mii = new Mii(Buffer.from(miiData, 'base64')); const miiStudioUrl: string = mii.studioUrl({ type: 'face', - width: '128', - instanceCount: '1', + width: 128, + instanceCount: 1, }); const miiStudioNormalFaceImageData: Buffer = await got(miiStudioUrl).buffer(); const pngData: { @@ -193,8 +193,8 @@ PNIDSchema.method('generateMiiImages', async function generateMiiImages(): Promi const miiStudioExpressionUrl: string = mii.studioUrl({ type: 'face', expression: expression, - width: '128', - instanceCount: '1', + width: 128, + instanceCount: 1, }); const miiStudioExpressionImageData: Buffer = await got(miiStudioExpressionUrl).buffer(); await uploadCDNAsset('pn-cdn', `${userMiiKey}/${expression}.png`, miiStudioExpressionImageData, 'public-read'); @@ -202,8 +202,8 @@ PNIDSchema.method('generateMiiImages', async function generateMiiImages(): Promi const miiStudioBodyUrl: string = mii.studioUrl({ type: 'all_body', - width: '270', - instanceCount: '1', + width: 270, + instanceCount: 1, }); const miiStudioBodyImageData: Buffer = await got(miiStudioBodyUrl).buffer(); await uploadCDNAsset('pn-cdn', `${userMiiKey}/body.png`, miiStudioBodyImageData, 'public-read'); diff --git a/src/types/express-subdomain.d.ts b/src/types/express-subdomain.d.ts new file mode 100644 index 0000000..cb1cbdc --- /dev/null +++ b/src/types/express-subdomain.d.ts @@ -0,0 +1,3 @@ +declare module 'express-subdomain'; + +// TODO - Add proper types \ No newline at end of file diff --git a/src/types/image-pixels.d.ts b/src/types/image-pixels.d.ts new file mode 100644 index 0000000..bd142c3 --- /dev/null +++ b/src/types/image-pixels.d.ts @@ -0,0 +1,3 @@ +declare module 'image-pixels'; + +// TODO - Add proper types \ No newline at end of file diff --git a/src/types/mii-js.d.ts b/src/types/mii-js.d.ts new file mode 100644 index 0000000..9421b0f --- /dev/null +++ b/src/types/mii-js.d.ts @@ -0,0 +1,98 @@ +declare module 'mii-js'; + +// TODO - These have limits, and should be validated + +type MiiStudioURLOptions = { + type?: string; + expression?: string; + width?: number; + bgColor?: string; + clothesColor?: string; + cameraXRotate?: number; + cameraYRotate?: number; + cameraZRotate?: number; + characterXRotate?: number; + characterYRotate?: number; + characterZRotate?: number; + lightXDirection?: number; + lightYDirection?: number; + lightZDirection?: number; + lightDirectionMode?: string; + instanceCount?: number; + instanceRotationMode?: string; +}; + +type Mii = { + version: number; + allowCopying: boolean; + profanityFlag: boolean; + regionLock: number; + characterSet: number; + pageIndex: number; + slotIndex: number; + unknown1: number; + deviceOrigin: number; + systemId: Buffer; + normalMii: boolean; + dsMii: boolean; + nonUserMii: boolean; + isValid: boolean; + creationTime: number; + consoleMAC: Buffer; + gender: number; + birthMonth: number; + birthDay: number; + favoriteColor: number; + favorite: boolean; + miiName: string; + height: number; + build: number; + disableSharing: boolean; + faceType: number; + skinColor: number; + wrinklesType: number; + makeupType: number; + hairType: number; + hairColor: number; + flipHair: boolean; + eyeType: number; + eyeColor: number; + eyeScale: number; + eyeVerticalStretch: number; + eyeRotation: number; + eyeSpacing: number; + eyeYPosition: number; + eyebrowType: number; + eyebrowColor: number; + eyebrowScale: number; + eyebrowVerticalStretch: number; + eyebrowRotation: number; + eyebrowSpacing: number; + eyebrowYPosition: number; + noseType: number; + noseScale: number; + noseYPosition: number; + mouthType: number; + mouthColor: number; + mouthScale: number; + mouthHorizontalStretch: number; + mouthYPosition: number; + mustacheType: number; + unknown2: number; + beardType: number; + facialHairColor: number; + mustacheScale: number; + mustacheYPosition: number; + glassesType: number; + glassesColor: number; + glassesScale: number; + glassesYPosition: number; + moleEnabled: boolean; + moleScale: number; + moleXPosition: number; + moleYPosition: number; + creatorName: string; + checksum: number; + + studioUrl: (options: MiiStudioURLOptions) => string +}; \ No newline at end of file diff --git a/src/types/mongoose-unique-validator.d.ts b/src/types/mongoose-unique-validator.d.ts new file mode 100644 index 0000000..0909028 --- /dev/null +++ b/src/types/mongoose-unique-validator.d.ts @@ -0,0 +1 @@ +declare module 'mongoose-unique-validator'; \ No newline at end of file diff --git a/src/types/tga.d.ts b/src/types/tga.d.ts new file mode 100644 index 0000000..e1c6188 --- /dev/null +++ b/src/types/tga.d.ts @@ -0,0 +1,3 @@ +declare module 'tga'; + +// TODO - Add proper types \ No newline at end of file From b309eecff9503ae4e1977900f70eccf81ecb185d Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Sat, 18 Mar 2023 16:58:20 -0400 Subject: [PATCH 043/219] Finish strict mode errors --- src/models/nex-account.ts | 2 +- src/nintendo-certificate.ts | 21 ++++++++-------- src/services/datastore/routes/upload.ts | 8 +++++-- src/services/nnid/routes/admin.ts | 23 +++++++++++++----- src/services/nnid/routes/content.ts | 2 +- src/services/nnid/routes/people.ts | 32 +++++++++++++++++++++---- src/types/mii-js.d.ts | 3 ++- 7 files changed, 65 insertions(+), 26 deletions(-) diff --git a/src/models/nex-account.ts b/src/models/nex-account.ts index abfa407..cddd72b 100644 --- a/src/models/nex-account.ts +++ b/src/models/nex-account.ts @@ -43,7 +43,7 @@ NEXAccountSchema.method('generatePID', async function generatePID(): Promise { - // TODO - strict mode yells here - fileName = RE_FILE_NAME.exec(header['content-disposition'][0])[1]; + const contentDisposition = header['content-disposition' as keyof object]; + const regexResult = RE_FILE_NAME.exec(contentDisposition); + + if (regexResult) { + fileName = regexResult[0]; + } }); part.on('data', (data: Buffer | string) => { diff --git a/src/services/nnid/routes/admin.ts b/src/services/nnid/routes/admin.ts index c5f44d6..6a4437c 100644 --- a/src/services/nnid/routes/admin.ts +++ b/src/services/nnid/routes/admin.ts @@ -39,10 +39,10 @@ router.get('/mapped_ids', async (request: express.Request, response: express.Res queryOutput = 'pid'; } - // This is slower than PNID.where() - // but it ensures that each input - // ALWAYS has an output and filters - // out unwanted input/output types + // * This is slower than PNID.where() + // * but it ensures that each input + // * ALWAYS has an output and filters + // * out unwanted input/output types const results: { in_id: string; out_id: string; @@ -64,8 +64,19 @@ router.get('/mapped_ids', async (request: express.Request, response: express.Res pid?: number; } = {}; - // TODO - TS strict mode...what? - query[queryInput] = input; + if (queryInput === 'usernameLower') { + query.usernameLower = input; + } + + if (queryInput === 'pid') { + query.pid = Number(input); + + if (isNaN(query.pid)) { + // * Bail early + results.push(result); + continue; + } + } const searchResult: HydratedPNIDDocument | null = await PNID.findOne(query); diff --git a/src/services/nnid/routes/content.ts b/src/services/nnid/routes/content.ts index 5d45aa6..56dc2f5 100644 --- a/src/services/nnid/routes/content.ts +++ b/src/services/nnid/routes/content.ts @@ -154,7 +154,7 @@ router.get('/time_zones/:countryCode/:language', (request: express.Request, resp const countryCode: string = request.params.countryCode; const language: string = request.params.language; - const regionLanguages: RegionLanguages = timezones[countryCode]; + const regionLanguages: RegionLanguages = timezones[countryCode as keyof typeof timezones]; const regionTimezones: RegionTimezones = regionLanguages[language] ? regionLanguages[language] : Object.values(regionLanguages)[0]; response.send(xmlbuilder.create({ diff --git a/src/services/nnid/routes/people.ts b/src/services/nnid/routes/people.ts index 239c9a0..3258375 100644 --- a/src/services/nnid/routes/people.ts +++ b/src/services/nnid/routes/people.ts @@ -56,7 +56,7 @@ router.get('/:username', async (request: express.Request, response: express.Resp * Description: Registers a new NNID */ router.post('/', ratelimit, deviceCertificateMiddleware, async (request: express.Request, response: express.Response) => { - if (!request.certificate.valid) { + if (!request.certificate || !request.certificate.valid) { // TODO: Change this to a different error response.status(400); @@ -118,9 +118,20 @@ router.post('/', ratelimit, deviceCertificateMiddleware, async (request: express const language: string = person.get('language'); const timezoneName: string = person.get('tz_name'); - const regionLanguages: RegionLanguages = timezones[countryCode]; + const regionLanguages: RegionLanguages = timezones[countryCode as keyof typeof timezones]; const regionTimezones: RegionTimezones = regionLanguages[language] ? regionLanguages[language] : Object.values(regionLanguages)[0]; - const timezone: RegionTimezone = regionTimezones.find(tz => tz.area === timezoneName); + let timezone: RegionTimezone | undefined = regionTimezones.find(tz => tz.area === timezoneName); + + if (!timezone) { + // TODO - Change this, handle the error + timezone = { + area: 'America/New_York', + language: 'en', + name: 'Eastern Time (US & Canada)', + order: '11', + utc_offset: '-14400' + }; + } pnid = new PNID({ pid: nexAccount.get('pid'), @@ -475,9 +486,20 @@ router.put('/@me', async (request: express.Request, response: express.Response) const marketingFlag: boolean = person.get('marketing_flag') ? person.get('marketing_flag') === 'Y' : pnid.get('flags.marketing'); const offDeviceFlag: boolean = person.get('off_device_flag') ? person.get('off_device_flag') === 'Y' : pnid.get('flags.off_device'); - const regionLanguages: RegionLanguages = timezones[countryCode]; + const regionLanguages: RegionLanguages = timezones[countryCode as keyof typeof timezones]; const regionTimezones: RegionTimezones = regionLanguages[language] ? regionLanguages[language] : Object.values(regionLanguages)[0]; - const timezone: RegionTimezone = regionTimezones.find(tz => tz.area === timezoneName); + let timezone: RegionTimezone | undefined = regionTimezones.find(tz => tz.area === timezoneName); + + if (!timezone) { + // TODO - Change this, handle the error + timezone = { + area: 'America/New_York', + language: 'en', + name: 'Eastern Time (US & Canada)', + order: '11', + utc_offset: '-14400' + }; + } if (person.get('password')) { const primaryPasswordHash: string = nintendoPasswordHash(person.get('password'), pnid.get('pid')); diff --git a/src/types/mii-js.d.ts b/src/types/mii-js.d.ts index 9421b0f..a63a5b8 100644 --- a/src/types/mii-js.d.ts +++ b/src/types/mii-js.d.ts @@ -94,5 +94,6 @@ type Mii = { creatorName: string; checksum: number; - studioUrl: (options: MiiStudioURLOptions) => string + studioUrl: (options: MiiStudioURLOptions) => string; + encode: () => Buffer; }; \ No newline at end of file From a2415faab88f469face22041168187072c83917f Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Sat, 18 Mar 2023 17:00:12 -0400 Subject: [PATCH 044/219] Removed unnecessary regex escapes --- src/services/api/routes/v1/register.ts | 12 ++++++------ src/services/api/routes/v1/resetPassword.ts | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/services/api/routes/v1/register.ts b/src/services/api/routes/v1/register.ts index b1e37b1..ef6cda8 100644 --- a/src/services/api/routes/v1/register.ts +++ b/src/services/api/routes/v1/register.ts @@ -22,15 +22,15 @@ import { HydratedPNIDDocument } from '@/types/mongoose/pnid'; const router: express.Router = express.Router(); -const PNID_VALID_CHARACTERS_REGEX: RegExp = /^[\w\-\.]*$/gm; -const PNID_PUNCTUATION_START_REGEX: RegExp = /^[\_\-\.]/gm; -const PNID_PUNCTUATION_END_REGEX: RegExp = /[\_\-\.]$/gm; -const PNID_PUNCTUATION_DUPLICATE_REGEX: RegExp = /[\_\-\.]{2,}/gm; +const PNID_VALID_CHARACTERS_REGEX: RegExp = /^[\w\-.]*$/gm; +const PNID_PUNCTUATION_START_REGEX: RegExp = /^[_\-.]/gm; +const PNID_PUNCTUATION_END_REGEX: RegExp = /[_\-.]$/gm; +const PNID_PUNCTUATION_DUPLICATE_REGEX: RegExp = /[_\-.]{2,}/gm; // This sucks const PASSWORD_WORD_OR_NUMBER_REGEX: RegExp = /(?=.*[a-zA-Z])(?=.*\d).*/; -const PASSWORD_WORD_OR_PUNCTUATION_REGEX: RegExp = /(?=.*[a-zA-Z])(?=.*[\_\-\.]).*/; -const PASSWORD_NUMBER_OR_PUNCTUATION_REGEX: RegExp = /(?=.*\d)(?=.*[\_\-\.]).*/; +const PASSWORD_WORD_OR_PUNCTUATION_REGEX: RegExp = /(?=.*[a-zA-Z])(?=.*[_\-.]).*/; +const PASSWORD_NUMBER_OR_PUNCTUATION_REGEX: RegExp = /(?=.*\d)(?=.*[_\-.]).*/; const PASSWORD_REPEATED_CHARACTER_REGEX: RegExp = /(.)\1\1/; const DEFAULT_MII_DATA: Buffer = Buffer.from('AwAAQOlVognnx0GC2/uogAOzuI0n2QAAAEBEAGUAZgBhAHUAbAB0AAAAAAAAAEBAAAAhAQJoRBgmNEYUgRIXaA0AACkAUkhQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGm9', 'base64'); diff --git a/src/services/api/routes/v1/resetPassword.ts b/src/services/api/routes/v1/resetPassword.ts index 95266f7..0a52acf 100644 --- a/src/services/api/routes/v1/resetPassword.ts +++ b/src/services/api/routes/v1/resetPassword.ts @@ -9,8 +9,8 @@ const router: express.Router = express.Router(); // This sucks const PASSWORD_WORD_OR_NUMBER_REGEX: RegExp = /(?=.*[a-zA-Z])(?=.*\d).*/; -const PASSWORD_WORD_OR_PUNCTUATION_REGEX: RegExp = /(?=.*[a-zA-Z])(?=.*[\_\-\.]).*/; -const PASSWORD_NUMBER_OR_PUNCTUATION_REGEX: RegExp = /(?=.*\d)(?=.*[\_\-\.]).*/; +const PASSWORD_WORD_OR_PUNCTUATION_REGEX: RegExp = /(?=.*[a-zA-Z])(?=.*[_\-.]).*/; +const PASSWORD_NUMBER_OR_PUNCTUATION_REGEX: RegExp = /(?=.*\d)(?=.*[_\-.]).*/; const PASSWORD_REPEATED_CHARACTER_REGEX: RegExp = /(.)\1\1/; router.post('/', async (request: express.Request, response: express.Response) => { From 5c7738d1d0a6a3ec2e87ec5d8d6b0318cf10393a Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Sat, 18 Mar 2023 17:04:32 -0400 Subject: [PATCH 045/219] Changed ACCESS_LEVEL to enum --- src/types/mongoose/nex-account.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/types/mongoose/nex-account.ts b/src/types/mongoose/nex-account.ts index 0e89b51..99342e4 100644 --- a/src/types/mongoose/nex-account.ts +++ b/src/types/mongoose/nex-account.ts @@ -1,7 +1,12 @@ import { Model, HydratedDocument } from 'mongoose'; type DEVICE = 'wiiu' | '3ds'; -type ACCESS_LEVEL = 0 | 1 | 2 | 3; +enum ACCESS_LEVEL { + User = 0, + Admin = 1, + Owner = 2, + Dev = 3 +} export interface INEXAccount { device_type: DEVICE; From a9de4d94dcc63b55613bd5c8a1fdaeb363919714 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Sat, 18 Mar 2023 17:07:46 -0400 Subject: [PATCH 046/219] Removed useless await in nasc middleware --- src/middleware/nasc.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/middleware/nasc.ts b/src/middleware/nasc.ts index 9308bb9..301e6f4 100644 --- a/src/middleware/nasc.ts +++ b/src/middleware/nasc.ts @@ -128,7 +128,7 @@ async function NASCMiddleware(request: express.Request, response: express.Respon try { // Create new NEX account - const nexAccount: HydratedNEXAccountDocument = await new NEXAccount({ + const nexAccount: HydratedNEXAccountDocument = new NEXAccount({ device_type: '3ds', password }); From b28aababbe3a2818de500c1d253336a27899d190 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Sat, 18 Mar 2023 17:18:39 -0400 Subject: [PATCH 047/219] Made logger object as const --- src/logger.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/logger.ts b/src/logger.ts index 798ef62..917b677 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -6,13 +6,13 @@ colors.enable(); const root: string = process.env.PN_ACT_LOGGER_PATH ? process.env.PN_ACT_LOGGER_PATH : `${__dirname}/..`; fs.ensureDirSync(`${root}/logs`); -const streams: { [key: string]: fs.WriteStream } = { +const streams = { latest: fs.createWriteStream(`${root}/logs/latest.log`), success: fs.createWriteStream(`${root}/logs/success.log`), error: fs.createWriteStream(`${root}/logs/error.log`), warn: fs.createWriteStream(`${root}/logs/warn.log`), info: fs.createWriteStream(`${root}/logs/info.log`) -}; +} as const; export function LOG_SUCCESS(input: string): void { const time: Date = new Date(); From a95d4e10a615b1bfaa138b547295d148acfbab82 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Sun, 19 Mar 2023 11:52:43 -0400 Subject: [PATCH 048/219] Handle arrays and proper types on headers and query strings --- package-lock.json | 64 ++++++++++++++++++++++++---- package.json | 2 + src/middleware/api.ts | 3 +- src/middleware/client-header.ts | 5 ++- src/middleware/device-certificate.ts | 3 +- src/middleware/pnid.ts | 3 +- src/middleware/ratelimit.ts | 8 +++- src/middleware/xml-parser.ts | 5 ++- src/nintendo-certificate.ts | 4 +- src/server.ts | 14 ++++-- src/services/api/routes/v1/email.ts | 4 +- src/services/local-cdn/routes/get.ts | 2 +- src/services/nnid/routes/admin.ts | 28 +++++++----- src/services/nnid/routes/miis.ts | 20 ++++++--- src/services/nnid/routes/people.ts | 27 +++++++++--- src/services/nnid/routes/provider.ts | 33 ++++++++++++-- src/types/common/safe-qs.ts | 3 ++ src/util.ts | 54 +++++++++++++++++++++++ 18 files changed, 230 insertions(+), 52 deletions(-) create mode 100644 src/types/common/safe-qs.ts diff --git a/package-lock.json b/package-lock.json index dc8a274..3c28cac 100644 --- a/package-lock.json +++ b/package-lock.json @@ -33,6 +33,7 @@ "nodemailer": "^6.4.2", "redis": "^4.3.1", "tga": "^1.0.4", + "typescript-is": "^0.19.0", "validator": "^13.7.0", "xmlbuilder": "^13.0.2", "xmlbuilder2": "0.0.4" @@ -48,6 +49,7 @@ "@types/node": "^18.14.4", "@types/node-rsa": "^1.1.1", "@types/nodemailer": "^6.4.7", + "@types/qs": "^6.9.7", "@types/validator": "^13.7.14", "@typescript-eslint/eslint-plugin": "^5.54.1", "@typescript-eslint/parser": "^5.54.1", @@ -5465,6 +5467,11 @@ "node": ">= 0.6" } }, + "node_modules/nested-error-stacks": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nested-error-stacks/-/nested-error-stacks-2.1.1.tgz", + "integrity": "sha512-9iN1ka/9zmX1ZvLV9ewJYEk9h7RyRRtqdK0woXcqohu8EWIerfPUjYJPg0ULy0UqP7cslmdGc8xKDJcojlKiaw==" + }, "node_modules/next-tick": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", @@ -5976,6 +5983,12 @@ "@redis/time-series": "1.0.4" } }, + "node_modules/reflect-metadata": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", + "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==", + "optional": true + }, "node_modules/regexpp": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", @@ -6679,14 +6692,12 @@ "node_modules/tslib": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "devOptional": true + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" }, "node_modules/tsutils": { "version": "3.21.0", "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", - "dev": true, "dependencies": { "tslib": "^1.8.1" }, @@ -6762,7 +6773,6 @@ "version": "4.9.5", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", - "dev": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -6771,6 +6781,24 @@ "node": ">=4.2.0" } }, + "node_modules/typescript-is": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/typescript-is/-/typescript-is-0.19.0.tgz", + "integrity": "sha512-SAJEx2cxbQZhfOjDEjPnQJt1qRS1M3wrKbUwvsywVHWGbMgM1dcIf9gPWNDS1/dgTa/7Iexk2mmAHHsP9MeCsA==", + "dependencies": { + "nested-error-stacks": "^2", + "tsutils": "^3.17.1" + }, + "engines": { + "node": ">=6.14.4" + }, + "optionalDependencies": { + "reflect-metadata": ">=0.1.12" + }, + "peerDependencies": { + "typescript": "^4.1.5" + } + }, "node_modules/universalify": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", @@ -11517,6 +11545,11 @@ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==" }, + "nested-error-stacks": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nested-error-stacks/-/nested-error-stacks-2.1.1.tgz", + "integrity": "sha512-9iN1ka/9zmX1ZvLV9ewJYEk9h7RyRRtqdK0woXcqohu8EWIerfPUjYJPg0ULy0UqP7cslmdGc8xKDJcojlKiaw==" + }, "next-tick": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", @@ -11892,6 +11925,12 @@ "@redis/time-series": "1.0.4" } }, + "reflect-metadata": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", + "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==", + "optional": true + }, "regexpp": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", @@ -12440,14 +12479,12 @@ "tslib": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "devOptional": true + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" }, "tsutils": { "version": "3.21.0", "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", - "dev": true, "requires": { "tslib": "^1.8.1" } @@ -12501,8 +12538,17 @@ "typescript": { "version": "4.9.5", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", - "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", - "dev": true + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==" + }, + "typescript-is": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/typescript-is/-/typescript-is-0.19.0.tgz", + "integrity": "sha512-SAJEx2cxbQZhfOjDEjPnQJt1qRS1M3wrKbUwvsywVHWGbMgM1dcIf9gPWNDS1/dgTa/7Iexk2mmAHHsP9MeCsA==", + "requires": { + "nested-error-stacks": "^2", + "reflect-metadata": ">=0.1.12", + "tsutils": "^3.17.1" + } }, "universalify": { "version": "0.1.2", diff --git a/package.json b/package.json index 5ca8b49..8e2b0dc 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,7 @@ "nodemailer": "^6.4.2", "redis": "^4.3.1", "tga": "^1.0.4", + "typescript-is": "^0.19.0", "validator": "^13.7.0", "xmlbuilder": "^13.0.2", "xmlbuilder2": "0.0.4" @@ -64,6 +65,7 @@ "@types/node": "^18.14.4", "@types/node-rsa": "^1.1.1", "@types/nodemailer": "^6.4.7", + "@types/qs": "^6.9.7", "@types/validator": "^13.7.14", "@typescript-eslint/eslint-plugin": "^5.54.1", "@typescript-eslint/parser": "^5.54.1", diff --git a/src/middleware/api.ts b/src/middleware/api.ts index e4fa82a..c196d0b 100644 --- a/src/middleware/api.ts +++ b/src/middleware/api.ts @@ -1,9 +1,10 @@ import express from 'express'; +import { getValueFromHeaders } from '@/util'; import { getUserBearer } from '@/database'; import { HydratedPNIDDocument } from '@/types/mongoose/pnid'; async function APIMiddleware(request: express.Request, _response: express.Response, next: express.NextFunction): Promise { - const authHeader: string | undefined = request.headers.authorization; + const authHeader: string | undefined = getValueFromHeaders(request.headers, 'authorization'); if (!authHeader || !(authHeader.startsWith('Bearer'))) { return next(); diff --git a/src/middleware/client-header.ts b/src/middleware/client-header.ts index 18b21fd..fc9c1e5 100644 --- a/src/middleware/client-header.ts +++ b/src/middleware/client-header.ts @@ -1,5 +1,6 @@ import express from 'express'; import xmlbuilder from 'xmlbuilder'; +import { getValueFromHeaders } from '@/util'; const VALID_CLIENT_ID_SECRET_PAIRS: { [key: string]: string } = { // * 'Key' is the client ID, 'Value' is the client secret @@ -14,8 +15,8 @@ function nintendoClientHeaderCheck(request: express.Request, response: express.R response.set('Server', 'Nintendo 3DS (http)'); response.set('X-Nintendo-Date', new Date().getTime().toString()); - const clientId: string = request.headers['x-nintendo-client-id'] as string; - const clientSecret: string = request.headers['x-nintendo-client-secret'] as string; + const clientId: string | undefined = getValueFromHeaders(request.headers, 'x-nintendo-client-id'); + const clientSecret: string | undefined = getValueFromHeaders(request.headers, 'x-nintendo-client-secret'); if ( !clientId || diff --git a/src/middleware/device-certificate.ts b/src/middleware/device-certificate.ts index e74c11c..9e7d224 100644 --- a/src/middleware/device-certificate.ts +++ b/src/middleware/device-certificate.ts @@ -1,8 +1,9 @@ import express from 'express'; import NintendoCertificate from '@/nintendo-certificate'; +import { getValueFromHeaders } from '@/util'; function deviceCertificateMiddleware(request: express.Request, _response: express.Response, next: express.NextFunction): void { - const certificate: string = request.headers['x-nintendo-device-cert'] as string; + const certificate: string | undefined = getValueFromHeaders(request.headers, 'x-nintendo-device-cert'); if (!certificate) { return next(); diff --git a/src/middleware/pnid.ts b/src/middleware/pnid.ts index 96f58cd..c1e0933 100644 --- a/src/middleware/pnid.ts +++ b/src/middleware/pnid.ts @@ -1,10 +1,11 @@ import express from 'express'; import xmlbuilder from 'xmlbuilder'; +import { getValueFromHeaders } from '@/util'; import { getUserBasic, getUserBearer } from '@/database'; import { HydratedPNIDDocument } from '@/types/mongoose/pnid'; async function PNIDMiddleware(request: express.Request, response: express.Response, next: express.NextFunction): Promise { - const authHeader: string | undefined = request.headers.authorization; + const authHeader: string | undefined = getValueFromHeaders(request.headers, 'authorization'); if (!authHeader || !(authHeader.startsWith('Bearer') || authHeader.startsWith('Basic'))) { return next(); diff --git a/src/middleware/ratelimit.ts b/src/middleware/ratelimit.ts index e6909f2..0cc8b38 100644 --- a/src/middleware/ratelimit.ts +++ b/src/middleware/ratelimit.ts @@ -1,12 +1,18 @@ import crypto from 'node:crypto'; import express from 'express'; import ratelimit from 'express-rate-limit'; +import { getValueFromHeaders } from '@/util'; export default ratelimit({ windowMs: 60 * 1000, max: 1, keyGenerator: (request: express.Request) => { - const data: string = request.headers['x-nintendo-device-cert'] as string; + let data: string | undefined = getValueFromHeaders(request.headers, 'x-nintendo-device-cert'); + + if (!data) { + data = request.ip; + } + return crypto.createHash('md5').update(data).digest('hex'); } }); \ No newline at end of file diff --git a/src/middleware/xml-parser.ts b/src/middleware/xml-parser.ts index cf1335e..31bb8b4 100644 --- a/src/middleware/xml-parser.ts +++ b/src/middleware/xml-parser.ts @@ -1,11 +1,12 @@ import express from 'express'; import xmlbuilder from 'xmlbuilder'; import { document as xmlParser } from 'xmlbuilder2'; +import { getValueFromHeaders } from '@/util'; function XMLMiddleware(request: express.Request, response: express.Response, next: express.NextFunction): void { if (request.method == 'POST' || request.method == 'PUT') { - const contentType: string | undefined = request.headers['content-type']; - const contentLength: string | undefined = request.headers['content-length']; + const contentType: string | undefined = getValueFromHeaders(request.headers, 'content-type'); + const contentLength: string | undefined = getValueFromHeaders(request.headers, 'content-length'); let body: string = ''; if ( diff --git a/src/nintendo-certificate.ts b/src/nintendo-certificate.ts index 92dc9f0..4cc3105 100644 --- a/src/nintendo-certificate.ts +++ b/src/nintendo-certificate.ts @@ -42,7 +42,7 @@ const CTR_LFCS_B_PUB = Buffer.from([ ]); // Signature options -const SIGNATURE_SIZES: { [key: string]: SignatureSize } = { +const SIGNATURE_SIZES = { RSA_4096_SHA1: { SIZE: 0x200, PADDING_SIZE: 0x3C @@ -67,7 +67,7 @@ const SIGNATURE_SIZES: { [key: string]: SignatureSize } = { SIZE: 0x3C, PADDING_SIZE: 0x40 } -}; +} as const; class NintendoCertificate { _certificate: Buffer; diff --git a/src/server.ts b/src/server.ts index 52fe75a..c18941f 100644 --- a/src/server.ts +++ b/src/server.ts @@ -9,7 +9,7 @@ import morgan from 'morgan'; import xmlparser from '@/middleware/xml-parser'; import { connect as connectCache } from '@/cache'; import { connect as connectDatabase } from '@/database'; -import { fullUrl } from '@/util'; +import { fullUrl, getValueFromHeaders } from '@/util'; import { LOG_INFO, LOG_SUCCESS, LOG_WARN } from '@/logger'; import conntest from '@/services/conntest'; @@ -49,7 +49,11 @@ app.use(assets); LOG_INFO('Creating 404 status handler'); app.use((request: express.Request, response: express.Response) => { const url: string = fullUrl(request); - const deviceId: string = request.headers['X-Nintendo-Device-ID'] as string || 'Unknown'; + let deviceId: string | undefined = getValueFromHeaders(request.headers, 'X-Nintendo-Device-ID'); + + if (!deviceId) { + deviceId = 'Unknown'; + } LOG_WARN(`HTTP 404 at ${url} from ${deviceId}`); @@ -66,7 +70,11 @@ LOG_INFO('Creating non-404 status handler'); app.use((error: any, request: express.Request, response: express.Response, _next: express.NextFunction) => { const status: number = error.status || 500; const url: string = fullUrl(request); - const deviceId: string = request.headers['X-Nintendo-Device-ID'] as string || 'Unknown'; + let deviceId: string | undefined = getValueFromHeaders(request.headers, 'X-Nintendo-Device-ID'); + + if (!deviceId) { + deviceId = 'Unknown'; + } LOG_WARN(`HTTP ${status} at ${url} from ${deviceId}: ${error.message}`); diff --git a/src/services/api/routes/v1/email.ts b/src/services/api/routes/v1/email.ts index a7c6eb7..2db0fd6 100644 --- a/src/services/api/routes/v1/email.ts +++ b/src/services/api/routes/v1/email.ts @@ -1,13 +1,13 @@ import express from 'express'; import moment from 'moment'; import { PNID } from '@/models/pnid'; -import { sendEmailConfirmedEmail } from '@/util'; +import { getValueFromQueryString, sendEmailConfirmedEmail } from '@/util'; import { HydratedPNIDDocument } from '@/types/mongoose/pnid'; const router: express.Router = express.Router(); router.get('/verify', async (request: express.Request, response: express.Response) => { - const token: string = request.query.token as string; + const token: string | undefined = getValueFromQueryString(request.query, 'token'); if (!token || token.trim() == '') { return response.status(400).json({ diff --git a/src/services/local-cdn/routes/get.ts b/src/services/local-cdn/routes/get.ts index ff18990..6c98b71 100644 --- a/src/services/local-cdn/routes/get.ts +++ b/src/services/local-cdn/routes/get.ts @@ -4,7 +4,7 @@ import { getLocalCDNFile } from '@/cache'; const router: express.Router = express.Router(); router.get('/*', async (request: express.Request, response: express.Response) => { - const filePath: string = request.params[0] as string; + const filePath: string = request.params[0]; const file: Buffer = await getLocalCDNFile(filePath); diff --git a/src/services/nnid/routes/admin.ts b/src/services/nnid/routes/admin.ts index 6a4437c..f1b63b4 100644 --- a/src/services/nnid/routes/admin.ts +++ b/src/services/nnid/routes/admin.ts @@ -1,5 +1,6 @@ import express from 'express'; import xmlbuilder from 'xmlbuilder'; +import { getValueFromQueryString } from '@/util'; import { PNID } from '@/models/pnid'; import { HydratedPNIDDocument } from '@/types/mongoose/pnid'; @@ -11,19 +12,26 @@ const router: express.Router = express.Router(); * Description: Maps between NNID usernames and PIDs */ router.get('/mapped_ids', async (request: express.Request, response: express.Response) => { - const inputType: string = request.query.input_type as string; - const outputType: string = request.query.output_type as string; - let inputList: string[]; + const inputType: string | undefined = getValueFromQueryString(request.query, 'input_type'); + const outputType: string | undefined = getValueFromQueryString(request.query, 'output_type'); + const input: string | undefined = getValueFromQueryString(request.query, 'input'); + + if (!inputType || !outputType || !input) { + return response.status(400).send(xmlbuilder.create({ + errors: { + error: { + cause: 'Bad Request', + code: '1600', + message: 'Unable to process request' + } + } + }).end()); + } + + let inputList: string[] = input.split(','); let queryInput: string; let queryOutput: string; - if (Array.isArray(request.query.input)) { - inputList = request.query.input as string[]; - } else { - const input = request.query.input as string; - inputList = input.split(','); - } - inputList = inputList.filter(input => input); // * Remove null inputs if (inputType === 'user_id') { diff --git a/src/services/nnid/routes/miis.ts b/src/services/nnid/routes/miis.ts index 368cb9e..4333e29 100644 --- a/src/services/nnid/routes/miis.ts +++ b/src/services/nnid/routes/miis.ts @@ -1,5 +1,6 @@ import express from 'express'; import xmlbuilder from 'xmlbuilder'; +import { getValueFromQueryString } from '@/util'; import { PNID } from '@/models/pnid'; import { config } from '@/config-manager'; import { HydratedPNIDDocument } from '@/types/mongoose/pnid'; @@ -13,15 +14,22 @@ const router: express.Router = express.Router(); * Description: Returns a list of NNID miis */ router.get('/', async (request: express.Request, response: express.Response) => { - let pids: number[]; + const input: string | undefined = getValueFromQueryString(request.query, 'pids'); - if (Array.isArray(request.query.pids)) { - pids = request.query.pids.map(pid => Number(pid)); - } else { - const input = request.query.pids as string; - pids = input.split(',').map(pid => Number(pid)); + if (!input) { + return response.status(400).send(xmlbuilder.create({ + errors: { + error: { + cause: 'Bad Request', + code: '1600', + message: 'Unable to process request' + } + } + }).end()); } + const pids: number[] = input.split(',').map(pid => Number(pid)); + const results: HydratedPNIDDocument[] = await PNID.where('pid', pids); const miis: { data: string; diff --git a/src/services/nnid/routes/people.ts b/src/services/nnid/routes/people.ts index 3258375..c145759 100644 --- a/src/services/nnid/routes/people.ts +++ b/src/services/nnid/routes/people.ts @@ -7,7 +7,7 @@ import mongoose from 'mongoose'; import deviceCertificateMiddleware from '@/middleware/device-certificate'; import ratelimit from '@/middleware/ratelimit'; import { connection as databaseConnection, doesUserExist, getUserProfileJSONByPID } from '@/database'; -import { nintendoPasswordHash, sendConfirmationEmail } from '@/util'; +import { getValueFromHeaders, nintendoPasswordHash, sendConfirmationEmail } from '@/util'; import { PNID } from '@/models/pnid'; import { NEXAccount } from '@/models/nex-account'; import { LOG_ERROR } from '@/logger'; @@ -292,12 +292,25 @@ router.get('/@me/devices', async (request: express.Request, response: express.Re response.set('X-Nintendo-Date', new Date().getTime().toString()); const pnid: HydratedPNIDDocument | null = request.pnid; - const deviceId: string = request.headers['x-nintendo-device-id'] as string; - const acceptLanguage: string = request.headers['accept-language'] as string; - const platformId: string = request.headers['x-nintendo-platform-id'] as string; - const region: string = request.headers['x-nintendo-region'] as string; - const serialNumber: string = request.headers['x-nintendo-serial-number'] as string; - const systemVersion: string = request.headers['x-nintendo-system-version'] as string; + const deviceId: string | undefined = getValueFromHeaders(request.headers, 'x-nintendo-device-id'); + const acceptLanguage: string | undefined = getValueFromHeaders(request.headers, 'accept-language'); + const platformId: string | undefined = getValueFromHeaders(request.headers, 'x-nintendo-platform-id'); + const region: string | undefined = getValueFromHeaders(request.headers, 'x-nintendo-region'); + const serialNumber: string | undefined = getValueFromHeaders(request.headers, 'x-nintendo-serial-number'); + const systemVersion: string | undefined = getValueFromHeaders(request.headers, 'x-nintendo-system-version'); + + if (!deviceId || !acceptLanguage || !platformId || !region || !serialNumber || !systemVersion) { + // TODO - Research these error more + return response.status(400).send(xmlbuilder.create({ + errors: { + error: { + cause: 'Bad Request', + code: '1600', + message: 'Unable to process request' + } + } + }).end()); + } if (!pnid) { // TODO - Research this error more diff --git a/src/services/nnid/routes/provider.ts b/src/services/nnid/routes/provider.ts index e2cfa4d..793739c 100644 --- a/src/services/nnid/routes/provider.ts +++ b/src/services/nnid/routes/provider.ts @@ -2,7 +2,7 @@ import express from 'express'; import xmlbuilder from 'xmlbuilder'; import fs from 'fs-extra'; import { getServerByTitleId, getServer } from '@/database'; -import { generateToken } from '@/util'; +import { generateToken, getValueFromHeaders, getValueFromQueryString } from '@/util'; import { getServicePublicKey, getServiceSecretKey, getNEXPublicKey, getNEXSecretKey } from '@/cache'; import { NEXAccount } from '@/models/nex-account'; import { CryptoOptions } from '@/types/common/crypto-options'; @@ -35,7 +35,20 @@ router.get('/service_token/@me', async (request: express.Request, response: expr }).end()); } - const titleId: string = request.headers['x-nintendo-title-id'] as string; + const titleId: string | undefined = getValueFromHeaders(request.headers, 'x-nintendo-title-id'); + + if (!titleId) { + // TODO - Research this error more + return response.send(xmlbuilder.create({ + errors: { + error: { + code: '1021', + message: 'The requested game server was not found' + } + } + }).end()); + } + const serverAccessLevel: string = pnid.get('server_access_level'); const server: HydratedServerDocument | null = await getServerByTitleId(titleId, serverAccessLevel); @@ -121,7 +134,7 @@ router.get('/nex_token/@me', async (request: express.Request, response: express. }).end()); } - const gameServerID: string = request.query.game_server_id as string; + const gameServerID: string | undefined = getValueFromQueryString(request.query, 'game_server_id'); if (!gameServerID) { return response.send(xmlbuilder.create({ @@ -152,7 +165,19 @@ router.get('/nex_token/@me', async (request: express.Request, response: express. const ip: string = server.ip; const port: number = server.port; const device: number = server.device; - const titleId: string = request.headers['x-nintendo-title-id'] as string; + const titleId: string | undefined = getValueFromHeaders(request.headers, 'x-nintendo-title-id'); + + if (!titleId) { + // TODO - Research this error more + return response.send(xmlbuilder.create({ + errors: { + error: { + code: '1021', + message: 'The requested game server was not found' + } + } + }).end()); + } const cryptoPath: string = `${__dirname}/../../../../certs/nex/${serverName}`; diff --git a/src/types/common/safe-qs.ts b/src/types/common/safe-qs.ts new file mode 100644 index 0000000..a17df29 --- /dev/null +++ b/src/types/common/safe-qs.ts @@ -0,0 +1,3 @@ +export interface SafeQs { + [key: string]: string | undefined +} \ No newline at end of file diff --git a/src/util.ts b/src/util.ts index 5a79d1f..d791840 100644 --- a/src/util.ts +++ b/src/util.ts @@ -5,6 +5,7 @@ import aws from 'aws-sdk'; import fs from 'fs-extra'; import express from 'express'; import mongoose from 'mongoose'; +import { ParsedQs } from 'qs'; import { sendMail } from '@/mailer'; import { getServiceAESKey, getServicePrivateKey, getServiceSecretKey, getServicePublicKey } from '@/cache'; import { config, disabledFeatures } from '@/config-manager'; @@ -13,6 +14,8 @@ import { TokenOptions } from '@/types/common/token-options'; import { Token } from '@/types/common/token'; import { IPNID, IPNIDMethods } from '@/types/mongoose/pnid'; import { MailerOptions } from '@/types/common/mailer-options'; +import { SafeQs } from '@/types/common/safe-qs'; +import { IncomingHttpHeaders } from 'node:http'; let s3: aws.S3; @@ -320,4 +323,55 @@ export async function sendForgotPasswordEmail(pnid: mongoose.HydratedDocumentproperty); + value = (property)[key]; + } else { + value = property; + } + } + + return value; +} + +export function getValueFromHeaders(headers: IncomingHttpHeaders, key: string): string | undefined { + let header: string | string[] | undefined = headers[key]; + let value: string | undefined; + + if (header) { + if (Array.isArray(header)) { + header = header[0]; + } + + value = header; + } + + return value; } \ No newline at end of file From 821ad69b4748f366ed343f97a1123ba3a64e468d Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Sun, 19 Mar 2023 11:59:44 -0400 Subject: [PATCH 049/219] Removed hard coded XML string responses --- src/server.ts | 12 +++- src/services/nnid/routes/people.ts | 88 +++++++++++++++++++++++----- src/services/nnid/routes/provider.ts | 11 +++- 3 files changed, 91 insertions(+), 20 deletions(-) diff --git a/src/server.ts b/src/server.ts index c18941f..1d9eac4 100644 --- a/src/server.ts +++ b/src/server.ts @@ -6,6 +6,7 @@ process.on('uncaughtException', (err, origin) => { import express from 'express'; import morgan from 'morgan'; +import xmlbuilder from 'xmlbuilder'; import xmlparser from '@/middleware/xml-parser'; import { connect as connectCache } from '@/cache'; import { connect as connectDatabase } from '@/database'; @@ -61,8 +62,15 @@ app.use((request: express.Request, response: express.Response) => { response.set('Server', 'Nintendo 3DS (http)'); response.set('X-Nintendo-Date', new Date().getTime().toString()); - response.status(404); - response.send('0008Not Found'); + response.status(404).send(xmlbuilder.create({ + errors: { + error: { + cause: '', + code: '0008', + message: 'Not Found' + } + } + }).end()); }); // non-404 error handler diff --git a/src/services/nnid/routes/people.ts b/src/services/nnid/routes/people.ts index c145759..770cec5 100644 --- a/src/services/nnid/routes/people.ts +++ b/src/services/nnid/routes/people.ts @@ -227,16 +227,30 @@ router.get('/@me/profile', async (request: express.Request, response: express.Re if (!pnid) { // TODO - Research this error more - response.status(404); - return response.send('0008Not Found'); + return response.status(404).send(xmlbuilder.create({ + errors: { + error: { + cause: '', + code: '0008', + message: 'Not Found' + } + } + }).end()); } const person: PNIDProfile | null = await getUserProfileJSONByPID(pnid.get('pid')); if (!person) { // TODO - Research this error more - response.status(404); - return response.send('0008Not Found'); + return response.status(404).send(xmlbuilder.create({ + errors: { + error: { + cause: '', + code: '0008', + message: 'Not Found' + } + } + }).end()); } response.send(xmlbuilder.create({ @@ -264,16 +278,30 @@ router.post('/@me/devices', async (request: express.Request, response: express.R if (!pnid) { // TODO - Research this error more - response.status(404); - return response.send('0008Not Found'); + return response.status(404).send(xmlbuilder.create({ + errors: { + error: { + cause: '', + code: '0008', + message: 'Not Found' + } + } + }).end()); } const person: PNIDProfile | null = await getUserProfileJSONByPID(pnid.get('pid')); if (!person) { // TODO - Research this error more - response.status(404); - return response.send('0008Not Found'); + return response.status(404).send(xmlbuilder.create({ + errors: { + error: { + cause: '', + code: '0008', + message: 'Not Found' + } + } + }).end()); } response.send(xmlbuilder.create({ @@ -314,8 +342,15 @@ router.get('/@me/devices', async (request: express.Request, response: express.Re if (!pnid) { // TODO - Research this error more - response.status(404); - return response.send('0008Not Found'); + return response.status(404).send(xmlbuilder.create({ + errors: { + error: { + cause: '', + code: '0008', + message: 'Not Found' + } + } + }).end()); } response.send(xmlbuilder.create({ @@ -353,16 +388,30 @@ router.get('/@me/devices/owner', async (request: express.Request, response: expr if (!pnid) { // TODO - Research this error more - response.status(404); - return response.send('0008Not Found'); + return response.status(404).send(xmlbuilder.create({ + errors: { + error: { + cause: '', + code: '0008', + message: 'Not Found' + } + } + }).end()); } const person: PNIDProfile | null = await getUserProfileJSONByPID(pnid.get('pid')); if (!person) { // TODO - Research this error more - response.status(404); - return response.send('0008Not Found'); + return response.status(404).send(xmlbuilder.create({ + errors: { + error: { + cause: '', + code: '0008', + message: 'Not Found' + } + } + }).end()); } response.send(xmlbuilder.create({ @@ -396,8 +445,15 @@ router.put('/@me/miis/@primary', async (request: express.Request, response: expr if (!pnid) { // TODO - Research this error more - response.status(404); - return response.send('0008Not Found'); + return response.status(404).send(xmlbuilder.create({ + errors: { + error: { + cause: '', + code: '0008', + message: 'Not Found' + } + } + }).end()); } // TODO - Make this more strictly typed? diff --git a/src/services/nnid/routes/provider.ts b/src/services/nnid/routes/provider.ts index 793739c..546463b 100644 --- a/src/services/nnid/routes/provider.ts +++ b/src/services/nnid/routes/provider.ts @@ -215,8 +215,15 @@ router.get('/nex_token/@me', async (request: express.Request, response: express. }); if (!nexUser) { - response.status(404); - return response.send('0008Not Found'); + return response.status(404).send(xmlbuilder.create({ + errors: { + error: { + cause: '', + code: '0008', + message: 'Not Found' + } + } + }).end()); } let nexToken: string | null = await generateToken(cryptoOptions, tokenOptions); From 5b00bae36f50f1a6f42f68f3afaa486e77713858 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Sun, 19 Mar 2023 13:27:28 -0400 Subject: [PATCH 050/219] Removed .get calls on documents --- src/database.ts | 67 +++++++++++---------- src/middleware/nasc.ts | 4 +- src/middleware/pnid.ts | 2 +- src/services/api/routes/v1/login.ts | 10 +-- src/services/api/routes/v1/register.ts | 10 +-- src/services/api/routes/v1/resetPassword.ts | 4 +- src/services/nasc/routes/ac.ts | 4 +- src/services/nnid/routes/oauth.ts | 6 +- src/services/nnid/routes/people.ts | 46 +++++++------- src/services/nnid/routes/provider.ts | 18 +++--- src/services/nnid/routes/support.ts | 2 +- src/types/services/nnid/pnid-profile.ts | 4 +- src/util.ts | 26 ++++---- 13 files changed, 102 insertions(+), 101 deletions(-) diff --git a/src/database.ts b/src/database.ts index 3406aab..09ca816 100644 --- a/src/database.ts +++ b/src/database.ts @@ -7,7 +7,8 @@ import { Server } from '@/models/server'; import { LOG_ERROR } from '@/logger'; import { config } from '@/config-manager'; import { HydratedPNIDDocument } from '@/types/mongoose/pnid'; -import { HydratedDeviceDocument } from '@/types/mongoose/device'; +import { IDevice } from '@/types/mongoose/device'; +import { IDeviceAttribute } from '@/types/mongoose/device-attribute'; import { HydratedServerDocument } from '@/types/mongoose/server'; import { Token } from '@/types/common/token'; import { PNIDProfile } from '@/types/services/nnid/pnid-profile'; @@ -133,7 +134,7 @@ export async function getUserProfileJSONByPID(pid: number): Promise { + device_attributes = device.device_attributes.map((attribute: IDeviceAttribute) => { const name: string = attribute.name; const value: string = attribute.value; - const created_date: string = attribute.created_date; + const created_date: string | undefined = attribute.created_date; return { device_attribute: { @@ -160,50 +161,50 @@ export async function getUserProfileJSONByPID(pid: number): Promise { - await PNID.updateOne({ pid: pnid.get('pid') }, { + await PNID.updateOne({ pid: pnid.pid }, { $set: { 'connections.discord.id': '' } diff --git a/src/middleware/nasc.ts b/src/middleware/nasc.ts index 301e6f4..ead3387 100644 --- a/src/middleware/nasc.ts +++ b/src/middleware/nasc.ts @@ -137,7 +137,7 @@ async function NASCMiddleware(request: express.Request, response: express.Respon await nexAccount.save({ session }); - pid = Number(nexAccount.get('pid')); + pid = nexAccount.pid; // Set password @@ -176,7 +176,7 @@ async function NASCMiddleware(request: express.Request, response: express.Respon const nexUser: HydratedNEXAccountDocument | null = await NEXAccount.findOne({ pid }); - if (!nexUser || nexUser.get('access_level') < 0) { + if (!nexUser || nexUser.access_level < 0) { response.status(200).send(nascError('102')); return; } diff --git a/src/middleware/pnid.ts b/src/middleware/pnid.ts index c1e0933..75471a1 100644 --- a/src/middleware/pnid.ts +++ b/src/middleware/pnid.ts @@ -55,7 +55,7 @@ async function PNIDMiddleware(request: express.Request, response: express.Respon return; } - if (user.get('access_level') < 0) { + if (user.access_level < 0) { response.status(400).send(xmlbuilder.create({ errors: { error: { diff --git a/src/services/api/routes/v1/login.ts b/src/services/api/routes/v1/login.ts index 692f13d..ff36efa 100644 --- a/src/services/api/routes/v1/login.ts +++ b/src/services/api/routes/v1/login.ts @@ -67,7 +67,7 @@ router.post('/', async (request: express.Request, response: express.Response) => }); } - const hashedPassword: string = nintendoPasswordHash(password, pnid.get('pid')); + const hashedPassword: string = nintendoPasswordHash(password, pnid.pid); if (!pnid || !bcrypt.compareSync(hashedPassword, pnid.password)) { return response.status(400).json({ @@ -110,8 +110,8 @@ router.post('/', async (request: express.Request, response: express.Response) => const accessTokenOptions: TokenOptions = { system_type: 0xF, // API token_type: 0x1, // OAuth Access, - pid: pnid.get('pid'), - access_level: pnid.get('access_level'), + pid: pnid.pid, + access_level: pnid.access_level, title_id: BigInt(0), expire_time: BigInt(Date.now() + (3600 * 1000)) }; @@ -119,8 +119,8 @@ router.post('/', async (request: express.Request, response: express.Response) => const refreshTokenOptions: TokenOptions = { system_type: 0xF, // API token_type: 0x2, // OAuth Refresh, - pid: pnid.get('pid'), - access_level: pnid.get('access_level'), + pid: pnid.pid, + access_level: pnid.access_level, title_id: BigInt(0), expire_time: BigInt(Date.now() + (3600 * 1000)) }; diff --git a/src/services/api/routes/v1/register.ts b/src/services/api/routes/v1/register.ts index ef6cda8..ca61d96 100644 --- a/src/services/api/routes/v1/register.ts +++ b/src/services/api/routes/v1/register.ts @@ -250,15 +250,15 @@ router.post('/', async (request: express.Request, response: express.Response) => // NN with a NNID will always use the NNID PID // even if the provided NEX PID is different // To fix this we make them the same PID - nexAccount.owning_pid = nexAccount.get('pid'); + nexAccount.owning_pid = nexAccount.pid; await nexAccount.save({ session }); - const primaryPasswordHash: string = nintendoPasswordHash(password, nexAccount.get('pid')); + const primaryPasswordHash: string = nintendoPasswordHash(password, nexAccount.pid); const passwordHash: string = await bcrypt.hash(primaryPasswordHash, 10); pnid = new PNID({ - pid: nexAccount.get('pid'), + pid: nexAccount.pid, creation_date: creationDate, updated: creationDate, username: username, @@ -348,7 +348,7 @@ router.post('/', async (request: express.Request, response: express.Response) => const accessTokenOptions: TokenOptions = { system_type: 0xF, // API token_type: 0x1, // OAuth Access, - pid: pnid.get('pid'), + pid: pnid.pid, access_level: 0, title_id: BigInt(0), expire_time: BigInt(Date.now() + (3600 * 1000)) @@ -357,7 +357,7 @@ router.post('/', async (request: express.Request, response: express.Response) => const refreshTokenOptions: TokenOptions = { system_type: 0xF, // API token_type: 0x2, // OAuth Refresh, - pid: pnid.get('pid'), + pid: pnid.pid, access_level: 0, title_id: BigInt(0), expire_time: BigInt(Date.now() + (3600 * 1000)) diff --git a/src/services/api/routes/v1/resetPassword.ts b/src/services/api/routes/v1/resetPassword.ts index 0a52acf..9b90138 100644 --- a/src/services/api/routes/v1/resetPassword.ts +++ b/src/services/api/routes/v1/resetPassword.ts @@ -82,7 +82,7 @@ router.post('/', async (request: express.Request, response: express.Response) => }); } - if (password.toLowerCase() === pnid.get('usernameLower')) { + if (password.toLowerCase() === pnid.usernameLower) { return response.status(400).json({ app: 'api', status: 400, @@ -114,7 +114,7 @@ router.post('/', async (request: express.Request, response: express.Response) => }); } - const primaryPasswordHash: string = nintendoPasswordHash(password, pnid.get('pid')); + const primaryPasswordHash: string = nintendoPasswordHash(password, pnid.pid); const passwordHash: string = await bcrypt.hash(primaryPasswordHash, 10); pnid.password = passwordHash; diff --git a/src/services/nasc/routes/ac.ts b/src/services/nasc/routes/ac.ts index a46fc04..ca1f9aa 100644 --- a/src/services/nasc/routes/ac.ts +++ b/src/services/nasc/routes/ac.ts @@ -47,7 +47,7 @@ async function processLoginRequest(request: express.Request): Promise): Promise { const options: MailerOptions = { - to: pnid.get('email.address'), + to: pnid.email.address, subject: '[Pretendo Network] Please confirm your email address', - username: pnid.get('username'), + username: pnid.username, confirmation: { - href: `https://api.pretendo.cc/v1/email/verify?token=${pnid.get('identification.email_token')}`, - code: pnid.get('identification.email_code') + href: `https://api.pretendo.cc/v1/email/verify?token=${pnid.identification.email_token}`, + code: pnid.identification.email_code }, - text: `Hello ${pnid.get('username')}! \r\n\r\nYour Pretendo Network ID activation is almost complete. Please click the link to confirm your e-mail address and complete the activation process: \r\nhttps://api.pretendo.cc/v1/email/verify?token=${pnid.get('identification.email_token')} \r\n\r\nYou may also enter the following 6-digit code on your console: ${pnid.get('identification.email_code')}` + text: `Hello ${pnid.username}! \r\n\r\nYour Pretendo Network ID activation is almost complete. Please click the link to confirm your e-mail address and complete the activation process: \r\nhttps://api.pretendo.cc/v1/email/verify?token=${pnid.identification.email_token} \r\n\r\nYou may also enter the following 6-digit code on your console: ${pnid.identification.email_code}` }; await sendMail(options); @@ -278,11 +278,11 @@ export async function sendConfirmationEmail(pnid: mongoose.HydratedDocument): Promise { const options: MailerOptions = { - to: pnid.get('email.address'), + to: pnid.email.address, subject: '[Pretendo Network] Email address confirmed', - username: pnid.get('username'), + username: pnid.username, paragraph: 'your email address has been confirmed. We hope you have fun on Pretendo Network!', - text: `Dear ${pnid.get('username')}, \r\n\r\nYour email address has been confirmed. We hope you have fun on Pretendo Network!` + text: `Dear ${pnid.username}, \r\n\r\nYour email address has been confirmed. We hope you have fun on Pretendo Network!` }; await sendMail(options); @@ -300,8 +300,8 @@ export async function sendForgotPasswordEmail(pnid: mongoose.HydratedDocument Date: Sun, 19 Mar 2023 13:36:01 -0400 Subject: [PATCH 051/219] Removed all .set calls from documents --- src/models/nex-account.ts | 4 ++-- src/models/pnid.ts | 18 +++++++++--------- src/services/api/routes/v1/email.ts | 6 +++--- src/services/nnid/routes/people.ts | 10 +++++----- src/services/nnid/routes/support.ts | 6 +++--- 5 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/models/nex-account.ts b/src/models/nex-account.ts index cddd72b..c302c4b 100644 --- a/src/models/nex-account.ts +++ b/src/models/nex-account.ts @@ -48,7 +48,7 @@ NEXAccountSchema.method('generatePID', async function generatePID(): Promise('NEXAccount', NEXAccountSchema); \ No newline at end of file diff --git a/src/models/pnid.ts b/src/models/pnid.ts index dd09b8f..9fa984a 100644 --- a/src/models/pnid.ts +++ b/src/models/pnid.ts @@ -128,7 +128,7 @@ PNIDSchema.method('generatePID', async function generatePID(): Promise { if (inuse) { await this.generatePID(); } else { - this.set('pid', pid); + this.pid = pid; } }); @@ -137,7 +137,7 @@ PNIDSchema.method('generateEmailValidationCode', async function generateEmailVal // Does not actually need to be unique to all users const code: string = Math.random().toFixed(6).split('.')[1]; // Dirty one-liner to generate numbers of 6 length and padded 0 - this.set('identification.email_code', code); + this.identification.email_code = code; }); PNIDSchema.method('generateEmailValidationToken', async function generateEmailValidationToken(): Promise { @@ -150,17 +150,17 @@ PNIDSchema.method('generateEmailValidationToken', async function generateEmailVa if (inuse) { await this.generateEmailValidationToken(); } else { - this.set('identification.email_token', token); + this.identification.email_token = token; } }); PNIDSchema.method('updateMii', async function updateMii({name, primary, data}): Promise { - this.set('mii.name', name); - this.set('mii.primary', primary === 'Y'); - this.set('mii.data', data); - this.set('mii.hash', crypto.randomBytes(7).toString('hex')); - this.set('mii.id', crypto.randomBytes(4).readUInt32LE()); - this.set('mii.image_id', crypto.randomBytes(4).readUInt32LE()); + this.mii.name = name; + this.mii.primary = primary === 'Y'; + this.mii.data = data; + this.mii.hash = crypto.randomBytes(7).toString('hex'); + this.mii.id = crypto.randomBytes(4).readUInt32LE(); + this.mii.image_id = crypto.randomBytes(4).readUInt32LE(); await this.generateMiiImages(); diff --git a/src/services/api/routes/v1/email.ts b/src/services/api/routes/v1/email.ts index 2db0fd6..7a2efed 100644 --- a/src/services/api/routes/v1/email.ts +++ b/src/services/api/routes/v1/email.ts @@ -31,9 +31,9 @@ router.get('/verify', async (request: express.Request, response: express.Respons const validatedDate: string = moment().format('YYYY-MM-DDTHH:MM:SS'); - pnid.set('email.reachable', true); - pnid.set('email.validated', true); - pnid.set('email.validated_date', validatedDate); + pnid.email.reachable = true; + pnid.email.validated = true; + pnid.email.validated_date = validatedDate; await pnid.save(); diff --git a/src/services/nnid/routes/people.ts b/src/services/nnid/routes/people.ts index bca1dde..6d5cc5d 100644 --- a/src/services/nnid/routes/people.ts +++ b/src/services/nnid/routes/people.ts @@ -656,11 +656,11 @@ router.put('/@me/emails/@primary', async (request: express.Request, response: ex } // TODO - Better email check - pnid.set('email.address', (email.get('address') || '').toLowerCase()); - pnid.set('email.reachable', false); - pnid.set('email.validated', false); - pnid.set('email.validated_date', ''); - pnid.set('email.id', crypto.randomBytes(4).readUInt32LE()); + pnid.email.address = (email.get('address') || '').toLowerCase(); + pnid.email.reachable = false; + pnid.email.validated = false; + pnid.email.validated_date = ''; + pnid.email.id = crypto.randomBytes(4).readUInt32LE(); await pnid.generateEmailValidationCode(); await pnid.generateEmailValidationToken(); diff --git a/src/services/nnid/routes/support.ts b/src/services/nnid/routes/support.ts index 583dee7..dfb8eec 100644 --- a/src/services/nnid/routes/support.ts +++ b/src/services/nnid/routes/support.ts @@ -82,9 +82,9 @@ router.put('/email_confirmation/:pid/:code', async (request: express.Request, re const validatedDate: string = moment().format('YYYY-MM-DDTHH:MM:SS'); - pnid.set('email.reachable', true); - pnid.set('email.validated', true); - pnid.set('email.validated_date', validatedDate); + pnid.email.reachable = true; + pnid.email.validated = true; + pnid.email.validated_date = validatedDate; await pnid.save(); From b89d3a15b091dbcd203bc6717fa2da2a33786575 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Sun, 19 Mar 2023 13:42:08 -0400 Subject: [PATCH 052/219] Renamed user variables to better match their types --- src/database.ts | 80 ++++++++++++++-------------- src/middleware/api.ts | 4 +- src/middleware/nasc.ts | 6 +-- src/middleware/pnid.ts | 12 ++--- src/services/nasc/routes/ac.ts | 8 +-- src/services/nnid/routes/provider.ts | 8 +-- src/types/express.d.ts | 2 +- 7 files changed, 60 insertions(+), 60 deletions(-) diff --git a/src/database.ts b/src/database.ts index 09ca816..a73ced4 100644 --- a/src/database.ts +++ b/src/database.ts @@ -85,19 +85,19 @@ export async function getUserBasic(token: string): Promise { @@ -107,9 +107,9 @@ export async function getUserBearer(token: string): Promise expireTime) { @@ -117,7 +117,7 @@ export async function getUserBearer(token: string): Promise { verifyConnected(); - const user: HydratedPNIDDocument | null = await getUserByPID(pid); + const pnid: HydratedPNIDDocument | null = await getUserByPID(pid); - if (!user) { + if (!pnid) { return null; } - const device: IDevice = user.devices[0]; // * Just grab the first device + const device: IDevice = pnid.devices[0]; // * Just grab the first device let device_attributes: { device_attribute: { name: string; @@ -161,50 +161,50 @@ export async function getUserProfileJSONByPID(pid: number): Promise async function processLoginRequest(request: express.Request): Promise { const requestParams: NASCRequestParams = request.body; const titleID: string = nintendoBase64Decode(requestParams.titleid).toString(); - const nexUser: HydratedNEXAccountDocument | null = request.nexUser; + const nexAccount: HydratedNEXAccountDocument | null = request.nexAccount; - if (!nexUser) { + if (!nexAccount) { // TODO - Research this error more return nascError('null'); } @@ -47,7 +47,7 @@ async function processLoginRequest(request: express.Request): Promise; certificate?: NintendoCertificate; From f977d77c5ccfc4064425b22badcb1907fd8a73ac Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Sun, 19 Mar 2023 13:46:29 -0400 Subject: [PATCH 053/219] Renamed user functions to PNID for clarity --- src/database.ts | 32 ++++++++++---------- src/middleware/api.ts | 4 +-- src/middleware/pnid.ts | 6 ++-- src/services/api/routes/v1/connections.ts | 6 ++-- src/services/api/routes/v1/forgotPassword.ts | 6 ++-- src/services/api/routes/v1/login.ts | 6 ++-- src/services/api/routes/v1/register.ts | 4 +-- src/services/nnid/routes/oauth.ts | 4 +-- src/services/nnid/routes/people.ts | 12 ++++---- src/services/nnid/routes/provider.ts | 4 +-- src/services/nnid/routes/support.ts | 8 ++--- 11 files changed, 46 insertions(+), 46 deletions(-) diff --git a/src/database.ts b/src/database.ts index a73ced4..33319c8 100644 --- a/src/database.ts +++ b/src/database.ts @@ -43,7 +43,7 @@ export function verifyConnected(): void { } } -export async function getUserByUsername(username: string): Promise { +export async function getPNIDByUsername(username: string): Promise { verifyConnected(); return await PNID.findOne({ @@ -51,7 +51,7 @@ export async function getUserByUsername(username: string): Promise { +export async function getPNIDByPID(pid: number): Promise { verifyConnected(); return await PNID.findOne({ @@ -59,7 +59,7 @@ export async function getUserByPID(pid: number): Promise { +export async function getPNIDByEmailAddress(email: string): Promise { verifyConnected(); // TODO - Update documents to store email normalized @@ -68,13 +68,13 @@ export async function getUserByEmailAddress(email: string): Promise { +export async function doesPNIDExist(username: string): Promise { verifyConnected(); - return !!await getUserByUsername(username); + return !!await getPNIDByUsername(username); } -export async function getUserBasic(token: string): Promise { +export async function getPNIDByBasicAuth(token: string): Promise { verifyConnected(); // * Wii U sends Basic auth as `username password`, where the password may not have spaces @@ -85,7 +85,7 @@ export async function getUserBasic(token: string): Promise { +export async function getPNIDByBearerAuth(token: string): Promise { verifyConnected(); try { const decryptedToken: Buffer = await decryptToken(Buffer.from(token, 'base64')); const unpackedToken: Token = unpackToken(decryptedToken); - const pnid: HydratedPNIDDocument | null = await getUserByPID(unpackedToken.pid); + const pnid: HydratedPNIDDocument | null = await getPNIDByPID(unpackedToken.pid); if (pnid) { const expireTime: number = Math.floor((Number(unpackedToken.expire_time) / 1000)); @@ -125,10 +125,10 @@ export async function getUserBearer(token: string): Promise { +export async function getPNIDProfileJSONByPID(pid: number): Promise { verifyConnected(); - const pnid: HydratedPNIDDocument | null = await getUserByPID(pid); + const pnid: HydratedPNIDDocument | null = await getPNIDByPID(pid); if (!pnid) { return null; @@ -208,7 +208,7 @@ export async function getUserProfileJSONByPID(pid: number): Promise { +export async function getServerByGameServerId(gameServerId: string, accessMode: string): Promise { return await Server.findOne({ game_server_id: gameServerId, access_mode: accessMode @@ -222,13 +222,13 @@ export async function getServerByTitleId(titleId: string, accessMode: string): P }); } -export async function addUserConnection(pnid: HydratedPNIDDocument, data: ConnectionData, type: string): Promise { +export async function addPNIDConnection(pnid: HydratedPNIDDocument, data: ConnectionData, type: string): Promise { if (type === 'discord') { - return await addUserConnectionDiscord(pnid, data); + return await addPNIDConnectionDiscord(pnid, data); } } -export async function addUserConnectionDiscord(pnid: HydratedPNIDDocument, data: DiscordConnectionData): Promise { +export async function addPNIDConnectionDiscord(pnid: HydratedPNIDDocument, data: DiscordConnectionData): Promise { const valid: joi.ValidationResult = discordConnectionSchema.validate(data); if (valid.error) { @@ -251,7 +251,7 @@ export async function addUserConnectionDiscord(pnid: HydratedPNIDDocument, data: }; } -export async function removeUserConnection(pnid: HydratedPNIDDocument, type: string): Promise { +export async function removePNIDConnection(pnid: HydratedPNIDDocument, type: string): Promise { // * Add more connections later? if (type === 'discord') { return await removeUserConnectionDiscord(pnid); diff --git a/src/middleware/api.ts b/src/middleware/api.ts index c47af01..8645559 100644 --- a/src/middleware/api.ts +++ b/src/middleware/api.ts @@ -1,6 +1,6 @@ import express from 'express'; import { getValueFromHeaders } from '@/util'; -import { getUserBearer } from '@/database'; +import { getPNIDByBearerAuth } from '@/database'; import { HydratedPNIDDocument } from '@/types/mongoose/pnid'; async function APIMiddleware(request: express.Request, _response: express.Response, next: express.NextFunction): Promise { @@ -11,7 +11,7 @@ async function APIMiddleware(request: express.Request, _response: express.Respon } const token: string = authHeader.split(' ')[1]; - const pnid: HydratedPNIDDocument | null = await getUserBearer(token); + const pnid: HydratedPNIDDocument | null = await getPNIDByBearerAuth(token); request.pnid = pnid; diff --git a/src/middleware/pnid.ts b/src/middleware/pnid.ts index d8b955d..3ab6d96 100644 --- a/src/middleware/pnid.ts +++ b/src/middleware/pnid.ts @@ -1,7 +1,7 @@ import express from 'express'; import xmlbuilder from 'xmlbuilder'; import { getValueFromHeaders } from '@/util'; -import { getUserBasic, getUserBearer } from '@/database'; +import { getPNIDByBasicAuth, getPNIDByBearerAuth } from '@/database'; import { HydratedPNIDDocument } from '@/types/mongoose/pnid'; async function PNIDMiddleware(request: express.Request, response: express.Response, next: express.NextFunction): Promise { @@ -21,9 +21,9 @@ async function PNIDMiddleware(request: express.Request, response: express.Respon } if (type === 'Basic') { - pnid = await getUserBasic(token); + pnid = await getPNIDByBasicAuth(token); } else { - pnid = await getUserBearer(token); + pnid = await getPNIDByBearerAuth(token); } if (!pnid) { diff --git a/src/services/api/routes/v1/connections.ts b/src/services/api/routes/v1/connections.ts index a828c2d..5bf79b4 100644 --- a/src/services/api/routes/v1/connections.ts +++ b/src/services/api/routes/v1/connections.ts @@ -1,5 +1,5 @@ import express from 'express'; -import { addUserConnection, removeUserConnection } from '@/database'; +import { addPNIDConnection, removePNIDConnection } from '@/database'; import { ConnectionData } from '@/types/services/api/connection-data'; import { ConnectionResponse } from '@/types/services/api/connection-response'; import { HydratedPNIDDocument } from '@/types/mongoose/pnid'; @@ -44,7 +44,7 @@ router.post('/add/:type', async (request: express.Request, response: express.Res }); } - let result: ConnectionResponse | undefined = await addUserConnection(pnid, data, type); + let result: ConnectionResponse | undefined = await addPNIDConnection(pnid, data, type); if (!result) { result = { @@ -82,7 +82,7 @@ router.delete('/remove/:type', async (request: express.Request, response: expres }); } - let result: ConnectionResponse | undefined = await removeUserConnection(pnid, type); + let result: ConnectionResponse | undefined = await removePNIDConnection(pnid, type); if (!result) { result = { diff --git a/src/services/api/routes/v1/forgotPassword.ts b/src/services/api/routes/v1/forgotPassword.ts index bfc6d61..1cb3283 100644 --- a/src/services/api/routes/v1/forgotPassword.ts +++ b/src/services/api/routes/v1/forgotPassword.ts @@ -1,6 +1,6 @@ import express from 'express'; import validator from 'validator'; -import { getUserByEmailAddress, getUserByUsername } from '@/database'; +import { getPNIDByEmailAddress, getPNIDByUsername } from '@/database'; import { sendForgotPasswordEmail } from '@/util'; import { HydratedPNIDDocument } from '@/types/mongoose/pnid'; @@ -20,9 +20,9 @@ router.post('/', async (request: express.Request, response: express.Response) => let pnid: HydratedPNIDDocument | null; if (validator.isEmail(input)) { - pnid = await getUserByEmailAddress(input); + pnid = await getPNIDByEmailAddress(input); } else { - pnid = await getUserByUsername(input); + pnid = await getPNIDByUsername(input); } if (pnid) { diff --git a/src/services/api/routes/v1/login.ts b/src/services/api/routes/v1/login.ts index ff36efa..61fcac9 100644 --- a/src/services/api/routes/v1/login.ts +++ b/src/services/api/routes/v1/login.ts @@ -1,7 +1,7 @@ import express from 'express'; import bcrypt from 'bcrypt'; import fs from 'fs-extra'; -import { getUserByUsername, getUserBearer } from '@/database'; +import { getPNIDByUsername, getPNIDByBearerAuth } from '@/database'; import { getServicePublicKey, getServiceSecretKey } from '@/cache'; import { nintendoPasswordHash, generateToken} from '@/util'; import { CryptoOptions } from '@/types/common/crypto-options'; @@ -57,7 +57,7 @@ router.post('/', async (request: express.Request, response: express.Response) => let pnid: HydratedPNIDDocument | null; if (grantType === 'password') { - pnid = await getUserByUsername(username); + pnid = await getPNIDByUsername(username); if (!pnid) { return response.status(400).json({ @@ -77,7 +77,7 @@ router.post('/', async (request: express.Request, response: express.Response) => }); } } else { - pnid = await getUserBearer(refreshToken); + pnid = await getPNIDByBearerAuth(refreshToken); if (!pnid) { return response.status(400).json({ diff --git a/src/services/api/routes/v1/register.ts b/src/services/api/routes/v1/register.ts index ca61d96..2487ae2 100644 --- a/src/services/api/routes/v1/register.ts +++ b/src/services/api/routes/v1/register.ts @@ -8,7 +8,7 @@ import moment from 'moment'; import hcaptcha from 'hcaptcha'; import Mii from 'mii-js'; import mongoose from 'mongoose'; -import { doesUserExist, connection as databaseConnection } from '@/database'; +import { doesPNIDExist, connection as databaseConnection } from '@/database'; import { getServicePublicKey, getServiceSecretKey } from '@/cache'; import { nintendoPasswordHash, sendConfirmationEmail, generateToken } from '@/util'; import { LOG_ERROR } from '@/logger'; @@ -141,7 +141,7 @@ router.post('/', async (request: express.Request, response: express.Response) => }); } - const userExists: boolean = await doesUserExist(username); + const userExists: boolean = await doesPNIDExist(username); if (userExists) { return response.status(400).json({ diff --git a/src/services/nnid/routes/oauth.ts b/src/services/nnid/routes/oauth.ts index 559329b..867f93d 100644 --- a/src/services/nnid/routes/oauth.ts +++ b/src/services/nnid/routes/oauth.ts @@ -2,7 +2,7 @@ import express from 'express'; import xmlbuilder from 'xmlbuilder'; import bcrypt from 'bcrypt'; import fs from 'fs-extra'; -import { getUserByUsername } from '@/database'; +import { getPNIDByUsername } from '@/database'; import { generateToken } from '@/util'; import { TokenOptions } from '@/types/common/token-options'; import { HydratedPNIDDocument } from '@/types/mongoose/pnid'; @@ -52,7 +52,7 @@ router.post('/access_token/generate', async (request: express.Request, response: }).end()); } - const pnid: HydratedPNIDDocument | null = await getUserByUsername(username); + const pnid: HydratedPNIDDocument | null = await getPNIDByUsername(username); if (!pnid || !await bcrypt.compare(password, pnid.password)) { response.status(400); diff --git a/src/services/nnid/routes/people.ts b/src/services/nnid/routes/people.ts index 6d5cc5d..8f48453 100644 --- a/src/services/nnid/routes/people.ts +++ b/src/services/nnid/routes/people.ts @@ -6,7 +6,7 @@ import moment from 'moment'; import mongoose from 'mongoose'; import deviceCertificateMiddleware from '@/middleware/device-certificate'; import ratelimit from '@/middleware/ratelimit'; -import { connection as databaseConnection, doesUserExist, getUserProfileJSONByPID } from '@/database'; +import { connection as databaseConnection, doesPNIDExist, getPNIDProfileJSONByPID } from '@/database'; import { getValueFromHeaders, nintendoPasswordHash, sendConfirmationEmail } from '@/util'; import { PNID } from '@/models/pnid'; import { NEXAccount } from '@/models/nex-account'; @@ -31,7 +31,7 @@ const router: express.Router = express.Router(); router.get('/:username', async (request: express.Request, response: express.Response) => { const username: string = request.params.username; - const userExists: boolean = await doesUserExist(username); + const userExists: boolean = await doesPNIDExist(username); if (userExists) { response.status(400); @@ -72,7 +72,7 @@ router.post('/', ratelimit, deviceCertificateMiddleware, async (request: express // * request.body is a Map, as is request.body.person const person: Person = request.body.get('person'); - const userExists: boolean = await doesUserExist(person.get('user_id')); + const userExists: boolean = await doesPNIDExist(person.get('user_id')); if (userExists) { response.status(400); @@ -238,7 +238,7 @@ router.get('/@me/profile', async (request: express.Request, response: express.Re }).end()); } - const person: PNIDProfile | null = await getUserProfileJSONByPID(pnid.pid); + const person: PNIDProfile | null = await getPNIDProfileJSONByPID(pnid.pid); if (!person) { // TODO - Research this error more @@ -289,7 +289,7 @@ router.post('/@me/devices', async (request: express.Request, response: express.R }).end()); } - const person: PNIDProfile | null = await getUserProfileJSONByPID(pnid.pid); + const person: PNIDProfile | null = await getPNIDProfileJSONByPID(pnid.pid); if (!person) { // TODO - Research this error more @@ -399,7 +399,7 @@ router.get('/@me/devices/owner', async (request: express.Request, response: expr }).end()); } - const person: PNIDProfile | null = await getUserProfileJSONByPID(pnid.pid); + const person: PNIDProfile | null = await getPNIDProfileJSONByPID(pnid.pid); if (!person) { // TODO - Research this error more diff --git a/src/services/nnid/routes/provider.ts b/src/services/nnid/routes/provider.ts index 560380b..93633c7 100644 --- a/src/services/nnid/routes/provider.ts +++ b/src/services/nnid/routes/provider.ts @@ -1,7 +1,7 @@ import express from 'express'; import xmlbuilder from 'xmlbuilder'; import fs from 'fs-extra'; -import { getServerByTitleId, getServer } from '@/database'; +import { getServerByTitleId, getServerByGameServerId } from '@/database'; import { generateToken, getValueFromHeaders, getValueFromQueryString } from '@/util'; import { getServicePublicKey, getServiceSecretKey, getNEXPublicKey, getNEXSecretKey } from '@/cache'; import { NEXAccount } from '@/models/nex-account'; @@ -148,7 +148,7 @@ router.get('/nex_token/@me', async (request: express.Request, response: express. } const serverAccessLevel: string = pnid.server_access_level; - const server: HydratedServerDocument | null = await getServer(gameServerID, serverAccessLevel); + const server: HydratedServerDocument | null = await getServerByGameServerId(gameServerID, serverAccessLevel); if (!server) { return response.send(xmlbuilder.create({ diff --git a/src/services/nnid/routes/support.ts b/src/services/nnid/routes/support.ts index dfb8eec..a8e4811 100644 --- a/src/services/nnid/routes/support.ts +++ b/src/services/nnid/routes/support.ts @@ -2,7 +2,7 @@ import dns from 'node:dns'; import express from 'express'; import xmlbuilder from 'xmlbuilder'; import moment from 'moment'; -import { getUserByPID } from '@/database'; +import { getPNIDByPID } from '@/database'; import { sendEmailConfirmedEmail, sendConfirmationEmail, sendForgotPasswordEmail } from '@/util'; import { HydratedPNIDDocument } from '@/types/mongoose/pnid'; @@ -56,7 +56,7 @@ router.put('/email_confirmation/:pid/:code', async (request: express.Request, re const code: string = request.params.code; const pid: number = Number(request.params.pid); - const pnid: HydratedPNIDDocument | null = await getUserByPID(pid); + const pnid: HydratedPNIDDocument | null = await getPNIDByPID(pid); if (!pnid) { return response.status(400).send(xmlbuilder.create({ @@ -101,7 +101,7 @@ router.put('/email_confirmation/:pid/:code', async (request: express.Request, re router.get('/resend_confirmation', async (request: express.Request, response: express.Response) => { const pid: number = Number(request.headers['x-nintendo-pid']); - const pnid: HydratedPNIDDocument | null = await getUserByPID(pid); + const pnid: HydratedPNIDDocument | null = await getPNIDByPID(pid); if (!pnid) { // TODO - Unsure if this is the right error @@ -129,7 +129,7 @@ router.get('/resend_confirmation', async (request: express.Request, response: ex router.get('/forgotten_password/:pid', async (request: express.Request, response: express.Response) => { const pid: number = Number(request.params.pid); - const pnid: HydratedPNIDDocument | null = await getUserByPID(pid); + const pnid: HydratedPNIDDocument | null = await getPNIDByPID(pid); if (!pnid) { // TODO - Better errors From 39ccd942cb6a3dad737d7192bc2ad94c9806c499 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Sun, 19 Mar 2023 16:21:30 -0400 Subject: [PATCH 054/219] Convert Maps to JSON objects on request body --- src/middleware/xml-parser.ts | 3 +- src/services/nnid/routes/miis.ts | 2 - src/services/nnid/routes/people.ts | 85 +++++++++++++++-------------- src/services/nnid/routes/support.ts | 2 +- src/types/services/nnid/person.ts | 4 +- src/util.ts | 4 ++ 6 files changed, 53 insertions(+), 47 deletions(-) diff --git a/src/middleware/xml-parser.ts b/src/middleware/xml-parser.ts index 31bb8b4..4f639d5 100644 --- a/src/middleware/xml-parser.ts +++ b/src/middleware/xml-parser.ts @@ -1,7 +1,7 @@ import express from 'express'; import xmlbuilder from 'xmlbuilder'; import { document as xmlParser } from 'xmlbuilder2'; -import { getValueFromHeaders } from '@/util'; +import { getValueFromHeaders, mapToObject } from '@/util'; function XMLMiddleware(request: express.Request, response: express.Response, next: express.NextFunction): void { if (request.method == 'POST' || request.method == 'PUT') { @@ -32,6 +32,7 @@ function XMLMiddleware(request: express.Request, response: express.Response, nex try { request.body = xmlParser(body); request.body = request.body.toObject(); + request.body = mapToObject(request.body); } catch (error) { response.status(401); diff --git a/src/services/nnid/routes/miis.ts b/src/services/nnid/routes/miis.ts index 4333e29..dd58978 100644 --- a/src/services/nnid/routes/miis.ts +++ b/src/services/nnid/routes/miis.ts @@ -121,8 +121,6 @@ router.get('/', async (request: express.Request, response: express.Response) => }); } - //console.log(results[0].mii.data.replace(/(\r\n|\n|\r)/gm, '')); - response.send(xmlbuilder.create({ miis: { mii: miis diff --git a/src/services/nnid/routes/people.ts b/src/services/nnid/routes/people.ts index 8f48453..a1c271e 100644 --- a/src/services/nnid/routes/people.ts +++ b/src/services/nnid/routes/people.ts @@ -69,10 +69,9 @@ router.post('/', ratelimit, deviceCertificateMiddleware, async (request: express }).end()); } - // * request.body is a Map, as is request.body.person - const person: Person = request.body.get('person'); + const person: Person = request.body.person; - const userExists: boolean = await doesPNIDExist(person.get('user_id')); + const userExists: boolean = await doesPNIDExist(person.user_id); if (userExists) { response.status(400); @@ -111,12 +110,12 @@ router.post('/', ratelimit, deviceCertificateMiddleware, async (request: express await nexAccount.save({ session }); - const primaryPasswordHash: string = nintendoPasswordHash(person.get('password'), nexAccount.pid); + const primaryPasswordHash: string = nintendoPasswordHash(person.password, nexAccount.pid); const passwordHash: string = await bcrypt.hash(primaryPasswordHash, 10); - const countryCode: string = person.get('country'); - const language: string = person.get('language'); - const timezoneName: string = person.get('tz_name'); + const countryCode: string = person.country; + const language: string = person.language; + const timezoneName: string = person.tz_name; const regionLanguages: RegionLanguages = timezones[countryCode as keyof typeof timezones]; const regionTimezones: RegionTimezones = regionLanguages[language] ? regionLanguages[language] : Object.values(regionLanguages)[0]; @@ -137,30 +136,30 @@ router.post('/', ratelimit, deviceCertificateMiddleware, async (request: express pid: nexAccount.pid, creation_date: creationDate, updated: creationDate, - username: person.get('user_id'), - usernameLower: person.get('user_id').toLowerCase(), + username: person.user_id, + usernameLower: person.user_id.toLowerCase(), password: passwordHash, - birthdate: person.get('birth_date'), - gender: person.get('gender'), + birthdate: person.birth_date, + gender: person.gender, country: countryCode, language: language, email: { - address: person.get('email').get('address').toLowerCase(), - primary: person.get('email').get('primary') === 'Y', - parent: person.get('email').get('parent') === 'Y', + address: person.email.address.toLowerCase(), + primary: person.email.primary === 'Y', + parent: person.email.parent === 'Y', reachable: false, - validated: person.get('email').get('validated') === 'Y', + validated: person.email.validated === 'Y', id: crypto.randomBytes(4).readUInt32LE() }, - region: person.get('region'), + region: person.region, timezone: { name: timezoneName, offset: Number(timezone.utc_offset) }, mii: { - name: person.get('mii').get('name'), - primary: person.get('mii').get('name') === 'Y', - data: person.get('mii').get('data'), + name: person.mii.name, + primary: person.mii.name === 'Y', + data: person.mii.data, id: crypto.randomBytes(4).readUInt32LE(), hash: crypto.randomBytes(7).toString('hex'), image_url: '', // deprecated, will be removed in the future @@ -168,8 +167,8 @@ router.post('/', ratelimit, deviceCertificateMiddleware, async (request: express }, flags: { active: true, - marketing: person.get('marketing_flag') === 'Y', - off_device: person.get('off_device_flag') === 'Y' + marketing: person.marketing_flag === 'Y', + off_device: person.off_device_flag === 'Y' }, identification: { email_code: 1, // will be overwritten before saving @@ -456,14 +455,17 @@ router.put('/@me/miis/@primary', async (request: express.Request, response: expr }).end()); } - // TODO - Make this more strictly typed? - const mii: Map = request.body.get('mii'); + const mii: { + name: string; + primary: string; + data: string; + } = request.body.mii; // TODO - Better checks - const name: string | undefined = mii.get('name') || ''; - const primary: string | undefined = mii.get('primary') || ''; - const data: string | undefined = mii.get('data') || ''; + const name: string = mii.name; + const primary: string = mii.primary; + const data: string = mii.data; await pnid.updateMii({ name, primary, data }); @@ -531,7 +533,7 @@ router.put('/@me/deletion', async (request: express.Request, response: express.R */ router.put('/@me', async (request: express.Request, response: express.Response) => { const pnid: HydratedPNIDDocument | null = request.pnid; - const person: Person = request.body.get('person'); + const person: Person = request.body.person; if (!pnid) { response.status(400); @@ -547,13 +549,13 @@ router.put('/@me', async (request: express.Request, response: express.Response) }).end()); } - const gender: string = person.get('gender') ? person.get('gender') : pnid.gender; - const region: string = person.get('region') ? person.get('region') : pnid.region; - const countryCode: string = person.get('country') ? person.get('country') : pnid.country; - const language: string = person.get('language') ? person.get('language') : pnid.language; - const timezoneName: string = person.get('tz_name') ? person.get('tz_name') : pnid.timezone.name; - const marketingFlag: boolean = person.get('marketing_flag') ? person.get('marketing_flag') === 'Y' : pnid.flags.marketing; - const offDeviceFlag: boolean = person.get('off_device_flag') ? person.get('off_device_flag') === 'Y' : pnid.flags.off_device; + const gender: string = person.gender ? person.gender : pnid.gender; + const region: number = person.region ? person.region : pnid.region; + const countryCode: string = person.country ? person.country : pnid.country; + const language: string = person.language ? person.language : pnid.language; + const timezoneName: string = person.tz_name ? person.tz_name : pnid.timezone.name; + const marketingFlag: boolean = person.marketing_flag ? person.marketing_flag === 'Y' : pnid.flags.marketing; + const offDeviceFlag: boolean = person.off_device_flag ? person.off_device_flag === 'Y' : pnid.flags.off_device; const regionLanguages: RegionLanguages = timezones[countryCode as keyof typeof timezones]; const regionTimezones: RegionTimezones = regionLanguages[language] ? regionLanguages[language] : Object.values(regionLanguages)[0]; @@ -570,15 +572,15 @@ router.put('/@me', async (request: express.Request, response: express.Response) }; } - if (person.get('password')) { - const primaryPasswordHash: string = nintendoPasswordHash(person.get('password'), pnid.pid); + if (person.password) { + const primaryPasswordHash: string = nintendoPasswordHash(person.password, pnid.pid); const passwordHash: string = await bcrypt.hash(primaryPasswordHash, 10); pnid.password = passwordHash; } pnid.gender = gender; - pnid.region = Number(region); + pnid.region = region; pnid.timezone.name = timezoneName; pnid.timezone.offset = Number(timezone.utc_offset); pnid.timezone.marketing = marketingFlag; @@ -638,10 +640,11 @@ router.get('/@me/emails', async (request: express.Request, response: express.Res router.put('/@me/emails/@primary', async (request: express.Request, response: express.Response) => { const pnid: HydratedPNIDDocument | null = request.pnid; - // TODO - Make this more strictly typed? - const email: Map = request.body.get('email'); + const email: { + address: string; + } = request.body.email; - if (!pnid || !email) { + if (!pnid || !email || !email.address) { response.status(400); return response.end(xmlbuilder.create({ @@ -656,7 +659,7 @@ router.put('/@me/emails/@primary', async (request: express.Request, response: ex } // TODO - Better email check - pnid.email.address = (email.get('address') || '').toLowerCase(); + pnid.email.address = email.address.toLowerCase(); pnid.email.reachable = false; pnid.email.validated = false; pnid.email.validated_date = ''; diff --git a/src/services/nnid/routes/support.ts b/src/services/nnid/routes/support.ts index a8e4811..be795d6 100644 --- a/src/services/nnid/routes/support.ts +++ b/src/services/nnid/routes/support.ts @@ -14,7 +14,7 @@ const router: express.Router = express.Router(); * Description: Verifies a provided email address is valid */ router.post('/validate/email', async (request: express.Request, response: express.Response) => { - const email: string = request.body; + const email: string = request.body.email; if (!email) { return response.send(xmlbuilder.create({ diff --git a/src/types/services/nnid/person.ts b/src/types/services/nnid/person.ts index 9da3757..00958e5 100644 --- a/src/types/services/nnid/person.ts +++ b/src/types/services/nnid/person.ts @@ -2,7 +2,7 @@ import { YesNoBoolString } from '@/types/common/yes-no-bool-string'; import { IDeviceAttribute } from '@/types/mongoose/device-attribute'; // TODO - Change this Map type to something more strongly typed -export interface Person extends Map { +export interface Person { birth_date: string; user_id: string; password: string; @@ -35,7 +35,7 @@ export interface Person extends Map { approval_id: string; }; gender: string; - region: string; + region: number; marketing_flag: YesNoBoolString; device_attributes: { device_attribute: IDeviceAttribute[] diff --git a/src/util.ts b/src/util.ts index 32db6da..d43d14a 100644 --- a/src/util.ts +++ b/src/util.ts @@ -374,4 +374,8 @@ export function getValueFromHeaders(headers: IncomingHttpHeaders, key: string): } return value; +} + +export function mapToObject(map: Map): object { + return Object.fromEntries(Array.from(map.entries(), ([ k, v ]) => v instanceof Map ? [ k, mapToObject(v) ] : [ k, v ])); } \ No newline at end of file From 77348791303a3ec47b98495375242479c3c8486d Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Sun, 19 Mar 2023 16:34:14 -0400 Subject: [PATCH 055/219] Fixed email secure boolean check/conversion --- src/config-manager.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/config-manager.ts b/src/config-manager.ts index 9f98cc8..9dc36e5 100644 --- a/src/config-manager.ts +++ b/src/config-manager.ts @@ -21,6 +21,13 @@ if (process.env.PN_ACT_CONFIG_MONGOOSE_CONNECT_OPTIONS_PATH) { mongooseConnectOptions = fs.readJSONSync(process.env.PN_ACT_CONFIG_MONGOOSE_CONNECT_OPTIONS_PATH); } +if (process.env.PN_ACT_CONFIG_EMAIL_SECURE) { + if (process.env.PN_ACT_CONFIG_EMAIL_SECURE !== 'true' && process.env.PN_ACT_CONFIG_EMAIL_SECURE !== 'false') { + LOG_ERROR(`PN_ACT_CONFIG_EMAIL_SECURE must be either true or false, got ${process.env.PN_ACT_CONFIG_EMAIL_SECURE}`); + process.exit(0); + } +} + export const config: Config = { http: { port: Number(process.env.PN_ACT_CONFIG_HTTP_PORT || '') @@ -37,7 +44,7 @@ export const config: Config = { email: { host: process.env.PN_ACT_CONFIG_EMAIL_HOST || '', port: Number(process.env.PN_ACT_CONFIG_EMAIL_PORT || ''), - secure: Boolean(process.env.PN_ACT_CONFIG_EMAIL_SECURE || ''), + secure: (process.env.PN_ACT_CONFIG_EMAIL_SECURE || '') === 'true', auth: { user: process.env.PN_ACT_CONFIG_EMAIL_USERNAME || '', pass: process.env.PN_ACT_CONFIG_EMAIL_PASSWORD || '' From 00b1aecd70398fae2b29408825e67bfbbb3b9ec7 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Thu, 30 Mar 2023 10:22:31 -0400 Subject: [PATCH 056/219] Fixed token generation for production users --- src/database.ts | 4 ++-- src/util.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/database.ts b/src/database.ts index 33319c8..09ef40d 100644 --- a/src/database.ts +++ b/src/database.ts @@ -254,11 +254,11 @@ export async function addPNIDConnectionDiscord(pnid: HydratedPNIDDocument, data: export async function removePNIDConnection(pnid: HydratedPNIDDocument, type: string): Promise { // * Add more connections later? if (type === 'discord') { - return await removeUserConnectionDiscord(pnid); + return await removePNIDConnectionDiscord(pnid); } } -export async function removeUserConnectionDiscord(pnid: HydratedPNIDDocument): Promise { +export async function removePNIDConnectionDiscord(pnid: HydratedPNIDDocument): Promise { await PNID.updateOne({ pid: pnid.pid }, { $set: { 'connections.discord.id': '' diff --git a/src/util.ts b/src/util.ts index d43d14a..16b1b08 100644 --- a/src/util.ts +++ b/src/util.ts @@ -70,7 +70,7 @@ export async function generateToken(cryptoOptions: CryptoOptions | null, tokenOp encryptedBody = Buffer.concat([encryptedBody, cipher.final()]); return encryptedBody.toString('base64'); - } else if (!tokenOptions.access_level || !tokenOptions.title_id) { + } else if (tokenOptions.access_level === undefined || tokenOptions.title_id === undefined) { return null; } From 2b53c5d5a064220d57d0ae09427f3a1c88e9e8fc Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Fri, 31 Mar 2023 12:09:12 -0400 Subject: [PATCH 057/219] Fixed 3DS cert not being verified correctly --- src/nintendo-certificate.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/nintendo-certificate.ts b/src/nintendo-certificate.ts index 4cc3105..a371c8f 100644 --- a/src/nintendo-certificate.ts +++ b/src/nintendo-certificate.ts @@ -103,7 +103,6 @@ class NintendoCertificate { } this._parseCertificateData(); - this._verifySignature(); } } @@ -128,6 +127,8 @@ class NintendoCertificate { this.certificateName = this._certificate.subarray(0xC4, 0x104).toString().split('\0')[0]; this.ngKeyId = this._certificate.readUInt32BE(0x104); this.publicKeyData = this._certificate.subarray(0x108); + + this._verifySignature(); } } From b8f1486fbf253c46425104c09036b68b5793d8f9 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Fri, 31 Mar 2023 12:11:36 -0400 Subject: [PATCH 058/219] Fixed NASC errors not being sent correctly in middleware --- src/middleware/nasc.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/middleware/nasc.ts b/src/middleware/nasc.ts index f30ca45..5b4da42 100644 --- a/src/middleware/nasc.ts +++ b/src/middleware/nasc.ts @@ -21,7 +21,7 @@ async function NASCMiddleware(request: express.Request, response: express.Respon !requestParams.titleid || !requestParams.servertype ) { - response.status(200).send(nascError('null')); // This is what Nintendo sends + response.status(200).send(nascError('null').toString()); // This is what Nintendo sends return; } @@ -52,19 +52,19 @@ async function NASCMiddleware(request: express.Request, response: express.Respon } if (action !== 'LOGIN' && action !== 'SVCLOC') { - response.status(200).send(nascError('null')); // This is what Nintendo sends + response.status(200).send(nascError('null').toString()); // This is what Nintendo sends return; } const cert: NintendoCertificate = new NintendoCertificate(fcdcert); if (!cert.valid) { - response.status(200).send(nascError('121')); + response.status(200).send(nascError('121').toString()); return; } if (!validNintendoMACAddress(macAddress)) { - response.status(200).send(nascError('null')); + response.status(200).send(nascError('null').toString()); return; } @@ -91,7 +91,7 @@ async function NASCMiddleware(request: express.Request, response: express.Respon } if (!model) { - response.status(200).send(nascError('null')); + response.status(200).send(nascError('null').toString()); return; } @@ -105,7 +105,7 @@ async function NASCMiddleware(request: express.Request, response: express.Respon if (device) { if (device.get('access_level') < 0) { - response.status(200).send(nascError('102')); + response.status(200).send(nascError('102').toString()); return; } @@ -113,7 +113,7 @@ async function NASCMiddleware(request: express.Request, response: express.Respon const linkedPIDs = device.get('linked_pids'); if (!linkedPIDs.includes(pid)) { - response.status(200).send(nascError('102')); + response.status(200).send(nascError('102').toString()); return; } } @@ -164,7 +164,7 @@ async function NASCMiddleware(request: express.Request, response: express.Respon await session.abortTransaction(); // 3DS expects 200 even on error - response.status(200).send(nascError('102')); + response.status(200).send(nascError('102').toString()); return; } finally { // * This runs regardless of failure @@ -177,7 +177,7 @@ async function NASCMiddleware(request: express.Request, response: express.Respon const nexAccount: HydratedNEXAccountDocument | null = await NEXAccount.findOne({ pid }); if (!nexAccount || nexAccount.access_level < 0) { - response.status(200).send(nascError('102')); + response.status(200).send(nascError('102').toString()); return; } From 2e0bb207a9f03587ec22cf6f350e69e32c2ee060 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Fri, 31 Mar 2023 16:15:36 -0400 Subject: [PATCH 059/219] Fixed bug where 0.0.0.0:0 NEX addresses were seen as invalid --- src/services/nasc/routes/ac.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/services/nasc/routes/ac.ts b/src/services/nasc/routes/ac.ts index 25147c8..d00fae2 100644 --- a/src/services/nasc/routes/ac.ts +++ b/src/services/nasc/routes/ac.ts @@ -52,7 +52,13 @@ async function processLoginRequest(request: express.Request): Promise Date: Sat, 1 Apr 2023 11:15:59 -0400 Subject: [PATCH 060/219] Changed token format to be much smaller (thanks 3DS) --- src/cache.ts | 130 ------------- src/config-manager.ts | 8 +- src/database.ts | 2 +- src/models/server.ts | 19 +- src/services/api/routes/v1/login.ts | 42 +--- src/services/api/routes/v1/register.ts | 41 +--- src/services/api/routes/v1/resetPassword.ts | 3 +- src/services/nasc/routes/ac.ts | 85 ++++---- src/services/nnid/routes/oauth.ts | 36 +--- src/services/nnid/routes/provider.ts | 182 ++++++----------- src/types/common/config.ts | 1 + src/types/common/crypto-options.ts | 4 - src/types/mongoose/server.ts | 19 +- src/util.ts | 205 ++++---------------- 14 files changed, 203 insertions(+), 574 deletions(-) delete mode 100644 src/types/common/crypto-options.ts diff --git a/src/cache.ts b/src/cache.ts index 912f78c..593554b 100644 --- a/src/cache.ts +++ b/src/cache.ts @@ -6,8 +6,6 @@ let client: redis.RedisClientType; const memoryCache: { [key: string]: Buffer } = {}; -const SERVICE_CERTS_BASE: string = `${__dirname}/../certs/service`; -const NEX_CERTS_BASE: string = `${__dirname}/../certs/nex`; const LOCAL_CDN_BASE: string = `${__dirname}/../cdn`; export async function connect(): Promise { @@ -42,134 +40,6 @@ export async function getCachedFile(fileName: string, encoding?: BufferEncoding) return cachedFile; } -// * NEX server cache functions - -export async function getNEXPublicKey(name: string, encoding?: BufferEncoding): Promise { - let publicKey: Buffer = await getCachedFile(`nex:${name}:public_key`, encoding); - - if (publicKey === null) { - publicKey = await fs.readFile(`${NEX_CERTS_BASE}/${name}/public.pem`); - await setNEXPublicKey(name, publicKey); - } - - return publicKey; -} - -export async function getNEXPrivateKey(name: string, encoding?: BufferEncoding): Promise { - let privateKey: Buffer = await getCachedFile(`nex:${name}:private_key`, encoding); - - if (privateKey === null) { - privateKey = await fs.readFile(`${NEX_CERTS_BASE}/${name}/private.pem`); - await setNEXPrivateKey(name, privateKey); - } - - return privateKey; -} - -export async function getNEXSecretKey(name: string, encoding?: BufferEncoding): Promise { - let secretKey: Buffer = await getCachedFile(`nex:${name}:secret_key`, encoding); - - if (secretKey === null) { - const fileBuffer: string = await fs.readFile(`${NEX_CERTS_BASE}/${name}/secret.key`, { encoding: 'utf8' }); - secretKey = Buffer.from(fileBuffer, encoding); - await setNEXSecretKey(name, secretKey); - } - - return secretKey; -} - -export async function getNEXAESKey(name: string, encoding?: BufferEncoding): Promise { - let aesKey: Buffer = await getCachedFile(`nex:${name}:aes_key`, encoding); - - if (aesKey === null) { - const fileBuffer: string = await fs.readFile(`${NEX_CERTS_BASE}/${name}/aes.key`, { encoding: 'utf8' }); - aesKey = Buffer.from(fileBuffer, encoding); - await setNEXAESKey(name, aesKey); - } - - return aesKey; -} - -export async function setNEXPublicKey(name: string, value: Buffer): Promise { - await setCachedFile(`nex:${name}:public_key`, value); -} - -export async function setNEXPrivateKey(name: string, value: Buffer): Promise { - await setCachedFile(`nex:${name}:private_key`, value); -} - -export async function setNEXSecretKey(name: string, value: Buffer): Promise { - await setCachedFile(`nex:${name}:secret_key`, value); -} - -export async function setNEXAESKey(name: string, value: Buffer): Promise { - await setCachedFile(`nex:${name}:aes_key`, value); -} - -// * 3rd party service cache functions - -export async function getServicePublicKey(name: string, encoding?: BufferEncoding): Promise { - let publicKey: Buffer = await getCachedFile(`service:${name}:public_key`, encoding); - - if (publicKey === null) { - publicKey = await fs.readFile(`${SERVICE_CERTS_BASE}/${name}/public.pem`); - await setServicePublicKey(name, publicKey); - } - - return publicKey; -} - -export async function getServicePrivateKey(name: string, encoding?: BufferEncoding): Promise { - let privateKey: Buffer = await getCachedFile(`service:${name}:private_key`, encoding); - - if (privateKey === null) { - privateKey = await fs.readFile(`${SERVICE_CERTS_BASE}/${name}/private.pem`); - await setServicePrivateKey(name, privateKey); - } - - return privateKey; -} - -export async function getServiceSecretKey(name: string, encoding?: BufferEncoding): Promise { - let secretKey: Buffer = await getCachedFile(`service:${name}:secret_key`, encoding); - - if (secretKey === null) { - const fileBuffer: string = await fs.readFile(`${SERVICE_CERTS_BASE}/${name}/secret.key`, { encoding: 'utf8' }); - secretKey = Buffer.from(fileBuffer, encoding); - await setServiceSecretKey(name, secretKey); - } - - return secretKey; -} - -export async function getServiceAESKey(name: string, encoding?: BufferEncoding): Promise { - let aesKey: Buffer = await getCachedFile(`service:${name}:aes_key`, encoding); - - if (aesKey === null) { - const fileBuffer: string = await fs.readFile(`${SERVICE_CERTS_BASE}/${name}/aes.key`, { encoding: 'utf8' }); - aesKey = Buffer.from(fileBuffer, encoding); - await setServiceAESKey(name, aesKey); - } - - return aesKey; -} - -export async function setServicePublicKey(name: string, value: Buffer): Promise { - await setCachedFile(`service:${name}:public_key`, value); -} - -export async function setServicePrivateKey(name: string, value: Buffer): Promise { - await setCachedFile(`service:${name}:private_key`, value); -} - -export async function setServiceSecretKey(name: string, value: Buffer): Promise { - await setCachedFile(`service:${name}:secret_key`, value); -} - -export async function setServiceAESKey(name: string, value: Buffer): Promise { - await setCachedFile(`service:${name}:aes_key`, value); -} - // * Local CDN cache functions export async function getLocalCDNFile(name: string, encoding?: BufferEncoding): Promise { diff --git a/src/config-manager.ts b/src/config-manager.ts index 9dc36e5..23c9f84 100644 --- a/src/config-manager.ts +++ b/src/config-manager.ts @@ -64,7 +64,8 @@ export const config: Config = { disk_path: process.env.PN_ACT_CONFIG_CDN_DISK_PATH || '', base_url: process.env.PN_ACT_CONFIG_CDN_BASE_URL || '' }, - website_base: process.env.PN_ACT_CONFIG_WEBSITE_BASE || '' + website_base: process.env.PN_ACT_CONFIG_WEBSITE_BASE || '', + aes_key: process.env.PN_ACT_CONFIG_AES_KEY || '' }; LOG_INFO('Config loaded, checking integrity'); @@ -162,4 +163,9 @@ if (disabledFeatures.s3) { if (disabledFeatures.redis) { LOG_WARN('Both s3 and Redis are disabled. Large CDN files will use the in-memory cache, which may result in high memory use. Please enable s3 if you\'re running a production server.'); } +} + +if (!config.aes_key) { + LOG_ERROR('Token AES key is not set. Set the PN_ACT_CONFIG_AES_KEY environment variable to your AES-256-CBC key'); + process.exit(0); } \ No newline at end of file diff --git a/src/database.ts b/src/database.ts index 09ef40d..e7f7748 100644 --- a/src/database.ts +++ b/src/database.ts @@ -104,7 +104,7 @@ export async function getPNIDByBearerAuth(token: string): Promise({ - ip: String, // Example: 1.1.1.1 - port: Number, // Example: 60000 - service_name: String, // Example: friends - service_type: String, // Example: nex - game_server_id: String, // Example: 00003200 - title_ids: [String], // Example: ["000500001018DB00", "000500001018DC00", "000500001018DD00"] - access_mode: String, // Example: prod - maintenance_mode: Boolean, // Example: false - device: Number, // Example: 1 (WiiU) + ip: String, + port: Number, + service_name: String, + service_type: String, + game_server_id: String, + title_ids: [String], + access_mode: String, + maintenance_mode: Boolean, + device: Number, + aes_key: String }); ServerSchema.plugin(uniqueValidator, { message: '{PATH} already in use.' }); diff --git a/src/services/api/routes/v1/login.ts b/src/services/api/routes/v1/login.ts index 61fcac9..400b3fe 100644 --- a/src/services/api/routes/v1/login.ts +++ b/src/services/api/routes/v1/login.ts @@ -1,10 +1,8 @@ import express from 'express'; import bcrypt from 'bcrypt'; -import fs from 'fs-extra'; import { getPNIDByUsername, getPNIDByBearerAuth } from '@/database'; -import { getServicePublicKey, getServiceSecretKey } from '@/cache'; import { nintendoPasswordHash, generateToken} from '@/util'; -import { CryptoOptions } from '@/types/common/crypto-options'; +import { config } from '@/config-manager'; import { TokenOptions } from '@/types/common/token-options'; import { HydratedPNIDDocument } from '@/types/mongoose/pnid'; @@ -88,45 +86,25 @@ router.post('/', async (request: express.Request, response: express.Response) => } } - const cryptoPath: string = `${__dirname}/../../../../../certs/service/account`; - - if (!await fs.pathExists(cryptoPath)) { - // Need to generate keys - return response.status(500).json({ - app: 'api', - status: 500, - error: 'Failed to locate crypto keys. Please contact an administrator' - }); - } - - const publicKey: Buffer = await getServicePublicKey('account'); - const secretKey: Buffer = await getServiceSecretKey('account'); - - const cryptoOptions: CryptoOptions = { - public_key: publicKey, - hmac_secret: secretKey - }; - const accessTokenOptions: TokenOptions = { - system_type: 0xF, // API - token_type: 0x1, // OAuth Access, + system_type: 0x1, // * WiiU + token_type: 0x1, // * OAuth Access pid: pnid.pid, - access_level: pnid.access_level, - title_id: BigInt(0), expire_time: BigInt(Date.now() + (3600 * 1000)) }; const refreshTokenOptions: TokenOptions = { - system_type: 0xF, // API - token_type: 0x2, // OAuth Refresh, + system_type: 0x1, // * WiiU + token_type: 0x2, // * OAuth Refresh pid: pnid.pid, - access_level: pnid.access_level, - title_id: BigInt(0), expire_time: BigInt(Date.now() + (3600 * 1000)) }; - const accessToken: string | null = await generateToken(cryptoOptions, accessTokenOptions); - const newRefreshToken: string | null = await generateToken(cryptoOptions, refreshTokenOptions); + const accessTokenBuffer: Buffer | null = await generateToken(config.aes_key, accessTokenOptions); + const refreshTokenBuffer: Buffer | null = await generateToken(config.aes_key, refreshTokenOptions); + + const accessToken: string = accessTokenBuffer ? accessTokenBuffer.toString('hex') : ''; + const newRefreshToken: string = refreshTokenBuffer ? refreshTokenBuffer.toString('hex') : ''; // TODO - Handle null tokens diff --git a/src/services/api/routes/v1/register.ts b/src/services/api/routes/v1/register.ts index 2487ae2..25d30ca 100644 --- a/src/services/api/routes/v1/register.ts +++ b/src/services/api/routes/v1/register.ts @@ -3,19 +3,16 @@ import crypto from 'node:crypto'; import express from 'express'; import emailvalidator from 'email-validator'; import bcrypt from 'bcrypt'; -import fs from 'fs-extra'; import moment from 'moment'; import hcaptcha from 'hcaptcha'; import Mii from 'mii-js'; import mongoose from 'mongoose'; import { doesPNIDExist, connection as databaseConnection } from '@/database'; -import { getServicePublicKey, getServiceSecretKey } from '@/cache'; import { nintendoPasswordHash, sendConfirmationEmail, generateToken } from '@/util'; import { LOG_ERROR } from '@/logger'; import { PNID } from '@/models/pnid'; import { NEXAccount } from '@/models/nex-account'; import { config, disabledFeatures } from '@/config-manager'; -import { CryptoOptions } from '@/types/common/crypto-options'; import { TokenOptions } from '@/types/common/token-options'; import { HydratedNEXAccountDocument } from '@/types/mongoose/nex-account'; import { HydratedPNIDDocument } from '@/types/mongoose/pnid'; @@ -326,45 +323,25 @@ router.post('/', async (request: express.Request, response: express.Response) => await sendConfirmationEmail(pnid); - const cryptoPath: string = `${__dirname}/../../../../../certs/service/account`; - - if (!await fs.pathExists(cryptoPath)) { - // Need to generate keys - return response.status(500).json({ - app: 'api', - status: 500, - error: 'Failed to locate crypto keys. Please contact an administrator' - }); - } - - const publicKey: Buffer = await getServicePublicKey('account'); - const secretKey: Buffer = await getServiceSecretKey('account'); - - const cryptoOptions: CryptoOptions = { - public_key: publicKey, - hmac_secret: secretKey - }; - const accessTokenOptions: TokenOptions = { - system_type: 0xF, // API - token_type: 0x1, // OAuth Access, + system_type: 0x1, // * WiiU + token_type: 0x1, // * OAuth Access pid: pnid.pid, - access_level: 0, - title_id: BigInt(0), expire_time: BigInt(Date.now() + (3600 * 1000)) }; const refreshTokenOptions: TokenOptions = { - system_type: 0xF, // API - token_type: 0x2, // OAuth Refresh, + system_type: 0x1, // * WiiU + token_type: 0x2, // * OAuth Refresh pid: pnid.pid, - access_level: 0, - title_id: BigInt(0), expire_time: BigInt(Date.now() + (3600 * 1000)) }; - const accessToken: string | null = await generateToken(cryptoOptions, accessTokenOptions); - const refreshToken: string | null = await generateToken(cryptoOptions, refreshTokenOptions); + const accessTokenBuffer: Buffer | null = await generateToken(config.aes_key, accessTokenOptions); + const refreshTokenBuffer: Buffer | null = await generateToken(config.aes_key, refreshTokenOptions); + + const accessToken: string = accessTokenBuffer ? accessTokenBuffer.toString('hex') : ''; + const refreshToken: string = refreshTokenBuffer ? refreshTokenBuffer.toString('hex') : ''; // TODO - Handle null tokens diff --git a/src/services/api/routes/v1/resetPassword.ts b/src/services/api/routes/v1/resetPassword.ts index 9b90138..266e887 100644 --- a/src/services/api/routes/v1/resetPassword.ts +++ b/src/services/api/routes/v1/resetPassword.ts @@ -2,6 +2,7 @@ import express from 'express'; import bcrypt from 'bcrypt'; import { PNID } from '@/models/pnid'; import { decryptToken, unpackToken, nintendoPasswordHash } from '@/util'; +import { config } from '@/config-manager'; import { Token } from '@/types/common/token'; import { HydratedPNIDDocument } from '@/types/mongoose/pnid'; @@ -28,7 +29,7 @@ router.post('/', async (request: express.Request, response: express.Response) => let unpackedToken: Token; try { - const decryptedToken: Buffer = await decryptToken(Buffer.from(token, 'base64')); + const decryptedToken: Buffer = await decryptToken(config.aes_key, Buffer.from(token, 'base64')); unpackedToken = unpackToken(decryptedToken); } catch (error) { console.log(error); diff --git a/src/services/nasc/routes/ac.ts b/src/services/nasc/routes/ac.ts index d00fae2..cd688d3 100644 --- a/src/services/nasc/routes/ac.ts +++ b/src/services/nasc/routes/ac.ts @@ -1,8 +1,6 @@ import express from 'express'; import { nintendoBase64Encode, nintendoBase64Decode, nascError, generateToken } from '@/util'; import { getServerByTitleId } from '@/database'; -import { getNEXPublicKey, getNEXSecretKey } from '@/cache'; -import { CryptoOptions } from '@/types/common/crypto-options'; import { TokenOptions } from '@/types/common/token-options'; import { NASCRequestParams } from '@/types/services/nasc/request-params'; import { HydratedNEXAccountDocument } from '@/types/mongoose/nex-account'; @@ -18,33 +16,17 @@ const router: express.Router = express.Router(); router.post('/', async (request: express.Request, response: express.Response) => { const requestParams: NASCRequestParams = request.body; const action: string = nintendoBase64Decode(requestParams.action).toString(); - let responseData: URLSearchParams = nascError('null'); - - switch (action) { - case 'LOGIN': - responseData = await processLoginRequest(request); - break; - case 'SVCLOC': - responseData = await processServiceTokenRequest(request); - break; - } - - response.status(200).send(responseData.toString()); -}); - -async function processLoginRequest(request: express.Request): Promise { - const requestParams: NASCRequestParams = request.body; const titleID: string = nintendoBase64Decode(requestParams.titleid).toString(); const nexAccount: HydratedNEXAccountDocument | null = request.nexAccount; + let responseData: URLSearchParams = nascError('null'); if (!nexAccount) { - // TODO - Research this error more - return nascError('null'); + return response.status(200).send(responseData.toString()); } // TODO: REMOVE AFTER PUBLIC LAUNCH - // LET EVERYONE IN THE `test` FRIENDS SERVER - // THAT WAY EVERYONE CAN GET AN ASSIGNED PID + // * LET EVERYONE IN THE `test` FRIENDS SERVER + // * THAT WAY EVERYONE CAN GET AN ASSIGNED PID let serverAccessLevel: string = 'test'; if (titleID !== '0004013000003202') { serverAccessLevel = nexAccount.server_access_level; @@ -52,32 +34,33 @@ async function processLoginRequest(request: express.Request): Promise { const tokenOptions: TokenOptions = { - system_type: 0x2, // 3DS - token_type: 0x3, // nex token, - pid: nexAccount.pid, + system_type: 0x2, // * 3DS + token_type: 0x3, // * NEX token + pid: pid, access_level: 0, title_id: BigInt(parseInt(titleID, 16)), expire_time: BigInt(Date.now() + (3600 * 1000)) @@ -85,11 +68,11 @@ async function processLoginRequest(request: express.Request): Promise { +async function processServiceTokenRequest(server: HydratedServerDocument, pid: number, titleID: string): Promise { + const tokenOptions: TokenOptions = { + system_type: 0x2, // * 3DS + token_type: 0x4, // * Service token + pid: pid, + access_level: 0, + title_id: BigInt(parseInt(titleID, 16)), + expire_time: BigInt(Date.now() + (3600 * 1000)) + }; + + // TODO - Handle null tokens + + const serviceTokenBuffer: Buffer | null = await generateToken(server.aes_key, tokenOptions); + const serviceToken: string = nintendoBase64Encode(serviceTokenBuffer || ''); + return new URLSearchParams({ retry: nintendoBase64Encode('0'), returncd: nintendoBase64Encode('007'), - servicetoken: nintendoBase64Encode(Buffer.alloc(64).toString()), // hard coded for now + servicetoken: serviceToken, statusdata: nintendoBase64Encode('Y'), svchost: nintendoBase64Encode('n/a'), datetime: nintendoBase64Encode(Date.now().toString()), diff --git a/src/services/nnid/routes/oauth.ts b/src/services/nnid/routes/oauth.ts index 867f93d..c276b9f 100644 --- a/src/services/nnid/routes/oauth.ts +++ b/src/services/nnid/routes/oauth.ts @@ -1,9 +1,9 @@ import express from 'express'; import xmlbuilder from 'xmlbuilder'; import bcrypt from 'bcrypt'; -import fs from 'fs-extra'; import { getPNIDByUsername } from '@/database'; import { generateToken } from '@/util'; +import { config } from '@/config-manager'; import { TokenOptions } from '@/types/common/token-options'; import { HydratedPNIDDocument } from '@/types/mongoose/pnid'; @@ -75,44 +75,28 @@ router.post('/access_token/generate', async (request: express.Request, response: }).end()); } - const cryptoPath: string = `${__dirname}/../../../../certs/service/account`; - - if (!await fs.pathExists(cryptoPath)) { - // Need to generate keys - return response.send(xmlbuilder.create({ - errors: { - error: { - code: '0000', - message: 'Could not find account access key crypto path' - } - } - }).end()); - } - const accessTokenOptions: TokenOptions = { - system_type: 0x1, // WiiU - token_type: 0x1, // OAuth Access, + system_type: 0x1, // * WiiU + token_type: 0x1, // * OAuth Access pid: pnid.pid, expire_time: BigInt(Date.now() + (3600 * 1000)) }; const refreshTokenOptions: TokenOptions = { - system_type: 0x1, // WiiU - token_type: 0x2, // OAuth Refresh, + system_type: 0x1, // * WiiU + token_type: 0x2, // * OAuth Refresh pid: pnid.pid, expire_time: BigInt(Date.now() + (3600 * 1000)) }; - let accessToken: string | null = await generateToken(null, accessTokenOptions); - let refreshToken: string | null = await generateToken(null, refreshTokenOptions); + const accessTokenBuffer: Buffer | null = await generateToken(config.aes_key, accessTokenOptions); + const refreshTokenBuffer: Buffer | null = await generateToken(config.aes_key, refreshTokenOptions); + + const accessToken: string = accessTokenBuffer ? accessTokenBuffer.toString('hex') : ''; + const refreshToken: string = refreshTokenBuffer ? refreshTokenBuffer.toString('hex') : ''; // TODO - Handle null tokens - if (request.isCemu) { - accessToken = Buffer.from(accessToken || '', 'base64').toString('hex'); - refreshToken = Buffer.from(refreshToken || '', 'base64').toString('hex'); - } - response.send(xmlbuilder.create({ OAuth20: { access_token: { diff --git a/src/services/nnid/routes/provider.ts b/src/services/nnid/routes/provider.ts index 93633c7..21160d3 100644 --- a/src/services/nnid/routes/provider.ts +++ b/src/services/nnid/routes/provider.ts @@ -1,11 +1,8 @@ import express from 'express'; import xmlbuilder from 'xmlbuilder'; -import fs from 'fs-extra'; import { getServerByTitleId, getServerByGameServerId } from '@/database'; import { generateToken, getValueFromHeaders, getValueFromQueryString } from '@/util'; -import { getServicePublicKey, getServiceSecretKey, getNEXPublicKey, getNEXSecretKey } from '@/cache'; import { NEXAccount } from '@/models/nex-account'; -import { CryptoOptions } from '@/types/common/crypto-options'; import { TokenOptions } from '@/types/common/token-options'; import { HydratedPNIDDocument } from '@/types/mongoose/pnid'; import { HydratedServerDocument } from '@/types/mongoose/server'; @@ -35,9 +32,9 @@ router.get('/service_token/@me', async (request: express.Request, response: expr }).end()); } - const titleId: string | undefined = getValueFromHeaders(request.headers, 'x-nintendo-title-id'); + const titleID: string | undefined = getValueFromHeaders(request.headers, 'x-nintendo-title-id'); - if (!titleId) { + if (!titleID) { // TODO - Research this error more return response.send(xmlbuilder.create({ errors: { @@ -50,9 +47,9 @@ router.get('/service_token/@me', async (request: express.Request, response: expr } const serverAccessLevel: string = pnid.server_access_level; - const server: HydratedServerDocument | null = await getServerByTitleId(titleId, serverAccessLevel); + const server: HydratedServerDocument | null = await getServerByTitleId(titleID, serverAccessLevel); - if (!server) { + if (!server || !server.aes_key) { return response.send(xmlbuilder.create({ errors: { error: { @@ -63,46 +60,20 @@ router.get('/service_token/@me', async (request: express.Request, response: expr }).end()); } - const serverName: string = server.service_name; - const device: number = server.device; - - const cryptoPath: string = `${__dirname}/../../../../certs/service/${serverName}`; - - if (!await fs.pathExists(cryptoPath)) { - // Need to generate keys - return response.send(xmlbuilder.create({ - errors: { - error: { - code: '1021', - message: 'The requested game server was not found' - } - } - }).end()); - } - - const publicKey: Buffer = await getServicePublicKey(serverName); - const secretKey: Buffer = await getServiceSecretKey(serverName); - - const cryptoOptions: CryptoOptions = { - public_key: publicKey, - hmac_secret: secretKey - }; - const tokenOptions: TokenOptions = { - system_type: device, - token_type: 0x4, // service token, + system_type: server.device, + token_type: 0x4, // * Service token pid: pnid.pid, access_level: pnid.access_level, - title_id: BigInt(parseInt(titleId, 16)), + title_id: BigInt(parseInt(titleID, 16)), expire_time: BigInt(Date.now() + (3600 * 1000)) }; - let serviceToken: string | null = await generateToken(cryptoOptions, tokenOptions); - - // TODO - Handle null tokens + const serviceTokenBuffer: Buffer | null = await generateToken(server.aes_key, tokenOptions); + let serviceToken: string = serviceTokenBuffer ? serviceTokenBuffer.toString('base64') : ''; if (request.isCemu) { - serviceToken = Buffer.from(serviceToken || '', 'base64').toString('hex'); + serviceToken = Buffer.from(serviceToken, 'base64').toString('hex'); } response.send(xmlbuilder.create({ @@ -134,82 +105,6 @@ router.get('/nex_token/@me', async (request: express.Request, response: express. }).end()); } - const gameServerID: string | undefined = getValueFromQueryString(request.query, 'game_server_id'); - - if (!gameServerID) { - return response.send(xmlbuilder.create({ - errors: { - error: { - code: '0118', - message: 'Unique ID and Game Server ID are not linked' - } - } - }).end()); - } - - const serverAccessLevel: string = pnid.server_access_level; - const server: HydratedServerDocument | null = await getServerByGameServerId(gameServerID, serverAccessLevel); - - if (!server) { - return response.send(xmlbuilder.create({ - errors: { - error: { - code: '1021', - message: 'The requested game server was not found' - } - } - }).end()); - } - - const serverName: string = server.service_name; - const ip: string = server.ip; - const port: number = server.port; - const device: number = server.device; - const titleId: string | undefined = getValueFromHeaders(request.headers, 'x-nintendo-title-id'); - - if (!titleId) { - // TODO - Research this error more - return response.send(xmlbuilder.create({ - errors: { - error: { - code: '1021', - message: 'The requested game server was not found' - } - } - }).end()); - } - - const cryptoPath: string = `${__dirname}/../../../../certs/nex/${serverName}`; - - if (!await fs.pathExists(cryptoPath)) { - // Need to generate keys - return response.send(xmlbuilder.create({ - errors: { - error: { - code: '1021', - message: 'The requested game server was not found' - } - } - }).end()); - } - - const publicKey: Buffer = await getNEXPublicKey(serverName); - const secretKey: Buffer = await getNEXSecretKey(serverName); - - const cryptoOptions: CryptoOptions = { - public_key: publicKey, - hmac_secret: secretKey - }; - - const tokenOptions: TokenOptions = { - system_type: device, - token_type: 0x3, // nex token, - pid: pnid.pid, - access_level: pnid.access_level, - title_id: BigInt(parseInt(titleId, 16)), - expire_time: BigInt(Date.now() + (3600 * 1000)) - }; - const nexAccount: HydratedNEXAccountDocument | null = await NEXAccount.findOne({ owning_pid: pnid.pid }); @@ -226,9 +121,58 @@ router.get('/nex_token/@me', async (request: express.Request, response: express. }).end()); } - let nexToken: string | null = await generateToken(cryptoOptions, tokenOptions); + const gameServerID: string | undefined = getValueFromQueryString(request.query, 'game_server_id'); - // TODO = Handle null tokens + if (!gameServerID) { + return response.send(xmlbuilder.create({ + errors: { + error: { + code: '0118', + message: 'Unique ID and Game Server ID are not linked' + } + } + }).end()); + } + + const serverAccessLevel: string = pnid.server_access_level; + const server: HydratedServerDocument | null = await getServerByGameServerId(gameServerID, serverAccessLevel); + + if (!server || !server.aes_key) { + return response.send(xmlbuilder.create({ + errors: { + error: { + code: '1021', + message: 'The requested game server was not found' + } + } + }).end()); + } + + const titleID: string | undefined = getValueFromHeaders(request.headers, 'x-nintendo-title-id'); + + if (!titleID) { + // TODO - Research this error more + return response.send(xmlbuilder.create({ + errors: { + error: { + code: '1021', + message: 'The requested game server was not found' + } + } + }).end()); + } + + const tokenOptions: TokenOptions = { + system_type: server.device, + token_type: 0x3, // nex token, + pid: pnid.pid, + access_level: pnid.access_level, + title_id: BigInt(parseInt(titleID, 16)), + expire_time: BigInt(Date.now() + (3600 * 1000)) + }; + + const nexTokenBuffer: Buffer | null = await generateToken(server.aes_key, tokenOptions); + let nexToken: string = nexTokenBuffer ? nexTokenBuffer.toString('base64') : ''; if (request.isCemu) { nexToken = Buffer.from(nexToken || '', 'base64').toString('hex'); @@ -236,10 +180,10 @@ router.get('/nex_token/@me', async (request: express.Request, response: express. response.send(xmlbuilder.create({ nex_token: { - host: ip, + host: server.ip, nex_password: nexAccount.password, pid: nexAccount.pid, - port: port, + port: server.port, token: nexToken } }).end()); diff --git a/src/types/common/config.ts b/src/types/common/config.ts index 4ce668d..4780d00 100644 --- a/src/types/common/config.ts +++ b/src/types/common/config.ts @@ -37,6 +37,7 @@ export interface Config { base_url: string; }; website_base: string; + aes_key: string; } export interface DisabledFeatures { diff --git a/src/types/common/crypto-options.ts b/src/types/common/crypto-options.ts deleted file mode 100644 index d4a5918..0000000 --- a/src/types/common/crypto-options.ts +++ /dev/null @@ -1,4 +0,0 @@ -export interface CryptoOptions { - public_key: Buffer; - hmac_secret: Buffer; -}; \ No newline at end of file diff --git a/src/types/mongoose/server.ts b/src/types/mongoose/server.ts index a385196..02fc372 100644 --- a/src/types/mongoose/server.ts +++ b/src/types/mongoose/server.ts @@ -1,15 +1,16 @@ import { Model, HydratedDocument } from 'mongoose'; export interface IServer { - ip: string; // Example: 1.1.1.1 - port: number; // Example: 60000 - service_name: string; // Example: friends - service_type: string; // Example: nex - game_server_id: string; // Example: 00003200 - title_ids: string[]; // Example: ["000500001018DB00", "000500001018DC00", "000500001018DD00"] - access_mode: string; // Example: prod - maintenance_mode: boolean; // Example: false - device: number; // Example: 1 (WiiU) + ip: string; + port: number; + service_name: string; + service_type: string; + game_server_id: string; + title_ids: string[]; + access_mode: string; + maintenance_mode: boolean; + device: number; + aes_key: string; } export interface IServerMethods {} diff --git a/src/util.ts b/src/util.ts index 16b1b08..a884cad 100644 --- a/src/util.ts +++ b/src/util.ts @@ -1,15 +1,12 @@ import crypto from 'node:crypto'; import path from 'node:path'; -import NodeRSA from 'node-rsa'; import aws from 'aws-sdk'; import fs from 'fs-extra'; import express from 'express'; import mongoose from 'mongoose'; import { ParsedQs } from 'qs'; import { sendMail } from '@/mailer'; -import { getServiceAESKey, getServicePrivateKey, getServiceSecretKey, getServicePublicKey } from '@/cache'; import { config, disabledFeatures } from '@/config-manager'; -import { CryptoOptions } from '@/types/common/crypto-options'; import { TokenOptions } from '@/types/common/token-options'; import { Token } from '@/types/common/token'; import { IPNID, IPNIDMethods } from '@/types/mongoose/pnid'; @@ -50,178 +47,61 @@ export function nintendoBase64Encode(decoded: string | Buffer): string { return encoded.replaceAll('+', '.').replaceAll('/', '-').replaceAll('=', '*'); } -export async function generateToken(cryptoOptions: CryptoOptions | null, tokenOptions: TokenOptions): Promise { - // Access and refresh tokens use a different format since they must be much smaller - // They take no extra crypto options - if (!cryptoOptions) { - const aesKey: Buffer = await getServiceAESKey('account', 'hex'); +export function generateToken(key: string, options: TokenOptions): Buffer | null { + let dataBuffer: Buffer = Buffer.alloc(1 + 1 + 4 + 8); - const dataBuffer: Buffer = Buffer.alloc(1 + 1 + 4 + 8); + dataBuffer.writeUInt8(options.system_type, 0x0); + dataBuffer.writeUInt8(options.token_type, 0x1); + dataBuffer.writeUInt32LE(options.pid, 0x2); + dataBuffer.writeBigUInt64LE(options.expire_time, 0x6); - dataBuffer.writeUInt8(tokenOptions.system_type, 0x0); - dataBuffer.writeUInt8(tokenOptions.token_type, 0x1); - dataBuffer.writeUInt32LE(tokenOptions.pid, 0x2); - dataBuffer.writeBigUInt64LE(tokenOptions.expire_time, 0x6); + if (options.token_type !== 0x1 && options.token_type !== 0x2) { + if (options.access_level === undefined || options.title_id === undefined) { + return null; + } - const iv: Buffer = Buffer.alloc(16); - const cipher: crypto.Cipher = crypto.createCipheriv('aes-128-cbc', aesKey, iv); + dataBuffer = Buffer.concat([ + dataBuffer, + Buffer.alloc(8 + 1) + ]); - let encryptedBody: Buffer = cipher.update(dataBuffer); - encryptedBody = Buffer.concat([encryptedBody, cipher.final()]); - - return encryptedBody.toString('base64'); - } else if (tokenOptions.access_level === undefined || tokenOptions.title_id === undefined) { - return null; + dataBuffer.writeBigUInt64LE(options.title_id, 0xE); + dataBuffer.writeUInt8(options.access_level, 0x16); } - const publicKey: NodeRSA = new NodeRSA(cryptoOptions.public_key, 'pkcs8-public-pem', { - environment: 'browser', - encryptionScheme: { - scheme: 'pkcs1_oaep', - hash: 'sha256' - } - }); + const iv: Buffer = Buffer.alloc(16); + const cipher: crypto.Cipher = crypto.createCipheriv('aes-256-cbc', Buffer.from(key, 'hex'), iv); - // Create the buffer containing the token data - const dataBuffer: Buffer = Buffer.alloc(1 + 1 + 4 + 1 + 8 + 8); - - dataBuffer.writeUInt8(tokenOptions.system_type, 0x0); - dataBuffer.writeUInt8(tokenOptions.token_type, 0x1); - dataBuffer.writeUInt32LE(tokenOptions.pid, 0x2); - dataBuffer.writeUInt8(tokenOptions.access_level, 0x6); - dataBuffer.writeBigUInt64LE(tokenOptions.title_id, 0x7); - dataBuffer.writeBigUInt64LE(tokenOptions.expire_time, 0xF); - - // Calculate the signature of the token body - const hmac: crypto.Hmac = crypto.createHmac('sha1', cryptoOptions.hmac_secret).update(dataBuffer); - const signature: Buffer = hmac.digest(); - - // You can thank the 3DS for the shit thats about to happen with the AES IV - // The 3DS only allows for strings up to 255 characters in NEX - // So this is done to reduce the token size as much as possible - // I am sorry, and have already asked every God I could think of for forgiveness - - // Generate random AES key - const key: Buffer = crypto.randomBytes(16); - - // Encrypt the AES key with RSA public key - const encryptedKey: Buffer = publicKey.encrypt(key); - - // Take two random points in the RSA encrypted key - const point1: number = ~~((encryptedKey.length - 8) * Math.random()); - const point2: number = ~~((encryptedKey.length - 8) * Math.random()); - - // Build an IV from each of the two points - const iv: Buffer = Buffer.concat([ - Buffer.from(encryptedKey.subarray(point1, point1 + 8)), - Buffer.from(encryptedKey.subarray(point2, point2 + 8)) - ]); - - const cipher: crypto.Cipher = crypto.createCipheriv('aes-128-cbc', key, iv); - - // Encrypt the token body with AES - const encryptedBody: Buffer = Buffer.concat([ + return Buffer.concat([ cipher.update(dataBuffer), cipher.final() ]); - - // Create crypto config token section - const cryptoConfig: Buffer = Buffer.concat([ - encryptedKey, - Buffer.from([point1, point2]) - ]); - - // Build the token - const token: Buffer = Buffer.concat([ - cryptoConfig, - signature, - encryptedBody - ]); - - return token.toString('base64'); // Encode to base64 for transport } -export async function decryptToken(token: Buffer): Promise { - // Access and refresh tokens use a different format since they must be much smaller - // Assume a small length means access or refresh token - if (token.length <= 32) { - const aesKey: Buffer = await getServiceAESKey('account', 'hex'); +export function decryptToken(key: string, token: Buffer): Buffer { + const iv: Buffer = Buffer.alloc(16); + const decipher: crypto.Decipher = crypto.createDecipheriv('aes-256-cbc', Buffer.from(key, 'hex'), iv); - const iv: Buffer = Buffer.alloc(16); - - const decipher: crypto.Decipher = crypto.createDecipheriv('aes-128-cbc', aesKey, iv); - - const decryptedBody: Buffer = Buffer.concat([ - decipher.update(token), - decipher.final() - ]); - - return decryptedBody; - } - - const privateKeyBytes: Buffer = await getServicePrivateKey('account'); - const secretKey: Buffer = await getServiceSecretKey('account'); - - const privateKey: NodeRSA = new NodeRSA(privateKeyBytes, 'pkcs1-private-pem', { - environment: 'browser', - encryptionScheme: { - scheme: 'pkcs1_oaep', - hash: 'sha256' - } - }); - - const cryptoConfig: Buffer = token.subarray(0, 0x82); - const signature: Buffer = token.subarray(0x82, 0x96); - const encryptedBody: Buffer = token.subarray(0x96); - - const encryptedAESKey: Buffer = cryptoConfig.subarray(0, 128); - const point1: number = cryptoConfig.readInt8(0x80); - const point2: number = cryptoConfig.readInt8(0x81); - - const iv: Buffer = Buffer.concat([ - Buffer.from(encryptedAESKey.subarray(point1, point1 + 8)), - Buffer.from(encryptedAESKey.subarray(point2, point2 + 8)) - ]); - - const decryptedAESKey: Buffer = privateKey.decrypt(encryptedAESKey); - - const decipher: crypto.Decipher = crypto.createDecipheriv('aes-128-cbc', decryptedAESKey, iv); - - const decryptedBody: Buffer = Buffer.concat([ - decipher.update(encryptedBody), + return Buffer.concat([ + decipher.update(token), decipher.final() ]); - - const hmac: crypto.Hmac = crypto.createHmac('sha1', secretKey).update(decryptedBody); - const calculatedSignature: Buffer = hmac.digest(); - - if (!signature.equals(calculatedSignature)) { - // TODO - FIX THIS. ONLY DONE SO STRICT MODE DOESN'T YELL - console.log('Token signature did not match'); - return Buffer.alloc(0); - } - - return decryptedBody; } export function unpackToken(token: Buffer): Token { - if (token.length <= 14) { - return { - system_type: token.readUInt8(0x0), - token_type: token.readUInt8(0x1), - pid: token.readUInt32LE(0x2), - expire_time: token.readBigUInt64LE(0x6) - }; - } - - return { + const unpacked: Token = { system_type: token.readUInt8(0x0), token_type: token.readUInt8(0x1), pid: token.readUInt32LE(0x2), - access_level: token.readUInt8(0x6), - title_id: token.readBigUInt64LE(0x7), - expire_time: token.readBigUInt64LE(0xF) + expire_time: token.readBigUInt64LE(0x6) }; + + if (unpacked.token_type !== 0x1 && unpacked.token_type !== 0x2) { + unpacked.title_id = token.readBigUInt64LE(0xE); + unpacked.access_level = token.readUInt8(0x16); + } + + return unpacked; } export function fullUrl(request: express.Request): string { @@ -289,24 +169,17 @@ export async function sendEmailConfirmedEmail(pnid: mongoose.HydratedDocument): Promise { - const publicKey: Buffer = await getServicePublicKey('account'); - const secretKey: Buffer = await getServiceSecretKey('account'); - - const cryptoOptions: CryptoOptions = { - public_key: publicKey, - hmac_secret: secretKey - }; - const tokenOptions: TokenOptions = { - system_type: 0xF, // API - token_type: 0x5, // Password reset + system_type: 0xF, // * API + token_type: 0x5, // * Password reset pid: pnid.pid, access_level: pnid.access_level, title_id: BigInt(0), expire_time: BigInt(Date.now() + (24 * 60 * 60 * 1000)) // Only valid for 24 hours }; - const passwordResetToken: string | null = await generateToken(cryptoOptions, tokenOptions); + const tokenBuffer: Buffer | null = await generateToken(config.aes_key, tokenOptions); + const passwordResetToken: string = tokenBuffer ? tokenBuffer.toString('hex') : ''; // TODO - Handle null token @@ -317,9 +190,9 @@ export async function sendForgotPasswordEmail(pnid: mongoose.HydratedDocument Date: Thu, 27 Apr 2023 14:35:27 -0400 Subject: [PATCH 061/219] Updated lint and build commands --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 8e2b0dc..e0c005f 100644 --- a/package.json +++ b/package.json @@ -4,8 +4,8 @@ "description": "", "main": "./dist/server.js", "scripts": { - "lint": "./node_modules/.bin/eslint .", - "build": "npm run clean && tsc && tsc-alias && npm run copy-static", + "lint": "npx eslint .", + "build": "npm run lint && npm run clean && tsc && tsc-alias && npm run copy-static", "clean": "rm -rf ./dist", "copy-static": "npm run copy-assets && npm run copy-timezones", "copy-assets": "cp -r ./src/assets ./dist/assets", From bbf6a9340a0cea83bdc9399ee8d6853ee1612a86 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Thu, 27 Apr 2023 14:38:03 -0400 Subject: [PATCH 062/219] Fix linting issues --- src/types/common/mailer-options.ts | 2 +- src/types/common/signature-size.ts | 2 +- src/types/common/token-options.ts | 2 +- src/types/common/token.ts | 2 +- src/types/services/api/discord-connection-data.ts | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/types/common/mailer-options.ts b/src/types/common/mailer-options.ts index 277cf33..265b42a 100644 --- a/src/types/common/mailer-options.ts +++ b/src/types/common/mailer-options.ts @@ -13,4 +13,4 @@ export interface MailerOptions { href: string; code: string; }; -}; \ No newline at end of file +} \ No newline at end of file diff --git a/src/types/common/signature-size.ts b/src/types/common/signature-size.ts index 3fe755e..c9f9bbf 100644 --- a/src/types/common/signature-size.ts +++ b/src/types/common/signature-size.ts @@ -1,4 +1,4 @@ export interface SignatureSize { SIZE: number; PADDING_SIZE: number; -}; \ No newline at end of file +} \ No newline at end of file diff --git a/src/types/common/token-options.ts b/src/types/common/token-options.ts index 4a9bb15..6dcc85e 100644 --- a/src/types/common/token-options.ts +++ b/src/types/common/token-options.ts @@ -5,4 +5,4 @@ export interface TokenOptions { access_level?: number; title_id?: bigint; expire_time: bigint; -}; \ No newline at end of file +} \ No newline at end of file diff --git a/src/types/common/token.ts b/src/types/common/token.ts index 84673c2..4c7c558 100644 --- a/src/types/common/token.ts +++ b/src/types/common/token.ts @@ -5,4 +5,4 @@ export interface Token { access_level?: number; title_id?: bigint; expire_time: bigint; -}; \ No newline at end of file +} \ No newline at end of file diff --git a/src/types/services/api/discord-connection-data.ts b/src/types/services/api/discord-connection-data.ts index b9ed367..0ee459a 100644 --- a/src/types/services/api/discord-connection-data.ts +++ b/src/types/services/api/discord-connection-data.ts @@ -1,3 +1,3 @@ export interface DiscordConnectionData { id: string; -}; \ No newline at end of file +} \ No newline at end of file From 36bf30a64a6a355b00a173dfc64cc5da1f898e4f Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Thu, 27 Apr 2023 14:38:28 -0400 Subject: [PATCH 063/219] Replace rm -rf with rimraf --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e0c005f..7128e53 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "scripts": { "lint": "npx eslint .", "build": "npm run lint && npm run clean && tsc && tsc-alias && npm run copy-static", - "clean": "rm -rf ./dist", + "clean": "rimraf ./dist", "copy-static": "npm run copy-assets && npm run copy-timezones", "copy-assets": "cp -r ./src/assets ./dist/assets", "copy-timezones": "cp ./src/services/nnid/timezones.json ./dist/services/nnid/timezones.json", From 84c2086da2b97c54886eb1d49f491729b3d64138 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Thu, 27 Apr 2023 14:39:02 -0400 Subject: [PATCH 064/219] Updated build command --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7128e53..535bcef 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "main": "./dist/server.js", "scripts": { "lint": "npx eslint .", - "build": "npm run lint && npm run clean && tsc && tsc-alias && npm run copy-static", + "build": "npm run lint && npm run clean && npx tsc && npx tsc-alias && npm run copy-static", "clean": "rimraf ./dist", "copy-static": "npm run copy-assets && npm run copy-timezones", "copy-assets": "cp -r ./src/assets ./dist/assets", From d6d92ca27c1246cf2f8efd65b768186963e3fa2d Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Thu, 27 Apr 2023 15:05:22 -0400 Subject: [PATCH 065/219] Added types to modules without types --- package-lock.json | 794 ++++++++++++----------- package.json | 4 + src/models/pnid.ts | 8 +- src/types/express-subdomain.d.ts | 17 +- src/types/image-pixels.d.ts | 35 +- src/types/mongoose-unique-validator.d.ts | 1 - src/types/tga.d.ts | 59 +- 7 files changed, 525 insertions(+), 393 deletions(-) delete mode 100644 src/types/mongoose-unique-validator.d.ts diff --git a/package-lock.json b/package-lock.json index 3c28cac..91b3c7b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -45,15 +45,19 @@ "@types/dicer": "^0.2.2", "@types/express": "^4.17.17", "@types/fs-extra": "^11.0.1", + "@types/mongoose-unique-validator": "^1.0.7", "@types/morgan": "^1.9.4", + "@types/ndarray": "^1.0.11", "@types/node": "^18.14.4", "@types/node-rsa": "^1.1.1", "@types/nodemailer": "^6.4.7", "@types/qs": "^6.9.7", "@types/validator": "^13.7.14", + "@types/w3c-css-typed-object-model-level-1": "^20180410.0.5", "@typescript-eslint/eslint-plugin": "^5.54.1", "@typescript-eslint/parser": "^5.54.1", "eslint": "^8.35.0", + "ndarray": "^1.0.19", "prompt": "^1.0.0", "typescript": "^4.9.5", "yesno": "^0.4.0" @@ -64,7 +68,6 @@ "resolved": "https://registry.npmjs.org/@aws-crypto/ie11-detection/-/ie11-detection-3.0.0.tgz", "integrity": "sha512-341lBBkiY1DfDNKai/wXM3aujNBkXR7tq1URPQDL9wi3AUbI80NR74uF1TXHMm7po1AcnFk8iu2S2IeU/+/A+Q==", "optional": true, - "peer": true, "dependencies": { "tslib": "^1.11.1" } @@ -74,7 +77,6 @@ "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-3.0.0.tgz", "integrity": "sha512-8VLmW2B+gjFbU5uMeqtQM6Nj0/F1bro80xQXCW6CQBWgosFWXTx77aeOF5CAIAmbOK64SdMBJdNr6J41yP5mvQ==", "optional": true, - "peer": true, "dependencies": { "@aws-crypto/ie11-detection": "^3.0.0", "@aws-crypto/sha256-js": "^3.0.0", @@ -91,7 +93,6 @@ "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-3.0.0.tgz", "integrity": "sha512-PnNN7os0+yd1XvXAy23CFOmTbMaDxgxXtTKHybrJ39Y8kGzBATgBFibWJKH6BhytLI/Zyszs87xCOBNyBig6vQ==", "optional": true, - "peer": true, "dependencies": { "@aws-crypto/util": "^3.0.0", "@aws-sdk/types": "^3.222.0", @@ -103,7 +104,6 @@ "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-3.0.0.tgz", "integrity": "sha512-06hBdMwUAb2WFTuGG73LSC0wfPu93xWwo5vL2et9eymgmu3Id5vFAHBbajVWiGhPO37qcsdCap/FqXvJGJWPIg==", "optional": true, - "peer": true, "dependencies": { "tslib": "^1.11.1" } @@ -113,7 +113,6 @@ "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-3.0.0.tgz", "integrity": "sha512-2OJlpeJpCR48CC8r+uKVChzs9Iungj9wkZrl8Z041DWEWvyIHILYKCPNzJghKsivj+S3mLo6BVc7mBNzdxA46w==", "optional": true, - "peer": true, "dependencies": { "@aws-sdk/types": "^3.222.0", "@aws-sdk/util-utf8-browser": "^3.0.0", @@ -125,7 +124,6 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/abort-controller/-/abort-controller-3.292.0.tgz", "integrity": "sha512-lf+OPptL01kvryIJy7+dvFux5KbJ6OTwLPPEekVKZ2AfEvwcVtOZWFUhyw3PJCBTVncjKB1Kjl3V/eTS3YuPXQ==", "optional": true, - "peer": true, "dependencies": { "@aws-sdk/types": "3.292.0", "tslib": "^2.3.1" @@ -138,15 +136,13 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true }, "node_modules/@aws-sdk/client-cognito-identity": { "version": "3.294.0", "resolved": "https://registry.npmjs.org/@aws-sdk/client-cognito-identity/-/client-cognito-identity-3.294.0.tgz", "integrity": "sha512-QMk/QratNvAvmnJ77pu9KDjpfUf/5LOJplHcLKcY962ewJGBtFFY4XZVnmUgQMSG0phRODtex7pyH//FueGKLQ==", "optional": true, - "peer": true, "dependencies": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", @@ -192,15 +188,13 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true }, "node_modules/@aws-sdk/client-sso": { "version": "3.294.0", "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.294.0.tgz", "integrity": "sha512-+FuxQTi5WvnaXM5JbNLkBIzQ3An4gA0ox61N1u+3xled+nywKb1yQ7WmRpyMG5bLbkmnj3aqoo5/uskFc4c4EA==", "optional": true, - "peer": true, "dependencies": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", @@ -244,7 +238,6 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.294.0.tgz", "integrity": "sha512-/ZfDud76MdSPJ/TxjV2xLE30XbBQDZwKQ32axwoK1eziPvrAIUBYVgpBwj+m0quhoiQhBKkg3aFl6j39AF2thw==", "optional": true, - "peer": true, "dependencies": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", @@ -287,22 +280,19 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true }, "node_modules/@aws-sdk/client-sso/node_modules/tslib": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true }, "node_modules/@aws-sdk/client-sts": { "version": "3.294.0", "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.294.0.tgz", "integrity": "sha512-AefqwhFjTDzelZuSYhriJbiI+GQwf2yKiKAnCt0gRj6rswewStM63Gtlhfb01sFPp+ZiqPcyQ47LqUaHp1mz/g==", "optional": true, - "peer": true, "dependencies": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", @@ -349,15 +339,13 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true }, "node_modules/@aws-sdk/config-resolver": { "version": "3.292.0", "resolved": "https://registry.npmjs.org/@aws-sdk/config-resolver/-/config-resolver-3.292.0.tgz", "integrity": "sha512-cB3twnNR7vYvlt2jvw8VlA1+iv/tVzl+/S39MKqw2tepU+AbJAM0EHwb/dkf1OKSmlrnANXhshx80MHF9zL4mA==", "optional": true, - "peer": true, "dependencies": { "@aws-sdk/signature-v4": "3.292.0", "@aws-sdk/types": "3.292.0", @@ -373,15 +361,13 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true }, "node_modules/@aws-sdk/credential-provider-cognito-identity": { "version": "3.294.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-cognito-identity/-/credential-provider-cognito-identity-3.294.0.tgz", "integrity": "sha512-YSPqHEbLC0dnbFF5LdlMH0B50sMSN/CyG/sHkPYUwL/hkbUk9URnVW7ZJlt6lRftT7X4C7muzLqUP8sJAaiJEA==", "optional": true, - "peer": true, "dependencies": { "@aws-sdk/client-cognito-identity": "3.294.0", "@aws-sdk/property-provider": "3.292.0", @@ -396,15 +382,13 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true }, "node_modules/@aws-sdk/credential-provider-env": { "version": "3.292.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.292.0.tgz", "integrity": "sha512-YbafSG0ZEKE2969CJWVtUhh3hfOeLPecFVoXOtegCyAJgY5Ghtu4TsVhL4DgiGAgOC30ojAmUVQEXzd7xJF5xA==", "optional": true, - "peer": true, "dependencies": { "@aws-sdk/property-provider": "3.292.0", "@aws-sdk/types": "3.292.0", @@ -418,15 +402,13 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true }, "node_modules/@aws-sdk/credential-provider-imds": { "version": "3.292.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-imds/-/credential-provider-imds-3.292.0.tgz", "integrity": "sha512-W/peOgDSRYulgzFpUhvgi1pCm6piBz6xrVN17N4QOy+3NHBXRVMVzYk6ct2qpLPgJUSEZkcpP+Gds+bBm8ed1A==", "optional": true, - "peer": true, "dependencies": { "@aws-sdk/node-config-provider": "3.292.0", "@aws-sdk/property-provider": "3.292.0", @@ -442,15 +424,13 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true }, "node_modules/@aws-sdk/credential-provider-ini": { "version": "3.294.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.294.0.tgz", "integrity": "sha512-pdTPbaAb5bWA+DnuKoL2TpXeNDp6Ejpv/OYt+bw2gdzl9w5r/ZCtUTTbW+Vvejr4WL5s3c1bY96kwdqCn7iLqA==", "optional": true, - "peer": true, "dependencies": { "@aws-sdk/credential-provider-env": "3.292.0", "@aws-sdk/credential-provider-imds": "3.292.0", @@ -470,15 +450,13 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true }, "node_modules/@aws-sdk/credential-provider-node": { "version": "3.294.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.294.0.tgz", "integrity": "sha512-zUL1Qhb4BsQIZCs/TPpG4oIYH/9YsGiS+Se1tasSGjTOLfBy7jhOZ0QIdpEeyAx/EP8blOBredM9xWfEXgiHVA==", "optional": true, - "peer": true, "dependencies": { "@aws-sdk/credential-provider-env": "3.292.0", "@aws-sdk/credential-provider-imds": "3.292.0", @@ -499,15 +477,13 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true }, "node_modules/@aws-sdk/credential-provider-process": { "version": "3.292.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.292.0.tgz", "integrity": "sha512-CFVXuMuUvg/a4tknzRikEDwZBnKlHs1LZCpTXIGjBdUTdosoi4WNzDLzGp93ZRTtcgFz+4wirz2f7P3lC0NrQw==", "optional": true, - "peer": true, "dependencies": { "@aws-sdk/property-provider": "3.292.0", "@aws-sdk/shared-ini-file-loader": "3.292.0", @@ -522,15 +498,13 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true }, "node_modules/@aws-sdk/credential-provider-sso": { "version": "3.294.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.294.0.tgz", "integrity": "sha512-UxrcAA/0l7j9+3tolYcG5M61D/IE1Bjd/9H87H1i2A2BrwUUBhW1Dp/vvROEDrrywlMDG3CDF3T/7ADtTak+sg==", "optional": true, - "peer": true, "dependencies": { "@aws-sdk/client-sso": "3.294.0", "@aws-sdk/property-provider": "3.292.0", @@ -547,15 +521,13 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true }, "node_modules/@aws-sdk/credential-provider-web-identity": { "version": "3.292.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.292.0.tgz", "integrity": "sha512-4DbtIEM9gGVfqYlMdYXg3XY+vBhemjB1zXIequottW8loLYM8Vuz4/uGxxKNze6evVVzowsA0wKrYclE1aj/Rg==", "optional": true, - "peer": true, "dependencies": { "@aws-sdk/property-provider": "3.292.0", "@aws-sdk/types": "3.292.0", @@ -569,15 +541,13 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true }, "node_modules/@aws-sdk/credential-providers": { "version": "3.294.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-providers/-/credential-providers-3.294.0.tgz", "integrity": "sha512-CRGwCs6F31mQzjYi5YF2Z6c1T+UrFKtJSa6Ff/Q1rrPnROexyhBVnpP8WzpkODx/pZxKtTX50IX7ehIxbFDIyQ==", "optional": true, - "peer": true, "dependencies": { "@aws-sdk/client-cognito-identity": "3.294.0", "@aws-sdk/client-sso": "3.294.0", @@ -603,15 +573,13 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true }, "node_modules/@aws-sdk/fetch-http-handler": { "version": "3.292.0", "resolved": "https://registry.npmjs.org/@aws-sdk/fetch-http-handler/-/fetch-http-handler-3.292.0.tgz", "integrity": "sha512-zh3bhUJbL8RSa39ZKDcy+AghtUkIP8LwcNlwRIoxMQh3Row4D1s4fCq0KZCx98NJBEXoiTLyTQlZxxI//BOb1Q==", "optional": true, - "peer": true, "dependencies": { "@aws-sdk/protocol-http": "3.292.0", "@aws-sdk/querystring-builder": "3.292.0", @@ -624,15 +592,13 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true }, "node_modules/@aws-sdk/hash-node": { "version": "3.292.0", "resolved": "https://registry.npmjs.org/@aws-sdk/hash-node/-/hash-node-3.292.0.tgz", "integrity": "sha512-1yLxmIsvE+eK36JXEgEIouTITdykQLVhsA5Oai//Lar6Ddgu1sFpLDbdkMtKbrh4I0jLN9RacNCkeVQjZPTCCQ==", "optional": true, - "peer": true, "dependencies": { "@aws-sdk/types": "3.292.0", "@aws-sdk/util-buffer-from": "3.292.0", @@ -647,15 +613,13 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true }, "node_modules/@aws-sdk/invalid-dependency": { "version": "3.292.0", "resolved": "https://registry.npmjs.org/@aws-sdk/invalid-dependency/-/invalid-dependency-3.292.0.tgz", "integrity": "sha512-39OUV78CD3TmEbjhpt+V+Fk4wAGWhixqHxDSN8+4WL0uB4Fl7k5m3Z9hNY78AttHQSl2twR7WtLztnXPAFsriw==", "optional": true, - "peer": true, "dependencies": { "@aws-sdk/types": "3.292.0", "tslib": "^2.3.1" @@ -665,15 +629,13 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true }, "node_modules/@aws-sdk/is-array-buffer": { "version": "3.292.0", "resolved": "https://registry.npmjs.org/@aws-sdk/is-array-buffer/-/is-array-buffer-3.292.0.tgz", "integrity": "sha512-kW/G5T/fzI0sJH5foZG6XJiNCevXqKLxV50qIT4B1pMuw7regd4ALIy0HwSqj1nnn9mSbRWBfmby0jWCJsMcwg==", "optional": true, - "peer": true, "dependencies": { "tslib": "^2.3.1" }, @@ -685,15 +647,13 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true }, "node_modules/@aws-sdk/middleware-content-length": { "version": "3.292.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-content-length/-/middleware-content-length-3.292.0.tgz", "integrity": "sha512-2gMWzQus5mj14menolpPDbYBeaOYcj7KNFZOjTjjI3iQ0KqyetG6XasirNrcJ/8QX1BRmpTol8Xjp2Ue3Gbzwg==", "optional": true, - "peer": true, "dependencies": { "@aws-sdk/protocol-http": "3.292.0", "@aws-sdk/types": "3.292.0", @@ -707,15 +667,13 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true }, "node_modules/@aws-sdk/middleware-endpoint": { "version": "3.292.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-endpoint/-/middleware-endpoint-3.292.0.tgz", "integrity": "sha512-cPMkiSxpZGG6tYlW4OS+ucS6r43f9ddX9kcUoemJCY10MOuogdPjulCAjE0HTs2PLKSOrrG4CTP4Q4wWDrH4Bw==", "optional": true, - "peer": true, "dependencies": { "@aws-sdk/middleware-serde": "3.292.0", "@aws-sdk/protocol-http": "3.292.0", @@ -734,15 +692,13 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true }, "node_modules/@aws-sdk/middleware-host-header": { "version": "3.292.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.292.0.tgz", "integrity": "sha512-mHuCWe3Yg2S5YZ7mB7sKU6C97XspfqrimWjMW9pfV2usAvLA3R0HrB03jpR5vpZ3P4q7HB6wK3S6CjYMGGRNag==", "optional": true, - "peer": true, "dependencies": { "@aws-sdk/protocol-http": "3.292.0", "@aws-sdk/types": "3.292.0", @@ -756,15 +712,13 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true }, "node_modules/@aws-sdk/middleware-logger": { "version": "3.292.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.292.0.tgz", "integrity": "sha512-yZNY1XYmG3NG+uonET7jzKXNiwu61xm/ZZ6i/l51SusuaYN+qQtTAhOFsieQqTehF9kP4FzbsWgPDwD8ZZX9lw==", "optional": true, - "peer": true, "dependencies": { "@aws-sdk/types": "3.292.0", "tslib": "^2.3.1" @@ -777,15 +731,13 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true }, "node_modules/@aws-sdk/middleware-recursion-detection": { "version": "3.292.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.292.0.tgz", "integrity": "sha512-kA3VZpPko0Zqd7CYPTKAxhjEv0HJqFu2054L04dde1JLr43ro+2MTdX7vsHzeAFUVRphqatFFofCumvXmU6Mig==", "optional": true, - "peer": true, "dependencies": { "@aws-sdk/protocol-http": "3.292.0", "@aws-sdk/types": "3.292.0", @@ -799,15 +751,13 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true }, "node_modules/@aws-sdk/middleware-retry": { "version": "3.293.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-retry/-/middleware-retry-3.293.0.tgz", "integrity": "sha512-7tiaz2GzRecNHaZ6YnF+Nrtk3au8qF6oiipf11R7MJiqJ0fkMLnz/iRrlakDziS9qF/a9v+3yxb4W4NHK3f4Tw==", "optional": true, - "peer": true, "dependencies": { "@aws-sdk/protocol-http": "3.292.0", "@aws-sdk/service-error-classification": "3.292.0", @@ -825,15 +775,13 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true }, "node_modules/@aws-sdk/middleware-retry/node_modules/uuid": { "version": "8.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", "optional": true, - "peer": true, "bin": { "uuid": "dist/bin/uuid" } @@ -843,7 +791,6 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-sts/-/middleware-sdk-sts-3.292.0.tgz", "integrity": "sha512-GN5ZHEqXZqDi+HkVbaXRX9HaW/vA5rikYpWKYsmxTUZ7fB7ijvEO3co3lleJv2C+iGYRtUIHC4wYNB5xgoTCxg==", "optional": true, - "peer": true, "dependencies": { "@aws-sdk/middleware-signing": "3.292.0", "@aws-sdk/property-provider": "3.292.0", @@ -860,15 +807,13 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true }, "node_modules/@aws-sdk/middleware-serde": { "version": "3.292.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-serde/-/middleware-serde-3.292.0.tgz", "integrity": "sha512-6hN9mTQwSvV8EcGvtXbS/MpK7WMCokUku5Wu7X24UwCNMVkoRHLIkYcxHcvBTwttuOU0d8hph1/lIX4dkLwkQw==", "optional": true, - "peer": true, "dependencies": { "@aws-sdk/types": "3.292.0", "tslib": "^2.3.1" @@ -881,15 +826,13 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true }, "node_modules/@aws-sdk/middleware-signing": { "version": "3.292.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-signing/-/middleware-signing-3.292.0.tgz", "integrity": "sha512-GVfoSjDjEQ4TaO6x9MffyP3uRV+2KcS5FtexLCYOM9pJcnE9tqq9FJOrZ1xl1g+YjUVKxo4x8lu3tpEtIb17qg==", "optional": true, - "peer": true, "dependencies": { "@aws-sdk/property-provider": "3.292.0", "@aws-sdk/protocol-http": "3.292.0", @@ -906,15 +849,13 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true }, "node_modules/@aws-sdk/middleware-stack": { "version": "3.292.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-stack/-/middleware-stack-3.292.0.tgz", "integrity": "sha512-WdQpRkuMysrEwrkByCM1qCn2PPpFGGQ2iXqaFha5RzCdZDlxJni9cVNb6HzWUcgjLEYVTXCmOR9Wxm3CNW44Qg==", "optional": true, - "peer": true, "dependencies": { "tslib": "^2.3.1" }, @@ -926,15 +867,13 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true }, "node_modules/@aws-sdk/middleware-user-agent": { "version": "3.293.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.293.0.tgz", "integrity": "sha512-gZ7/e6XwpKk9mvgA78q4Ffc796jTn02TUKx2qMDnkLVbeJXBNN2jnvYEKq8v70+o7fd/ALRudg8gBDmkkhM/Hw==", "optional": true, - "peer": true, "dependencies": { "@aws-sdk/protocol-http": "3.292.0", "@aws-sdk/types": "3.292.0", @@ -949,15 +888,13 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true }, "node_modules/@aws-sdk/node-config-provider": { "version": "3.292.0", "resolved": "https://registry.npmjs.org/@aws-sdk/node-config-provider/-/node-config-provider-3.292.0.tgz", "integrity": "sha512-S3NnC9dQ5GIbJYSDIldZb4zdpCOEua1tM7bjYL3VS5uqCEM93kIi/o/UkIUveMp/eqTS2LJa5HjNIz5Te6je0A==", "optional": true, - "peer": true, "dependencies": { "@aws-sdk/property-provider": "3.292.0", "@aws-sdk/shared-ini-file-loader": "3.292.0", @@ -972,15 +909,13 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true }, "node_modules/@aws-sdk/node-http-handler": { "version": "3.292.0", "resolved": "https://registry.npmjs.org/@aws-sdk/node-http-handler/-/node-http-handler-3.292.0.tgz", "integrity": "sha512-L/E3UDSwXLXjt1XWWh0RBD55F+aZI1AEdPwdES9i1PjnZLyuxuDhEDptVibNN56+I9/4Q3SbmuVRVlOD0uzBag==", "optional": true, - "peer": true, "dependencies": { "@aws-sdk/abort-controller": "3.292.0", "@aws-sdk/protocol-http": "3.292.0", @@ -996,15 +931,13 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true }, "node_modules/@aws-sdk/property-provider": { "version": "3.292.0", "resolved": "https://registry.npmjs.org/@aws-sdk/property-provider/-/property-provider-3.292.0.tgz", "integrity": "sha512-dHArSvsiqhno/g55N815gXmAMrmN8DP7OeFNqJ4wJG42xsF2PFN3DAsjIuHuXMwu+7A3R1LHqIpvv0hA9KeoJQ==", "optional": true, - "peer": true, "dependencies": { "@aws-sdk/types": "3.292.0", "tslib": "^2.3.1" @@ -1017,15 +950,13 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true }, "node_modules/@aws-sdk/protocol-http": { "version": "3.292.0", "resolved": "https://registry.npmjs.org/@aws-sdk/protocol-http/-/protocol-http-3.292.0.tgz", "integrity": "sha512-NLi4fq3k41aXIh1I97yX0JTy+3p6aW1NdwFwdMa674z86QNfb4SfRQRZBQe9wEnAZ/eWHVnlKIuII+U1URk/Kg==", "optional": true, - "peer": true, "dependencies": { "@aws-sdk/types": "3.292.0", "tslib": "^2.3.1" @@ -1038,15 +969,13 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true }, "node_modules/@aws-sdk/querystring-builder": { "version": "3.292.0", "resolved": "https://registry.npmjs.org/@aws-sdk/querystring-builder/-/querystring-builder-3.292.0.tgz", "integrity": "sha512-XElIFJaReIm24eEvBtV2dOtZvcm3gXsGu/ftG8MLJKbKXFKpAP1q+K6En0Bs7/T88voKghKdKpKT+eZUWgTqlg==", "optional": true, - "peer": true, "dependencies": { "@aws-sdk/types": "3.292.0", "@aws-sdk/util-uri-escape": "3.292.0", @@ -1060,15 +989,13 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true }, "node_modules/@aws-sdk/querystring-parser": { "version": "3.292.0", "resolved": "https://registry.npmjs.org/@aws-sdk/querystring-parser/-/querystring-parser-3.292.0.tgz", "integrity": "sha512-iTYpYo7a8X9RxiPbjjewIpm6XQPx2EOcF1dWCPRII9EFlmZ4bwnX+PDI36fIo9oVs8TIKXmwNGODU9nsg7CSAw==", "optional": true, - "peer": true, "dependencies": { "@aws-sdk/types": "3.292.0", "tslib": "^2.3.1" @@ -1081,15 +1008,13 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true }, "node_modules/@aws-sdk/service-error-classification": { "version": "3.292.0", "resolved": "https://registry.npmjs.org/@aws-sdk/service-error-classification/-/service-error-classification-3.292.0.tgz", "integrity": "sha512-X1k3sixCeC45XSNHBe+kRBQBwPDyTFtFITb8O5Qw4dS9XWGhrUJT4CX0qE5aj8qP3F9U5nRizs9c2mBVVP0Caw==", "optional": true, - "peer": true, "engines": { "node": ">=14.0.0" } @@ -1099,7 +1024,6 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/shared-ini-file-loader/-/shared-ini-file-loader-3.292.0.tgz", "integrity": "sha512-Av2TTYg1Jig2kbkD56ybiqZJB6vVrYjv1W5UQwY/q3nA/T2mcrgQ20ByCOt5Bv9VvY7FSgC+znj+L4a7RLGmBg==", "optional": true, - "peer": true, "dependencies": { "@aws-sdk/types": "3.292.0", "tslib": "^2.3.1" @@ -1112,15 +1036,13 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true }, "node_modules/@aws-sdk/signature-v4": { "version": "3.292.0", "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4/-/signature-v4-3.292.0.tgz", "integrity": "sha512-+rw47VY5mvBecn13tDQTl1ipGWg5tE63faWgmZe68HoBL87ZiDzsd7bUKOvjfW21iMgWlwAppkaNNQayYRb2zg==", "optional": true, - "peer": true, "dependencies": { "@aws-sdk/is-array-buffer": "3.292.0", "@aws-sdk/types": "3.292.0", @@ -1138,15 +1060,13 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true }, "node_modules/@aws-sdk/smithy-client": { "version": "3.292.0", "resolved": "https://registry.npmjs.org/@aws-sdk/smithy-client/-/smithy-client-3.292.0.tgz", "integrity": "sha512-S8PKzjPkZ6SXYZuZiU787dMsvQ0d/LFEhw2OI4Oe2An9Fc2IwJ2FYukyHoQJOV2tV0DiuMebPo7eMyQyjKElvA==", "optional": true, - "peer": true, "dependencies": { "@aws-sdk/middleware-stack": "3.292.0", "@aws-sdk/types": "3.292.0", @@ -1160,15 +1080,13 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true }, "node_modules/@aws-sdk/token-providers": { "version": "3.294.0", "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.294.0.tgz", "integrity": "sha512-6nwO04LtC5f4AsUvGZXyjaswuEK4Rr2VsuANpMKrPCgunRfI58a8YXLniudOSXN6e7CFJ6M3uo/h5YXqtnzGug==", "optional": true, - "peer": true, "dependencies": { "@aws-sdk/client-sso-oidc": "3.294.0", "@aws-sdk/property-provider": "3.292.0", @@ -1184,15 +1102,13 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true }, "node_modules/@aws-sdk/types": { "version": "3.292.0", "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.292.0.tgz", "integrity": "sha512-1teYAY2M73UXZxMAxqZxVS2qwXjQh0OWtt7qyLfha0TtIk/fZ1hRwFgxbDCHUFcdNBSOSbKH/ESor90KROXLCQ==", "optional": true, - "peer": true, "dependencies": { "tslib": "^2.3.1" }, @@ -1204,15 +1120,13 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true }, "node_modules/@aws-sdk/url-parser": { "version": "3.292.0", "resolved": "https://registry.npmjs.org/@aws-sdk/url-parser/-/url-parser-3.292.0.tgz", "integrity": "sha512-NZeAuZCk1x6TIiWuRfbOU6wHPBhf0ly2qOHzWut4BCH+b4RrDmFF8EmXcH1auEfGhE7yRyR6XqIN0t3S+hYACA==", "optional": true, - "peer": true, "dependencies": { "@aws-sdk/querystring-parser": "3.292.0", "@aws-sdk/types": "3.292.0", @@ -1223,15 +1137,13 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true }, "node_modules/@aws-sdk/util-base64": { "version": "3.292.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-base64/-/util-base64-3.292.0.tgz", "integrity": "sha512-zjNCwNdy617yFvEjZorepNWXB2sQCVfsShCwFy/kIQ5iW5tT2jQKaqc0K77diU9atkooxw9p1W9m9sOgrkOFNw==", "optional": true, - "peer": true, "dependencies": { "@aws-sdk/util-buffer-from": "3.292.0", "tslib": "^2.3.1" @@ -1244,15 +1156,13 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true }, "node_modules/@aws-sdk/util-body-length-browser": { "version": "3.292.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-body-length-browser/-/util-body-length-browser-3.292.0.tgz", "integrity": "sha512-Wd/BM+JsMiKvKs/bN3z6TredVEHh2pKudGfg3CSjTRpqFpOG903KDfyHBD42yg5PuCHoHoewJvTPKwgn7/vhaw==", "optional": true, - "peer": true, "dependencies": { "tslib": "^2.3.1" } @@ -1261,15 +1171,13 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true }, "node_modules/@aws-sdk/util-body-length-node": { "version": "3.292.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-body-length-node/-/util-body-length-node-3.292.0.tgz", "integrity": "sha512-BBgipZ2P6RhogWE/qj0oqpdlyd3iSBYmb+aD/TBXwB2lA/X8A99GxweBd/kp06AmcJRoMS9WIXgbWkiiBlRlSA==", "optional": true, - "peer": true, "dependencies": { "tslib": "^2.3.1" }, @@ -1281,15 +1189,13 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true }, "node_modules/@aws-sdk/util-buffer-from": { "version": "3.292.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-buffer-from/-/util-buffer-from-3.292.0.tgz", "integrity": "sha512-RxNZjLoXNxHconH9TYsk5RaEBjSgTtozHeyIdacaHPj5vlQKi4hgL2hIfKeeNiAfQEVjaUFF29lv81xpNMzVMQ==", "optional": true, - "peer": true, "dependencies": { "@aws-sdk/is-array-buffer": "3.292.0", "tslib": "^2.3.1" @@ -1302,15 +1208,13 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true }, "node_modules/@aws-sdk/util-config-provider": { "version": "3.292.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-config-provider/-/util-config-provider-3.292.0.tgz", "integrity": "sha512-t3noYll6bPRSxeeNNEkC5czVjAiTPcsq00OwfJ2xyUqmquhLEfLwoJKmrT1uP7DjIEXdUtfoIQ2jWiIVm/oO5A==", "optional": true, - "peer": true, "dependencies": { "tslib": "^2.3.1" }, @@ -1322,15 +1226,13 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true }, "node_modules/@aws-sdk/util-defaults-mode-browser": { "version": "3.292.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-defaults-mode-browser/-/util-defaults-mode-browser-3.292.0.tgz", "integrity": "sha512-7+zVUlMGfa8/KT++9humHo6IDxTnxMCmWUj5jVNlkpk6h7Ecmppf7aXotviyVIA43lhtz0p2AErs0N0ekEUK+w==", "optional": true, - "peer": true, "dependencies": { "@aws-sdk/property-provider": "3.292.0", "@aws-sdk/types": "3.292.0", @@ -1345,15 +1247,13 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true }, "node_modules/@aws-sdk/util-defaults-mode-node": { "version": "3.292.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-defaults-mode-node/-/util-defaults-mode-node-3.292.0.tgz", "integrity": "sha512-SSIw85eF4BVs0fOJRyshT+R3b/UmBPhiVKCUZm2rq6+lIGkDPiSwQU3d/80AhXtiL5SFT/IzAKKgQd8qMa7q3A==", "optional": true, - "peer": true, "dependencies": { "@aws-sdk/config-resolver": "3.292.0", "@aws-sdk/credential-provider-imds": "3.292.0", @@ -1370,15 +1270,13 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true }, "node_modules/@aws-sdk/util-endpoints": { "version": "3.293.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.293.0.tgz", "integrity": "sha512-R/99aNV49Refpv5guiUjEUrZYlvnfaNBniB+/ZtMO3ixxUopapssCrUivuJrmhccmrYaTCZw7dRzIWjU1jJhKg==", "optional": true, - "peer": true, "dependencies": { "@aws-sdk/types": "3.292.0", "tslib": "^2.3.1" @@ -1391,15 +1289,13 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true }, "node_modules/@aws-sdk/util-hex-encoding": { "version": "3.292.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-hex-encoding/-/util-hex-encoding-3.292.0.tgz", "integrity": "sha512-qBd5KFIUywQ3qSSbj814S2srk0vfv8A6QMI+Obs1y2LHZFdQN5zViptI4UhXhKOHe+NnrHWxSuLC/LMH6q3SmA==", "optional": true, - "peer": true, "dependencies": { "tslib": "^2.3.1" }, @@ -1411,15 +1307,13 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true }, "node_modules/@aws-sdk/util-locate-window": { "version": "3.292.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.292.0.tgz", "integrity": "sha512-6xnFJXZI9pKw5lQCDvuWA5PnOaUtNRKWwdxvGkkLx5orboFaoVMS6zowjSQxwVNRjW82u6dYNkhmj9mZ8VSjWg==", "optional": true, - "peer": true, "dependencies": { "tslib": "^2.3.1" }, @@ -1431,15 +1325,13 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true }, "node_modules/@aws-sdk/util-middleware": { "version": "3.292.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-middleware/-/util-middleware-3.292.0.tgz", "integrity": "sha512-KjhS7flfoBKDxbiBZjLjMvEizXgjfQb7GQEItgzGoI9rfGCmZtvqCcqQQoIlxb8bIzGRggAUHtBGWnlLbpb+GQ==", "optional": true, - "peer": true, "dependencies": { "tslib": "^2.3.1" }, @@ -1451,15 +1343,13 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true }, "node_modules/@aws-sdk/util-retry": { "version": "3.292.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-retry/-/util-retry-3.292.0.tgz", "integrity": "sha512-JEHyF7MpVeRF5uR4LDYgpOKcFpOPiAj8TqN46SVOQQcL1K+V7cSr7O7N7J6MwJaN9XOzAcBadeIupMm7/BFbgw==", "optional": true, - "peer": true, "dependencies": { "@aws-sdk/service-error-classification": "3.292.0", "tslib": "^2.3.1" @@ -1472,15 +1362,13 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true }, "node_modules/@aws-sdk/util-uri-escape": { "version": "3.292.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-uri-escape/-/util-uri-escape-3.292.0.tgz", "integrity": "sha512-hOQtUMQ4VcQ9iwKz50AoCp1XBD5gJ9nly/gJZccAM7zSA5mOO8RRKkbdonqquVHxrO0CnYgiFeCh3V35GFecUw==", "optional": true, - "peer": true, "dependencies": { "tslib": "^2.3.1" }, @@ -1492,15 +1380,13 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true }, "node_modules/@aws-sdk/util-user-agent-browser": { "version": "3.292.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.292.0.tgz", "integrity": "sha512-dld+lpC3QdmTQHdBWJ0WFDkXDSrJgfz03q6mQ8+7H+BC12ZhT0I0g9iuvUjolqy7QR00OxOy47Y9FVhq8EC0Gg==", "optional": true, - "peer": true, "dependencies": { "@aws-sdk/types": "3.292.0", "bowser": "^2.11.0", @@ -1511,15 +1397,13 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true }, "node_modules/@aws-sdk/util-user-agent-node": { "version": "3.292.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.292.0.tgz", "integrity": "sha512-f+NfIMal5E61MDc5WGhUEoicr7b1eNNhA+GgVdSB/Hg5fYhEZvFK9RZizH5rrtsLjjgcr9nPYSR7/nDKCJLumw==", "optional": true, - "peer": true, "dependencies": { "@aws-sdk/node-config-provider": "3.292.0", "@aws-sdk/types": "3.292.0", @@ -1541,15 +1425,13 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true }, "node_modules/@aws-sdk/util-utf8": { "version": "3.292.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-utf8/-/util-utf8-3.292.0.tgz", "integrity": "sha512-FPkj+Z59/DQWvoVu2wFaRncc3KVwe/pgK3MfVb0Lx+Ibey5KUx+sNpJmYcVYHUAe/Nv/JeIpOtYuC96IXOnI6w==", "optional": true, - "peer": true, "dependencies": { "@aws-sdk/util-buffer-from": "3.292.0", "tslib": "^2.3.1" @@ -1563,7 +1445,6 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/util-utf8-browser/-/util-utf8-browser-3.259.0.tgz", "integrity": "sha512-UvFa/vR+e19XookZF8RzFZBrw2EUkQWxiBW0yYQAhvk3C+QVGl0H3ouca8LDBlBfQKXwmW3huo/59H8rwb1wJw==", "optional": true, - "peer": true, "dependencies": { "tslib": "^2.3.1" } @@ -1572,15 +1453,13 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true }, "node_modules/@aws-sdk/util-utf8/node_modules/tslib": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true }, "node_modules/@colors/colors": { "version": "1.5.0", @@ -2108,6 +1987,132 @@ "integrity": "sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==", "dev": true }, + "node_modules/@types/mongoose-unique-validator": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@types/mongoose-unique-validator/-/mongoose-unique-validator-1.0.7.tgz", + "integrity": "sha512-wgPWN7x9mHdy8x1jcluDF7EDWFb/s8LD7skLr012C8i2Q3tUhdTlp3JxRhFbEM3TD7J1INaoTrWaSaguhuqilQ==", + "dev": true, + "dependencies": { + "mongoose": "^6.3.0" + } + }, + "node_modules/@types/mongoose-unique-validator/node_modules/bson": { + "version": "4.7.2", + "resolved": "https://registry.npmjs.org/bson/-/bson-4.7.2.tgz", + "integrity": "sha512-Ry9wCtIZ5kGqkJoi6aD8KjxFZEx78guTQDnpXWiNthsxzrxAK/i8E6pCHAIZTbaEFWcOCvbecMukfK7XUvyLpQ==", + "dev": true, + "dependencies": { + "buffer": "^5.6.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@types/mongoose-unique-validator/node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/@types/mongoose-unique-validator/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@types/mongoose-unique-validator/node_modules/debug/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/@types/mongoose-unique-validator/node_modules/mongodb": { + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.14.0.tgz", + "integrity": "sha512-coGKkWXIBczZPr284tYKFLg+KbGPPLlSbdgfKAb6QqCFt5bo5VFZ50O3FFzsw4rnkqjwT6D8Qcoo9nshYKM7Mg==", + "dev": true, + "dependencies": { + "bson": "^4.7.0", + "mongodb-connection-string-url": "^2.5.4", + "socks": "^2.7.1" + }, + "engines": { + "node": ">=12.9.0" + }, + "optionalDependencies": { + "@aws-sdk/credential-providers": "^3.186.0", + "saslprep": "^1.0.3" + } + }, + "node_modules/@types/mongoose-unique-validator/node_modules/mongoose": { + "version": "6.10.5", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-6.10.5.tgz", + "integrity": "sha512-y4HL4/9EySec7L0gJ+pCm9heLSF45uIIvRS4fSeAFWDfe4vXW1vRZJwTz7OGkra3ZoSfRnFTo9bNZkuggDVlVA==", + "dev": true, + "dependencies": { + "bson": "^4.7.0", + "kareem": "2.5.1", + "mongodb": "4.14.0", + "mpath": "0.9.0", + "mquery": "4.0.3", + "ms": "2.1.3", + "sift": "16.0.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mongoose" + } + }, + "node_modules/@types/mongoose-unique-validator/node_modules/mquery": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/mquery/-/mquery-4.0.3.tgz", + "integrity": "sha512-J5heI+P08I6VJ2Ky3+33IpCdAvlYGTSUjwTPxkAr8i8EoduPMBX2OY/wa3IKZIQl7MU4SbFk8ndgSKyB/cl1zA==", + "dev": true, + "dependencies": { + "debug": "4.x" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@types/mongoose-unique-validator/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, "node_modules/@types/morgan": { "version": "1.9.4", "resolved": "https://registry.npmjs.org/@types/morgan/-/morgan-1.9.4.tgz", @@ -2117,6 +2122,12 @@ "@types/node": "*" } }, + "node_modules/@types/ndarray": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@types/ndarray/-/ndarray-1.0.11.tgz", + "integrity": "sha512-hOZVTN24zDHwCHaW7mF9n1vHJt83fZhNZ0YYRBwQGhA96yBWWDPTDDlqJatagHIOJB0a4xoNkNc+t/Cxd+6qUA==", + "dev": true + }, "node_modules/@types/node": { "version": "18.14.6", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.14.6.tgz", @@ -2182,6 +2193,12 @@ "integrity": "sha512-J6OAed6rhN6zyqL9Of6ZMamhlsOEU/poBVvbHr/dKOYKTeuYYMlDkMv+b6UUV0o2i0tw73cgyv/97WTWaUl0/g==", "dev": true }, + "node_modules/@types/w3c-css-typed-object-model-level-1": { + "version": "20180410.0.5", + "resolved": "https://registry.npmjs.org/@types/w3c-css-typed-object-model-level-1/-/w3c-css-typed-object-model-level-1-20180410.0.5.tgz", + "integrity": "sha512-khoqDQqA/fUdK6mEJtQEfMAPCKkk8E1D+ahM/0HCy3mc5L33wJinqQNXu9Uf4+mqzMvORljHg76jvrmwS0bEjA==", + "dev": true + }, "node_modules/@types/webidl-conversions": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.0.tgz", @@ -2864,8 +2881,7 @@ "version": "2.11.0", "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.11.0.tgz", "integrity": "sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==", - "optional": true, - "peer": true + "optional": true }, "node_modules/brace-expansion": { "version": "1.1.11", @@ -4036,7 +4052,6 @@ "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.1.2.tgz", "integrity": "sha512-CDYeykkle1LiA/uqQyNwYpFbyF6Axec6YapmpUP+/RHWIoR1zKjocdvNaTsxCxZzQ6v9MLXaSYm9Qq0thv0DHg==", "optional": true, - "peer": true, "dependencies": { "strnum": "^1.0.5" }, @@ -4686,6 +4701,12 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, + "node_modules/iota-array": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/iota-array/-/iota-array-1.0.0.tgz", + "integrity": "sha512-pZ2xT+LOHckCatGQ3DcG/a+QuEqvoxqkiL7tvE8nn3uuu+f6i1TtpB5/FtWFbxUuVr5PZCx8KskuGatbJDXOWA==", + "dev": true + }, "node_modules/ip": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz", @@ -5459,6 +5480,22 @@ "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", "dev": true }, + "node_modules/ndarray": { + "version": "1.0.19", + "resolved": "https://registry.npmjs.org/ndarray/-/ndarray-1.0.19.tgz", + "integrity": "sha512-B4JHA4vdyZU30ELBw3g7/p9bZupyew5a7tX1Y/gGeF2hafrPaQZhgrGQfsvgfYbgdFZjYwuEcnaobeM/WMW+HQ==", + "dev": true, + "dependencies": { + "iota-array": "^1.0.0", + "is-buffer": "^1.0.2" + } + }, + "node_modules/ndarray/node_modules/is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, "node_modules/negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", @@ -6540,8 +6577,7 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz", "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==", - "optional": true, - "peer": true + "optional": true }, "node_modules/supports-color": { "version": "7.2.0", @@ -7090,7 +7126,6 @@ "resolved": "https://registry.npmjs.org/@aws-crypto/ie11-detection/-/ie11-detection-3.0.0.tgz", "integrity": "sha512-341lBBkiY1DfDNKai/wXM3aujNBkXR7tq1URPQDL9wi3AUbI80NR74uF1TXHMm7po1AcnFk8iu2S2IeU/+/A+Q==", "optional": true, - "peer": true, "requires": { "tslib": "^1.11.1" } @@ -7100,7 +7135,6 @@ "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-3.0.0.tgz", "integrity": "sha512-8VLmW2B+gjFbU5uMeqtQM6Nj0/F1bro80xQXCW6CQBWgosFWXTx77aeOF5CAIAmbOK64SdMBJdNr6J41yP5mvQ==", "optional": true, - "peer": true, "requires": { "@aws-crypto/ie11-detection": "^3.0.0", "@aws-crypto/sha256-js": "^3.0.0", @@ -7117,7 +7151,6 @@ "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-3.0.0.tgz", "integrity": "sha512-PnNN7os0+yd1XvXAy23CFOmTbMaDxgxXtTKHybrJ39Y8kGzBATgBFibWJKH6BhytLI/Zyszs87xCOBNyBig6vQ==", "optional": true, - "peer": true, "requires": { "@aws-crypto/util": "^3.0.0", "@aws-sdk/types": "^3.222.0", @@ -7129,7 +7162,6 @@ "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-3.0.0.tgz", "integrity": "sha512-06hBdMwUAb2WFTuGG73LSC0wfPu93xWwo5vL2et9eymgmu3Id5vFAHBbajVWiGhPO37qcsdCap/FqXvJGJWPIg==", "optional": true, - "peer": true, "requires": { "tslib": "^1.11.1" } @@ -7139,7 +7171,6 @@ "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-3.0.0.tgz", "integrity": "sha512-2OJlpeJpCR48CC8r+uKVChzs9Iungj9wkZrl8Z041DWEWvyIHILYKCPNzJghKsivj+S3mLo6BVc7mBNzdxA46w==", "optional": true, - "peer": true, "requires": { "@aws-sdk/types": "^3.222.0", "@aws-sdk/util-utf8-browser": "^3.0.0", @@ -7151,7 +7182,6 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/abort-controller/-/abort-controller-3.292.0.tgz", "integrity": "sha512-lf+OPptL01kvryIJy7+dvFux5KbJ6OTwLPPEekVKZ2AfEvwcVtOZWFUhyw3PJCBTVncjKB1Kjl3V/eTS3YuPXQ==", "optional": true, - "peer": true, "requires": { "@aws-sdk/types": "3.292.0", "tslib": "^2.3.1" @@ -7161,8 +7191,7 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true } } }, @@ -7171,7 +7200,6 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/client-cognito-identity/-/client-cognito-identity-3.294.0.tgz", "integrity": "sha512-QMk/QratNvAvmnJ77pu9KDjpfUf/5LOJplHcLKcY962ewJGBtFFY4XZVnmUgQMSG0phRODtex7pyH//FueGKLQ==", "optional": true, - "peer": true, "requires": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", @@ -7214,8 +7242,7 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true } } }, @@ -7224,7 +7251,6 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.294.0.tgz", "integrity": "sha512-+FuxQTi5WvnaXM5JbNLkBIzQ3An4gA0ox61N1u+3xled+nywKb1yQ7WmRpyMG5bLbkmnj3aqoo5/uskFc4c4EA==", "optional": true, - "peer": true, "requires": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", @@ -7264,8 +7290,7 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true } } }, @@ -7274,7 +7299,6 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.294.0.tgz", "integrity": "sha512-/ZfDud76MdSPJ/TxjV2xLE30XbBQDZwKQ32axwoK1eziPvrAIUBYVgpBwj+m0quhoiQhBKkg3aFl6j39AF2thw==", "optional": true, - "peer": true, "requires": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", @@ -7314,8 +7338,7 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true } } }, @@ -7324,7 +7347,6 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.294.0.tgz", "integrity": "sha512-AefqwhFjTDzelZuSYhriJbiI+GQwf2yKiKAnCt0gRj6rswewStM63Gtlhfb01sFPp+ZiqPcyQ47LqUaHp1mz/g==", "optional": true, - "peer": true, "requires": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", @@ -7368,8 +7390,7 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true } } }, @@ -7378,7 +7399,6 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/config-resolver/-/config-resolver-3.292.0.tgz", "integrity": "sha512-cB3twnNR7vYvlt2jvw8VlA1+iv/tVzl+/S39MKqw2tepU+AbJAM0EHwb/dkf1OKSmlrnANXhshx80MHF9zL4mA==", "optional": true, - "peer": true, "requires": { "@aws-sdk/signature-v4": "3.292.0", "@aws-sdk/types": "3.292.0", @@ -7391,8 +7411,7 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true } } }, @@ -7401,7 +7420,6 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-cognito-identity/-/credential-provider-cognito-identity-3.294.0.tgz", "integrity": "sha512-YSPqHEbLC0dnbFF5LdlMH0B50sMSN/CyG/sHkPYUwL/hkbUk9URnVW7ZJlt6lRftT7X4C7muzLqUP8sJAaiJEA==", "optional": true, - "peer": true, "requires": { "@aws-sdk/client-cognito-identity": "3.294.0", "@aws-sdk/property-provider": "3.292.0", @@ -7413,8 +7431,7 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true } } }, @@ -7423,7 +7440,6 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.292.0.tgz", "integrity": "sha512-YbafSG0ZEKE2969CJWVtUhh3hfOeLPecFVoXOtegCyAJgY5Ghtu4TsVhL4DgiGAgOC30ojAmUVQEXzd7xJF5xA==", "optional": true, - "peer": true, "requires": { "@aws-sdk/property-provider": "3.292.0", "@aws-sdk/types": "3.292.0", @@ -7434,8 +7450,7 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true } } }, @@ -7444,7 +7459,6 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-imds/-/credential-provider-imds-3.292.0.tgz", "integrity": "sha512-W/peOgDSRYulgzFpUhvgi1pCm6piBz6xrVN17N4QOy+3NHBXRVMVzYk6ct2qpLPgJUSEZkcpP+Gds+bBm8ed1A==", "optional": true, - "peer": true, "requires": { "@aws-sdk/node-config-provider": "3.292.0", "@aws-sdk/property-provider": "3.292.0", @@ -7457,8 +7471,7 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true } } }, @@ -7467,7 +7480,6 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.294.0.tgz", "integrity": "sha512-pdTPbaAb5bWA+DnuKoL2TpXeNDp6Ejpv/OYt+bw2gdzl9w5r/ZCtUTTbW+Vvejr4WL5s3c1bY96kwdqCn7iLqA==", "optional": true, - "peer": true, "requires": { "@aws-sdk/credential-provider-env": "3.292.0", "@aws-sdk/credential-provider-imds": "3.292.0", @@ -7484,8 +7496,7 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true } } }, @@ -7494,7 +7505,6 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.294.0.tgz", "integrity": "sha512-zUL1Qhb4BsQIZCs/TPpG4oIYH/9YsGiS+Se1tasSGjTOLfBy7jhOZ0QIdpEeyAx/EP8blOBredM9xWfEXgiHVA==", "optional": true, - "peer": true, "requires": { "@aws-sdk/credential-provider-env": "3.292.0", "@aws-sdk/credential-provider-imds": "3.292.0", @@ -7512,8 +7522,7 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true } } }, @@ -7522,7 +7531,6 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.292.0.tgz", "integrity": "sha512-CFVXuMuUvg/a4tknzRikEDwZBnKlHs1LZCpTXIGjBdUTdosoi4WNzDLzGp93ZRTtcgFz+4wirz2f7P3lC0NrQw==", "optional": true, - "peer": true, "requires": { "@aws-sdk/property-provider": "3.292.0", "@aws-sdk/shared-ini-file-loader": "3.292.0", @@ -7534,8 +7542,7 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true } } }, @@ -7544,7 +7551,6 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.294.0.tgz", "integrity": "sha512-UxrcAA/0l7j9+3tolYcG5M61D/IE1Bjd/9H87H1i2A2BrwUUBhW1Dp/vvROEDrrywlMDG3CDF3T/7ADtTak+sg==", "optional": true, - "peer": true, "requires": { "@aws-sdk/client-sso": "3.294.0", "@aws-sdk/property-provider": "3.292.0", @@ -7558,8 +7564,7 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true } } }, @@ -7568,7 +7573,6 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.292.0.tgz", "integrity": "sha512-4DbtIEM9gGVfqYlMdYXg3XY+vBhemjB1zXIequottW8loLYM8Vuz4/uGxxKNze6evVVzowsA0wKrYclE1aj/Rg==", "optional": true, - "peer": true, "requires": { "@aws-sdk/property-provider": "3.292.0", "@aws-sdk/types": "3.292.0", @@ -7579,8 +7583,7 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true } } }, @@ -7589,7 +7592,6 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-providers/-/credential-providers-3.294.0.tgz", "integrity": "sha512-CRGwCs6F31mQzjYi5YF2Z6c1T+UrFKtJSa6Ff/Q1rrPnROexyhBVnpP8WzpkODx/pZxKtTX50IX7ehIxbFDIyQ==", "optional": true, - "peer": true, "requires": { "@aws-sdk/client-cognito-identity": "3.294.0", "@aws-sdk/client-sso": "3.294.0", @@ -7612,8 +7614,7 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true } } }, @@ -7622,7 +7623,6 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/fetch-http-handler/-/fetch-http-handler-3.292.0.tgz", "integrity": "sha512-zh3bhUJbL8RSa39ZKDcy+AghtUkIP8LwcNlwRIoxMQh3Row4D1s4fCq0KZCx98NJBEXoiTLyTQlZxxI//BOb1Q==", "optional": true, - "peer": true, "requires": { "@aws-sdk/protocol-http": "3.292.0", "@aws-sdk/querystring-builder": "3.292.0", @@ -7635,8 +7635,7 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true } } }, @@ -7645,7 +7644,6 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/hash-node/-/hash-node-3.292.0.tgz", "integrity": "sha512-1yLxmIsvE+eK36JXEgEIouTITdykQLVhsA5Oai//Lar6Ddgu1sFpLDbdkMtKbrh4I0jLN9RacNCkeVQjZPTCCQ==", "optional": true, - "peer": true, "requires": { "@aws-sdk/types": "3.292.0", "@aws-sdk/util-buffer-from": "3.292.0", @@ -7657,8 +7655,7 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true } } }, @@ -7667,7 +7664,6 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/invalid-dependency/-/invalid-dependency-3.292.0.tgz", "integrity": "sha512-39OUV78CD3TmEbjhpt+V+Fk4wAGWhixqHxDSN8+4WL0uB4Fl7k5m3Z9hNY78AttHQSl2twR7WtLztnXPAFsriw==", "optional": true, - "peer": true, "requires": { "@aws-sdk/types": "3.292.0", "tslib": "^2.3.1" @@ -7677,8 +7673,7 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true } } }, @@ -7687,7 +7682,6 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/is-array-buffer/-/is-array-buffer-3.292.0.tgz", "integrity": "sha512-kW/G5T/fzI0sJH5foZG6XJiNCevXqKLxV50qIT4B1pMuw7regd4ALIy0HwSqj1nnn9mSbRWBfmby0jWCJsMcwg==", "optional": true, - "peer": true, "requires": { "tslib": "^2.3.1" }, @@ -7696,8 +7690,7 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true } } }, @@ -7706,7 +7699,6 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-content-length/-/middleware-content-length-3.292.0.tgz", "integrity": "sha512-2gMWzQus5mj14menolpPDbYBeaOYcj7KNFZOjTjjI3iQ0KqyetG6XasirNrcJ/8QX1BRmpTol8Xjp2Ue3Gbzwg==", "optional": true, - "peer": true, "requires": { "@aws-sdk/protocol-http": "3.292.0", "@aws-sdk/types": "3.292.0", @@ -7717,8 +7709,7 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true } } }, @@ -7727,7 +7718,6 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-endpoint/-/middleware-endpoint-3.292.0.tgz", "integrity": "sha512-cPMkiSxpZGG6tYlW4OS+ucS6r43f9ddX9kcUoemJCY10MOuogdPjulCAjE0HTs2PLKSOrrG4CTP4Q4wWDrH4Bw==", "optional": true, - "peer": true, "requires": { "@aws-sdk/middleware-serde": "3.292.0", "@aws-sdk/protocol-http": "3.292.0", @@ -7743,8 +7733,7 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true } } }, @@ -7753,7 +7742,6 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.292.0.tgz", "integrity": "sha512-mHuCWe3Yg2S5YZ7mB7sKU6C97XspfqrimWjMW9pfV2usAvLA3R0HrB03jpR5vpZ3P4q7HB6wK3S6CjYMGGRNag==", "optional": true, - "peer": true, "requires": { "@aws-sdk/protocol-http": "3.292.0", "@aws-sdk/types": "3.292.0", @@ -7764,8 +7752,7 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true } } }, @@ -7774,7 +7761,6 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.292.0.tgz", "integrity": "sha512-yZNY1XYmG3NG+uonET7jzKXNiwu61xm/ZZ6i/l51SusuaYN+qQtTAhOFsieQqTehF9kP4FzbsWgPDwD8ZZX9lw==", "optional": true, - "peer": true, "requires": { "@aws-sdk/types": "3.292.0", "tslib": "^2.3.1" @@ -7784,8 +7770,7 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true } } }, @@ -7794,7 +7779,6 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.292.0.tgz", "integrity": "sha512-kA3VZpPko0Zqd7CYPTKAxhjEv0HJqFu2054L04dde1JLr43ro+2MTdX7vsHzeAFUVRphqatFFofCumvXmU6Mig==", "optional": true, - "peer": true, "requires": { "@aws-sdk/protocol-http": "3.292.0", "@aws-sdk/types": "3.292.0", @@ -7805,8 +7789,7 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true } } }, @@ -7815,7 +7798,6 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-retry/-/middleware-retry-3.293.0.tgz", "integrity": "sha512-7tiaz2GzRecNHaZ6YnF+Nrtk3au8qF6oiipf11R7MJiqJ0fkMLnz/iRrlakDziS9qF/a9v+3yxb4W4NHK3f4Tw==", "optional": true, - "peer": true, "requires": { "@aws-sdk/protocol-http": "3.292.0", "@aws-sdk/service-error-classification": "3.292.0", @@ -7830,15 +7812,13 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true }, "uuid": { "version": "8.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "optional": true, - "peer": true + "optional": true } } }, @@ -7847,7 +7827,6 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-sts/-/middleware-sdk-sts-3.292.0.tgz", "integrity": "sha512-GN5ZHEqXZqDi+HkVbaXRX9HaW/vA5rikYpWKYsmxTUZ7fB7ijvEO3co3lleJv2C+iGYRtUIHC4wYNB5xgoTCxg==", "optional": true, - "peer": true, "requires": { "@aws-sdk/middleware-signing": "3.292.0", "@aws-sdk/property-provider": "3.292.0", @@ -7861,8 +7840,7 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true } } }, @@ -7871,7 +7849,6 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-serde/-/middleware-serde-3.292.0.tgz", "integrity": "sha512-6hN9mTQwSvV8EcGvtXbS/MpK7WMCokUku5Wu7X24UwCNMVkoRHLIkYcxHcvBTwttuOU0d8hph1/lIX4dkLwkQw==", "optional": true, - "peer": true, "requires": { "@aws-sdk/types": "3.292.0", "tslib": "^2.3.1" @@ -7881,8 +7858,7 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true } } }, @@ -7891,7 +7867,6 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-signing/-/middleware-signing-3.292.0.tgz", "integrity": "sha512-GVfoSjDjEQ4TaO6x9MffyP3uRV+2KcS5FtexLCYOM9pJcnE9tqq9FJOrZ1xl1g+YjUVKxo4x8lu3tpEtIb17qg==", "optional": true, - "peer": true, "requires": { "@aws-sdk/property-provider": "3.292.0", "@aws-sdk/protocol-http": "3.292.0", @@ -7905,8 +7880,7 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true } } }, @@ -7915,7 +7889,6 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-stack/-/middleware-stack-3.292.0.tgz", "integrity": "sha512-WdQpRkuMysrEwrkByCM1qCn2PPpFGGQ2iXqaFha5RzCdZDlxJni9cVNb6HzWUcgjLEYVTXCmOR9Wxm3CNW44Qg==", "optional": true, - "peer": true, "requires": { "tslib": "^2.3.1" }, @@ -7924,8 +7897,7 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true } } }, @@ -7934,7 +7906,6 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.293.0.tgz", "integrity": "sha512-gZ7/e6XwpKk9mvgA78q4Ffc796jTn02TUKx2qMDnkLVbeJXBNN2jnvYEKq8v70+o7fd/ALRudg8gBDmkkhM/Hw==", "optional": true, - "peer": true, "requires": { "@aws-sdk/protocol-http": "3.292.0", "@aws-sdk/types": "3.292.0", @@ -7946,8 +7917,7 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true } } }, @@ -7956,7 +7926,6 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/node-config-provider/-/node-config-provider-3.292.0.tgz", "integrity": "sha512-S3NnC9dQ5GIbJYSDIldZb4zdpCOEua1tM7bjYL3VS5uqCEM93kIi/o/UkIUveMp/eqTS2LJa5HjNIz5Te6je0A==", "optional": true, - "peer": true, "requires": { "@aws-sdk/property-provider": "3.292.0", "@aws-sdk/shared-ini-file-loader": "3.292.0", @@ -7968,8 +7937,7 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true } } }, @@ -7978,7 +7946,6 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/node-http-handler/-/node-http-handler-3.292.0.tgz", "integrity": "sha512-L/E3UDSwXLXjt1XWWh0RBD55F+aZI1AEdPwdES9i1PjnZLyuxuDhEDptVibNN56+I9/4Q3SbmuVRVlOD0uzBag==", "optional": true, - "peer": true, "requires": { "@aws-sdk/abort-controller": "3.292.0", "@aws-sdk/protocol-http": "3.292.0", @@ -7991,8 +7958,7 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true } } }, @@ -8001,7 +7967,6 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/property-provider/-/property-provider-3.292.0.tgz", "integrity": "sha512-dHArSvsiqhno/g55N815gXmAMrmN8DP7OeFNqJ4wJG42xsF2PFN3DAsjIuHuXMwu+7A3R1LHqIpvv0hA9KeoJQ==", "optional": true, - "peer": true, "requires": { "@aws-sdk/types": "3.292.0", "tslib": "^2.3.1" @@ -8011,8 +7976,7 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true } } }, @@ -8021,7 +7985,6 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/protocol-http/-/protocol-http-3.292.0.tgz", "integrity": "sha512-NLi4fq3k41aXIh1I97yX0JTy+3p6aW1NdwFwdMa674z86QNfb4SfRQRZBQe9wEnAZ/eWHVnlKIuII+U1URk/Kg==", "optional": true, - "peer": true, "requires": { "@aws-sdk/types": "3.292.0", "tslib": "^2.3.1" @@ -8031,8 +7994,7 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true } } }, @@ -8041,7 +8003,6 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/querystring-builder/-/querystring-builder-3.292.0.tgz", "integrity": "sha512-XElIFJaReIm24eEvBtV2dOtZvcm3gXsGu/ftG8MLJKbKXFKpAP1q+K6En0Bs7/T88voKghKdKpKT+eZUWgTqlg==", "optional": true, - "peer": true, "requires": { "@aws-sdk/types": "3.292.0", "@aws-sdk/util-uri-escape": "3.292.0", @@ -8052,8 +8013,7 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true } } }, @@ -8062,7 +8022,6 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/querystring-parser/-/querystring-parser-3.292.0.tgz", "integrity": "sha512-iTYpYo7a8X9RxiPbjjewIpm6XQPx2EOcF1dWCPRII9EFlmZ4bwnX+PDI36fIo9oVs8TIKXmwNGODU9nsg7CSAw==", "optional": true, - "peer": true, "requires": { "@aws-sdk/types": "3.292.0", "tslib": "^2.3.1" @@ -8072,8 +8031,7 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true } } }, @@ -8081,15 +8039,13 @@ "version": "3.292.0", "resolved": "https://registry.npmjs.org/@aws-sdk/service-error-classification/-/service-error-classification-3.292.0.tgz", "integrity": "sha512-X1k3sixCeC45XSNHBe+kRBQBwPDyTFtFITb8O5Qw4dS9XWGhrUJT4CX0qE5aj8qP3F9U5nRizs9c2mBVVP0Caw==", - "optional": true, - "peer": true + "optional": true }, "@aws-sdk/shared-ini-file-loader": { "version": "3.292.0", "resolved": "https://registry.npmjs.org/@aws-sdk/shared-ini-file-loader/-/shared-ini-file-loader-3.292.0.tgz", "integrity": "sha512-Av2TTYg1Jig2kbkD56ybiqZJB6vVrYjv1W5UQwY/q3nA/T2mcrgQ20ByCOt5Bv9VvY7FSgC+znj+L4a7RLGmBg==", "optional": true, - "peer": true, "requires": { "@aws-sdk/types": "3.292.0", "tslib": "^2.3.1" @@ -8099,8 +8055,7 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true } } }, @@ -8109,7 +8064,6 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4/-/signature-v4-3.292.0.tgz", "integrity": "sha512-+rw47VY5mvBecn13tDQTl1ipGWg5tE63faWgmZe68HoBL87ZiDzsd7bUKOvjfW21iMgWlwAppkaNNQayYRb2zg==", "optional": true, - "peer": true, "requires": { "@aws-sdk/is-array-buffer": "3.292.0", "@aws-sdk/types": "3.292.0", @@ -8124,8 +8078,7 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true } } }, @@ -8134,7 +8087,6 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/smithy-client/-/smithy-client-3.292.0.tgz", "integrity": "sha512-S8PKzjPkZ6SXYZuZiU787dMsvQ0d/LFEhw2OI4Oe2An9Fc2IwJ2FYukyHoQJOV2tV0DiuMebPo7eMyQyjKElvA==", "optional": true, - "peer": true, "requires": { "@aws-sdk/middleware-stack": "3.292.0", "@aws-sdk/types": "3.292.0", @@ -8145,8 +8097,7 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true } } }, @@ -8155,7 +8106,6 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.294.0.tgz", "integrity": "sha512-6nwO04LtC5f4AsUvGZXyjaswuEK4Rr2VsuANpMKrPCgunRfI58a8YXLniudOSXN6e7CFJ6M3uo/h5YXqtnzGug==", "optional": true, - "peer": true, "requires": { "@aws-sdk/client-sso-oidc": "3.294.0", "@aws-sdk/property-provider": "3.292.0", @@ -8168,8 +8118,7 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true } } }, @@ -8178,7 +8127,6 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.292.0.tgz", "integrity": "sha512-1teYAY2M73UXZxMAxqZxVS2qwXjQh0OWtt7qyLfha0TtIk/fZ1hRwFgxbDCHUFcdNBSOSbKH/ESor90KROXLCQ==", "optional": true, - "peer": true, "requires": { "tslib": "^2.3.1" }, @@ -8187,8 +8135,7 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true } } }, @@ -8197,7 +8144,6 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/url-parser/-/url-parser-3.292.0.tgz", "integrity": "sha512-NZeAuZCk1x6TIiWuRfbOU6wHPBhf0ly2qOHzWut4BCH+b4RrDmFF8EmXcH1auEfGhE7yRyR6XqIN0t3S+hYACA==", "optional": true, - "peer": true, "requires": { "@aws-sdk/querystring-parser": "3.292.0", "@aws-sdk/types": "3.292.0", @@ -8208,8 +8154,7 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true } } }, @@ -8218,7 +8163,6 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/util-base64/-/util-base64-3.292.0.tgz", "integrity": "sha512-zjNCwNdy617yFvEjZorepNWXB2sQCVfsShCwFy/kIQ5iW5tT2jQKaqc0K77diU9atkooxw9p1W9m9sOgrkOFNw==", "optional": true, - "peer": true, "requires": { "@aws-sdk/util-buffer-from": "3.292.0", "tslib": "^2.3.1" @@ -8228,8 +8172,7 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true } } }, @@ -8238,7 +8181,6 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/util-body-length-browser/-/util-body-length-browser-3.292.0.tgz", "integrity": "sha512-Wd/BM+JsMiKvKs/bN3z6TredVEHh2pKudGfg3CSjTRpqFpOG903KDfyHBD42yg5PuCHoHoewJvTPKwgn7/vhaw==", "optional": true, - "peer": true, "requires": { "tslib": "^2.3.1" }, @@ -8247,8 +8189,7 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true } } }, @@ -8257,7 +8198,6 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/util-body-length-node/-/util-body-length-node-3.292.0.tgz", "integrity": "sha512-BBgipZ2P6RhogWE/qj0oqpdlyd3iSBYmb+aD/TBXwB2lA/X8A99GxweBd/kp06AmcJRoMS9WIXgbWkiiBlRlSA==", "optional": true, - "peer": true, "requires": { "tslib": "^2.3.1" }, @@ -8266,8 +8206,7 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true } } }, @@ -8276,7 +8215,6 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/util-buffer-from/-/util-buffer-from-3.292.0.tgz", "integrity": "sha512-RxNZjLoXNxHconH9TYsk5RaEBjSgTtozHeyIdacaHPj5vlQKi4hgL2hIfKeeNiAfQEVjaUFF29lv81xpNMzVMQ==", "optional": true, - "peer": true, "requires": { "@aws-sdk/is-array-buffer": "3.292.0", "tslib": "^2.3.1" @@ -8286,8 +8224,7 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true } } }, @@ -8296,7 +8233,6 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/util-config-provider/-/util-config-provider-3.292.0.tgz", "integrity": "sha512-t3noYll6bPRSxeeNNEkC5czVjAiTPcsq00OwfJ2xyUqmquhLEfLwoJKmrT1uP7DjIEXdUtfoIQ2jWiIVm/oO5A==", "optional": true, - "peer": true, "requires": { "tslib": "^2.3.1" }, @@ -8305,8 +8241,7 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true } } }, @@ -8315,7 +8250,6 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/util-defaults-mode-browser/-/util-defaults-mode-browser-3.292.0.tgz", "integrity": "sha512-7+zVUlMGfa8/KT++9humHo6IDxTnxMCmWUj5jVNlkpk6h7Ecmppf7aXotviyVIA43lhtz0p2AErs0N0ekEUK+w==", "optional": true, - "peer": true, "requires": { "@aws-sdk/property-provider": "3.292.0", "@aws-sdk/types": "3.292.0", @@ -8327,8 +8261,7 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true } } }, @@ -8337,7 +8270,6 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/util-defaults-mode-node/-/util-defaults-mode-node-3.292.0.tgz", "integrity": "sha512-SSIw85eF4BVs0fOJRyshT+R3b/UmBPhiVKCUZm2rq6+lIGkDPiSwQU3d/80AhXtiL5SFT/IzAKKgQd8qMa7q3A==", "optional": true, - "peer": true, "requires": { "@aws-sdk/config-resolver": "3.292.0", "@aws-sdk/credential-provider-imds": "3.292.0", @@ -8351,8 +8283,7 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true } } }, @@ -8361,7 +8292,6 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.293.0.tgz", "integrity": "sha512-R/99aNV49Refpv5guiUjEUrZYlvnfaNBniB+/ZtMO3ixxUopapssCrUivuJrmhccmrYaTCZw7dRzIWjU1jJhKg==", "optional": true, - "peer": true, "requires": { "@aws-sdk/types": "3.292.0", "tslib": "^2.3.1" @@ -8371,8 +8301,7 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true } } }, @@ -8381,7 +8310,6 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/util-hex-encoding/-/util-hex-encoding-3.292.0.tgz", "integrity": "sha512-qBd5KFIUywQ3qSSbj814S2srk0vfv8A6QMI+Obs1y2LHZFdQN5zViptI4UhXhKOHe+NnrHWxSuLC/LMH6q3SmA==", "optional": true, - "peer": true, "requires": { "tslib": "^2.3.1" }, @@ -8390,8 +8318,7 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true } } }, @@ -8400,7 +8327,6 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.292.0.tgz", "integrity": "sha512-6xnFJXZI9pKw5lQCDvuWA5PnOaUtNRKWwdxvGkkLx5orboFaoVMS6zowjSQxwVNRjW82u6dYNkhmj9mZ8VSjWg==", "optional": true, - "peer": true, "requires": { "tslib": "^2.3.1" }, @@ -8409,8 +8335,7 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true } } }, @@ -8419,7 +8344,6 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/util-middleware/-/util-middleware-3.292.0.tgz", "integrity": "sha512-KjhS7flfoBKDxbiBZjLjMvEizXgjfQb7GQEItgzGoI9rfGCmZtvqCcqQQoIlxb8bIzGRggAUHtBGWnlLbpb+GQ==", "optional": true, - "peer": true, "requires": { "tslib": "^2.3.1" }, @@ -8428,8 +8352,7 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true } } }, @@ -8438,7 +8361,6 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/util-retry/-/util-retry-3.292.0.tgz", "integrity": "sha512-JEHyF7MpVeRF5uR4LDYgpOKcFpOPiAj8TqN46SVOQQcL1K+V7cSr7O7N7J6MwJaN9XOzAcBadeIupMm7/BFbgw==", "optional": true, - "peer": true, "requires": { "@aws-sdk/service-error-classification": "3.292.0", "tslib": "^2.3.1" @@ -8448,8 +8370,7 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true } } }, @@ -8458,7 +8379,6 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/util-uri-escape/-/util-uri-escape-3.292.0.tgz", "integrity": "sha512-hOQtUMQ4VcQ9iwKz50AoCp1XBD5gJ9nly/gJZccAM7zSA5mOO8RRKkbdonqquVHxrO0CnYgiFeCh3V35GFecUw==", "optional": true, - "peer": true, "requires": { "tslib": "^2.3.1" }, @@ -8467,8 +8387,7 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true } } }, @@ -8477,7 +8396,6 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.292.0.tgz", "integrity": "sha512-dld+lpC3QdmTQHdBWJ0WFDkXDSrJgfz03q6mQ8+7H+BC12ZhT0I0g9iuvUjolqy7QR00OxOy47Y9FVhq8EC0Gg==", "optional": true, - "peer": true, "requires": { "@aws-sdk/types": "3.292.0", "bowser": "^2.11.0", @@ -8488,8 +8406,7 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true } } }, @@ -8498,7 +8415,6 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.292.0.tgz", "integrity": "sha512-f+NfIMal5E61MDc5WGhUEoicr7b1eNNhA+GgVdSB/Hg5fYhEZvFK9RZizH5rrtsLjjgcr9nPYSR7/nDKCJLumw==", "optional": true, - "peer": true, "requires": { "@aws-sdk/node-config-provider": "3.292.0", "@aws-sdk/types": "3.292.0", @@ -8509,8 +8425,7 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true } } }, @@ -8519,7 +8434,6 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/util-utf8/-/util-utf8-3.292.0.tgz", "integrity": "sha512-FPkj+Z59/DQWvoVu2wFaRncc3KVwe/pgK3MfVb0Lx+Ibey5KUx+sNpJmYcVYHUAe/Nv/JeIpOtYuC96IXOnI6w==", "optional": true, - "peer": true, "requires": { "@aws-sdk/util-buffer-from": "3.292.0", "tslib": "^2.3.1" @@ -8529,8 +8443,7 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true } } }, @@ -8539,7 +8452,6 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/util-utf8-browser/-/util-utf8-browser-3.259.0.tgz", "integrity": "sha512-UvFa/vR+e19XookZF8RzFZBrw2EUkQWxiBW0yYQAhvk3C+QVGl0H3ouca8LDBlBfQKXwmW3huo/59H8rwb1wJw==", "optional": true, - "peer": true, "requires": { "tslib": "^2.3.1" }, @@ -8548,8 +8460,7 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true, - "peer": true + "optional": true } } }, @@ -8985,6 +8896,96 @@ "integrity": "sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==", "dev": true }, + "@types/mongoose-unique-validator": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@types/mongoose-unique-validator/-/mongoose-unique-validator-1.0.7.tgz", + "integrity": "sha512-wgPWN7x9mHdy8x1jcluDF7EDWFb/s8LD7skLr012C8i2Q3tUhdTlp3JxRhFbEM3TD7J1INaoTrWaSaguhuqilQ==", + "dev": true, + "requires": { + "mongoose": "^6.3.0" + }, + "dependencies": { + "bson": { + "version": "4.7.2", + "resolved": "https://registry.npmjs.org/bson/-/bson-4.7.2.tgz", + "integrity": "sha512-Ry9wCtIZ5kGqkJoi6aD8KjxFZEx78guTQDnpXWiNthsxzrxAK/i8E6pCHAIZTbaEFWcOCvbecMukfK7XUvyLpQ==", + "dev": true, + "requires": { + "buffer": "^5.6.0" + } + }, + "buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + }, + "dependencies": { + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "mongodb": { + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.14.0.tgz", + "integrity": "sha512-coGKkWXIBczZPr284tYKFLg+KbGPPLlSbdgfKAb6QqCFt5bo5VFZ50O3FFzsw4rnkqjwT6D8Qcoo9nshYKM7Mg==", + "dev": true, + "requires": { + "@aws-sdk/credential-providers": "^3.186.0", + "bson": "^4.7.0", + "mongodb-connection-string-url": "^2.5.4", + "saslprep": "^1.0.3", + "socks": "^2.7.1" + } + }, + "mongoose": { + "version": "6.10.5", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-6.10.5.tgz", + "integrity": "sha512-y4HL4/9EySec7L0gJ+pCm9heLSF45uIIvRS4fSeAFWDfe4vXW1vRZJwTz7OGkra3ZoSfRnFTo9bNZkuggDVlVA==", + "dev": true, + "requires": { + "bson": "^4.7.0", + "kareem": "2.5.1", + "mongodb": "4.14.0", + "mpath": "0.9.0", + "mquery": "4.0.3", + "ms": "2.1.3", + "sift": "16.0.1" + } + }, + "mquery": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/mquery/-/mquery-4.0.3.tgz", + "integrity": "sha512-J5heI+P08I6VJ2Ky3+33IpCdAvlYGTSUjwTPxkAr8i8EoduPMBX2OY/wa3IKZIQl7MU4SbFk8ndgSKyB/cl1zA==", + "dev": true, + "requires": { + "debug": "4.x" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + } + } + }, "@types/morgan": { "version": "1.9.4", "resolved": "https://registry.npmjs.org/@types/morgan/-/morgan-1.9.4.tgz", @@ -8994,6 +8995,12 @@ "@types/node": "*" } }, + "@types/ndarray": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@types/ndarray/-/ndarray-1.0.11.tgz", + "integrity": "sha512-hOZVTN24zDHwCHaW7mF9n1vHJt83fZhNZ0YYRBwQGhA96yBWWDPTDDlqJatagHIOJB0a4xoNkNc+t/Cxd+6qUA==", + "dev": true + }, "@types/node": { "version": "18.14.6", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.14.6.tgz", @@ -9059,6 +9066,12 @@ "integrity": "sha512-J6OAed6rhN6zyqL9Of6ZMamhlsOEU/poBVvbHr/dKOYKTeuYYMlDkMv+b6UUV0o2i0tw73cgyv/97WTWaUl0/g==", "dev": true }, + "@types/w3c-css-typed-object-model-level-1": { + "version": "20180410.0.5", + "resolved": "https://registry.npmjs.org/@types/w3c-css-typed-object-model-level-1/-/w3c-css-typed-object-model-level-1-20180410.0.5.tgz", + "integrity": "sha512-khoqDQqA/fUdK6mEJtQEfMAPCKkk8E1D+ahM/0HCy3mc5L33wJinqQNXu9Uf4+mqzMvORljHg76jvrmwS0bEjA==", + "dev": true + }, "@types/webidl-conversions": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.0.tgz", @@ -9543,8 +9556,7 @@ "version": "2.11.0", "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.11.0.tgz", "integrity": "sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==", - "optional": true, - "peer": true + "optional": true }, "brace-expansion": { "version": "1.1.11", @@ -10477,7 +10489,6 @@ "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.1.2.tgz", "integrity": "sha512-CDYeykkle1LiA/uqQyNwYpFbyF6Axec6YapmpUP+/RHWIoR1zKjocdvNaTsxCxZzQ6v9MLXaSYm9Qq0thv0DHg==", "optional": true, - "peer": true, "requires": { "strnum": "^1.0.5" } @@ -10973,6 +10984,12 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, + "iota-array": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/iota-array/-/iota-array-1.0.0.tgz", + "integrity": "sha512-pZ2xT+LOHckCatGQ3DcG/a+QuEqvoxqkiL7tvE8nn3uuu+f6i1TtpB5/FtWFbxUuVr5PZCx8KskuGatbJDXOWA==", + "dev": true + }, "ip": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz", @@ -11540,6 +11557,24 @@ "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", "dev": true }, + "ndarray": { + "version": "1.0.19", + "resolved": "https://registry.npmjs.org/ndarray/-/ndarray-1.0.19.tgz", + "integrity": "sha512-B4JHA4vdyZU30ELBw3g7/p9bZupyew5a7tX1Y/gGeF2hafrPaQZhgrGQfsvgfYbgdFZjYwuEcnaobeM/WMW+HQ==", + "dev": true, + "requires": { + "iota-array": "^1.0.0", + "is-buffer": "^1.0.2" + }, + "dependencies": { + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + } + } + }, "negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", @@ -12352,8 +12387,7 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz", "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==", - "optional": true, - "peer": true + "optional": true }, "supports-color": { "version": "7.2.0", diff --git a/package.json b/package.json index 535bcef..5409fc7 100644 --- a/package.json +++ b/package.json @@ -61,15 +61,19 @@ "@types/dicer": "^0.2.2", "@types/express": "^4.17.17", "@types/fs-extra": "^11.0.1", + "@types/mongoose-unique-validator": "^1.0.7", "@types/morgan": "^1.9.4", + "@types/ndarray": "^1.0.11", "@types/node": "^18.14.4", "@types/node-rsa": "^1.1.1", "@types/nodemailer": "^6.4.7", "@types/qs": "^6.9.7", "@types/validator": "^13.7.14", + "@types/w3c-css-typed-object-model-level-1": "^20180410.0.5", "@typescript-eslint/eslint-plugin": "^5.54.1", "@typescript-eslint/parser": "^5.54.1", "eslint": "^8.35.0", + "ndarray": "^1.0.19", "prompt": "^1.0.0", "typescript": "^4.9.5", "yesno": "^0.4.0" diff --git a/src/models/pnid.ts b/src/models/pnid.ts index 9fa984a..74613f6 100644 --- a/src/models/pnid.ts +++ b/src/models/pnid.ts @@ -176,12 +176,8 @@ PNIDSchema.method('generateMiiImages', async function generateMiiImages(): Promi instanceCount: 1, }); const miiStudioNormalFaceImageData: Buffer = await got(miiStudioUrl).buffer(); - const pngData: { - width: number; - height: number; - data: Buffer; - } = await imagePixels(miiStudioNormalFaceImageData); - const tga: Buffer = TGA.createTgaBuffer(pngData.width, pngData.height, pngData.data); + const pngData: ImageData = await imagePixels(miiStudioNormalFaceImageData); + const tga: Buffer = TGA.createTgaBuffer(pngData.width, pngData.height, Uint8Array.from(pngData.data), false); const userMiiKey: string = `mii/${this.get('pid')}`; diff --git a/src/types/express-subdomain.d.ts b/src/types/express-subdomain.d.ts index cb1cbdc..933d1f0 100644 --- a/src/types/express-subdomain.d.ts +++ b/src/types/express-subdomain.d.ts @@ -1,3 +1,16 @@ -declare module 'express-subdomain'; +// * Credit to https://github.com/bmullan91/express-subdomain/pull/61 for the types! -// TODO - Add proper types \ No newline at end of file +declare module 'express-subdomain'{ + import type { Request, Response, Router } from 'express'; + + /** + * @description The subdomain function. + * @param subdomain The subdomain to listen on. + * @param fn The listener function, takes a response and request. + * @returns A function call to the value passed as FN, or void (the next function). + */ + export default function subdomain( + subdomain: string, + fn: Router | ((req: Request, res: Response) => void | any) + ): (req: Request, res: Response, next: () => void) => void | typeof fn; +} \ No newline at end of file diff --git a/src/types/image-pixels.d.ts b/src/types/image-pixels.d.ts index bd142c3..762209c 100644 --- a/src/types/image-pixels.d.ts +++ b/src/types/image-pixels.d.ts @@ -1,3 +1,34 @@ -declare module 'image-pixels'; +declare module 'image-pixels' { + import { NdArray } from 'ndarray'; -// TODO - Add proper types \ No newline at end of file + export interface ImagePixelsOptions { + source: ImageSource; + shape: [number, number]; + width: number; + height: number; + type: string; + mime: string; + clip: [number, number, number, number] | { + x?: number; + y?: number; + width?: number; + height?: number; + }; + cache: boolean; + } + + export type ImageSource = string | + HTMLImageElement | SVGImageElement | HTMLVideoElement | CSSImageValue | + typeof Image | ImageData | ImageBitmap | + File | Blob | + //Canvas | Context2D | + //WebGLContext | + Buffer | ArrayBuffer | Uint8Array | Uint8ClampedArray | + Float32Array | Float64Array | Array | Array> | + NdArray | + ImagePixelsOptions; + + export default function pixels( + src: ImageSource | Promise + ): ImageData; +} \ No newline at end of file diff --git a/src/types/mongoose-unique-validator.d.ts b/src/types/mongoose-unique-validator.d.ts deleted file mode 100644 index 0909028..0000000 --- a/src/types/mongoose-unique-validator.d.ts +++ /dev/null @@ -1 +0,0 @@ -declare module 'mongoose-unique-validator'; \ No newline at end of file diff --git a/src/types/tga.d.ts b/src/types/tga.d.ts index e1c6188..16eb4a2 100644 --- a/src/types/tga.d.ts +++ b/src/types/tga.d.ts @@ -1,3 +1,58 @@ -declare module 'tga'; +declare module 'tga' { + export interface TGAHeader { + idLength: number; + colorMapType: number; + dataType: number; + colorMapOrigin: number; + colorMapLength: number; + colorMapDepth: number; + xOrigin: number; + yOrigin: number; + width: number; + height: number; + bitsPerPixel: number; + flags: number; + id: string; + } -// TODO - Add proper types \ No newline at end of file + export default class TGA { + constructor(buf: Buffer, opt?: { dontFixAlpha: boolean }); + + static createTgaBuffer(width: number, height: number, pixels: Uint8Array, dontFlipY: boolean): Buffer; + + parseHeader(): void; + parseFooter(): void; + parseExtension(extensionAreaOffset: number): void; + readColor(offset: number, bytesPerPixel: number): Uint8Array; + readColorWithColorMap(offset: number): Uint8Array; + readColorAuto(offset: number, bytesPerPixel: number, isUsingColorMap: boolean): Uint8Array; + parseColorMap(): void; + setPixel(pixels: Uint8Array, idx: number, color: Uint8Array): void; + parsePixels(): void; + parse(): void; + fixForAlpha(): void; + + public dontFixAlpha: boolean; + public _buff: Buffer; + public data: Uint8Array; + public currentOffset: number; + + public header: TGAHeader; + + public width: number; + public height: number; + + public isUsingColorMap: boolean; + public isUsingRLE: boolean; + public isGray: boolean; + + public hasAlpha: boolean; + + public isFlipX: boolean; + public isFlipY: boolean; + + public colorMap: Uint8Array; + + public pixels: Uint8Array; + } +} \ No newline at end of file From dc036cd6627b208e3a92ff1fe51d34af2deb25ba Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Thu, 27 Apr 2023 16:01:58 -0400 Subject: [PATCH 066/219] Added SETUP.md and updated README --- README.md | 5 ++-- SETUP.md | 77 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+), 3 deletions(-) create mode 100644 SETUP.md diff --git a/README.md b/README.md index 80fc5d8..1e2a869 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,6 @@ # Account server -## What is this? -The account server is a replacement for several account-based services used by the WiiU and 3DS. It replaces the NNID api as well as NASC for the 3DS. It also contains a dedicated PNID api service for getting details of PNIDs outside of the consoles +Replacement for several account-based services used by the WiiU and 3DS. It replaces the NNID api as well as NASC for the 3DS. It also contains a dedicated PNID api service for getting details of PNIDs outside of the consoles (used by the website) ## Setup -TODO \ No newline at end of file +See [SETUP.md](SETUP.md) for how to self host \ No newline at end of file diff --git a/SETUP.md b/SETUP.md new file mode 100644 index 0000000..d3873d5 --- /dev/null +++ b/SETUP.md @@ -0,0 +1,77 @@ +# Setup + +- [Required software](#required-software) + - [NodeJS](#nodejs) + - [MongoDB](#mongodb) +- [Optional features](#optional-features) + - [Redis (optional)](#redis-optional) + - [Email (optional)](#email-optional) + - [Amazon s3 server (optional)](#amazon-s3-server-optional) + - [hCaptcha (optional)](#hcaptcha-optional) + - [CDN](#cdn) +- [Configuration](#configuration) + + +## Required software + +- [NodeJS](https://nodejs.org/) +- [MongoDB](https://www.mongodb.com) + +### NodeJS + +Download and install the latest LTS version of [NodeJS](https://nodejs.org/). If using a Linux based operating system, using [nvm](https://github.com/nvm-sh/nvm) is the recommended method. _Tested on NodeJS version v18.12.1_ + +### MongoDB + +Download and install the latest version of [MongoDB](https://www.mongodb.com) + +The server assumes that MongoDB is running as replica set, ensure you have configured this properly + +## Optional features + +- [Redis](https://redis.io/) file caching +- Email address for sending automatic emails (tested with gmail) +- Amazon s3, or compatible, server for CDN methods +- [hCaptcha](https://hcaptcha.com/) for website API captcha verification + +### Redis (optional) + +Redis can be used to cache files read from disk. If Redis is not configured, then an in-memory object store is used instead + +### Email (optional) + +Events such as account creation, email verification, etc, support sending emails to users. To enable email sending, create an email address which is compatible with [Nodemailer](https://nodemailer.com/). Which email service you use does not matter, see the Nodemailer documentation for more details + +### Amazon s3 server (optional) + +Certain endpoints expect URLs for static CDN assets, such as pre-rendered Mii images. An [Amazon s3](https://aws.amazon.com/s3/) or compatible server, such as [Spaces by DigitalOcean](https://www.digitalocean.com/products/spaces), [Cloudflare R2](https://www.cloudflare.com/products/r2/), or [Backblaze B2](https://www.backblaze.com/b2/docs/), can optionally be used to store and upload these assets. If an s3 server is not configured, CDN contents will be stored on disk and served from this server. See [Configuration](#configuration) for more details + +### hCaptcha (optional) + +The Pretendo Network website uses this server as an API for querying user information. Certain endpoints are considered more secure than others, such as registration, and can optionally be protected using [hCaptcha](https://hcaptcha.com/). If hCaptcha is not configured, no endpoints on the public facing API will be protected + +## Configuration + +Configurations are loaded through environment variables. `.env` files are supported. All configuration options will be gone over, both required and optional. There also exists an example `.env` file + +| Name | Description | Optional | +|-----------------------------------------------|----------------------------------------------------------------|----------| +| `PN_ACT_CONFIG_HTTP_PORT` | The HTTP port the server listens on | No | +| `PN_ACT_CONFIG_MONGO_CONNECTION_STRING` | MongoDB connection string | No | +| `PN_ACT_CONFIG_MONGOOSE_CONNECT_OPTIONS_PATH` | Path to a `.json` file containing Mongoose connection options | Yes | +| `PN_ACT_CONFIG_REDIS_URL` | Redis URL | Yes | +| `PN_ACT_CONFIG_EMAIL_HOST` | SMTP host | Yes | +| `PN_ACT_CONFIG_EMAIL_PORT` | SMTP port | Yes | +| `PN_ACT_CONFIG_EMAIL_SECURE` | Is the email server secure | Yes | +| `PN_ACT_CONFIG_EMAIL_USERNAME` | Email account username | Yes | +| `PN_ACT_CONFIG_EMAIL_PASSWORD` | Email account password | Yes | +| `PN_ACT_CONFIG_EMAIL_FROM` | Email "from" address | Yes | +| `PN_ACT_CONFIG_S3_ENDPOINT` | s3 server endpoint | Yes | +| `PN_ACT_CONFIG_S3_ACCESS_KEY` | s3 secret key | Yes | +| `PN_ACT_CONFIG_S3_ACCESS_SECRET` | s3 secret | Yes | +| `PN_ACT_CONFIG_HCAPTCHA_SECRET` | hCaptcha secret (in the form `0x...`) | Yes | +| `PN_ACT_CONFIG_CDN_SUBDOMAIN` | Subdomain used to serve CDN contents if s3 is disabled | Yes | +| `PN_ACT_CONFIG_CDN_DISK_PATH` | File system path used to store CDN contents if s3 is disabled | Yes | +| `PN_ACT_CONFIG_CDN_BASE_URL` | URL for serving CDN contents (usually the same as s3 endpoint) | Yes | +| `PN_ACT_CONFIG_WEBSITE_BASE` | Website URL | Yes | +| `PN_ACT_CONFIG_AES_KEY` | AES-256 key used for encrypting tokens | No | \ No newline at end of file From c420997de04edb1f3ef8cd5eb7642853c9b39965 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Thu, 27 Apr 2023 16:03:15 -0400 Subject: [PATCH 067/219] Updated example.env --- example.env | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/example.env b/example.env index 360615f..510757f 100644 --- a/example.env +++ b/example.env @@ -17,4 +17,5 @@ PN_ACT_CONFIG_HCAPTCHA_SECRET=0x0000000000000000000000000000000000000000 PN_ACT_CONFIG_CDN_BASE_URL=https://local-cdn.example.com PN_ACT_CONFIG_CDN_SUBDOMAIN=local-cdn PN_ACT_CONFIG_CDN_DISK_PATH=/home/jon/pretend-cdn -PN_ACT_CONFIG_WEBSITE_BASE=https://example.com \ No newline at end of file +PN_ACT_CONFIG_WEBSITE_BASE=https://example.com +PN_ACT_CONFIG_AES_KEY=abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789 \ No newline at end of file From 6c6ff81db1e04998ea99f1bbb6ccdc3da0d1b323 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Thu, 27 Apr 2023 16:03:35 -0400 Subject: [PATCH 068/219] Removed unused example.config.json --- example.config.json | 40 ---------------------------------------- 1 file changed, 40 deletions(-) delete mode 100644 example.config.json diff --git a/example.config.json b/example.config.json deleted file mode 100644 index 33f7b24..0000000 --- a/example.config.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "http": { - "port": 7070 - }, - "mongoose": { - "connection_string": "mongodb://localhost:27017/database_name", - "options": { - "useNewUrlParser": true - } - }, - "redis": { - "client": { - "url": "redis://localhost:6379" - } - }, - "email": { - "host": "smtp.gmail.com", - "port": 587, - "secure": false, - "auth": { - "user": "username", - "pass": "password" - }, - "from": "Company Name " - }, - "s3": { - "endpoint": "nyc3.digitaloceanspaces.com", - "key": "ACCESS_KEY", - "secret": "ACCESS_SECRET" - }, - "hcaptcha": { - "secret": "0x0000000000000000000000000000000000000000" - }, - "cdn": { - "base_url": "https://local-cdn.example.com", - "subdomain": "local-cdn", - "disk_path": "/home/jon/pretend-cdn" - }, - "website_base": "https://example.com" -} \ No newline at end of file From 1e42569cbb0ecb141c4c714b185ba529042537ad Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Thu, 27 Apr 2023 16:08:31 -0400 Subject: [PATCH 069/219] Updated image-pixels type definition --- src/types/image-pixels.d.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/types/image-pixels.d.ts b/src/types/image-pixels.d.ts index 762209c..a738a3f 100644 --- a/src/types/image-pixels.d.ts +++ b/src/types/image-pixels.d.ts @@ -18,11 +18,9 @@ declare module 'image-pixels' { } export type ImageSource = string | - HTMLImageElement | SVGImageElement | HTMLVideoElement | CSSImageValue | + HTMLImageElement | SVGImageElement | HTMLVideoElement | typeof Image | ImageData | ImageBitmap | File | Blob | - //Canvas | Context2D | - //WebGLContext | Buffer | ArrayBuffer | Uint8Array | Uint8ClampedArray | Float32Array | Float64Array | Array | Array> | NdArray | From b67b266b8dae008c5414da3254bb0f243ed07cfa Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Thu, 27 Apr 2023 23:34:49 -0400 Subject: [PATCH 070/219] Added missing Stripe connection on PNIDs --- src/models/pnid.ts | 8 ++++++++ src/types/mongoose-unique-validator.d.ts | 1 + src/types/mongoose/pnid.ts | 12 +++++++++++- 3 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 src/types/mongoose-unique-validator.d.ts diff --git a/src/models/pnid.ts b/src/models/pnid.ts index 74613f6..dfc01d6 100644 --- a/src/models/pnid.ts +++ b/src/models/pnid.ts @@ -101,6 +101,14 @@ const PNIDSchema = new Schema({ connections: { discord: { id: String + }, + stripe: { + customer_id: String, + subscription_id: String, + price_id: String, + tier_level: Number, + tier_name: String, + latest_webhook_timestamp: Number } } }, { id: false }); diff --git a/src/types/mongoose-unique-validator.d.ts b/src/types/mongoose-unique-validator.d.ts new file mode 100644 index 0000000..0909028 --- /dev/null +++ b/src/types/mongoose-unique-validator.d.ts @@ -0,0 +1 @@ +declare module 'mongoose-unique-validator'; \ No newline at end of file diff --git a/src/types/mongoose/pnid.ts b/src/types/mongoose/pnid.ts index ee1fda6..07a876e 100644 --- a/src/types/mongoose/pnid.ts +++ b/src/types/mongoose/pnid.ts @@ -60,7 +60,17 @@ export interface IPNID { connections: { discord: { id: string; - } + }; + connections: { + stripe: { + customer_id: string; + subscription_id: string; + price_id: string; + tier_level: number; + tier_name: string; + latest_webhook_timestamp: number; + }; + }; }; } From 528882b9e82196d3e4d35d766fb91716c163b1e0 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Thu, 27 Apr 2023 23:41:26 -0400 Subject: [PATCH 071/219] Fixed PNID Stripe connection type --- src/types/mongoose/pnid.ts | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/types/mongoose/pnid.ts b/src/types/mongoose/pnid.ts index 07a876e..0b75ee1 100644 --- a/src/types/mongoose/pnid.ts +++ b/src/types/mongoose/pnid.ts @@ -61,15 +61,13 @@ export interface IPNID { discord: { id: string; }; - connections: { - stripe: { - customer_id: string; - subscription_id: string; - price_id: string; - tier_level: number; - tier_name: string; - latest_webhook_timestamp: number; - }; + stripe: { + customer_id: string; + subscription_id: string; + price_id: string; + tier_level: number; + tier_name: string; + latest_webhook_timestamp: number; }; }; } From 85d9c65989f4abf63d8c1f2d84558938c28ff7a5 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Thu, 27 Apr 2023 23:58:02 -0400 Subject: [PATCH 072/219] Added getUserData gRPC method --- SETUP.md | 43 +- example.env | 3 +- package-lock.json | 8438 ++--------------------- package.json | 6 +- src/config-manager.ts | 8 +- src/server.ts | 2 + src/services/grpc/api-key-middleware.ts | 18 + src/services/grpc/get-user-data.ts | 35 + src/services/grpc/login.ts | 6 + src/services/grpc/register-pnid.ts | 7 + src/services/grpc/server.ts | 21 + src/types/common/config.ts | 1 + 12 files changed, 777 insertions(+), 7811 deletions(-) create mode 100644 src/services/grpc/api-key-middleware.ts create mode 100644 src/services/grpc/get-user-data.ts create mode 100644 src/services/grpc/login.ts create mode 100644 src/services/grpc/register-pnid.ts create mode 100644 src/services/grpc/server.ts diff --git a/SETUP.md b/SETUP.md index d3873d5..88db0b8 100644 --- a/SETUP.md +++ b/SETUP.md @@ -54,24 +54,25 @@ The Pretendo Network website uses this server as an API for querying user inform Configurations are loaded through environment variables. `.env` files are supported. All configuration options will be gone over, both required and optional. There also exists an example `.env` file -| Name | Description | Optional | -|-----------------------------------------------|----------------------------------------------------------------|----------| -| `PN_ACT_CONFIG_HTTP_PORT` | The HTTP port the server listens on | No | -| `PN_ACT_CONFIG_MONGO_CONNECTION_STRING` | MongoDB connection string | No | -| `PN_ACT_CONFIG_MONGOOSE_CONNECT_OPTIONS_PATH` | Path to a `.json` file containing Mongoose connection options | Yes | -| `PN_ACT_CONFIG_REDIS_URL` | Redis URL | Yes | -| `PN_ACT_CONFIG_EMAIL_HOST` | SMTP host | Yes | -| `PN_ACT_CONFIG_EMAIL_PORT` | SMTP port | Yes | -| `PN_ACT_CONFIG_EMAIL_SECURE` | Is the email server secure | Yes | -| `PN_ACT_CONFIG_EMAIL_USERNAME` | Email account username | Yes | -| `PN_ACT_CONFIG_EMAIL_PASSWORD` | Email account password | Yes | -| `PN_ACT_CONFIG_EMAIL_FROM` | Email "from" address | Yes | -| `PN_ACT_CONFIG_S3_ENDPOINT` | s3 server endpoint | Yes | -| `PN_ACT_CONFIG_S3_ACCESS_KEY` | s3 secret key | Yes | -| `PN_ACT_CONFIG_S3_ACCESS_SECRET` | s3 secret | Yes | -| `PN_ACT_CONFIG_HCAPTCHA_SECRET` | hCaptcha secret (in the form `0x...`) | Yes | -| `PN_ACT_CONFIG_CDN_SUBDOMAIN` | Subdomain used to serve CDN contents if s3 is disabled | Yes | -| `PN_ACT_CONFIG_CDN_DISK_PATH` | File system path used to store CDN contents if s3 is disabled | Yes | -| `PN_ACT_CONFIG_CDN_BASE_URL` | URL for serving CDN contents (usually the same as s3 endpoint) | Yes | -| `PN_ACT_CONFIG_WEBSITE_BASE` | Website URL | Yes | -| `PN_ACT_CONFIG_AES_KEY` | AES-256 key used for encrypting tokens | No | \ No newline at end of file +| Name | Description | Optional | +|-----------------------------------------------|------------------------------------------------------------------|----------| +| `PN_ACT_CONFIG_HTTP_PORT` | The HTTP port the server listens on | No | +| `PN_ACT_CONFIG_MONGO_CONNECTION_STRING` | MongoDB connection string | No | +| `PN_ACT_CONFIG_MONGOOSE_CONNECT_OPTIONS_PATH` | Path to a `.json` file containing Mongoose connection options | Yes | +| `PN_ACT_CONFIG_REDIS_URL` | Redis URL | Yes | +| `PN_ACT_CONFIG_EMAIL_HOST` | SMTP host | Yes | +| `PN_ACT_CONFIG_EMAIL_PORT` | SMTP port | Yes | +| `PN_ACT_CONFIG_EMAIL_SECURE` | Is the email server secure | Yes | +| `PN_ACT_CONFIG_EMAIL_USERNAME` | Email account username | Yes | +| `PN_ACT_CONFIG_EMAIL_PASSWORD` | Email account password | Yes | +| `PN_ACT_CONFIG_EMAIL_FROM` | Email "from" address | Yes | +| `PN_ACT_CONFIG_S3_ENDPOINT` | s3 server endpoint | Yes | +| `PN_ACT_CONFIG_S3_ACCESS_KEY` | s3 secret key | Yes | +| `PN_ACT_CONFIG_S3_ACCESS_SECRET` | s3 secret | Yes | +| `PN_ACT_CONFIG_HCAPTCHA_SECRET` | hCaptcha secret (in the form `0x...`) | Yes | +| `PN_ACT_CONFIG_CDN_SUBDOMAIN` | Subdomain used to serve CDN contents if s3 is disabled | Yes | +| `PN_ACT_CONFIG_CDN_DISK_PATH` | File system path used to store CDN contents if s3 is disabled | Yes | +| `PN_ACT_CONFIG_CDN_BASE_URL` | URL for serving CDN contents (usually the same as s3 endpoint) | No | +| `PN_ACT_CONFIG_WEBSITE_BASE` | Website URL | Yes | +| `PN_ACT_CONFIG_AES_KEY` | AES-256 key used for encrypting tokens | No | +| `PN_ACT_CONFIG_GRPC_API_KEY` | gRPC API key that other clients use to interact with this server | No | \ No newline at end of file diff --git a/example.env b/example.env index 510757f..14423b9 100644 --- a/example.env +++ b/example.env @@ -18,4 +18,5 @@ PN_ACT_CONFIG_CDN_BASE_URL=https://local-cdn.example.com PN_ACT_CONFIG_CDN_SUBDOMAIN=local-cdn PN_ACT_CONFIG_CDN_DISK_PATH=/home/jon/pretend-cdn PN_ACT_CONFIG_WEBSITE_BASE=https://example.com -PN_ACT_CONFIG_AES_KEY=abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789 \ No newline at end of file +PN_ACT_CONFIG_AES_KEY=abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789 +PN_ACT_CONFIG_GRPC_API_KEY=apikey \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 91b3c7b..e78ea55 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,7 +1,7 @@ { "name": "account", "version": "2.0.0", - "lockfileVersion": 2, + "lockfileVersion": 3, "requires": true, "packages": { "": { @@ -27,10 +27,12 @@ "mii-js": "github:PretendoNetwork/mii-js", "moment": "^2.29.4", "mongoose": "^7.0.0", - "mongoose-unique-validator": "github:stenneepro/mongoose-unique-validator#bump/mongoose7", + "mongoose-unique-validator": "^4.0.0", "morgan": "^1.9.1", + "nice-grpc": "^2.1.4", "node-rsa": "^1.0.7", "nodemailer": "^6.4.2", + "pretendo-grpc-ts": "github:PretendoNetwork/grpc-ts", "redis": "^4.3.1", "tga": "^1.0.4", "typescript-is": "^0.19.0", @@ -45,7 +47,6 @@ "@types/dicer": "^0.2.2", "@types/express": "^4.17.17", "@types/fs-extra": "^11.0.1", - "@types/mongoose-unique-validator": "^1.0.7", "@types/morgan": "^1.9.4", "@types/ndarray": "^1.0.11", "@types/node": "^18.14.4", @@ -53,7 +54,6 @@ "@types/nodemailer": "^6.4.7", "@types/qs": "^6.9.7", "@types/validator": "^13.7.14", - "@types/w3c-css-typed-object-model-level-1": "^20180410.0.5", "@typescript-eslint/eslint-plugin": "^5.54.1", "@typescript-eslint/parser": "^5.54.1", "eslint": "^8.35.0", @@ -63,1404 +63,6 @@ "yesno": "^0.4.0" } }, - "node_modules/@aws-crypto/ie11-detection": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/ie11-detection/-/ie11-detection-3.0.0.tgz", - "integrity": "sha512-341lBBkiY1DfDNKai/wXM3aujNBkXR7tq1URPQDL9wi3AUbI80NR74uF1TXHMm7po1AcnFk8iu2S2IeU/+/A+Q==", - "optional": true, - "dependencies": { - "tslib": "^1.11.1" - } - }, - "node_modules/@aws-crypto/sha256-browser": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-3.0.0.tgz", - "integrity": "sha512-8VLmW2B+gjFbU5uMeqtQM6Nj0/F1bro80xQXCW6CQBWgosFWXTx77aeOF5CAIAmbOK64SdMBJdNr6J41yP5mvQ==", - "optional": true, - "dependencies": { - "@aws-crypto/ie11-detection": "^3.0.0", - "@aws-crypto/sha256-js": "^3.0.0", - "@aws-crypto/supports-web-crypto": "^3.0.0", - "@aws-crypto/util": "^3.0.0", - "@aws-sdk/types": "^3.222.0", - "@aws-sdk/util-locate-window": "^3.0.0", - "@aws-sdk/util-utf8-browser": "^3.0.0", - "tslib": "^1.11.1" - } - }, - "node_modules/@aws-crypto/sha256-js": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-3.0.0.tgz", - "integrity": "sha512-PnNN7os0+yd1XvXAy23CFOmTbMaDxgxXtTKHybrJ39Y8kGzBATgBFibWJKH6BhytLI/Zyszs87xCOBNyBig6vQ==", - "optional": true, - "dependencies": { - "@aws-crypto/util": "^3.0.0", - "@aws-sdk/types": "^3.222.0", - "tslib": "^1.11.1" - } - }, - "node_modules/@aws-crypto/supports-web-crypto": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-3.0.0.tgz", - "integrity": "sha512-06hBdMwUAb2WFTuGG73LSC0wfPu93xWwo5vL2et9eymgmu3Id5vFAHBbajVWiGhPO37qcsdCap/FqXvJGJWPIg==", - "optional": true, - "dependencies": { - "tslib": "^1.11.1" - } - }, - "node_modules/@aws-crypto/util": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-3.0.0.tgz", - "integrity": "sha512-2OJlpeJpCR48CC8r+uKVChzs9Iungj9wkZrl8Z041DWEWvyIHILYKCPNzJghKsivj+S3mLo6BVc7mBNzdxA46w==", - "optional": true, - "dependencies": { - "@aws-sdk/types": "^3.222.0", - "@aws-sdk/util-utf8-browser": "^3.0.0", - "tslib": "^1.11.1" - } - }, - "node_modules/@aws-sdk/abort-controller": { - "version": "3.292.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/abort-controller/-/abort-controller-3.292.0.tgz", - "integrity": "sha512-lf+OPptL01kvryIJy7+dvFux5KbJ6OTwLPPEekVKZ2AfEvwcVtOZWFUhyw3PJCBTVncjKB1Kjl3V/eTS3YuPXQ==", - "optional": true, - "dependencies": { - "@aws-sdk/types": "3.292.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/abort-controller/node_modules/tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - }, - "node_modules/@aws-sdk/client-cognito-identity": { - "version": "3.294.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-cognito-identity/-/client-cognito-identity-3.294.0.tgz", - "integrity": "sha512-QMk/QratNvAvmnJ77pu9KDjpfUf/5LOJplHcLKcY962ewJGBtFFY4XZVnmUgQMSG0phRODtex7pyH//FueGKLQ==", - "optional": true, - "dependencies": { - "@aws-crypto/sha256-browser": "3.0.0", - "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/client-sts": "3.294.0", - "@aws-sdk/config-resolver": "3.292.0", - "@aws-sdk/credential-provider-node": "3.294.0", - "@aws-sdk/fetch-http-handler": "3.292.0", - "@aws-sdk/hash-node": "3.292.0", - "@aws-sdk/invalid-dependency": "3.292.0", - "@aws-sdk/middleware-content-length": "3.292.0", - "@aws-sdk/middleware-endpoint": "3.292.0", - "@aws-sdk/middleware-host-header": "3.292.0", - "@aws-sdk/middleware-logger": "3.292.0", - "@aws-sdk/middleware-recursion-detection": "3.292.0", - "@aws-sdk/middleware-retry": "3.293.0", - "@aws-sdk/middleware-serde": "3.292.0", - "@aws-sdk/middleware-signing": "3.292.0", - "@aws-sdk/middleware-stack": "3.292.0", - "@aws-sdk/middleware-user-agent": "3.293.0", - "@aws-sdk/node-config-provider": "3.292.0", - "@aws-sdk/node-http-handler": "3.292.0", - "@aws-sdk/protocol-http": "3.292.0", - "@aws-sdk/smithy-client": "3.292.0", - "@aws-sdk/types": "3.292.0", - "@aws-sdk/url-parser": "3.292.0", - "@aws-sdk/util-base64": "3.292.0", - "@aws-sdk/util-body-length-browser": "3.292.0", - "@aws-sdk/util-body-length-node": "3.292.0", - "@aws-sdk/util-defaults-mode-browser": "3.292.0", - "@aws-sdk/util-defaults-mode-node": "3.292.0", - "@aws-sdk/util-endpoints": "3.293.0", - "@aws-sdk/util-retry": "3.292.0", - "@aws-sdk/util-user-agent-browser": "3.292.0", - "@aws-sdk/util-user-agent-node": "3.292.0", - "@aws-sdk/util-utf8": "3.292.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - }, - "node_modules/@aws-sdk/client-sso": { - "version": "3.294.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.294.0.tgz", - "integrity": "sha512-+FuxQTi5WvnaXM5JbNLkBIzQ3An4gA0ox61N1u+3xled+nywKb1yQ7WmRpyMG5bLbkmnj3aqoo5/uskFc4c4EA==", - "optional": true, - "dependencies": { - "@aws-crypto/sha256-browser": "3.0.0", - "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/config-resolver": "3.292.0", - "@aws-sdk/fetch-http-handler": "3.292.0", - "@aws-sdk/hash-node": "3.292.0", - "@aws-sdk/invalid-dependency": "3.292.0", - "@aws-sdk/middleware-content-length": "3.292.0", - "@aws-sdk/middleware-endpoint": "3.292.0", - "@aws-sdk/middleware-host-header": "3.292.0", - "@aws-sdk/middleware-logger": "3.292.0", - "@aws-sdk/middleware-recursion-detection": "3.292.0", - "@aws-sdk/middleware-retry": "3.293.0", - "@aws-sdk/middleware-serde": "3.292.0", - "@aws-sdk/middleware-stack": "3.292.0", - "@aws-sdk/middleware-user-agent": "3.293.0", - "@aws-sdk/node-config-provider": "3.292.0", - "@aws-sdk/node-http-handler": "3.292.0", - "@aws-sdk/protocol-http": "3.292.0", - "@aws-sdk/smithy-client": "3.292.0", - "@aws-sdk/types": "3.292.0", - "@aws-sdk/url-parser": "3.292.0", - "@aws-sdk/util-base64": "3.292.0", - "@aws-sdk/util-body-length-browser": "3.292.0", - "@aws-sdk/util-body-length-node": "3.292.0", - "@aws-sdk/util-defaults-mode-browser": "3.292.0", - "@aws-sdk/util-defaults-mode-node": "3.292.0", - "@aws-sdk/util-endpoints": "3.293.0", - "@aws-sdk/util-retry": "3.292.0", - "@aws-sdk/util-user-agent-browser": "3.292.0", - "@aws-sdk/util-user-agent-node": "3.292.0", - "@aws-sdk/util-utf8": "3.292.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/client-sso-oidc": { - "version": "3.294.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.294.0.tgz", - "integrity": "sha512-/ZfDud76MdSPJ/TxjV2xLE30XbBQDZwKQ32axwoK1eziPvrAIUBYVgpBwj+m0quhoiQhBKkg3aFl6j39AF2thw==", - "optional": true, - "dependencies": { - "@aws-crypto/sha256-browser": "3.0.0", - "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/config-resolver": "3.292.0", - "@aws-sdk/fetch-http-handler": "3.292.0", - "@aws-sdk/hash-node": "3.292.0", - "@aws-sdk/invalid-dependency": "3.292.0", - "@aws-sdk/middleware-content-length": "3.292.0", - "@aws-sdk/middleware-endpoint": "3.292.0", - "@aws-sdk/middleware-host-header": "3.292.0", - "@aws-sdk/middleware-logger": "3.292.0", - "@aws-sdk/middleware-recursion-detection": "3.292.0", - "@aws-sdk/middleware-retry": "3.293.0", - "@aws-sdk/middleware-serde": "3.292.0", - "@aws-sdk/middleware-stack": "3.292.0", - "@aws-sdk/middleware-user-agent": "3.293.0", - "@aws-sdk/node-config-provider": "3.292.0", - "@aws-sdk/node-http-handler": "3.292.0", - "@aws-sdk/protocol-http": "3.292.0", - "@aws-sdk/smithy-client": "3.292.0", - "@aws-sdk/types": "3.292.0", - "@aws-sdk/url-parser": "3.292.0", - "@aws-sdk/util-base64": "3.292.0", - "@aws-sdk/util-body-length-browser": "3.292.0", - "@aws-sdk/util-body-length-node": "3.292.0", - "@aws-sdk/util-defaults-mode-browser": "3.292.0", - "@aws-sdk/util-defaults-mode-node": "3.292.0", - "@aws-sdk/util-endpoints": "3.293.0", - "@aws-sdk/util-retry": "3.292.0", - "@aws-sdk/util-user-agent-browser": "3.292.0", - "@aws-sdk/util-user-agent-node": "3.292.0", - "@aws-sdk/util-utf8": "3.292.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - }, - "node_modules/@aws-sdk/client-sso/node_modules/tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - }, - "node_modules/@aws-sdk/client-sts": { - "version": "3.294.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.294.0.tgz", - "integrity": "sha512-AefqwhFjTDzelZuSYhriJbiI+GQwf2yKiKAnCt0gRj6rswewStM63Gtlhfb01sFPp+ZiqPcyQ47LqUaHp1mz/g==", - "optional": true, - "dependencies": { - "@aws-crypto/sha256-browser": "3.0.0", - "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/config-resolver": "3.292.0", - "@aws-sdk/credential-provider-node": "3.294.0", - "@aws-sdk/fetch-http-handler": "3.292.0", - "@aws-sdk/hash-node": "3.292.0", - "@aws-sdk/invalid-dependency": "3.292.0", - "@aws-sdk/middleware-content-length": "3.292.0", - "@aws-sdk/middleware-endpoint": "3.292.0", - "@aws-sdk/middleware-host-header": "3.292.0", - "@aws-sdk/middleware-logger": "3.292.0", - "@aws-sdk/middleware-recursion-detection": "3.292.0", - "@aws-sdk/middleware-retry": "3.293.0", - "@aws-sdk/middleware-sdk-sts": "3.292.0", - "@aws-sdk/middleware-serde": "3.292.0", - "@aws-sdk/middleware-signing": "3.292.0", - "@aws-sdk/middleware-stack": "3.292.0", - "@aws-sdk/middleware-user-agent": "3.293.0", - "@aws-sdk/node-config-provider": "3.292.0", - "@aws-sdk/node-http-handler": "3.292.0", - "@aws-sdk/protocol-http": "3.292.0", - "@aws-sdk/smithy-client": "3.292.0", - "@aws-sdk/types": "3.292.0", - "@aws-sdk/url-parser": "3.292.0", - "@aws-sdk/util-base64": "3.292.0", - "@aws-sdk/util-body-length-browser": "3.292.0", - "@aws-sdk/util-body-length-node": "3.292.0", - "@aws-sdk/util-defaults-mode-browser": "3.292.0", - "@aws-sdk/util-defaults-mode-node": "3.292.0", - "@aws-sdk/util-endpoints": "3.293.0", - "@aws-sdk/util-retry": "3.292.0", - "@aws-sdk/util-user-agent-browser": "3.292.0", - "@aws-sdk/util-user-agent-node": "3.292.0", - "@aws-sdk/util-utf8": "3.292.0", - "fast-xml-parser": "4.1.2", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/client-sts/node_modules/tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - }, - "node_modules/@aws-sdk/config-resolver": { - "version": "3.292.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/config-resolver/-/config-resolver-3.292.0.tgz", - "integrity": "sha512-cB3twnNR7vYvlt2jvw8VlA1+iv/tVzl+/S39MKqw2tepU+AbJAM0EHwb/dkf1OKSmlrnANXhshx80MHF9zL4mA==", - "optional": true, - "dependencies": { - "@aws-sdk/signature-v4": "3.292.0", - "@aws-sdk/types": "3.292.0", - "@aws-sdk/util-config-provider": "3.292.0", - "@aws-sdk/util-middleware": "3.292.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/config-resolver/node_modules/tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - }, - "node_modules/@aws-sdk/credential-provider-cognito-identity": { - "version": "3.294.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-cognito-identity/-/credential-provider-cognito-identity-3.294.0.tgz", - "integrity": "sha512-YSPqHEbLC0dnbFF5LdlMH0B50sMSN/CyG/sHkPYUwL/hkbUk9URnVW7ZJlt6lRftT7X4C7muzLqUP8sJAaiJEA==", - "optional": true, - "dependencies": { - "@aws-sdk/client-cognito-identity": "3.294.0", - "@aws-sdk/property-provider": "3.292.0", - "@aws-sdk/types": "3.292.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - }, - "node_modules/@aws-sdk/credential-provider-env": { - "version": "3.292.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.292.0.tgz", - "integrity": "sha512-YbafSG0ZEKE2969CJWVtUhh3hfOeLPecFVoXOtegCyAJgY5Ghtu4TsVhL4DgiGAgOC30ojAmUVQEXzd7xJF5xA==", - "optional": true, - "dependencies": { - "@aws-sdk/property-provider": "3.292.0", - "@aws-sdk/types": "3.292.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-env/node_modules/tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - }, - "node_modules/@aws-sdk/credential-provider-imds": { - "version": "3.292.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-imds/-/credential-provider-imds-3.292.0.tgz", - "integrity": "sha512-W/peOgDSRYulgzFpUhvgi1pCm6piBz6xrVN17N4QOy+3NHBXRVMVzYk6ct2qpLPgJUSEZkcpP+Gds+bBm8ed1A==", - "optional": true, - "dependencies": { - "@aws-sdk/node-config-provider": "3.292.0", - "@aws-sdk/property-provider": "3.292.0", - "@aws-sdk/types": "3.292.0", - "@aws-sdk/url-parser": "3.292.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-imds/node_modules/tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - }, - "node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.294.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.294.0.tgz", - "integrity": "sha512-pdTPbaAb5bWA+DnuKoL2TpXeNDp6Ejpv/OYt+bw2gdzl9w5r/ZCtUTTbW+Vvejr4WL5s3c1bY96kwdqCn7iLqA==", - "optional": true, - "dependencies": { - "@aws-sdk/credential-provider-env": "3.292.0", - "@aws-sdk/credential-provider-imds": "3.292.0", - "@aws-sdk/credential-provider-process": "3.292.0", - "@aws-sdk/credential-provider-sso": "3.294.0", - "@aws-sdk/credential-provider-web-identity": "3.292.0", - "@aws-sdk/property-provider": "3.292.0", - "@aws-sdk/shared-ini-file-loader": "3.292.0", - "@aws-sdk/types": "3.292.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-ini/node_modules/tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - }, - "node_modules/@aws-sdk/credential-provider-node": { - "version": "3.294.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.294.0.tgz", - "integrity": "sha512-zUL1Qhb4BsQIZCs/TPpG4oIYH/9YsGiS+Se1tasSGjTOLfBy7jhOZ0QIdpEeyAx/EP8blOBredM9xWfEXgiHVA==", - "optional": true, - "dependencies": { - "@aws-sdk/credential-provider-env": "3.292.0", - "@aws-sdk/credential-provider-imds": "3.292.0", - "@aws-sdk/credential-provider-ini": "3.294.0", - "@aws-sdk/credential-provider-process": "3.292.0", - "@aws-sdk/credential-provider-sso": "3.294.0", - "@aws-sdk/credential-provider-web-identity": "3.292.0", - "@aws-sdk/property-provider": "3.292.0", - "@aws-sdk/shared-ini-file-loader": "3.292.0", - "@aws-sdk/types": "3.292.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-node/node_modules/tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - }, - "node_modules/@aws-sdk/credential-provider-process": { - "version": "3.292.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.292.0.tgz", - "integrity": "sha512-CFVXuMuUvg/a4tknzRikEDwZBnKlHs1LZCpTXIGjBdUTdosoi4WNzDLzGp93ZRTtcgFz+4wirz2f7P3lC0NrQw==", - "optional": true, - "dependencies": { - "@aws-sdk/property-provider": "3.292.0", - "@aws-sdk/shared-ini-file-loader": "3.292.0", - "@aws-sdk/types": "3.292.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-process/node_modules/tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - }, - "node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.294.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.294.0.tgz", - "integrity": "sha512-UxrcAA/0l7j9+3tolYcG5M61D/IE1Bjd/9H87H1i2A2BrwUUBhW1Dp/vvROEDrrywlMDG3CDF3T/7ADtTak+sg==", - "optional": true, - "dependencies": { - "@aws-sdk/client-sso": "3.294.0", - "@aws-sdk/property-provider": "3.292.0", - "@aws-sdk/shared-ini-file-loader": "3.292.0", - "@aws-sdk/token-providers": "3.294.0", - "@aws-sdk/types": "3.292.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-sso/node_modules/tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - }, - "node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.292.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.292.0.tgz", - "integrity": "sha512-4DbtIEM9gGVfqYlMdYXg3XY+vBhemjB1zXIequottW8loLYM8Vuz4/uGxxKNze6evVVzowsA0wKrYclE1aj/Rg==", - "optional": true, - "dependencies": { - "@aws-sdk/property-provider": "3.292.0", - "@aws-sdk/types": "3.292.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-web-identity/node_modules/tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - }, - "node_modules/@aws-sdk/credential-providers": { - "version": "3.294.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-providers/-/credential-providers-3.294.0.tgz", - "integrity": "sha512-CRGwCs6F31mQzjYi5YF2Z6c1T+UrFKtJSa6Ff/Q1rrPnROexyhBVnpP8WzpkODx/pZxKtTX50IX7ehIxbFDIyQ==", - "optional": true, - "dependencies": { - "@aws-sdk/client-cognito-identity": "3.294.0", - "@aws-sdk/client-sso": "3.294.0", - "@aws-sdk/client-sts": "3.294.0", - "@aws-sdk/credential-provider-cognito-identity": "3.294.0", - "@aws-sdk/credential-provider-env": "3.292.0", - "@aws-sdk/credential-provider-imds": "3.292.0", - "@aws-sdk/credential-provider-ini": "3.294.0", - "@aws-sdk/credential-provider-node": "3.294.0", - "@aws-sdk/credential-provider-process": "3.292.0", - "@aws-sdk/credential-provider-sso": "3.294.0", - "@aws-sdk/credential-provider-web-identity": "3.292.0", - "@aws-sdk/property-provider": "3.292.0", - "@aws-sdk/shared-ini-file-loader": "3.292.0", - "@aws-sdk/types": "3.292.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/credential-providers/node_modules/tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - }, - "node_modules/@aws-sdk/fetch-http-handler": { - "version": "3.292.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/fetch-http-handler/-/fetch-http-handler-3.292.0.tgz", - "integrity": "sha512-zh3bhUJbL8RSa39ZKDcy+AghtUkIP8LwcNlwRIoxMQh3Row4D1s4fCq0KZCx98NJBEXoiTLyTQlZxxI//BOb1Q==", - "optional": true, - "dependencies": { - "@aws-sdk/protocol-http": "3.292.0", - "@aws-sdk/querystring-builder": "3.292.0", - "@aws-sdk/types": "3.292.0", - "@aws-sdk/util-base64": "3.292.0", - "tslib": "^2.3.1" - } - }, - "node_modules/@aws-sdk/fetch-http-handler/node_modules/tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - }, - "node_modules/@aws-sdk/hash-node": { - "version": "3.292.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/hash-node/-/hash-node-3.292.0.tgz", - "integrity": "sha512-1yLxmIsvE+eK36JXEgEIouTITdykQLVhsA5Oai//Lar6Ddgu1sFpLDbdkMtKbrh4I0jLN9RacNCkeVQjZPTCCQ==", - "optional": true, - "dependencies": { - "@aws-sdk/types": "3.292.0", - "@aws-sdk/util-buffer-from": "3.292.0", - "@aws-sdk/util-utf8": "3.292.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/hash-node/node_modules/tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - }, - "node_modules/@aws-sdk/invalid-dependency": { - "version": "3.292.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/invalid-dependency/-/invalid-dependency-3.292.0.tgz", - "integrity": "sha512-39OUV78CD3TmEbjhpt+V+Fk4wAGWhixqHxDSN8+4WL0uB4Fl7k5m3Z9hNY78AttHQSl2twR7WtLztnXPAFsriw==", - "optional": true, - "dependencies": { - "@aws-sdk/types": "3.292.0", - "tslib": "^2.3.1" - } - }, - "node_modules/@aws-sdk/invalid-dependency/node_modules/tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - }, - "node_modules/@aws-sdk/is-array-buffer": { - "version": "3.292.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/is-array-buffer/-/is-array-buffer-3.292.0.tgz", - "integrity": "sha512-kW/G5T/fzI0sJH5foZG6XJiNCevXqKLxV50qIT4B1pMuw7regd4ALIy0HwSqj1nnn9mSbRWBfmby0jWCJsMcwg==", - "optional": true, - "dependencies": { - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/is-array-buffer/node_modules/tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - }, - "node_modules/@aws-sdk/middleware-content-length": { - "version": "3.292.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-content-length/-/middleware-content-length-3.292.0.tgz", - "integrity": "sha512-2gMWzQus5mj14menolpPDbYBeaOYcj7KNFZOjTjjI3iQ0KqyetG6XasirNrcJ/8QX1BRmpTol8Xjp2Ue3Gbzwg==", - "optional": true, - "dependencies": { - "@aws-sdk/protocol-http": "3.292.0", - "@aws-sdk/types": "3.292.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/middleware-content-length/node_modules/tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - }, - "node_modules/@aws-sdk/middleware-endpoint": { - "version": "3.292.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-endpoint/-/middleware-endpoint-3.292.0.tgz", - "integrity": "sha512-cPMkiSxpZGG6tYlW4OS+ucS6r43f9ddX9kcUoemJCY10MOuogdPjulCAjE0HTs2PLKSOrrG4CTP4Q4wWDrH4Bw==", - "optional": true, - "dependencies": { - "@aws-sdk/middleware-serde": "3.292.0", - "@aws-sdk/protocol-http": "3.292.0", - "@aws-sdk/signature-v4": "3.292.0", - "@aws-sdk/types": "3.292.0", - "@aws-sdk/url-parser": "3.292.0", - "@aws-sdk/util-config-provider": "3.292.0", - "@aws-sdk/util-middleware": "3.292.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/middleware-endpoint/node_modules/tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - }, - "node_modules/@aws-sdk/middleware-host-header": { - "version": "3.292.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.292.0.tgz", - "integrity": "sha512-mHuCWe3Yg2S5YZ7mB7sKU6C97XspfqrimWjMW9pfV2usAvLA3R0HrB03jpR5vpZ3P4q7HB6wK3S6CjYMGGRNag==", - "optional": true, - "dependencies": { - "@aws-sdk/protocol-http": "3.292.0", - "@aws-sdk/types": "3.292.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/middleware-host-header/node_modules/tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - }, - "node_modules/@aws-sdk/middleware-logger": { - "version": "3.292.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.292.0.tgz", - "integrity": "sha512-yZNY1XYmG3NG+uonET7jzKXNiwu61xm/ZZ6i/l51SusuaYN+qQtTAhOFsieQqTehF9kP4FzbsWgPDwD8ZZX9lw==", - "optional": true, - "dependencies": { - "@aws-sdk/types": "3.292.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/middleware-logger/node_modules/tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - }, - "node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.292.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.292.0.tgz", - "integrity": "sha512-kA3VZpPko0Zqd7CYPTKAxhjEv0HJqFu2054L04dde1JLr43ro+2MTdX7vsHzeAFUVRphqatFFofCumvXmU6Mig==", - "optional": true, - "dependencies": { - "@aws-sdk/protocol-http": "3.292.0", - "@aws-sdk/types": "3.292.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/middleware-recursion-detection/node_modules/tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - }, - "node_modules/@aws-sdk/middleware-retry": { - "version": "3.293.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-retry/-/middleware-retry-3.293.0.tgz", - "integrity": "sha512-7tiaz2GzRecNHaZ6YnF+Nrtk3au8qF6oiipf11R7MJiqJ0fkMLnz/iRrlakDziS9qF/a9v+3yxb4W4NHK3f4Tw==", - "optional": true, - "dependencies": { - "@aws-sdk/protocol-http": "3.292.0", - "@aws-sdk/service-error-classification": "3.292.0", - "@aws-sdk/types": "3.292.0", - "@aws-sdk/util-middleware": "3.292.0", - "@aws-sdk/util-retry": "3.292.0", - "tslib": "^2.3.1", - "uuid": "^8.3.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/middleware-retry/node_modules/tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - }, - "node_modules/@aws-sdk/middleware-retry/node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "optional": true, - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/@aws-sdk/middleware-sdk-sts": { - "version": "3.292.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-sts/-/middleware-sdk-sts-3.292.0.tgz", - "integrity": "sha512-GN5ZHEqXZqDi+HkVbaXRX9HaW/vA5rikYpWKYsmxTUZ7fB7ijvEO3co3lleJv2C+iGYRtUIHC4wYNB5xgoTCxg==", - "optional": true, - "dependencies": { - "@aws-sdk/middleware-signing": "3.292.0", - "@aws-sdk/property-provider": "3.292.0", - "@aws-sdk/protocol-http": "3.292.0", - "@aws-sdk/signature-v4": "3.292.0", - "@aws-sdk/types": "3.292.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/middleware-sdk-sts/node_modules/tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - }, - "node_modules/@aws-sdk/middleware-serde": { - "version": "3.292.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-serde/-/middleware-serde-3.292.0.tgz", - "integrity": "sha512-6hN9mTQwSvV8EcGvtXbS/MpK7WMCokUku5Wu7X24UwCNMVkoRHLIkYcxHcvBTwttuOU0d8hph1/lIX4dkLwkQw==", - "optional": true, - "dependencies": { - "@aws-sdk/types": "3.292.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/middleware-serde/node_modules/tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - }, - "node_modules/@aws-sdk/middleware-signing": { - "version": "3.292.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-signing/-/middleware-signing-3.292.0.tgz", - "integrity": "sha512-GVfoSjDjEQ4TaO6x9MffyP3uRV+2KcS5FtexLCYOM9pJcnE9tqq9FJOrZ1xl1g+YjUVKxo4x8lu3tpEtIb17qg==", - "optional": true, - "dependencies": { - "@aws-sdk/property-provider": "3.292.0", - "@aws-sdk/protocol-http": "3.292.0", - "@aws-sdk/signature-v4": "3.292.0", - "@aws-sdk/types": "3.292.0", - "@aws-sdk/util-middleware": "3.292.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/middleware-signing/node_modules/tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - }, - "node_modules/@aws-sdk/middleware-stack": { - "version": "3.292.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-stack/-/middleware-stack-3.292.0.tgz", - "integrity": "sha512-WdQpRkuMysrEwrkByCM1qCn2PPpFGGQ2iXqaFha5RzCdZDlxJni9cVNb6HzWUcgjLEYVTXCmOR9Wxm3CNW44Qg==", - "optional": true, - "dependencies": { - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/middleware-stack/node_modules/tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - }, - "node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.293.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.293.0.tgz", - "integrity": "sha512-gZ7/e6XwpKk9mvgA78q4Ffc796jTn02TUKx2qMDnkLVbeJXBNN2jnvYEKq8v70+o7fd/ALRudg8gBDmkkhM/Hw==", - "optional": true, - "dependencies": { - "@aws-sdk/protocol-http": "3.292.0", - "@aws-sdk/types": "3.292.0", - "@aws-sdk/util-endpoints": "3.293.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/middleware-user-agent/node_modules/tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - }, - "node_modules/@aws-sdk/node-config-provider": { - "version": "3.292.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/node-config-provider/-/node-config-provider-3.292.0.tgz", - "integrity": "sha512-S3NnC9dQ5GIbJYSDIldZb4zdpCOEua1tM7bjYL3VS5uqCEM93kIi/o/UkIUveMp/eqTS2LJa5HjNIz5Te6je0A==", - "optional": true, - "dependencies": { - "@aws-sdk/property-provider": "3.292.0", - "@aws-sdk/shared-ini-file-loader": "3.292.0", - "@aws-sdk/types": "3.292.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/node-config-provider/node_modules/tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - }, - "node_modules/@aws-sdk/node-http-handler": { - "version": "3.292.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/node-http-handler/-/node-http-handler-3.292.0.tgz", - "integrity": "sha512-L/E3UDSwXLXjt1XWWh0RBD55F+aZI1AEdPwdES9i1PjnZLyuxuDhEDptVibNN56+I9/4Q3SbmuVRVlOD0uzBag==", - "optional": true, - "dependencies": { - "@aws-sdk/abort-controller": "3.292.0", - "@aws-sdk/protocol-http": "3.292.0", - "@aws-sdk/querystring-builder": "3.292.0", - "@aws-sdk/types": "3.292.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/node-http-handler/node_modules/tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - }, - "node_modules/@aws-sdk/property-provider": { - "version": "3.292.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/property-provider/-/property-provider-3.292.0.tgz", - "integrity": "sha512-dHArSvsiqhno/g55N815gXmAMrmN8DP7OeFNqJ4wJG42xsF2PFN3DAsjIuHuXMwu+7A3R1LHqIpvv0hA9KeoJQ==", - "optional": true, - "dependencies": { - "@aws-sdk/types": "3.292.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/property-provider/node_modules/tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - }, - "node_modules/@aws-sdk/protocol-http": { - "version": "3.292.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/protocol-http/-/protocol-http-3.292.0.tgz", - "integrity": "sha512-NLi4fq3k41aXIh1I97yX0JTy+3p6aW1NdwFwdMa674z86QNfb4SfRQRZBQe9wEnAZ/eWHVnlKIuII+U1URk/Kg==", - "optional": true, - "dependencies": { - "@aws-sdk/types": "3.292.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/protocol-http/node_modules/tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - }, - "node_modules/@aws-sdk/querystring-builder": { - "version": "3.292.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/querystring-builder/-/querystring-builder-3.292.0.tgz", - "integrity": "sha512-XElIFJaReIm24eEvBtV2dOtZvcm3gXsGu/ftG8MLJKbKXFKpAP1q+K6En0Bs7/T88voKghKdKpKT+eZUWgTqlg==", - "optional": true, - "dependencies": { - "@aws-sdk/types": "3.292.0", - "@aws-sdk/util-uri-escape": "3.292.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/querystring-builder/node_modules/tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - }, - "node_modules/@aws-sdk/querystring-parser": { - "version": "3.292.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/querystring-parser/-/querystring-parser-3.292.0.tgz", - "integrity": "sha512-iTYpYo7a8X9RxiPbjjewIpm6XQPx2EOcF1dWCPRII9EFlmZ4bwnX+PDI36fIo9oVs8TIKXmwNGODU9nsg7CSAw==", - "optional": true, - "dependencies": { - "@aws-sdk/types": "3.292.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/querystring-parser/node_modules/tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - }, - "node_modules/@aws-sdk/service-error-classification": { - "version": "3.292.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/service-error-classification/-/service-error-classification-3.292.0.tgz", - "integrity": "sha512-X1k3sixCeC45XSNHBe+kRBQBwPDyTFtFITb8O5Qw4dS9XWGhrUJT4CX0qE5aj8qP3F9U5nRizs9c2mBVVP0Caw==", - "optional": true, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/shared-ini-file-loader": { - "version": "3.292.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/shared-ini-file-loader/-/shared-ini-file-loader-3.292.0.tgz", - "integrity": "sha512-Av2TTYg1Jig2kbkD56ybiqZJB6vVrYjv1W5UQwY/q3nA/T2mcrgQ20ByCOt5Bv9VvY7FSgC+znj+L4a7RLGmBg==", - "optional": true, - "dependencies": { - "@aws-sdk/types": "3.292.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/shared-ini-file-loader/node_modules/tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - }, - "node_modules/@aws-sdk/signature-v4": { - "version": "3.292.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4/-/signature-v4-3.292.0.tgz", - "integrity": "sha512-+rw47VY5mvBecn13tDQTl1ipGWg5tE63faWgmZe68HoBL87ZiDzsd7bUKOvjfW21iMgWlwAppkaNNQayYRb2zg==", - "optional": true, - "dependencies": { - "@aws-sdk/is-array-buffer": "3.292.0", - "@aws-sdk/types": "3.292.0", - "@aws-sdk/util-hex-encoding": "3.292.0", - "@aws-sdk/util-middleware": "3.292.0", - "@aws-sdk/util-uri-escape": "3.292.0", - "@aws-sdk/util-utf8": "3.292.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/signature-v4/node_modules/tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - }, - "node_modules/@aws-sdk/smithy-client": { - "version": "3.292.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/smithy-client/-/smithy-client-3.292.0.tgz", - "integrity": "sha512-S8PKzjPkZ6SXYZuZiU787dMsvQ0d/LFEhw2OI4Oe2An9Fc2IwJ2FYukyHoQJOV2tV0DiuMebPo7eMyQyjKElvA==", - "optional": true, - "dependencies": { - "@aws-sdk/middleware-stack": "3.292.0", - "@aws-sdk/types": "3.292.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/smithy-client/node_modules/tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - }, - "node_modules/@aws-sdk/token-providers": { - "version": "3.294.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.294.0.tgz", - "integrity": "sha512-6nwO04LtC5f4AsUvGZXyjaswuEK4Rr2VsuANpMKrPCgunRfI58a8YXLniudOSXN6e7CFJ6M3uo/h5YXqtnzGug==", - "optional": true, - "dependencies": { - "@aws-sdk/client-sso-oidc": "3.294.0", - "@aws-sdk/property-provider": "3.292.0", - "@aws-sdk/shared-ini-file-loader": "3.292.0", - "@aws-sdk/types": "3.292.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/token-providers/node_modules/tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - }, - "node_modules/@aws-sdk/types": { - "version": "3.292.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.292.0.tgz", - "integrity": "sha512-1teYAY2M73UXZxMAxqZxVS2qwXjQh0OWtt7qyLfha0TtIk/fZ1hRwFgxbDCHUFcdNBSOSbKH/ESor90KROXLCQ==", - "optional": true, - "dependencies": { - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/types/node_modules/tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - }, - "node_modules/@aws-sdk/url-parser": { - "version": "3.292.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/url-parser/-/url-parser-3.292.0.tgz", - "integrity": "sha512-NZeAuZCk1x6TIiWuRfbOU6wHPBhf0ly2qOHzWut4BCH+b4RrDmFF8EmXcH1auEfGhE7yRyR6XqIN0t3S+hYACA==", - "optional": true, - "dependencies": { - "@aws-sdk/querystring-parser": "3.292.0", - "@aws-sdk/types": "3.292.0", - "tslib": "^2.3.1" - } - }, - "node_modules/@aws-sdk/url-parser/node_modules/tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - }, - "node_modules/@aws-sdk/util-base64": { - "version": "3.292.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-base64/-/util-base64-3.292.0.tgz", - "integrity": "sha512-zjNCwNdy617yFvEjZorepNWXB2sQCVfsShCwFy/kIQ5iW5tT2jQKaqc0K77diU9atkooxw9p1W9m9sOgrkOFNw==", - "optional": true, - "dependencies": { - "@aws-sdk/util-buffer-from": "3.292.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/util-base64/node_modules/tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - }, - "node_modules/@aws-sdk/util-body-length-browser": { - "version": "3.292.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-body-length-browser/-/util-body-length-browser-3.292.0.tgz", - "integrity": "sha512-Wd/BM+JsMiKvKs/bN3z6TredVEHh2pKudGfg3CSjTRpqFpOG903KDfyHBD42yg5PuCHoHoewJvTPKwgn7/vhaw==", - "optional": true, - "dependencies": { - "tslib": "^2.3.1" - } - }, - "node_modules/@aws-sdk/util-body-length-browser/node_modules/tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - }, - "node_modules/@aws-sdk/util-body-length-node": { - "version": "3.292.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-body-length-node/-/util-body-length-node-3.292.0.tgz", - "integrity": "sha512-BBgipZ2P6RhogWE/qj0oqpdlyd3iSBYmb+aD/TBXwB2lA/X8A99GxweBd/kp06AmcJRoMS9WIXgbWkiiBlRlSA==", - "optional": true, - "dependencies": { - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/util-body-length-node/node_modules/tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - }, - "node_modules/@aws-sdk/util-buffer-from": { - "version": "3.292.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-buffer-from/-/util-buffer-from-3.292.0.tgz", - "integrity": "sha512-RxNZjLoXNxHconH9TYsk5RaEBjSgTtozHeyIdacaHPj5vlQKi4hgL2hIfKeeNiAfQEVjaUFF29lv81xpNMzVMQ==", - "optional": true, - "dependencies": { - "@aws-sdk/is-array-buffer": "3.292.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/util-buffer-from/node_modules/tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - }, - "node_modules/@aws-sdk/util-config-provider": { - "version": "3.292.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-config-provider/-/util-config-provider-3.292.0.tgz", - "integrity": "sha512-t3noYll6bPRSxeeNNEkC5czVjAiTPcsq00OwfJ2xyUqmquhLEfLwoJKmrT1uP7DjIEXdUtfoIQ2jWiIVm/oO5A==", - "optional": true, - "dependencies": { - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/util-config-provider/node_modules/tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - }, - "node_modules/@aws-sdk/util-defaults-mode-browser": { - "version": "3.292.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-defaults-mode-browser/-/util-defaults-mode-browser-3.292.0.tgz", - "integrity": "sha512-7+zVUlMGfa8/KT++9humHo6IDxTnxMCmWUj5jVNlkpk6h7Ecmppf7aXotviyVIA43lhtz0p2AErs0N0ekEUK+w==", - "optional": true, - "dependencies": { - "@aws-sdk/property-provider": "3.292.0", - "@aws-sdk/types": "3.292.0", - "bowser": "^2.11.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/@aws-sdk/util-defaults-mode-browser/node_modules/tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - }, - "node_modules/@aws-sdk/util-defaults-mode-node": { - "version": "3.292.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-defaults-mode-node/-/util-defaults-mode-node-3.292.0.tgz", - "integrity": "sha512-SSIw85eF4BVs0fOJRyshT+R3b/UmBPhiVKCUZm2rq6+lIGkDPiSwQU3d/80AhXtiL5SFT/IzAKKgQd8qMa7q3A==", - "optional": true, - "dependencies": { - "@aws-sdk/config-resolver": "3.292.0", - "@aws-sdk/credential-provider-imds": "3.292.0", - "@aws-sdk/node-config-provider": "3.292.0", - "@aws-sdk/property-provider": "3.292.0", - "@aws-sdk/types": "3.292.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/@aws-sdk/util-defaults-mode-node/node_modules/tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - }, - "node_modules/@aws-sdk/util-endpoints": { - "version": "3.293.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.293.0.tgz", - "integrity": "sha512-R/99aNV49Refpv5guiUjEUrZYlvnfaNBniB+/ZtMO3ixxUopapssCrUivuJrmhccmrYaTCZw7dRzIWjU1jJhKg==", - "optional": true, - "dependencies": { - "@aws-sdk/types": "3.292.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/util-endpoints/node_modules/tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - }, - "node_modules/@aws-sdk/util-hex-encoding": { - "version": "3.292.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-hex-encoding/-/util-hex-encoding-3.292.0.tgz", - "integrity": "sha512-qBd5KFIUywQ3qSSbj814S2srk0vfv8A6QMI+Obs1y2LHZFdQN5zViptI4UhXhKOHe+NnrHWxSuLC/LMH6q3SmA==", - "optional": true, - "dependencies": { - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/util-hex-encoding/node_modules/tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - }, - "node_modules/@aws-sdk/util-locate-window": { - "version": "3.292.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.292.0.tgz", - "integrity": "sha512-6xnFJXZI9pKw5lQCDvuWA5PnOaUtNRKWwdxvGkkLx5orboFaoVMS6zowjSQxwVNRjW82u6dYNkhmj9mZ8VSjWg==", - "optional": true, - "dependencies": { - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/util-locate-window/node_modules/tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - }, - "node_modules/@aws-sdk/util-middleware": { - "version": "3.292.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-middleware/-/util-middleware-3.292.0.tgz", - "integrity": "sha512-KjhS7flfoBKDxbiBZjLjMvEizXgjfQb7GQEItgzGoI9rfGCmZtvqCcqQQoIlxb8bIzGRggAUHtBGWnlLbpb+GQ==", - "optional": true, - "dependencies": { - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/util-middleware/node_modules/tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - }, - "node_modules/@aws-sdk/util-retry": { - "version": "3.292.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-retry/-/util-retry-3.292.0.tgz", - "integrity": "sha512-JEHyF7MpVeRF5uR4LDYgpOKcFpOPiAj8TqN46SVOQQcL1K+V7cSr7O7N7J6MwJaN9XOzAcBadeIupMm7/BFbgw==", - "optional": true, - "dependencies": { - "@aws-sdk/service-error-classification": "3.292.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@aws-sdk/util-retry/node_modules/tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - }, - "node_modules/@aws-sdk/util-uri-escape": { - "version": "3.292.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-uri-escape/-/util-uri-escape-3.292.0.tgz", - "integrity": "sha512-hOQtUMQ4VcQ9iwKz50AoCp1XBD5gJ9nly/gJZccAM7zSA5mOO8RRKkbdonqquVHxrO0CnYgiFeCh3V35GFecUw==", - "optional": true, - "dependencies": { - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/util-uri-escape/node_modules/tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - }, - "node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.292.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.292.0.tgz", - "integrity": "sha512-dld+lpC3QdmTQHdBWJ0WFDkXDSrJgfz03q6mQ8+7H+BC12ZhT0I0g9iuvUjolqy7QR00OxOy47Y9FVhq8EC0Gg==", - "optional": true, - "dependencies": { - "@aws-sdk/types": "3.292.0", - "bowser": "^2.11.0", - "tslib": "^2.3.1" - } - }, - "node_modules/@aws-sdk/util-user-agent-browser/node_modules/tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - }, - "node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.292.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.292.0.tgz", - "integrity": "sha512-f+NfIMal5E61MDc5WGhUEoicr7b1eNNhA+GgVdSB/Hg5fYhEZvFK9RZizH5rrtsLjjgcr9nPYSR7/nDKCJLumw==", - "optional": true, - "dependencies": { - "@aws-sdk/node-config-provider": "3.292.0", - "@aws-sdk/types": "3.292.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "aws-crt": ">=1.0.0" - }, - "peerDependenciesMeta": { - "aws-crt": { - "optional": true - } - } - }, - "node_modules/@aws-sdk/util-user-agent-node/node_modules/tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - }, - "node_modules/@aws-sdk/util-utf8": { - "version": "3.292.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-utf8/-/util-utf8-3.292.0.tgz", - "integrity": "sha512-FPkj+Z59/DQWvoVu2wFaRncc3KVwe/pgK3MfVb0Lx+Ibey5KUx+sNpJmYcVYHUAe/Nv/JeIpOtYuC96IXOnI6w==", - "optional": true, - "dependencies": { - "@aws-sdk/util-buffer-from": "3.292.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/util-utf8-browser": { - "version": "3.259.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-utf8-browser/-/util-utf8-browser-3.259.0.tgz", - "integrity": "sha512-UvFa/vR+e19XookZF8RzFZBrw2EUkQWxiBW0yYQAhvk3C+QVGl0H3ouca8LDBlBfQKXwmW3huo/59H8rwb1wJw==", - "optional": true, - "dependencies": { - "tslib": "^2.3.1" - } - }, - "node_modules/@aws-sdk/util-utf8-browser/node_modules/tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - }, - "node_modules/@aws-sdk/util-utf8/node_modules/tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - }, "node_modules/@colors/colors": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", @@ -1470,15 +72,39 @@ "node": ">=0.1.90" } }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.5.0.tgz", + "integrity": "sha512-vITaYzIcNmjn5tF5uxcZ/ft7/RXGrMUIS9HalWckEOF6ESiwXKoMzAQf2UW0aVd6rnOeExTJVd5hmWXucBKGXQ==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, "node_modules/@eslint/eslintrc": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.0.tgz", - "integrity": "sha512-fluIaaV+GyV24CCu/ggiHdV+j4RNh85yQnAYS/G2mZODZgGmmlrgCydjUcV3YvxCm9x8nMAfThsqTni4KiXT4A==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.2.tgz", + "integrity": "sha512-3W4f5tDUra+pA+FzgugqL2pRimUTDJWKr7BINqOpkZrC0uYI0NIc0/JFgBROCU07HR6GieA5m3/rsPIhDmCXTQ==", "dev": true, "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", - "espree": "^9.4.0", + "espree": "^9.5.1", "globals": "^13.19.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", @@ -1493,38 +119,45 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/@eslint/eslintrc/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@eslint/eslintrc/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, "node_modules/@eslint/js": { - "version": "8.35.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.35.0.tgz", - "integrity": "sha512-JXdzbRiWclLVoD8sNUjR443VVlYqiYmDVT6rGUEIEHU5YJW0gaVZwV2xgM7D4arkvASqD0IlLUVjHiFuxaftRw==", + "version": "8.39.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.39.0.tgz", + "integrity": "sha512-kf9RB0Fg7NZfap83B3QOqOGg9QmD9yBudqQXzzOtn3i4y7ZUXe5ONeW34Gwi+TxhH4mvj72R1Zc300KUMa9Bng==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@grpc/grpc-js": { + "version": "1.8.14", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.8.14.tgz", + "integrity": "sha512-w84maJ6CKl5aApCMzFll0hxtFNT6or9WwMslobKaqWUEf1K+zhlL43bSQhFreyYWIWR+Z0xnVFC1KtLm4ZpM/A==", + "dependencies": { + "@grpc/proto-loader": "^0.7.0", + "@types/node": ">=12.12.47" + }, + "engines": { + "node": "^8.13.0 || >=10.10.0" + } + }, + "node_modules/@grpc/proto-loader": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.6.tgz", + "integrity": "sha512-QyAXR8Hyh7uMDmveWxDSUcJr9NAWaZ2I6IXgAYvQmfflwouTM+rArE2eEaCtLlRqO81j7pRLCt81IefUei6Zbw==", + "dependencies": { + "@types/long": "^4.0.1", + "lodash.camelcase": "^4.3.0", + "long": "^4.0.0", + "protobufjs": "^7.0.0", + "yargs": "^16.2.0" + }, + "bin": { + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/@hapi/hoek": { "version": "9.3.0", "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", @@ -1558,29 +191,6 @@ "node": ">=10.10.0" } }, - "node_modules/@humanwhocodes/config-array/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@humanwhocodes/config-array/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, "node_modules/@humanwhocodes/module-importer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", @@ -1770,6 +380,60 @@ "node": ">=6" } }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" + }, "node_modules/@redis/bloom": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-1.2.0.tgz", @@ -1779,9 +443,9 @@ } }, "node_modules/@redis/client": { - "version": "1.5.6", - "resolved": "https://registry.npmjs.org/@redis/client/-/client-1.5.6.tgz", - "integrity": "sha512-dFD1S6je+A47Lj22jN/upVU2fj4huR7S9APd7/ziUXsIXDL+11GPYti4Suv5y8FuXaN+0ZG4JF+y1houEJ7ToA==", + "version": "1.5.7", + "resolved": "https://registry.npmjs.org/@redis/client/-/client-1.5.7.tgz", + "integrity": "sha512-gaOBOuJPjK5fGtxSseaKgSvjiZXQCdLlGg9WYQst+/GRUjmXaiB5kVkeQMRtPc7Q2t93XZcJfBMSwzs/XS9UZw==", "dependencies": { "cluster-key-slot": "1.1.2", "generic-pool": "3.9.0", @@ -1933,14 +597,15 @@ } }, "node_modules/@types/express-serve-static-core": { - "version": "4.17.33", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.33.tgz", - "integrity": "sha512-TPBqmR/HRYI3eC2E5hmiivIzv+bidAfXofM+sbonAGvyDhySGw9/PQZFt2BLOrjUUR++4eJVpx6KnLQK1Fk9tA==", + "version": "4.17.34", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.34.tgz", + "integrity": "sha512-fvr49XlCGoUj2Pp730AItckfjat4WNb0lb3kfrLWffd+RLeoGAMsq7UOy04PAPtoL01uKwcp6u8nhzpgpDYr3w==", "dev": true, "dependencies": { "@types/node": "*", "@types/qs": "*", - "@types/range-parser": "*" + "@types/range-parser": "*", + "@types/send": "*" } }, "node_modules/@types/fs-extra": { @@ -1981,136 +646,15 @@ "@types/node": "*" } }, + "node_modules/@types/long": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", + "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==" + }, "node_modules/@types/mime": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-3.0.1.tgz", - "integrity": "sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==", - "dev": true - }, - "node_modules/@types/mongoose-unique-validator": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/@types/mongoose-unique-validator/-/mongoose-unique-validator-1.0.7.tgz", - "integrity": "sha512-wgPWN7x9mHdy8x1jcluDF7EDWFb/s8LD7skLr012C8i2Q3tUhdTlp3JxRhFbEM3TD7J1INaoTrWaSaguhuqilQ==", - "dev": true, - "dependencies": { - "mongoose": "^6.3.0" - } - }, - "node_modules/@types/mongoose-unique-validator/node_modules/bson": { - "version": "4.7.2", - "resolved": "https://registry.npmjs.org/bson/-/bson-4.7.2.tgz", - "integrity": "sha512-Ry9wCtIZ5kGqkJoi6aD8KjxFZEx78guTQDnpXWiNthsxzrxAK/i8E6pCHAIZTbaEFWcOCvbecMukfK7XUvyLpQ==", - "dev": true, - "dependencies": { - "buffer": "^5.6.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@types/mongoose-unique-validator/node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, - "node_modules/@types/mongoose-unique-validator/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@types/mongoose-unique-validator/node_modules/debug/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/@types/mongoose-unique-validator/node_modules/mongodb": { - "version": "4.14.0", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.14.0.tgz", - "integrity": "sha512-coGKkWXIBczZPr284tYKFLg+KbGPPLlSbdgfKAb6QqCFt5bo5VFZ50O3FFzsw4rnkqjwT6D8Qcoo9nshYKM7Mg==", - "dev": true, - "dependencies": { - "bson": "^4.7.0", - "mongodb-connection-string-url": "^2.5.4", - "socks": "^2.7.1" - }, - "engines": { - "node": ">=12.9.0" - }, - "optionalDependencies": { - "@aws-sdk/credential-providers": "^3.186.0", - "saslprep": "^1.0.3" - } - }, - "node_modules/@types/mongoose-unique-validator/node_modules/mongoose": { - "version": "6.10.5", - "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-6.10.5.tgz", - "integrity": "sha512-y4HL4/9EySec7L0gJ+pCm9heLSF45uIIvRS4fSeAFWDfe4vXW1vRZJwTz7OGkra3ZoSfRnFTo9bNZkuggDVlVA==", - "dev": true, - "dependencies": { - "bson": "^4.7.0", - "kareem": "2.5.1", - "mongodb": "4.14.0", - "mpath": "0.9.0", - "mquery": "4.0.3", - "ms": "2.1.3", - "sift": "16.0.1" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mongoose" - } - }, - "node_modules/@types/mongoose-unique-validator/node_modules/mquery": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/mquery/-/mquery-4.0.3.tgz", - "integrity": "sha512-J5heI+P08I6VJ2Ky3+33IpCdAvlYGTSUjwTPxkAr8i8EoduPMBX2OY/wa3IKZIQl7MU4SbFk8ndgSKyB/cl1zA==", - "dev": true, - "dependencies": { - "debug": "4.x" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/@types/mongoose-unique-validator/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", + "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==", "dev": true }, "node_modules/@types/morgan": { @@ -2129,9 +673,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "18.14.6", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.14.6.tgz", - "integrity": "sha512-93+VvleD3mXwlLI/xASjw0FzKcwzl3OdTCzm1LaRfqgS21gfFtK3zDXM5Op9TeeMsJVOaJ2VRDpT9q4Y3d0AvA==" + "version": "18.16.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.16.2.tgz", + "integrity": "sha512-GQW/JL/5Fz/0I8RpeBG9lKp0+aNcXEaVL71c0D2Q0QHDTFvlYKT7an0onCUXj85anv7b4/WesqdfchLc0jtsCg==" }, "node_modules/@types/node-rsa": { "version": "1.1.1", @@ -2177,6 +721,16 @@ "integrity": "sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==", "dev": true }, + "node_modules/@types/send": { + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.1.tgz", + "integrity": "sha512-Cwo8LE/0rnvX7kIIa3QHCkcuF21c05Ayb0ZfxPiv0W8VRiZiNW/WuRupHKpqqGVGf7SUA44QSOUKaEd9lIrd/Q==", + "dev": true, + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, "node_modules/@types/serve-static": { "version": "1.15.1", "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.1.tgz", @@ -2188,15 +742,9 @@ } }, "node_modules/@types/validator": { - "version": "13.7.14", - "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.7.14.tgz", - "integrity": "sha512-J6OAed6rhN6zyqL9Of6ZMamhlsOEU/poBVvbHr/dKOYKTeuYYMlDkMv+b6UUV0o2i0tw73cgyv/97WTWaUl0/g==", - "dev": true - }, - "node_modules/@types/w3c-css-typed-object-model-level-1": { - "version": "20180410.0.5", - "resolved": "https://registry.npmjs.org/@types/w3c-css-typed-object-model-level-1/-/w3c-css-typed-object-model-level-1-20180410.0.5.tgz", - "integrity": "sha512-khoqDQqA/fUdK6mEJtQEfMAPCKkk8E1D+ahM/0HCy3mc5L33wJinqQNXu9Uf4+mqzMvORljHg76jvrmwS0bEjA==", + "version": "13.7.15", + "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.7.15.tgz", + "integrity": "sha512-yeinDVQunb03AEP8luErFcyf/7Lf7AzKCD0NXfgVoGCCQDNpZET8Jgq74oBgqKld3hafLbfzt/3inUdQvaFeXQ==", "dev": true }, "node_modules/@types/webidl-conversions": { @@ -2214,19 +762,19 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "5.54.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.54.1.tgz", - "integrity": "sha512-a2RQAkosH3d3ZIV08s3DcL/mcGc2M/UC528VkPULFxR9VnVPT8pBu0IyBAJJmVsCmhVfwQX1v6q+QGnmSe1bew==", + "version": "5.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.59.1.tgz", + "integrity": "sha512-AVi0uazY5quFB9hlp2Xv+ogpfpk77xzsgsIEWyVS7uK/c7MZ5tw7ZPbapa0SbfkqE0fsAMkz5UwtgMLVk2BQAg==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "5.54.1", - "@typescript-eslint/type-utils": "5.54.1", - "@typescript-eslint/utils": "5.54.1", + "@eslint-community/regexpp": "^4.4.0", + "@typescript-eslint/scope-manager": "5.59.1", + "@typescript-eslint/type-utils": "5.59.1", + "@typescript-eslint/utils": "5.59.1", "debug": "^4.3.4", "grapheme-splitter": "^1.0.4", "ignore": "^5.2.0", "natural-compare-lite": "^1.4.0", - "regexpp": "^3.2.0", "semver": "^7.3.7", "tsutils": "^3.21.0" }, @@ -2247,38 +795,15 @@ } } }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, "node_modules/@typescript-eslint/parser": { - "version": "5.54.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.54.1.tgz", - "integrity": "sha512-8zaIXJp/nG9Ff9vQNh7TI+C3nA6q6iIsGJ4B4L6MhZ7mHnTMR4YP5vp2xydmFXIy8rpyIVbNAG44871LMt6ujg==", + "version": "5.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.59.1.tgz", + "integrity": "sha512-nzjFAN8WEu6yPRDizIFyzAfgK7nybPodMNFGNH0M9tei2gYnYszRDqVA0xlnRjkl7Hkx2vYrEdb6fP2a21cG1g==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "5.54.1", - "@typescript-eslint/types": "5.54.1", - "@typescript-eslint/typescript-estree": "5.54.1", + "@typescript-eslint/scope-manager": "5.59.1", + "@typescript-eslint/types": "5.59.1", + "@typescript-eslint/typescript-estree": "5.59.1", "debug": "^4.3.4" }, "engines": { @@ -2297,37 +822,14 @@ } } }, - "node_modules/@typescript-eslint/parser/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/parser/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, "node_modules/@typescript-eslint/scope-manager": { - "version": "5.54.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.54.1.tgz", - "integrity": "sha512-zWKuGliXxvuxyM71UA/EcPxaviw39dB2504LqAmFDjmkpO8qNLHcmzlh6pbHs1h/7YQ9bnsO8CCcYCSA8sykUg==", + "version": "5.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.59.1.tgz", + "integrity": "sha512-mau0waO5frJctPuAzcxiNWqJR5Z8V0190FTSqRw1Q4Euop6+zTwHAf8YIXNwDOT29tyUDrQ65jSg9aTU/H0omA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.54.1", - "@typescript-eslint/visitor-keys": "5.54.1" + "@typescript-eslint/types": "5.59.1", + "@typescript-eslint/visitor-keys": "5.59.1" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -2338,13 +840,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "5.54.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.54.1.tgz", - "integrity": "sha512-WREHsTz0GqVYLIbzIZYbmUUr95DKEKIXZNH57W3s+4bVnuF1TKe2jH8ZNH8rO1CeMY3U4j4UQeqPNkHMiGem3g==", + "version": "5.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.59.1.tgz", + "integrity": "sha512-ZMWQ+Oh82jWqWzvM3xU+9y5U7MEMVv6GLioM3R5NJk6uvP47kZ7YvlgSHJ7ERD6bOY7Q4uxWm25c76HKEwIjZw==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "5.54.1", - "@typescript-eslint/utils": "5.54.1", + "@typescript-eslint/typescript-estree": "5.59.1", + "@typescript-eslint/utils": "5.59.1", "debug": "^4.3.4", "tsutils": "^3.21.0" }, @@ -2364,33 +866,10 @@ } } }, - "node_modules/@typescript-eslint/type-utils/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/type-utils/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, "node_modules/@typescript-eslint/types": { - "version": "5.54.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.54.1.tgz", - "integrity": "sha512-G9+1vVazrfAfbtmCapJX8jRo2E4MDXxgm/IMOF4oGh3kq7XuK3JRkOg6y2Qu1VsTRmWETyTkWt1wxy7X7/yLkw==", + "version": "5.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.59.1.tgz", + "integrity": "sha512-dg0ICB+RZwHlysIy/Dh1SP+gnXNzwd/KS0JprD3Lmgmdq+dJAJnUPe1gNG34p0U19HvRlGX733d/KqscrGC1Pg==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -2401,13 +880,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "5.54.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.54.1.tgz", - "integrity": "sha512-bjK5t+S6ffHnVwA0qRPTZrxKSaFYocwFIkZx5k7pvWfsB1I57pO/0M0Skatzzw1sCkjJ83AfGTL0oFIFiDX3bg==", + "version": "5.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.1.tgz", + "integrity": "sha512-lYLBBOCsFltFy7XVqzX0Ju+Lh3WPIAWxYpmH/Q7ZoqzbscLiCW00LeYCdsUnnfnj29/s1WovXKh2gwCoinHNGA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.54.1", - "@typescript-eslint/visitor-keys": "5.54.1", + "@typescript-eslint/types": "5.59.1", + "@typescript-eslint/visitor-keys": "5.59.1", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -2427,42 +906,19 @@ } } }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, "node_modules/@typescript-eslint/utils": { - "version": "5.54.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.54.1.tgz", - "integrity": "sha512-IY5dyQM8XD1zfDe5X8jegX6r2EVU5o/WJnLu/znLPWCBF7KNGC+adacXnt5jEYS9JixDcoccI6CvE4RCjHMzCQ==", + "version": "5.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.59.1.tgz", + "integrity": "sha512-MkTe7FE+K1/GxZkP5gRj3rCztg45bEhsd8HYjczBuYm+qFHP5vtZmjx3B0yUCDotceQ4sHgTyz60Ycl225njmA==", "dev": true, "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", "@types/json-schema": "^7.0.9", "@types/semver": "^7.3.12", - "@typescript-eslint/scope-manager": "5.54.1", - "@typescript-eslint/types": "5.54.1", - "@typescript-eslint/typescript-estree": "5.54.1", + "@typescript-eslint/scope-manager": "5.59.1", + "@typescript-eslint/types": "5.59.1", + "@typescript-eslint/typescript-estree": "5.59.1", "eslint-scope": "^5.1.1", - "eslint-utils": "^3.0.0", "semver": "^7.3.7" }, "engines": { @@ -2476,26 +932,13 @@ "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, - "node_modules/@typescript-eslint/utils/node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "5.54.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.54.1.tgz", - "integrity": "sha512-q8iSoHTgwCfgcRJ2l2x+xCbu8nBlRAlsQ33k24Adj8eoVBE0f8dUeI+bAa8F84Mv05UGbAx57g2zrRsYIooqQg==", + "version": "5.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.1.tgz", + "integrity": "sha512-6waEYwBTCWryx0VJmP7JaM4FpipLsFl9CvYf2foAE8Qh/Y0s+bxWysciwOs0LTBED4JCaNxTZ5rGadB14M6dwA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.54.1", + "@typescript-eslint/types": "5.59.1", "eslint-visitor-keys": "^3.3.0" }, "engines": { @@ -2511,6 +954,11 @@ "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" }, + "node_modules/abort-controller-x": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/abort-controller-x/-/abort-controller-x-0.4.1.tgz", + "integrity": "sha512-lJ2ssrl3FoTK3cX/g15lRCkXFWKiwRTRtBjfwounO2EM/Q65rI/MEZsfsch1juWU2pH2aLSaq0HGowlDP/imrw==" + }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -2524,9 +972,10 @@ } }, "node_modules/acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "version": "8.8.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", + "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", + "dev": true, "bin": { "acorn": "bin/acorn" }, @@ -2553,6 +1002,17 @@ "xtend": "^4.0.2" } }, + "node_modules/acorn-node/node_modules/acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/acorn-walk": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", @@ -2572,27 +1032,6 @@ "node": ">= 6.0.0" } }, - "node_modules/agent-base/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/agent-base/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -2620,7 +1059,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -2649,9 +1087,9 @@ } }, "node_modules/are-we-there-yet/node_modules/readable-stream": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.1.tgz", - "integrity": "sha512-+rQmrWMYGA90yenhTYsLWAsLsqVC8osOw6PKE1HDYiO0gdPeKe/xDHNzIAIn4C91YQ6oenEhfYqqc1883qHbjQ==", + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -2751,9 +1189,9 @@ } }, "node_modules/aws-sdk": { - "version": "2.1328.0", - "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1328.0.tgz", - "integrity": "sha512-ud8ieE+hGX/cWHkQ9kMxQw6w+onbv71PDrcPmP2j8cmYv5IPlM5Zh8/tpsmXApLYDmQMuZ3TtssiB1KmoSbzgA==", + "version": "2.1367.0", + "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1367.0.tgz", + "integrity": "sha512-ZlN3iXazEVPwjmQzC1TfkRUPOKruF6RkAFnVz4hOPjQQT91RYi2lCRWtipWk4ZoONBLX7gFLGUgIfiHjf/A+iA==", "dependencies": { "buffer": "4.9.2", "events": "1.1.1", @@ -2764,7 +1202,7 @@ "url": "0.10.3", "util": "^0.12.4", "uuid": "8.0.0", - "xml2js": "0.4.19" + "xml2js": "0.5.0" }, "engines": { "node": ">= 10.0.0" @@ -2877,11 +1315,18 @@ "npm": "1.2.8000 || >= 1.4.16" } }, - "node_modules/bowser": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.11.0.tgz", - "integrity": "sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==", - "optional": true + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, "node_modules/brace-expansion": { "version": "1.1.11", @@ -2919,9 +1364,9 @@ } }, "node_modules/bson": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/bson/-/bson-5.0.1.tgz", - "integrity": "sha512-y09gBGusgHtinMon/GVbv1J6FrXhnr/+6hqLlSmEFzkz6PodqF6TxjyvfvY3AfO+oG1mgUtbC86xSbOlwvM62Q==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/bson/-/bson-5.2.0.tgz", + "integrity": "sha512-HevkSpDbpUfsrHWmWiAsNavANKYIErV2ePXllp1bwq5CDreAaFVj6RVlZpJnxK4WWDCJ/5jMUpaY6G526q3Hjg==", "engines": { "node": ">=14.20.1" } @@ -3047,6 +1492,16 @@ "resolved": "https://registry.npmjs.org/clip-pixels/-/clip-pixels-1.0.1.tgz", "integrity": "sha512-nJ22fZvCwkJfMppkOEE7GciLX08rDnVzEJ+U46kBFZtwNzH2V4tNxMWa9Tc365WspCxy1c3NtGJ5EeT4SgjmCA==" }, + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, "node_modules/clone-response": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz", @@ -3070,7 +1525,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "dependencies": { "color-name": "~1.1.4" }, @@ -3081,8 +1535,7 @@ "node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, "node_modules/color-support": { "version": "1.1.3", @@ -3265,11 +1718,19 @@ } }, "node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dependencies": { - "ms": "2.0.0" + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, "node_modules/decompress-response": { @@ -3556,6 +2017,14 @@ "es6-symbol": "^3.1.1" } }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "engines": { + "node": ">=6" + } + }, "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -3594,14 +2063,63 @@ "source-map": "~0.6.1" } }, + "node_modules/escodegen/node_modules/levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", + "dependencies": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/escodegen/node_modules/optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "dependencies": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/escodegen/node_modules/prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/escodegen/node_modules/type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", + "dependencies": { + "prelude-ls": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/eslint": { - "version": "8.35.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.35.0.tgz", - "integrity": "sha512-BxAf1fVL7w+JLRQhWl2pzGeSiGqbWumV4WNvc9Rhp6tiCtm4oHnyPBSEtMGZwrQgudFQ+otqzWoPB7x+hxoWsw==", + "version": "8.39.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.39.0.tgz", + "integrity": "sha512-mwiok6cy7KTW7rBpo05k6+p4YVZByLNjAZ/ACB9DRCu4YDRwjXI01tWHp6KAUWelsBetTxKK/2sHB0vdS8Z2Og==", "dev": true, "dependencies": { - "@eslint/eslintrc": "^2.0.0", - "@eslint/js": "8.35.0", + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.4.0", + "@eslint/eslintrc": "^2.0.2", + "@eslint/js": "8.39.0", "@humanwhocodes/config-array": "^0.11.8", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", @@ -3611,10 +2129,9 @@ "debug": "^4.3.2", "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.1.1", - "eslint-utils": "^3.0.0", - "eslint-visitor-keys": "^3.3.0", - "espree": "^9.4.0", + "eslint-scope": "^7.2.0", + "eslint-visitor-keys": "^3.4.0", + "espree": "^9.5.1", "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", @@ -3636,7 +2153,6 @@ "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.1", - "regexpp": "^3.2.0", "strip-ansi": "^6.0.1", "strip-json-comments": "^3.1.0", "text-table": "^0.2.0" @@ -3652,147 +2168,39 @@ } }, "node_modules/eslint-scope": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", - "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.0.tgz", + "integrity": "sha512-HPpKPUBQcAsZOsHAFwTtIKcYlCje62XB7SEAcxjtmW6TD1WVpkS6i6/hOVtTZIl4zGj/mBqpFVGvaDneik+VoQ==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/eslint-scope": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.0.tgz", + "integrity": "sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw==", "dev": true, "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/eslint-scope/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/eslint-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", - "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", - "dev": true, - "dependencies": { - "eslint-visitor-keys": "^2.0.0" - }, - "engines": { - "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - }, - "peerDependencies": { - "eslint": ">=5" - } - }, - "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", - "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/eslint/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/eslint/node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/eslint/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/eslint/node_modules/optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", - "dev": true, - "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/eslint/node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/eslint/node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/espree": { - "version": "9.4.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.4.1.tgz", - "integrity": "sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg==", - "dev": true, - "dependencies": { - "acorn": "^8.8.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.3.0" - }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, @@ -3800,16 +2208,30 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/espree/node_modules/acorn": { - "version": "8.8.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", - "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", + "node_modules/eslint/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, - "bin": { - "acorn": "bin/acorn" + "engines": { + "node": ">=4.0" + } + }, + "node_modules/espree": { + "version": "9.5.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.1.tgz", + "integrity": "sha512-5yxtHSZXRSW5pvv3hAlXM5+/Oswi1AUFqBmbibKb5s6bp3rGIDkyXU6xCoyuuLhijr4SFwPrXRoZjz0AZDN9tg==", + "dev": true, + "dependencies": { + "acorn": "^8.8.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.0" }, "engines": { - "node": ">=0.4.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, "node_modules/esprima": { @@ -3969,6 +2391,19 @@ "resolved": "https://registry.npmjs.org/express-subdomain/-/express-subdomain-1.0.5.tgz", "integrity": "sha512-tpYy7MPgDoouxA4r+BnGI43yxYakbSSpQn7MjEYM0ssHeipTM1YiIoK3i4pCAgoXoks22Yb5C4QFkOYBYczZcw==" }, + "node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, "node_modules/ext": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz", @@ -4047,22 +2482,6 @@ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==" }, - "node_modules/fast-xml-parser": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.1.2.tgz", - "integrity": "sha512-CDYeykkle1LiA/uqQyNwYpFbyF6Axec6YapmpUP+/RHWIoR1zKjocdvNaTsxCxZzQ6v9MLXaSYm9Qq0thv0DHg==", - "optional": true, - "dependencies": { - "strnum": "^1.0.5" - }, - "bin": { - "fxparser": "src/cli/cli.js" - }, - "funding": { - "type": "paypal", - "url": "https://paypal.me/naturalintelligence" - } - }, "node_modules/fastq": { "version": "1.15.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", @@ -4121,6 +2540,19 @@ "node": ">= 0.8" } }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -4291,6 +2723,14 @@ "resolved": "https://registry.npmjs.org/get-assigned-identifiers/-/get-assigned-identifiers-1.2.0.tgz", "integrity": "sha512-mBBwmeGTrxEMO4pMaaf/uUEFHnYtwr8FTe8Y/mer4rcV/bye0qGm6pw1bGZFGStxC5O76c5ZAVBGnqHmOaJpdQ==" }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, "node_modules/get-intrinsic": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz", @@ -4428,9 +2868,9 @@ } }, "node_modules/graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" + "version": "4.2.11", + "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/grapheme-splitter": { "version": "1.0.4", @@ -4572,27 +3012,6 @@ "node": ">= 6" } }, - "node_modules/https-proxy-agent/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/https-proxy-agent/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, "node_modules/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -4790,9 +3209,9 @@ } }, "node_modules/is-core-module": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", - "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.0.tgz", + "integrity": "sha512-RECHCBCd/viahWmwj6enj19sKbHfJrddi/6cBDsNTKbNq0f7VeaUkBo60BqzvPqo/W54ChS62Z5qyun7cfOMqQ==", "dependencies": { "has": "^1.0.3" }, @@ -4927,9 +3346,9 @@ } }, "node_modules/joi": { - "version": "17.8.3", - "resolved": "https://registry.npmjs.org/joi/-/joi-17.8.3.tgz", - "integrity": "sha512-q5Fn6Tj/jR8PfrLrx4fpGH4v9qM6o+vDUfD4/3vxxyg34OmKcNqYZ1qn2mpLza96S8tL0p0rIw2gOZX+/cTg9w==", + "version": "17.9.2", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.9.2.tgz", + "integrity": "sha512-Itk/r+V4Dx0V3c7RLFdRh12IOjySm2/WGPMubBT92cQvRfYZhPM2W0hZlctjj72iES8jsRCwp7S/cRmWBnJ4nw==", "dependencies": { "@hapi/hoek": "^9.0.0", "@hapi/topo": "^5.0.0", @@ -4944,9 +3363,9 @@ "integrity": "sha512-9IXdWudL61npZjvLuVe/ktHiA41iE8qFyLB+4VDTblEsWBzeg8WQTlktdUK4CdncUqtUgUg0bbOmTE2bKBKaBQ==" }, "node_modules/js-sdsl": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.3.0.tgz", - "integrity": "sha512-mifzlm2+5nZ+lEcLJMoBK0/IH/bDg8XnJfd/Wq6IP+xoCjLZsTOnV2QpxlVbX9bMnkl5PdEjNtBJ9Cj1NjifhQ==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.4.0.tgz", + "integrity": "sha512-FfVSdx6pJ41Oa+CF7RDaFmTnCaFhua+SNYQX74riGOpl96x+2jQCqEfQ2bnXu/5DPCqlRuiqyvTJM0Qjz26IVg==", "dev": true, "funding": { "type": "opencollective", @@ -5035,12 +3454,13 @@ } }, "node_modules/levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, "dependencies": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" }, "engines": { "node": ">= 0.8.0" @@ -5067,6 +3487,11 @@ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dev": true }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==" + }, "node_modules/lodash.foreach": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.foreach/-/lodash.foreach-4.5.0.tgz", @@ -5082,6 +3507,11 @@ "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" }, + "node_modules/long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" + }, "node_modules/lowercase-keys": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", @@ -5262,9 +3692,9 @@ } }, "node_modules/minipass": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.4.tgz", - "integrity": "sha512-lwycX3cBMTvcejsHITUgYj6Gy6A7Nh4Q6h9NP4sTHY1ccJlC7yKzDmiShEHsJ16Jf1nKGDEaiHxiltsJEvk0nQ==", + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.8.tgz", + "integrity": "sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ==", "engines": { "node": ">=8" } @@ -5312,11 +3742,11 @@ } }, "node_modules/mongodb": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-5.1.0.tgz", - "integrity": "sha512-qgKb7y+EI90y4weY3z5+lIgm8wmexbonz0GalHkSElQXVKtRuwqXuhXKccyvIjXCJVy9qPV82zsinY0W1FBnJw==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-5.3.0.tgz", + "integrity": "sha512-Wy/sbahguL8c3TXQWXmuBabiLD+iVmz+tOgQf+FwkCjhUIorqbAxRbbz00g4ZoN4sXIPwpAlTANMaGRjGGTikQ==", "dependencies": { - "bson": "^5.0.1", + "bson": "^5.2.0", "mongodb-connection-string-url": "^2.6.0", "socks": "^2.7.1" }, @@ -5328,7 +3758,7 @@ }, "peerDependencies": { "@aws-sdk/credential-providers": "^3.201.0", - "mongodb-client-encryption": "^2.3.0", + "mongodb-client-encryption": ">=2.3.0 <3", "snappy": "^7.2.2" }, "peerDependenciesMeta": { @@ -5353,13 +3783,13 @@ } }, "node_modules/mongoose": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-7.0.0.tgz", - "integrity": "sha512-U0YPURDld+k/nvvSG1mRClQSjZMRXwQKSU5yb9PslRnOmVz0UlBD7SjSnjUuGT0yk+7BH+kJNimsKqMxYAKkMA==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-7.1.0.tgz", + "integrity": "sha512-shoo9z/7o96Ojx69wpJn65+EC+Mt3q1SWTducW+F2Y4ieCXo0lZwpCZedgC841MIvJ7V8o6gmzoN1NfcnOTOuw==", "dependencies": { - "bson": "^5.0.1", + "bson": "^5.2.0", "kareem": "2.5.1", - "mongodb": "5.1.0", + "mongodb": "5.3.0", "mpath": "0.9.0", "mquery": "5.0.0", "ms": "2.1.3", @@ -5374,9 +3804,9 @@ } }, "node_modules/mongoose-unique-validator": { - "version": "3.1.0", - "resolved": "git+ssh://git@github.com/stenneepro/mongoose-unique-validator.git#1cd976b70ddd8e8e65fed1d9a387677713a8d177", - "license": "MIT", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mongoose-unique-validator/-/mongoose-unique-validator-4.0.0.tgz", + "integrity": "sha512-lOvL7wOZ8wM2Kggk6zPZuglmld7hmzf6TdNk/h9EidTH9iZNLl3Y2ZgRPjYd/so1AODLawehY56roa6HjX6jew==", "dependencies": { "lodash.foreach": "^4.1.0", "lodash.get": "^4.0.2", @@ -5406,6 +3836,19 @@ "node": ">= 0.8.0" } }, + "node_modules/morgan/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/morgan/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, "node_modules/morgan/node_modules/on-finished": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", @@ -5436,32 +3879,11 @@ "node": ">=14.0.0" } }, - "node_modules/mquery/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/mquery/node_modules/ms": { + "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, - "node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, "node_modules/mute-stream": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", @@ -5514,6 +3936,24 @@ "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==" }, + "node_modules/nice-grpc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/nice-grpc/-/nice-grpc-2.1.4.tgz", + "integrity": "sha512-ZCSnFxg/k6PM1zZ2u/SbuySTrpK7q4klwRE4ymAdiMfZM3Rl1LRUdqUslKSbSjd9XQHzi80Y5JJL5fE58lSrVA==", + "dependencies": { + "@grpc/grpc-js": "^1.7.3", + "abort-controller-x": "^0.4.0", + "nice-grpc-common": "^2.0.2" + } + }, + "node_modules/nice-grpc-common": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/nice-grpc-common/-/nice-grpc-common-2.0.2.tgz", + "integrity": "sha512-7RNWbls5kAL1QVUOXvBsv1uO0wPQK3lHv+cY1gwkTzirnG1Nop4cBJZubpgziNbaVc/bl9QJcyvsf/NQxa3rjQ==", + "dependencies": { + "ts-error": "^1.0.6" + } + }, "node_modules/node-addon-api": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz", @@ -5666,16 +4106,17 @@ } }, "node_modules/optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, "dependencies": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" }, "engines": { "node": ">= 0.8.0" @@ -5823,13 +4264,29 @@ } }, "node_modules/prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, "engines": { "node": ">= 0.8.0" } }, + "node_modules/pretendo-grpc-ts": { + "version": "1.0.0", + "resolved": "git+ssh://git@github.com/PretendoNetwork/grpc-ts.git#d2703693db67c2c19d07abac5b3410542b74d855", + "hasInstallScript": true, + "license": "ISC", + "dependencies": { + "long": "^5.2.1", + "protobufjs": "^7.2.3" + } + }, + "node_modules/pretendo-grpc-ts/node_modules/long": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", + "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==" + }, "node_modules/primitive-pool": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/primitive-pool/-/primitive-pool-1.1.0.tgz", @@ -5856,6 +4313,34 @@ "node": ">= 6.0.0" } }, + "node_modules/protobufjs": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.2.3.tgz", + "integrity": "sha512-TtpvOqwB5Gdz/PQmOjgsrGH1nHjAQVCN7JG4A6r1sXRWESL5rNMAiRcBQlCAdKxZcAbstExQePYG8xof/JVRgg==", + "hasInstallScript": true, + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/protobufjs/node_modules/long": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", + "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==" + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -6008,12 +4493,12 @@ "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==" }, "node_modules/redis": { - "version": "4.6.5", - "resolved": "https://registry.npmjs.org/redis/-/redis-4.6.5.tgz", - "integrity": "sha512-O0OWA36gDQbswOdUuAhRL6mTZpHFN525HlgZgDaVNgCJIAZR3ya06NTESb0R+TUZ+BFaDpz6NnnVvoMx9meUFg==", + "version": "4.6.6", + "resolved": "https://registry.npmjs.org/redis/-/redis-4.6.6.tgz", + "integrity": "sha512-aLs2fuBFV/VJ28oLBqYykfnhGGkFxvx0HdCEBYdJ99FFbSEMZ7c1nVKwR6ZRv+7bb7JnC0mmCzaqu8frgOYhpA==", "dependencies": { "@redis/bloom": "1.2.0", - "@redis/client": "1.5.6", + "@redis/client": "1.5.7", "@redis/graph": "1.1.0", "@redis/json": "1.0.4", "@redis/search": "1.1.2", @@ -6026,18 +4511,6 @@ "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==", "optional": true }, - "node_modules/regexpp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", - "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - } - }, "node_modules/request": { "version": "2.88.2", "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", @@ -6086,12 +4559,20 @@ "uuid": "bin/uuid" } }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/resolve": { - "version": "1.22.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", - "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", + "version": "1.22.2", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", + "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==", "dependencies": { - "is-core-module": "^2.9.0", + "is-core-module": "^2.11.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, @@ -6244,9 +4725,9 @@ } }, "node_modules/semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.0.tgz", + "integrity": "sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==", "dependencies": { "lru-cache": "^6.0.0" }, @@ -6280,6 +4761,19 @@ "node": ">= 0.8.0" } }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, "node_modules/send/node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -6573,12 +5067,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/strnum": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz", - "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==", - "optional": true - }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -6636,6 +5124,19 @@ "node": ">=6.0.0" } }, + "node_modules/tga/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/tga/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, "node_modules/through2": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", @@ -6725,6 +5226,11 @@ "node": ">=12" } }, + "node_modules/ts-error": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/ts-error/-/ts-error-1.0.6.tgz", + "integrity": "sha512-tLJxacIQUM82IR7JO1UUkKlYuUTmoY9HBJAmNWFzheSlDS5SPMcNIepejHJa4BpPQLAcbRhRf3GDJzyj6rbKvA==" + }, "node_modules/tslib": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", @@ -6766,11 +5272,12 @@ "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==" }, "node_modules/type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, "dependencies": { - "prelude-ls": "~1.1.2" + "prelude-ls": "^1.2.1" }, "engines": { "node": ">= 0.8.0" @@ -7045,24 +5552,43 @@ "node": ">=0.10.0" } }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, "node_modules/xml2js": { - "version": "0.4.19", - "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz", - "integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==", + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.5.0.tgz", + "integrity": "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==", "dependencies": { "sax": ">=0.6.0", - "xmlbuilder": "~9.0.1" + "xmlbuilder": "~11.0.0" + }, + "engines": { + "node": ">=4.0.0" } }, "node_modules/xml2js/node_modules/xmlbuilder": { - "version": "9.0.7", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", - "integrity": "sha512-7YXTQc3P2l9+0rjaUbLwMKRhtmwg1M1eDf6nag7urC7pIPYLD9W/jmzQ4ptRSUbodw5S0jfoGTflLemQibSpeQ==", + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", "engines": { "node": ">=4.0" } @@ -7096,11 +5622,44 @@ "node": ">=0.4" } }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "engines": { + "node": ">=10" + } + }, "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, + "node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "engines": { + "node": ">=10" + } + }, "node_modules/yesno": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/yesno/-/yesno-0.4.0.tgz", @@ -7119,5696 +5678,5 @@ "url": "https://github.com/sponsors/sindresorhus" } } - }, - "dependencies": { - "@aws-crypto/ie11-detection": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/ie11-detection/-/ie11-detection-3.0.0.tgz", - "integrity": "sha512-341lBBkiY1DfDNKai/wXM3aujNBkXR7tq1URPQDL9wi3AUbI80NR74uF1TXHMm7po1AcnFk8iu2S2IeU/+/A+Q==", - "optional": true, - "requires": { - "tslib": "^1.11.1" - } - }, - "@aws-crypto/sha256-browser": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-3.0.0.tgz", - "integrity": "sha512-8VLmW2B+gjFbU5uMeqtQM6Nj0/F1bro80xQXCW6CQBWgosFWXTx77aeOF5CAIAmbOK64SdMBJdNr6J41yP5mvQ==", - "optional": true, - "requires": { - "@aws-crypto/ie11-detection": "^3.0.0", - "@aws-crypto/sha256-js": "^3.0.0", - "@aws-crypto/supports-web-crypto": "^3.0.0", - "@aws-crypto/util": "^3.0.0", - "@aws-sdk/types": "^3.222.0", - "@aws-sdk/util-locate-window": "^3.0.0", - "@aws-sdk/util-utf8-browser": "^3.0.0", - "tslib": "^1.11.1" - } - }, - "@aws-crypto/sha256-js": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-3.0.0.tgz", - "integrity": "sha512-PnNN7os0+yd1XvXAy23CFOmTbMaDxgxXtTKHybrJ39Y8kGzBATgBFibWJKH6BhytLI/Zyszs87xCOBNyBig6vQ==", - "optional": true, - "requires": { - "@aws-crypto/util": "^3.0.0", - "@aws-sdk/types": "^3.222.0", - "tslib": "^1.11.1" - } - }, - "@aws-crypto/supports-web-crypto": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-3.0.0.tgz", - "integrity": "sha512-06hBdMwUAb2WFTuGG73LSC0wfPu93xWwo5vL2et9eymgmu3Id5vFAHBbajVWiGhPO37qcsdCap/FqXvJGJWPIg==", - "optional": true, - "requires": { - "tslib": "^1.11.1" - } - }, - "@aws-crypto/util": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-3.0.0.tgz", - "integrity": "sha512-2OJlpeJpCR48CC8r+uKVChzs9Iungj9wkZrl8Z041DWEWvyIHILYKCPNzJghKsivj+S3mLo6BVc7mBNzdxA46w==", - "optional": true, - "requires": { - "@aws-sdk/types": "^3.222.0", - "@aws-sdk/util-utf8-browser": "^3.0.0", - "tslib": "^1.11.1" - } - }, - "@aws-sdk/abort-controller": { - "version": "3.292.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/abort-controller/-/abort-controller-3.292.0.tgz", - "integrity": "sha512-lf+OPptL01kvryIJy7+dvFux5KbJ6OTwLPPEekVKZ2AfEvwcVtOZWFUhyw3PJCBTVncjKB1Kjl3V/eTS3YuPXQ==", - "optional": true, - "requires": { - "@aws-sdk/types": "3.292.0", - "tslib": "^2.3.1" - }, - "dependencies": { - "tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - } - } - }, - "@aws-sdk/client-cognito-identity": { - "version": "3.294.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-cognito-identity/-/client-cognito-identity-3.294.0.tgz", - "integrity": "sha512-QMk/QratNvAvmnJ77pu9KDjpfUf/5LOJplHcLKcY962ewJGBtFFY4XZVnmUgQMSG0phRODtex7pyH//FueGKLQ==", - "optional": true, - "requires": { - "@aws-crypto/sha256-browser": "3.0.0", - "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/client-sts": "3.294.0", - "@aws-sdk/config-resolver": "3.292.0", - "@aws-sdk/credential-provider-node": "3.294.0", - "@aws-sdk/fetch-http-handler": "3.292.0", - "@aws-sdk/hash-node": "3.292.0", - "@aws-sdk/invalid-dependency": "3.292.0", - "@aws-sdk/middleware-content-length": "3.292.0", - "@aws-sdk/middleware-endpoint": "3.292.0", - "@aws-sdk/middleware-host-header": "3.292.0", - "@aws-sdk/middleware-logger": "3.292.0", - "@aws-sdk/middleware-recursion-detection": "3.292.0", - "@aws-sdk/middleware-retry": "3.293.0", - "@aws-sdk/middleware-serde": "3.292.0", - "@aws-sdk/middleware-signing": "3.292.0", - "@aws-sdk/middleware-stack": "3.292.0", - "@aws-sdk/middleware-user-agent": "3.293.0", - "@aws-sdk/node-config-provider": "3.292.0", - "@aws-sdk/node-http-handler": "3.292.0", - "@aws-sdk/protocol-http": "3.292.0", - "@aws-sdk/smithy-client": "3.292.0", - "@aws-sdk/types": "3.292.0", - "@aws-sdk/url-parser": "3.292.0", - "@aws-sdk/util-base64": "3.292.0", - "@aws-sdk/util-body-length-browser": "3.292.0", - "@aws-sdk/util-body-length-node": "3.292.0", - "@aws-sdk/util-defaults-mode-browser": "3.292.0", - "@aws-sdk/util-defaults-mode-node": "3.292.0", - "@aws-sdk/util-endpoints": "3.293.0", - "@aws-sdk/util-retry": "3.292.0", - "@aws-sdk/util-user-agent-browser": "3.292.0", - "@aws-sdk/util-user-agent-node": "3.292.0", - "@aws-sdk/util-utf8": "3.292.0", - "tslib": "^2.3.1" - }, - "dependencies": { - "tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - } - } - }, - "@aws-sdk/client-sso": { - "version": "3.294.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.294.0.tgz", - "integrity": "sha512-+FuxQTi5WvnaXM5JbNLkBIzQ3An4gA0ox61N1u+3xled+nywKb1yQ7WmRpyMG5bLbkmnj3aqoo5/uskFc4c4EA==", - "optional": true, - "requires": { - "@aws-crypto/sha256-browser": "3.0.0", - "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/config-resolver": "3.292.0", - "@aws-sdk/fetch-http-handler": "3.292.0", - "@aws-sdk/hash-node": "3.292.0", - "@aws-sdk/invalid-dependency": "3.292.0", - "@aws-sdk/middleware-content-length": "3.292.0", - "@aws-sdk/middleware-endpoint": "3.292.0", - "@aws-sdk/middleware-host-header": "3.292.0", - "@aws-sdk/middleware-logger": "3.292.0", - "@aws-sdk/middleware-recursion-detection": "3.292.0", - "@aws-sdk/middleware-retry": "3.293.0", - "@aws-sdk/middleware-serde": "3.292.0", - "@aws-sdk/middleware-stack": "3.292.0", - "@aws-sdk/middleware-user-agent": "3.293.0", - "@aws-sdk/node-config-provider": "3.292.0", - "@aws-sdk/node-http-handler": "3.292.0", - "@aws-sdk/protocol-http": "3.292.0", - "@aws-sdk/smithy-client": "3.292.0", - "@aws-sdk/types": "3.292.0", - "@aws-sdk/url-parser": "3.292.0", - "@aws-sdk/util-base64": "3.292.0", - "@aws-sdk/util-body-length-browser": "3.292.0", - "@aws-sdk/util-body-length-node": "3.292.0", - "@aws-sdk/util-defaults-mode-browser": "3.292.0", - "@aws-sdk/util-defaults-mode-node": "3.292.0", - "@aws-sdk/util-endpoints": "3.293.0", - "@aws-sdk/util-retry": "3.292.0", - "@aws-sdk/util-user-agent-browser": "3.292.0", - "@aws-sdk/util-user-agent-node": "3.292.0", - "@aws-sdk/util-utf8": "3.292.0", - "tslib": "^2.3.1" - }, - "dependencies": { - "tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - } - } - }, - "@aws-sdk/client-sso-oidc": { - "version": "3.294.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.294.0.tgz", - "integrity": "sha512-/ZfDud76MdSPJ/TxjV2xLE30XbBQDZwKQ32axwoK1eziPvrAIUBYVgpBwj+m0quhoiQhBKkg3aFl6j39AF2thw==", - "optional": true, - "requires": { - "@aws-crypto/sha256-browser": "3.0.0", - "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/config-resolver": "3.292.0", - "@aws-sdk/fetch-http-handler": "3.292.0", - "@aws-sdk/hash-node": "3.292.0", - "@aws-sdk/invalid-dependency": "3.292.0", - "@aws-sdk/middleware-content-length": "3.292.0", - "@aws-sdk/middleware-endpoint": "3.292.0", - "@aws-sdk/middleware-host-header": "3.292.0", - "@aws-sdk/middleware-logger": "3.292.0", - "@aws-sdk/middleware-recursion-detection": "3.292.0", - "@aws-sdk/middleware-retry": "3.293.0", - "@aws-sdk/middleware-serde": "3.292.0", - "@aws-sdk/middleware-stack": "3.292.0", - "@aws-sdk/middleware-user-agent": "3.293.0", - "@aws-sdk/node-config-provider": "3.292.0", - "@aws-sdk/node-http-handler": "3.292.0", - "@aws-sdk/protocol-http": "3.292.0", - "@aws-sdk/smithy-client": "3.292.0", - "@aws-sdk/types": "3.292.0", - "@aws-sdk/url-parser": "3.292.0", - "@aws-sdk/util-base64": "3.292.0", - "@aws-sdk/util-body-length-browser": "3.292.0", - "@aws-sdk/util-body-length-node": "3.292.0", - "@aws-sdk/util-defaults-mode-browser": "3.292.0", - "@aws-sdk/util-defaults-mode-node": "3.292.0", - "@aws-sdk/util-endpoints": "3.293.0", - "@aws-sdk/util-retry": "3.292.0", - "@aws-sdk/util-user-agent-browser": "3.292.0", - "@aws-sdk/util-user-agent-node": "3.292.0", - "@aws-sdk/util-utf8": "3.292.0", - "tslib": "^2.3.1" - }, - "dependencies": { - "tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - } - } - }, - "@aws-sdk/client-sts": { - "version": "3.294.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.294.0.tgz", - "integrity": "sha512-AefqwhFjTDzelZuSYhriJbiI+GQwf2yKiKAnCt0gRj6rswewStM63Gtlhfb01sFPp+ZiqPcyQ47LqUaHp1mz/g==", - "optional": true, - "requires": { - "@aws-crypto/sha256-browser": "3.0.0", - "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/config-resolver": "3.292.0", - "@aws-sdk/credential-provider-node": "3.294.0", - "@aws-sdk/fetch-http-handler": "3.292.0", - "@aws-sdk/hash-node": "3.292.0", - "@aws-sdk/invalid-dependency": "3.292.0", - "@aws-sdk/middleware-content-length": "3.292.0", - "@aws-sdk/middleware-endpoint": "3.292.0", - "@aws-sdk/middleware-host-header": "3.292.0", - "@aws-sdk/middleware-logger": "3.292.0", - "@aws-sdk/middleware-recursion-detection": "3.292.0", - "@aws-sdk/middleware-retry": "3.293.0", - "@aws-sdk/middleware-sdk-sts": "3.292.0", - "@aws-sdk/middleware-serde": "3.292.0", - "@aws-sdk/middleware-signing": "3.292.0", - "@aws-sdk/middleware-stack": "3.292.0", - "@aws-sdk/middleware-user-agent": "3.293.0", - "@aws-sdk/node-config-provider": "3.292.0", - "@aws-sdk/node-http-handler": "3.292.0", - "@aws-sdk/protocol-http": "3.292.0", - "@aws-sdk/smithy-client": "3.292.0", - "@aws-sdk/types": "3.292.0", - "@aws-sdk/url-parser": "3.292.0", - "@aws-sdk/util-base64": "3.292.0", - "@aws-sdk/util-body-length-browser": "3.292.0", - "@aws-sdk/util-body-length-node": "3.292.0", - "@aws-sdk/util-defaults-mode-browser": "3.292.0", - "@aws-sdk/util-defaults-mode-node": "3.292.0", - "@aws-sdk/util-endpoints": "3.293.0", - "@aws-sdk/util-retry": "3.292.0", - "@aws-sdk/util-user-agent-browser": "3.292.0", - "@aws-sdk/util-user-agent-node": "3.292.0", - "@aws-sdk/util-utf8": "3.292.0", - "fast-xml-parser": "4.1.2", - "tslib": "^2.3.1" - }, - "dependencies": { - "tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - } - } - }, - "@aws-sdk/config-resolver": { - "version": "3.292.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/config-resolver/-/config-resolver-3.292.0.tgz", - "integrity": "sha512-cB3twnNR7vYvlt2jvw8VlA1+iv/tVzl+/S39MKqw2tepU+AbJAM0EHwb/dkf1OKSmlrnANXhshx80MHF9zL4mA==", - "optional": true, - "requires": { - "@aws-sdk/signature-v4": "3.292.0", - "@aws-sdk/types": "3.292.0", - "@aws-sdk/util-config-provider": "3.292.0", - "@aws-sdk/util-middleware": "3.292.0", - "tslib": "^2.3.1" - }, - "dependencies": { - "tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - } - } - }, - "@aws-sdk/credential-provider-cognito-identity": { - "version": "3.294.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-cognito-identity/-/credential-provider-cognito-identity-3.294.0.tgz", - "integrity": "sha512-YSPqHEbLC0dnbFF5LdlMH0B50sMSN/CyG/sHkPYUwL/hkbUk9URnVW7ZJlt6lRftT7X4C7muzLqUP8sJAaiJEA==", - "optional": true, - "requires": { - "@aws-sdk/client-cognito-identity": "3.294.0", - "@aws-sdk/property-provider": "3.292.0", - "@aws-sdk/types": "3.292.0", - "tslib": "^2.3.1" - }, - "dependencies": { - "tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - } - } - }, - "@aws-sdk/credential-provider-env": { - "version": "3.292.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.292.0.tgz", - "integrity": "sha512-YbafSG0ZEKE2969CJWVtUhh3hfOeLPecFVoXOtegCyAJgY5Ghtu4TsVhL4DgiGAgOC30ojAmUVQEXzd7xJF5xA==", - "optional": true, - "requires": { - "@aws-sdk/property-provider": "3.292.0", - "@aws-sdk/types": "3.292.0", - "tslib": "^2.3.1" - }, - "dependencies": { - "tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - } - } - }, - "@aws-sdk/credential-provider-imds": { - "version": "3.292.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-imds/-/credential-provider-imds-3.292.0.tgz", - "integrity": "sha512-W/peOgDSRYulgzFpUhvgi1pCm6piBz6xrVN17N4QOy+3NHBXRVMVzYk6ct2qpLPgJUSEZkcpP+Gds+bBm8ed1A==", - "optional": true, - "requires": { - "@aws-sdk/node-config-provider": "3.292.0", - "@aws-sdk/property-provider": "3.292.0", - "@aws-sdk/types": "3.292.0", - "@aws-sdk/url-parser": "3.292.0", - "tslib": "^2.3.1" - }, - "dependencies": { - "tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - } - } - }, - "@aws-sdk/credential-provider-ini": { - "version": "3.294.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.294.0.tgz", - "integrity": "sha512-pdTPbaAb5bWA+DnuKoL2TpXeNDp6Ejpv/OYt+bw2gdzl9w5r/ZCtUTTbW+Vvejr4WL5s3c1bY96kwdqCn7iLqA==", - "optional": true, - "requires": { - "@aws-sdk/credential-provider-env": "3.292.0", - "@aws-sdk/credential-provider-imds": "3.292.0", - "@aws-sdk/credential-provider-process": "3.292.0", - "@aws-sdk/credential-provider-sso": "3.294.0", - "@aws-sdk/credential-provider-web-identity": "3.292.0", - "@aws-sdk/property-provider": "3.292.0", - "@aws-sdk/shared-ini-file-loader": "3.292.0", - "@aws-sdk/types": "3.292.0", - "tslib": "^2.3.1" - }, - "dependencies": { - "tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - } - } - }, - "@aws-sdk/credential-provider-node": { - "version": "3.294.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.294.0.tgz", - "integrity": "sha512-zUL1Qhb4BsQIZCs/TPpG4oIYH/9YsGiS+Se1tasSGjTOLfBy7jhOZ0QIdpEeyAx/EP8blOBredM9xWfEXgiHVA==", - "optional": true, - "requires": { - "@aws-sdk/credential-provider-env": "3.292.0", - "@aws-sdk/credential-provider-imds": "3.292.0", - "@aws-sdk/credential-provider-ini": "3.294.0", - "@aws-sdk/credential-provider-process": "3.292.0", - "@aws-sdk/credential-provider-sso": "3.294.0", - "@aws-sdk/credential-provider-web-identity": "3.292.0", - "@aws-sdk/property-provider": "3.292.0", - "@aws-sdk/shared-ini-file-loader": "3.292.0", - "@aws-sdk/types": "3.292.0", - "tslib": "^2.3.1" - }, - "dependencies": { - "tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - } - } - }, - "@aws-sdk/credential-provider-process": { - "version": "3.292.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.292.0.tgz", - "integrity": "sha512-CFVXuMuUvg/a4tknzRikEDwZBnKlHs1LZCpTXIGjBdUTdosoi4WNzDLzGp93ZRTtcgFz+4wirz2f7P3lC0NrQw==", - "optional": true, - "requires": { - "@aws-sdk/property-provider": "3.292.0", - "@aws-sdk/shared-ini-file-loader": "3.292.0", - "@aws-sdk/types": "3.292.0", - "tslib": "^2.3.1" - }, - "dependencies": { - "tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - } - } - }, - "@aws-sdk/credential-provider-sso": { - "version": "3.294.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.294.0.tgz", - "integrity": "sha512-UxrcAA/0l7j9+3tolYcG5M61D/IE1Bjd/9H87H1i2A2BrwUUBhW1Dp/vvROEDrrywlMDG3CDF3T/7ADtTak+sg==", - "optional": true, - "requires": { - "@aws-sdk/client-sso": "3.294.0", - "@aws-sdk/property-provider": "3.292.0", - "@aws-sdk/shared-ini-file-loader": "3.292.0", - "@aws-sdk/token-providers": "3.294.0", - "@aws-sdk/types": "3.292.0", - "tslib": "^2.3.1" - }, - "dependencies": { - "tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - } - } - }, - "@aws-sdk/credential-provider-web-identity": { - "version": "3.292.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.292.0.tgz", - "integrity": "sha512-4DbtIEM9gGVfqYlMdYXg3XY+vBhemjB1zXIequottW8loLYM8Vuz4/uGxxKNze6evVVzowsA0wKrYclE1aj/Rg==", - "optional": true, - "requires": { - "@aws-sdk/property-provider": "3.292.0", - "@aws-sdk/types": "3.292.0", - "tslib": "^2.3.1" - }, - "dependencies": { - "tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - } - } - }, - "@aws-sdk/credential-providers": { - "version": "3.294.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-providers/-/credential-providers-3.294.0.tgz", - "integrity": "sha512-CRGwCs6F31mQzjYi5YF2Z6c1T+UrFKtJSa6Ff/Q1rrPnROexyhBVnpP8WzpkODx/pZxKtTX50IX7ehIxbFDIyQ==", - "optional": true, - "requires": { - "@aws-sdk/client-cognito-identity": "3.294.0", - "@aws-sdk/client-sso": "3.294.0", - "@aws-sdk/client-sts": "3.294.0", - "@aws-sdk/credential-provider-cognito-identity": "3.294.0", - "@aws-sdk/credential-provider-env": "3.292.0", - "@aws-sdk/credential-provider-imds": "3.292.0", - "@aws-sdk/credential-provider-ini": "3.294.0", - "@aws-sdk/credential-provider-node": "3.294.0", - "@aws-sdk/credential-provider-process": "3.292.0", - "@aws-sdk/credential-provider-sso": "3.294.0", - "@aws-sdk/credential-provider-web-identity": "3.292.0", - "@aws-sdk/property-provider": "3.292.0", - "@aws-sdk/shared-ini-file-loader": "3.292.0", - "@aws-sdk/types": "3.292.0", - "tslib": "^2.3.1" - }, - "dependencies": { - "tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - } - } - }, - "@aws-sdk/fetch-http-handler": { - "version": "3.292.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/fetch-http-handler/-/fetch-http-handler-3.292.0.tgz", - "integrity": "sha512-zh3bhUJbL8RSa39ZKDcy+AghtUkIP8LwcNlwRIoxMQh3Row4D1s4fCq0KZCx98NJBEXoiTLyTQlZxxI//BOb1Q==", - "optional": true, - "requires": { - "@aws-sdk/protocol-http": "3.292.0", - "@aws-sdk/querystring-builder": "3.292.0", - "@aws-sdk/types": "3.292.0", - "@aws-sdk/util-base64": "3.292.0", - "tslib": "^2.3.1" - }, - "dependencies": { - "tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - } - } - }, - "@aws-sdk/hash-node": { - "version": "3.292.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/hash-node/-/hash-node-3.292.0.tgz", - "integrity": "sha512-1yLxmIsvE+eK36JXEgEIouTITdykQLVhsA5Oai//Lar6Ddgu1sFpLDbdkMtKbrh4I0jLN9RacNCkeVQjZPTCCQ==", - "optional": true, - "requires": { - "@aws-sdk/types": "3.292.0", - "@aws-sdk/util-buffer-from": "3.292.0", - "@aws-sdk/util-utf8": "3.292.0", - "tslib": "^2.3.1" - }, - "dependencies": { - "tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - } - } - }, - "@aws-sdk/invalid-dependency": { - "version": "3.292.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/invalid-dependency/-/invalid-dependency-3.292.0.tgz", - "integrity": "sha512-39OUV78CD3TmEbjhpt+V+Fk4wAGWhixqHxDSN8+4WL0uB4Fl7k5m3Z9hNY78AttHQSl2twR7WtLztnXPAFsriw==", - "optional": true, - "requires": { - "@aws-sdk/types": "3.292.0", - "tslib": "^2.3.1" - }, - "dependencies": { - "tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - } - } - }, - "@aws-sdk/is-array-buffer": { - "version": "3.292.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/is-array-buffer/-/is-array-buffer-3.292.0.tgz", - "integrity": "sha512-kW/G5T/fzI0sJH5foZG6XJiNCevXqKLxV50qIT4B1pMuw7regd4ALIy0HwSqj1nnn9mSbRWBfmby0jWCJsMcwg==", - "optional": true, - "requires": { - "tslib": "^2.3.1" - }, - "dependencies": { - "tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - } - } - }, - "@aws-sdk/middleware-content-length": { - "version": "3.292.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-content-length/-/middleware-content-length-3.292.0.tgz", - "integrity": "sha512-2gMWzQus5mj14menolpPDbYBeaOYcj7KNFZOjTjjI3iQ0KqyetG6XasirNrcJ/8QX1BRmpTol8Xjp2Ue3Gbzwg==", - "optional": true, - "requires": { - "@aws-sdk/protocol-http": "3.292.0", - "@aws-sdk/types": "3.292.0", - "tslib": "^2.3.1" - }, - "dependencies": { - "tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - } - } - }, - "@aws-sdk/middleware-endpoint": { - "version": "3.292.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-endpoint/-/middleware-endpoint-3.292.0.tgz", - "integrity": "sha512-cPMkiSxpZGG6tYlW4OS+ucS6r43f9ddX9kcUoemJCY10MOuogdPjulCAjE0HTs2PLKSOrrG4CTP4Q4wWDrH4Bw==", - "optional": true, - "requires": { - "@aws-sdk/middleware-serde": "3.292.0", - "@aws-sdk/protocol-http": "3.292.0", - "@aws-sdk/signature-v4": "3.292.0", - "@aws-sdk/types": "3.292.0", - "@aws-sdk/url-parser": "3.292.0", - "@aws-sdk/util-config-provider": "3.292.0", - "@aws-sdk/util-middleware": "3.292.0", - "tslib": "^2.3.1" - }, - "dependencies": { - "tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - } - } - }, - "@aws-sdk/middleware-host-header": { - "version": "3.292.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.292.0.tgz", - "integrity": "sha512-mHuCWe3Yg2S5YZ7mB7sKU6C97XspfqrimWjMW9pfV2usAvLA3R0HrB03jpR5vpZ3P4q7HB6wK3S6CjYMGGRNag==", - "optional": true, - "requires": { - "@aws-sdk/protocol-http": "3.292.0", - "@aws-sdk/types": "3.292.0", - "tslib": "^2.3.1" - }, - "dependencies": { - "tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - } - } - }, - "@aws-sdk/middleware-logger": { - "version": "3.292.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.292.0.tgz", - "integrity": "sha512-yZNY1XYmG3NG+uonET7jzKXNiwu61xm/ZZ6i/l51SusuaYN+qQtTAhOFsieQqTehF9kP4FzbsWgPDwD8ZZX9lw==", - "optional": true, - "requires": { - "@aws-sdk/types": "3.292.0", - "tslib": "^2.3.1" - }, - "dependencies": { - "tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - } - } - }, - "@aws-sdk/middleware-recursion-detection": { - "version": "3.292.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.292.0.tgz", - "integrity": "sha512-kA3VZpPko0Zqd7CYPTKAxhjEv0HJqFu2054L04dde1JLr43ro+2MTdX7vsHzeAFUVRphqatFFofCumvXmU6Mig==", - "optional": true, - "requires": { - "@aws-sdk/protocol-http": "3.292.0", - "@aws-sdk/types": "3.292.0", - "tslib": "^2.3.1" - }, - "dependencies": { - "tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - } - } - }, - "@aws-sdk/middleware-retry": { - "version": "3.293.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-retry/-/middleware-retry-3.293.0.tgz", - "integrity": "sha512-7tiaz2GzRecNHaZ6YnF+Nrtk3au8qF6oiipf11R7MJiqJ0fkMLnz/iRrlakDziS9qF/a9v+3yxb4W4NHK3f4Tw==", - "optional": true, - "requires": { - "@aws-sdk/protocol-http": "3.292.0", - "@aws-sdk/service-error-classification": "3.292.0", - "@aws-sdk/types": "3.292.0", - "@aws-sdk/util-middleware": "3.292.0", - "@aws-sdk/util-retry": "3.292.0", - "tslib": "^2.3.1", - "uuid": "^8.3.2" - }, - "dependencies": { - "tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - }, - "uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "optional": true - } - } - }, - "@aws-sdk/middleware-sdk-sts": { - "version": "3.292.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-sts/-/middleware-sdk-sts-3.292.0.tgz", - "integrity": "sha512-GN5ZHEqXZqDi+HkVbaXRX9HaW/vA5rikYpWKYsmxTUZ7fB7ijvEO3co3lleJv2C+iGYRtUIHC4wYNB5xgoTCxg==", - "optional": true, - "requires": { - "@aws-sdk/middleware-signing": "3.292.0", - "@aws-sdk/property-provider": "3.292.0", - "@aws-sdk/protocol-http": "3.292.0", - "@aws-sdk/signature-v4": "3.292.0", - "@aws-sdk/types": "3.292.0", - "tslib": "^2.3.1" - }, - "dependencies": { - "tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - } - } - }, - "@aws-sdk/middleware-serde": { - "version": "3.292.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-serde/-/middleware-serde-3.292.0.tgz", - "integrity": "sha512-6hN9mTQwSvV8EcGvtXbS/MpK7WMCokUku5Wu7X24UwCNMVkoRHLIkYcxHcvBTwttuOU0d8hph1/lIX4dkLwkQw==", - "optional": true, - "requires": { - "@aws-sdk/types": "3.292.0", - "tslib": "^2.3.1" - }, - "dependencies": { - "tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - } - } - }, - "@aws-sdk/middleware-signing": { - "version": "3.292.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-signing/-/middleware-signing-3.292.0.tgz", - "integrity": "sha512-GVfoSjDjEQ4TaO6x9MffyP3uRV+2KcS5FtexLCYOM9pJcnE9tqq9FJOrZ1xl1g+YjUVKxo4x8lu3tpEtIb17qg==", - "optional": true, - "requires": { - "@aws-sdk/property-provider": "3.292.0", - "@aws-sdk/protocol-http": "3.292.0", - "@aws-sdk/signature-v4": "3.292.0", - "@aws-sdk/types": "3.292.0", - "@aws-sdk/util-middleware": "3.292.0", - "tslib": "^2.3.1" - }, - "dependencies": { - "tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - } - } - }, - "@aws-sdk/middleware-stack": { - "version": "3.292.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-stack/-/middleware-stack-3.292.0.tgz", - "integrity": "sha512-WdQpRkuMysrEwrkByCM1qCn2PPpFGGQ2iXqaFha5RzCdZDlxJni9cVNb6HzWUcgjLEYVTXCmOR9Wxm3CNW44Qg==", - "optional": true, - "requires": { - "tslib": "^2.3.1" - }, - "dependencies": { - "tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - } - } - }, - "@aws-sdk/middleware-user-agent": { - "version": "3.293.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.293.0.tgz", - "integrity": "sha512-gZ7/e6XwpKk9mvgA78q4Ffc796jTn02TUKx2qMDnkLVbeJXBNN2jnvYEKq8v70+o7fd/ALRudg8gBDmkkhM/Hw==", - "optional": true, - "requires": { - "@aws-sdk/protocol-http": "3.292.0", - "@aws-sdk/types": "3.292.0", - "@aws-sdk/util-endpoints": "3.293.0", - "tslib": "^2.3.1" - }, - "dependencies": { - "tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - } - } - }, - "@aws-sdk/node-config-provider": { - "version": "3.292.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/node-config-provider/-/node-config-provider-3.292.0.tgz", - "integrity": "sha512-S3NnC9dQ5GIbJYSDIldZb4zdpCOEua1tM7bjYL3VS5uqCEM93kIi/o/UkIUveMp/eqTS2LJa5HjNIz5Te6je0A==", - "optional": true, - "requires": { - "@aws-sdk/property-provider": "3.292.0", - "@aws-sdk/shared-ini-file-loader": "3.292.0", - "@aws-sdk/types": "3.292.0", - "tslib": "^2.3.1" - }, - "dependencies": { - "tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - } - } - }, - "@aws-sdk/node-http-handler": { - "version": "3.292.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/node-http-handler/-/node-http-handler-3.292.0.tgz", - "integrity": "sha512-L/E3UDSwXLXjt1XWWh0RBD55F+aZI1AEdPwdES9i1PjnZLyuxuDhEDptVibNN56+I9/4Q3SbmuVRVlOD0uzBag==", - "optional": true, - "requires": { - "@aws-sdk/abort-controller": "3.292.0", - "@aws-sdk/protocol-http": "3.292.0", - "@aws-sdk/querystring-builder": "3.292.0", - "@aws-sdk/types": "3.292.0", - "tslib": "^2.3.1" - }, - "dependencies": { - "tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - } - } - }, - "@aws-sdk/property-provider": { - "version": "3.292.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/property-provider/-/property-provider-3.292.0.tgz", - "integrity": "sha512-dHArSvsiqhno/g55N815gXmAMrmN8DP7OeFNqJ4wJG42xsF2PFN3DAsjIuHuXMwu+7A3R1LHqIpvv0hA9KeoJQ==", - "optional": true, - "requires": { - "@aws-sdk/types": "3.292.0", - "tslib": "^2.3.1" - }, - "dependencies": { - "tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - } - } - }, - "@aws-sdk/protocol-http": { - "version": "3.292.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/protocol-http/-/protocol-http-3.292.0.tgz", - "integrity": "sha512-NLi4fq3k41aXIh1I97yX0JTy+3p6aW1NdwFwdMa674z86QNfb4SfRQRZBQe9wEnAZ/eWHVnlKIuII+U1URk/Kg==", - "optional": true, - "requires": { - "@aws-sdk/types": "3.292.0", - "tslib": "^2.3.1" - }, - "dependencies": { - "tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - } - } - }, - "@aws-sdk/querystring-builder": { - "version": "3.292.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/querystring-builder/-/querystring-builder-3.292.0.tgz", - "integrity": "sha512-XElIFJaReIm24eEvBtV2dOtZvcm3gXsGu/ftG8MLJKbKXFKpAP1q+K6En0Bs7/T88voKghKdKpKT+eZUWgTqlg==", - "optional": true, - "requires": { - "@aws-sdk/types": "3.292.0", - "@aws-sdk/util-uri-escape": "3.292.0", - "tslib": "^2.3.1" - }, - "dependencies": { - "tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - } - } - }, - "@aws-sdk/querystring-parser": { - "version": "3.292.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/querystring-parser/-/querystring-parser-3.292.0.tgz", - "integrity": "sha512-iTYpYo7a8X9RxiPbjjewIpm6XQPx2EOcF1dWCPRII9EFlmZ4bwnX+PDI36fIo9oVs8TIKXmwNGODU9nsg7CSAw==", - "optional": true, - "requires": { - "@aws-sdk/types": "3.292.0", - "tslib": "^2.3.1" - }, - "dependencies": { - "tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - } - } - }, - "@aws-sdk/service-error-classification": { - "version": "3.292.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/service-error-classification/-/service-error-classification-3.292.0.tgz", - "integrity": "sha512-X1k3sixCeC45XSNHBe+kRBQBwPDyTFtFITb8O5Qw4dS9XWGhrUJT4CX0qE5aj8qP3F9U5nRizs9c2mBVVP0Caw==", - "optional": true - }, - "@aws-sdk/shared-ini-file-loader": { - "version": "3.292.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/shared-ini-file-loader/-/shared-ini-file-loader-3.292.0.tgz", - "integrity": "sha512-Av2TTYg1Jig2kbkD56ybiqZJB6vVrYjv1W5UQwY/q3nA/T2mcrgQ20ByCOt5Bv9VvY7FSgC+znj+L4a7RLGmBg==", - "optional": true, - "requires": { - "@aws-sdk/types": "3.292.0", - "tslib": "^2.3.1" - }, - "dependencies": { - "tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - } - } - }, - "@aws-sdk/signature-v4": { - "version": "3.292.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4/-/signature-v4-3.292.0.tgz", - "integrity": "sha512-+rw47VY5mvBecn13tDQTl1ipGWg5tE63faWgmZe68HoBL87ZiDzsd7bUKOvjfW21iMgWlwAppkaNNQayYRb2zg==", - "optional": true, - "requires": { - "@aws-sdk/is-array-buffer": "3.292.0", - "@aws-sdk/types": "3.292.0", - "@aws-sdk/util-hex-encoding": "3.292.0", - "@aws-sdk/util-middleware": "3.292.0", - "@aws-sdk/util-uri-escape": "3.292.0", - "@aws-sdk/util-utf8": "3.292.0", - "tslib": "^2.3.1" - }, - "dependencies": { - "tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - } - } - }, - "@aws-sdk/smithy-client": { - "version": "3.292.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/smithy-client/-/smithy-client-3.292.0.tgz", - "integrity": "sha512-S8PKzjPkZ6SXYZuZiU787dMsvQ0d/LFEhw2OI4Oe2An9Fc2IwJ2FYukyHoQJOV2tV0DiuMebPo7eMyQyjKElvA==", - "optional": true, - "requires": { - "@aws-sdk/middleware-stack": "3.292.0", - "@aws-sdk/types": "3.292.0", - "tslib": "^2.3.1" - }, - "dependencies": { - "tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - } - } - }, - "@aws-sdk/token-providers": { - "version": "3.294.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.294.0.tgz", - "integrity": "sha512-6nwO04LtC5f4AsUvGZXyjaswuEK4Rr2VsuANpMKrPCgunRfI58a8YXLniudOSXN6e7CFJ6M3uo/h5YXqtnzGug==", - "optional": true, - "requires": { - "@aws-sdk/client-sso-oidc": "3.294.0", - "@aws-sdk/property-provider": "3.292.0", - "@aws-sdk/shared-ini-file-loader": "3.292.0", - "@aws-sdk/types": "3.292.0", - "tslib": "^2.3.1" - }, - "dependencies": { - "tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - } - } - }, - "@aws-sdk/types": { - "version": "3.292.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.292.0.tgz", - "integrity": "sha512-1teYAY2M73UXZxMAxqZxVS2qwXjQh0OWtt7qyLfha0TtIk/fZ1hRwFgxbDCHUFcdNBSOSbKH/ESor90KROXLCQ==", - "optional": true, - "requires": { - "tslib": "^2.3.1" - }, - "dependencies": { - "tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - } - } - }, - "@aws-sdk/url-parser": { - "version": "3.292.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/url-parser/-/url-parser-3.292.0.tgz", - "integrity": "sha512-NZeAuZCk1x6TIiWuRfbOU6wHPBhf0ly2qOHzWut4BCH+b4RrDmFF8EmXcH1auEfGhE7yRyR6XqIN0t3S+hYACA==", - "optional": true, - "requires": { - "@aws-sdk/querystring-parser": "3.292.0", - "@aws-sdk/types": "3.292.0", - "tslib": "^2.3.1" - }, - "dependencies": { - "tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - } - } - }, - "@aws-sdk/util-base64": { - "version": "3.292.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-base64/-/util-base64-3.292.0.tgz", - "integrity": "sha512-zjNCwNdy617yFvEjZorepNWXB2sQCVfsShCwFy/kIQ5iW5tT2jQKaqc0K77diU9atkooxw9p1W9m9sOgrkOFNw==", - "optional": true, - "requires": { - "@aws-sdk/util-buffer-from": "3.292.0", - "tslib": "^2.3.1" - }, - "dependencies": { - "tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - } - } - }, - "@aws-sdk/util-body-length-browser": { - "version": "3.292.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-body-length-browser/-/util-body-length-browser-3.292.0.tgz", - "integrity": "sha512-Wd/BM+JsMiKvKs/bN3z6TredVEHh2pKudGfg3CSjTRpqFpOG903KDfyHBD42yg5PuCHoHoewJvTPKwgn7/vhaw==", - "optional": true, - "requires": { - "tslib": "^2.3.1" - }, - "dependencies": { - "tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - } - } - }, - "@aws-sdk/util-body-length-node": { - "version": "3.292.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-body-length-node/-/util-body-length-node-3.292.0.tgz", - "integrity": "sha512-BBgipZ2P6RhogWE/qj0oqpdlyd3iSBYmb+aD/TBXwB2lA/X8A99GxweBd/kp06AmcJRoMS9WIXgbWkiiBlRlSA==", - "optional": true, - "requires": { - "tslib": "^2.3.1" - }, - "dependencies": { - "tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - } - } - }, - "@aws-sdk/util-buffer-from": { - "version": "3.292.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-buffer-from/-/util-buffer-from-3.292.0.tgz", - "integrity": "sha512-RxNZjLoXNxHconH9TYsk5RaEBjSgTtozHeyIdacaHPj5vlQKi4hgL2hIfKeeNiAfQEVjaUFF29lv81xpNMzVMQ==", - "optional": true, - "requires": { - "@aws-sdk/is-array-buffer": "3.292.0", - "tslib": "^2.3.1" - }, - "dependencies": { - "tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - } - } - }, - "@aws-sdk/util-config-provider": { - "version": "3.292.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-config-provider/-/util-config-provider-3.292.0.tgz", - "integrity": "sha512-t3noYll6bPRSxeeNNEkC5czVjAiTPcsq00OwfJ2xyUqmquhLEfLwoJKmrT1uP7DjIEXdUtfoIQ2jWiIVm/oO5A==", - "optional": true, - "requires": { - "tslib": "^2.3.1" - }, - "dependencies": { - "tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - } - } - }, - "@aws-sdk/util-defaults-mode-browser": { - "version": "3.292.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-defaults-mode-browser/-/util-defaults-mode-browser-3.292.0.tgz", - "integrity": "sha512-7+zVUlMGfa8/KT++9humHo6IDxTnxMCmWUj5jVNlkpk6h7Ecmppf7aXotviyVIA43lhtz0p2AErs0N0ekEUK+w==", - "optional": true, - "requires": { - "@aws-sdk/property-provider": "3.292.0", - "@aws-sdk/types": "3.292.0", - "bowser": "^2.11.0", - "tslib": "^2.3.1" - }, - "dependencies": { - "tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - } - } - }, - "@aws-sdk/util-defaults-mode-node": { - "version": "3.292.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-defaults-mode-node/-/util-defaults-mode-node-3.292.0.tgz", - "integrity": "sha512-SSIw85eF4BVs0fOJRyshT+R3b/UmBPhiVKCUZm2rq6+lIGkDPiSwQU3d/80AhXtiL5SFT/IzAKKgQd8qMa7q3A==", - "optional": true, - "requires": { - "@aws-sdk/config-resolver": "3.292.0", - "@aws-sdk/credential-provider-imds": "3.292.0", - "@aws-sdk/node-config-provider": "3.292.0", - "@aws-sdk/property-provider": "3.292.0", - "@aws-sdk/types": "3.292.0", - "tslib": "^2.3.1" - }, - "dependencies": { - "tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - } - } - }, - "@aws-sdk/util-endpoints": { - "version": "3.293.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.293.0.tgz", - "integrity": "sha512-R/99aNV49Refpv5guiUjEUrZYlvnfaNBniB+/ZtMO3ixxUopapssCrUivuJrmhccmrYaTCZw7dRzIWjU1jJhKg==", - "optional": true, - "requires": { - "@aws-sdk/types": "3.292.0", - "tslib": "^2.3.1" - }, - "dependencies": { - "tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - } - } - }, - "@aws-sdk/util-hex-encoding": { - "version": "3.292.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-hex-encoding/-/util-hex-encoding-3.292.0.tgz", - "integrity": "sha512-qBd5KFIUywQ3qSSbj814S2srk0vfv8A6QMI+Obs1y2LHZFdQN5zViptI4UhXhKOHe+NnrHWxSuLC/LMH6q3SmA==", - "optional": true, - "requires": { - "tslib": "^2.3.1" - }, - "dependencies": { - "tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - } - } - }, - "@aws-sdk/util-locate-window": { - "version": "3.292.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.292.0.tgz", - "integrity": "sha512-6xnFJXZI9pKw5lQCDvuWA5PnOaUtNRKWwdxvGkkLx5orboFaoVMS6zowjSQxwVNRjW82u6dYNkhmj9mZ8VSjWg==", - "optional": true, - "requires": { - "tslib": "^2.3.1" - }, - "dependencies": { - "tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - } - } - }, - "@aws-sdk/util-middleware": { - "version": "3.292.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-middleware/-/util-middleware-3.292.0.tgz", - "integrity": "sha512-KjhS7flfoBKDxbiBZjLjMvEizXgjfQb7GQEItgzGoI9rfGCmZtvqCcqQQoIlxb8bIzGRggAUHtBGWnlLbpb+GQ==", - "optional": true, - "requires": { - "tslib": "^2.3.1" - }, - "dependencies": { - "tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - } - } - }, - "@aws-sdk/util-retry": { - "version": "3.292.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-retry/-/util-retry-3.292.0.tgz", - "integrity": "sha512-JEHyF7MpVeRF5uR4LDYgpOKcFpOPiAj8TqN46SVOQQcL1K+V7cSr7O7N7J6MwJaN9XOzAcBadeIupMm7/BFbgw==", - "optional": true, - "requires": { - "@aws-sdk/service-error-classification": "3.292.0", - "tslib": "^2.3.1" - }, - "dependencies": { - "tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - } - } - }, - "@aws-sdk/util-uri-escape": { - "version": "3.292.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-uri-escape/-/util-uri-escape-3.292.0.tgz", - "integrity": "sha512-hOQtUMQ4VcQ9iwKz50AoCp1XBD5gJ9nly/gJZccAM7zSA5mOO8RRKkbdonqquVHxrO0CnYgiFeCh3V35GFecUw==", - "optional": true, - "requires": { - "tslib": "^2.3.1" - }, - "dependencies": { - "tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - } - } - }, - "@aws-sdk/util-user-agent-browser": { - "version": "3.292.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.292.0.tgz", - "integrity": "sha512-dld+lpC3QdmTQHdBWJ0WFDkXDSrJgfz03q6mQ8+7H+BC12ZhT0I0g9iuvUjolqy7QR00OxOy47Y9FVhq8EC0Gg==", - "optional": true, - "requires": { - "@aws-sdk/types": "3.292.0", - "bowser": "^2.11.0", - "tslib": "^2.3.1" - }, - "dependencies": { - "tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - } - } - }, - "@aws-sdk/util-user-agent-node": { - "version": "3.292.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.292.0.tgz", - "integrity": "sha512-f+NfIMal5E61MDc5WGhUEoicr7b1eNNhA+GgVdSB/Hg5fYhEZvFK9RZizH5rrtsLjjgcr9nPYSR7/nDKCJLumw==", - "optional": true, - "requires": { - "@aws-sdk/node-config-provider": "3.292.0", - "@aws-sdk/types": "3.292.0", - "tslib": "^2.3.1" - }, - "dependencies": { - "tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - } - } - }, - "@aws-sdk/util-utf8": { - "version": "3.292.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-utf8/-/util-utf8-3.292.0.tgz", - "integrity": "sha512-FPkj+Z59/DQWvoVu2wFaRncc3KVwe/pgK3MfVb0Lx+Ibey5KUx+sNpJmYcVYHUAe/Nv/JeIpOtYuC96IXOnI6w==", - "optional": true, - "requires": { - "@aws-sdk/util-buffer-from": "3.292.0", - "tslib": "^2.3.1" - }, - "dependencies": { - "tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - } - } - }, - "@aws-sdk/util-utf8-browser": { - "version": "3.259.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-utf8-browser/-/util-utf8-browser-3.259.0.tgz", - "integrity": "sha512-UvFa/vR+e19XookZF8RzFZBrw2EUkQWxiBW0yYQAhvk3C+QVGl0H3ouca8LDBlBfQKXwmW3huo/59H8rwb1wJw==", - "optional": true, - "requires": { - "tslib": "^2.3.1" - }, - "dependencies": { - "tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - } - } - }, - "@colors/colors": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", - "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", - "dev": true - }, - "@eslint/eslintrc": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.0.tgz", - "integrity": "sha512-fluIaaV+GyV24CCu/ggiHdV+j4RNh85yQnAYS/G2mZODZgGmmlrgCydjUcV3YvxCm9x8nMAfThsqTni4KiXT4A==", - "dev": true, - "requires": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.4.0", - "globals": "^13.19.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "dependencies": { - "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - } - } - }, - "@eslint/js": { - "version": "8.35.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.35.0.tgz", - "integrity": "sha512-JXdzbRiWclLVoD8sNUjR443VVlYqiYmDVT6rGUEIEHU5YJW0gaVZwV2xgM7D4arkvASqD0IlLUVjHiFuxaftRw==", - "dev": true - }, - "@hapi/hoek": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", - "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==" - }, - "@hapi/topo": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", - "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", - "requires": { - "@hapi/hoek": "^9.0.0" - } - }, - "@hcaptcha/types": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@hcaptcha/types/-/types-1.0.3.tgz", - "integrity": "sha512-1mbU6eSGawRrqeahRrOzZo/SVLI6oZ5/azuBpSyVrRRR96CnS3fOVDWfzxpngfxKD0/I9Rwu6c/3ITqD8rXeTQ==", - "dev": true - }, - "@humanwhocodes/config-array": { - "version": "0.11.8", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", - "integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==", - "dev": true, - "requires": { - "@humanwhocodes/object-schema": "^1.2.1", - "debug": "^4.1.1", - "minimatch": "^3.0.5" - }, - "dependencies": { - "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - } - } - }, - "@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": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", - "dev": true - }, - "@mapbox/node-pre-gyp": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.10.tgz", - "integrity": "sha512-4ySo4CjzStuprMwk35H5pPbkymjv1SF3jGLj6rAHp/xT/RF7TL7bd9CTm1xDY49K2qF7jmR/g7k+SkLETP6opA==", - "requires": { - "detect-libc": "^2.0.0", - "https-proxy-agent": "^5.0.0", - "make-dir": "^3.1.0", - "node-fetch": "^2.6.7", - "nopt": "^5.0.0", - "npmlog": "^5.0.1", - "rimraf": "^3.0.2", - "semver": "^7.3.5", - "tar": "^6.1.11" - } - }, - "@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "requires": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - } - }, - "@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true - }, - "@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "requires": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - } - }, - "@oozcitak/dom": { - "version": "0.0.11", - "resolved": "https://registry.npmjs.org/@oozcitak/dom/-/dom-0.0.11.tgz", - "integrity": "sha512-BUNzbWpSn0Y7Yo8qgjVCM0axw2A6ncZaY7iC8msZIpuL6vYDP19zRqYI5bYWgBkgDXiap4cFEFsFrauyYAgKnw==", - "requires": { - "@oozcitak/infra": "1.0.4", - "@oozcitak/url": "0.0.8", - "@oozcitak/util": "1.0.2" - }, - "dependencies": { - "@oozcitak/util": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@oozcitak/util/-/util-1.0.2.tgz", - "integrity": "sha512-4n8B1cWlJleSOSba5gxsMcN4tO8KkkcvXhNWW+ADqvq9Xj+Lrl9uCa90GRpjekqQJyt84aUX015DG81LFpZYXA==" - } - } - }, - "@oozcitak/infra": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@oozcitak/infra/-/infra-1.0.4.tgz", - "integrity": "sha512-PIYPFk/MUzdhdeEBWL8TOEmEHyN60pWDCzXdN6XyNQHwt1i0YNUCOZ434Qa8WNuwJlOCNTrZFK8nlirIOF06Eg==", - "requires": { - "@oozcitak/util": "1.0.2" - }, - "dependencies": { - "@oozcitak/util": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@oozcitak/util/-/util-1.0.2.tgz", - "integrity": "sha512-4n8B1cWlJleSOSba5gxsMcN4tO8KkkcvXhNWW+ADqvq9Xj+Lrl9uCa90GRpjekqQJyt84aUX015DG81LFpZYXA==" - } - } - }, - "@oozcitak/url": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/@oozcitak/url/-/url-0.0.8.tgz", - "integrity": "sha512-PR1ZCwaYvm781f0V14y2Uu8CM94k/znGfNpzJive5XjuDuCtMDFB3MYhSdVNZDG95faupVuFTph2lzn38S0zKg==", - "requires": { - "@oozcitak/infra": "1.0.3", - "@oozcitak/util": "1.0.2", - "@oozcitak/uts46": "0.0.8" - }, - "dependencies": { - "@oozcitak/infra": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@oozcitak/infra/-/infra-1.0.3.tgz", - "integrity": "sha512-9O2wxXGnRzy76O1XUxESxDGsXT5kzETJPvYbreO4mv6bqe1+YSuux2cZTagjJ/T4UfEwFJz5ixanOqB0QgYAag==", - "requires": { - "@oozcitak/util": "1.0.1" - }, - "dependencies": { - "@oozcitak/util": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@oozcitak/util/-/util-1.0.1.tgz", - "integrity": "sha512-dFwFqcKrQnJ2SapOmRD1nQWEZUtbtIy9Y6TyJquzsalWNJsKIPxmTI0KG6Ypyl8j7v89L2wixH9fQDNrF78hKg==" - } - } - }, - "@oozcitak/util": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@oozcitak/util/-/util-1.0.2.tgz", - "integrity": "sha512-4n8B1cWlJleSOSba5gxsMcN4tO8KkkcvXhNWW+ADqvq9Xj+Lrl9uCa90GRpjekqQJyt84aUX015DG81LFpZYXA==" - } - } - }, - "@oozcitak/util": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@oozcitak/util/-/util-1.0.3.tgz", - "integrity": "sha512-md38Xc0kBJ8I4aBI13xtX7r4RtSi9HByNVaqcVQeal3PSSbPKmtX1l6+8/YbfaIcP+fyLuwnOxHpYoh/R2DONw==" - }, - "@oozcitak/uts46": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/@oozcitak/uts46/-/uts46-0.0.8.tgz", - "integrity": "sha512-/m/ytADxqWfGiNvenR5wkX7NGBRKQlypufv0w2NRAoWIGDuRLeweClJPRxV41Mln3HNtlF08OKoFFRUG8ztvGQ==", - "requires": { - "@oozcitak/util": "1.0.2", - "punycode": "2.1.1" - }, - "dependencies": { - "@oozcitak/util": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@oozcitak/util/-/util-1.0.2.tgz", - "integrity": "sha512-4n8B1cWlJleSOSba5gxsMcN4tO8KkkcvXhNWW+ADqvq9Xj+Lrl9uCa90GRpjekqQJyt84aUX015DG81LFpZYXA==" - }, - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" - } - } - }, - "@redis/bloom": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-1.2.0.tgz", - "integrity": "sha512-HG2DFjYKbpNmVXsa0keLHp/3leGJz1mjh09f2RLGGLQZzSHpkmZWuwJbAvo3QcRY8p80m5+ZdXZdYOSBLlp7Cg==", - "requires": {} - }, - "@redis/client": { - "version": "1.5.6", - "resolved": "https://registry.npmjs.org/@redis/client/-/client-1.5.6.tgz", - "integrity": "sha512-dFD1S6je+A47Lj22jN/upVU2fj4huR7S9APd7/ziUXsIXDL+11GPYti4Suv5y8FuXaN+0ZG4JF+y1houEJ7ToA==", - "requires": { - "cluster-key-slot": "1.1.2", - "generic-pool": "3.9.0", - "yallist": "4.0.0" - } - }, - "@redis/graph": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@redis/graph/-/graph-1.1.0.tgz", - "integrity": "sha512-16yZWngxyXPd+MJxeSr0dqh2AIOi8j9yXKcKCwVaKDbH3HTuETpDVPcLujhFYVPtYrngSco31BUcSa9TH31Gqg==", - "requires": {} - }, - "@redis/json": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@redis/json/-/json-1.0.4.tgz", - "integrity": "sha512-LUZE2Gdrhg0Rx7AN+cZkb1e6HjoSKaeeW8rYnt89Tly13GBI5eP4CwDVr+MY8BAYfCg4/N15OUrtLoona9uSgw==", - "requires": {} - }, - "@redis/search": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@redis/search/-/search-1.1.2.tgz", - "integrity": "sha512-/cMfstG/fOh/SsE+4/BQGeuH/JJloeWuH+qJzM8dbxuWvdWibWAOAHHCZTMPhV3xIlH4/cUEIA8OV5QnYpaVoA==", - "requires": {} - }, - "@redis/time-series": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-1.0.4.tgz", - "integrity": "sha512-ThUIgo2U/g7cCuZavucQTQzA9g9JbDDY2f64u3AbAoz/8vE2lt2U37LamDUVChhaDA3IRT9R6VvJwqnUfTJzng==", - "requires": {} - }, - "@sideway/address": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.4.tgz", - "integrity": "sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw==", - "requires": { - "@hapi/hoek": "^9.0.0" - } - }, - "@sideway/formula": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz", - "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==" - }, - "@sideway/pinpoint": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", - "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==" - }, - "@sindresorhus/is": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", - "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==" - }, - "@szmarczak/http-timer": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", - "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", - "requires": { - "defer-to-connect": "^2.0.0" - } - }, - "@types/bcrypt": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@types/bcrypt/-/bcrypt-5.0.0.tgz", - "integrity": "sha512-agtcFKaruL8TmcvqbndlqHPSJgsolhf/qPWchFlgnW1gECTN/nKbFcoFnvKAQRFfKbh+BO6A3SWdJu9t+xF3Lw==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/body-parser": { - "version": "1.19.2", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", - "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==", - "dev": true, - "requires": { - "@types/connect": "*", - "@types/node": "*" - } - }, - "@types/cacheable-request": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz", - "integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==", - "requires": { - "@types/http-cache-semantics": "*", - "@types/keyv": "^3.1.4", - "@types/node": "*", - "@types/responselike": "^1.0.0" - } - }, - "@types/connect": { - "version": "3.4.35", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", - "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/cors": { - "version": "2.8.13", - "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.13.tgz", - "integrity": "sha512-RG8AStHlUiV5ysZQKq97copd2UmVYw3/pRMLefISZ3S1hK104Cwm7iLQ3fTKx+lsUH2CE8FlLaYeEA2LSeqYUA==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/dicer": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/@types/dicer/-/dicer-0.2.2.tgz", - "integrity": "sha512-UPLqCYey+jn5Mf57KFDwxD/7VZYDsbYUi3iyTehLFVjlbvl/JcUTPaot8uKNYLO0EoZpey+rC/s5AF3VxfeC2Q==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/express": { - "version": "4.17.17", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.17.tgz", - "integrity": "sha512-Q4FmmuLGBG58btUnfS1c1r/NQdlp3DMfGDGig8WhfpA2YRUtEkxAjkZb0yvplJGYdF1fsQ81iMDcH24sSCNC/Q==", - "dev": true, - "requires": { - "@types/body-parser": "*", - "@types/express-serve-static-core": "^4.17.33", - "@types/qs": "*", - "@types/serve-static": "*" - } - }, - "@types/express-serve-static-core": { - "version": "4.17.33", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.33.tgz", - "integrity": "sha512-TPBqmR/HRYI3eC2E5hmiivIzv+bidAfXofM+sbonAGvyDhySGw9/PQZFt2BLOrjUUR++4eJVpx6KnLQK1Fk9tA==", - "dev": true, - "requires": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*" - } - }, - "@types/fs-extra": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-11.0.1.tgz", - "integrity": "sha512-MxObHvNl4A69ofaTRU8DFqvgzzv8s9yRtaPPm5gud9HDNvpB3GPQFvNuTWAI59B9huVGV5jXYJwbCsmBsOGYWA==", - "dev": true, - "requires": { - "@types/jsonfile": "*", - "@types/node": "*" - } - }, - "@types/http-cache-semantics": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz", - "integrity": "sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==" - }, - "@types/json-schema": { - "version": "7.0.11", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", - "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", - "dev": true - }, - "@types/jsonfile": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/@types/jsonfile/-/jsonfile-6.1.1.tgz", - "integrity": "sha512-GSgiRCVeapDN+3pqA35IkQwasaCh/0YFH5dEF6S88iDvEn901DjOeH3/QPY+XYP1DFzDZPvIvfeEgk+7br5png==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/keyv": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz", - "integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==", - "requires": { - "@types/node": "*" - } - }, - "@types/mime": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-3.0.1.tgz", - "integrity": "sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==", - "dev": true - }, - "@types/mongoose-unique-validator": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/@types/mongoose-unique-validator/-/mongoose-unique-validator-1.0.7.tgz", - "integrity": "sha512-wgPWN7x9mHdy8x1jcluDF7EDWFb/s8LD7skLr012C8i2Q3tUhdTlp3JxRhFbEM3TD7J1INaoTrWaSaguhuqilQ==", - "dev": true, - "requires": { - "mongoose": "^6.3.0" - }, - "dependencies": { - "bson": { - "version": "4.7.2", - "resolved": "https://registry.npmjs.org/bson/-/bson-4.7.2.tgz", - "integrity": "sha512-Ry9wCtIZ5kGqkJoi6aD8KjxFZEx78guTQDnpXWiNthsxzrxAK/i8E6pCHAIZTbaEFWcOCvbecMukfK7XUvyLpQ==", - "dev": true, - "requires": { - "buffer": "^5.6.0" - } - }, - "buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "dev": true, - "requires": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, - "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "requires": { - "ms": "2.1.2" - }, - "dependencies": { - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - } - } - }, - "mongodb": { - "version": "4.14.0", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.14.0.tgz", - "integrity": "sha512-coGKkWXIBczZPr284tYKFLg+KbGPPLlSbdgfKAb6QqCFt5bo5VFZ50O3FFzsw4rnkqjwT6D8Qcoo9nshYKM7Mg==", - "dev": true, - "requires": { - "@aws-sdk/credential-providers": "^3.186.0", - "bson": "^4.7.0", - "mongodb-connection-string-url": "^2.5.4", - "saslprep": "^1.0.3", - "socks": "^2.7.1" - } - }, - "mongoose": { - "version": "6.10.5", - "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-6.10.5.tgz", - "integrity": "sha512-y4HL4/9EySec7L0gJ+pCm9heLSF45uIIvRS4fSeAFWDfe4vXW1vRZJwTz7OGkra3ZoSfRnFTo9bNZkuggDVlVA==", - "dev": true, - "requires": { - "bson": "^4.7.0", - "kareem": "2.5.1", - "mongodb": "4.14.0", - "mpath": "0.9.0", - "mquery": "4.0.3", - "ms": "2.1.3", - "sift": "16.0.1" - } - }, - "mquery": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/mquery/-/mquery-4.0.3.tgz", - "integrity": "sha512-J5heI+P08I6VJ2Ky3+33IpCdAvlYGTSUjwTPxkAr8i8EoduPMBX2OY/wa3IKZIQl7MU4SbFk8ndgSKyB/cl1zA==", - "dev": true, - "requires": { - "debug": "4.x" - } - }, - "ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true - } - } - }, - "@types/morgan": { - "version": "1.9.4", - "resolved": "https://registry.npmjs.org/@types/morgan/-/morgan-1.9.4.tgz", - "integrity": "sha512-cXoc4k+6+YAllH3ZHmx4hf7La1dzUk6keTR4bF4b4Sc0mZxU/zK4wO7l+ZzezXm/jkYj/qC+uYGZrarZdIVvyQ==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/ndarray": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@types/ndarray/-/ndarray-1.0.11.tgz", - "integrity": "sha512-hOZVTN24zDHwCHaW7mF9n1vHJt83fZhNZ0YYRBwQGhA96yBWWDPTDDlqJatagHIOJB0a4xoNkNc+t/Cxd+6qUA==", - "dev": true - }, - "@types/node": { - "version": "18.14.6", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.14.6.tgz", - "integrity": "sha512-93+VvleD3mXwlLI/xASjw0FzKcwzl3OdTCzm1LaRfqgS21gfFtK3zDXM5Op9TeeMsJVOaJ2VRDpT9q4Y3d0AvA==" - }, - "@types/node-rsa": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@types/node-rsa/-/node-rsa-1.1.1.tgz", - "integrity": "sha512-itzxtaBgk4OMbrCawVCvas934waMZWjW17v7EYgFVlfYS/cl0/P7KZdojWCq9SDJMI5cnLQLUP8ayhVCTY8TEg==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/nodemailer": { - "version": "6.4.7", - "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.7.tgz", - "integrity": "sha512-f5qCBGAn/f0qtRcd4SEn88c8Fp3Swct1731X4ryPKqS61/A3LmmzN8zaEz7hneJvpjFbUUgY7lru/B/7ODTazg==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/qs": { - "version": "6.9.7", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", - "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==", - "dev": true - }, - "@types/range-parser": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", - "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==", - "dev": true - }, - "@types/responselike": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.0.tgz", - "integrity": "sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==", - "requires": { - "@types/node": "*" - } - }, - "@types/semver": { - "version": "7.3.13", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz", - "integrity": "sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==", - "dev": true - }, - "@types/serve-static": { - "version": "1.15.1", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.1.tgz", - "integrity": "sha512-NUo5XNiAdULrJENtJXZZ3fHtfMolzZwczzBbnAeBbqBwG+LaG6YaJtuwzwGSQZ2wsCrxjEhNNjAkKigy3n8teQ==", - "dev": true, - "requires": { - "@types/mime": "*", - "@types/node": "*" - } - }, - "@types/validator": { - "version": "13.7.14", - "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.7.14.tgz", - "integrity": "sha512-J6OAed6rhN6zyqL9Of6ZMamhlsOEU/poBVvbHr/dKOYKTeuYYMlDkMv+b6UUV0o2i0tw73cgyv/97WTWaUl0/g==", - "dev": true - }, - "@types/w3c-css-typed-object-model-level-1": { - "version": "20180410.0.5", - "resolved": "https://registry.npmjs.org/@types/w3c-css-typed-object-model-level-1/-/w3c-css-typed-object-model-level-1-20180410.0.5.tgz", - "integrity": "sha512-khoqDQqA/fUdK6mEJtQEfMAPCKkk8E1D+ahM/0HCy3mc5L33wJinqQNXu9Uf4+mqzMvORljHg76jvrmwS0bEjA==", - "dev": true - }, - "@types/webidl-conversions": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.0.tgz", - "integrity": "sha512-xTE1E+YF4aWPJJeUzaZI5DRntlkY3+BCVJi0axFptnjGmAoWxkyREIh/XMrfxVLejwQxMCfDXdICo0VLxThrog==" - }, - "@types/whatwg-url": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-8.2.2.tgz", - "integrity": "sha512-FtQu10RWgn3D9U4aazdwIE2yzphmTJREDqNdODHrbrZmmMqI0vMheC/6NE/J1Yveaj8H+ela+YwWTjq5PGmuhA==", - "requires": { - "@types/node": "*", - "@types/webidl-conversions": "*" - } - }, - "@typescript-eslint/eslint-plugin": { - "version": "5.54.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.54.1.tgz", - "integrity": "sha512-a2RQAkosH3d3ZIV08s3DcL/mcGc2M/UC528VkPULFxR9VnVPT8pBu0IyBAJJmVsCmhVfwQX1v6q+QGnmSe1bew==", - "dev": true, - "requires": { - "@typescript-eslint/scope-manager": "5.54.1", - "@typescript-eslint/type-utils": "5.54.1", - "@typescript-eslint/utils": "5.54.1", - "debug": "^4.3.4", - "grapheme-splitter": "^1.0.4", - "ignore": "^5.2.0", - "natural-compare-lite": "^1.4.0", - "regexpp": "^3.2.0", - "semver": "^7.3.7", - "tsutils": "^3.21.0" - }, - "dependencies": { - "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - } - } - }, - "@typescript-eslint/parser": { - "version": "5.54.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.54.1.tgz", - "integrity": "sha512-8zaIXJp/nG9Ff9vQNh7TI+C3nA6q6iIsGJ4B4L6MhZ7mHnTMR4YP5vp2xydmFXIy8rpyIVbNAG44871LMt6ujg==", - "dev": true, - "requires": { - "@typescript-eslint/scope-manager": "5.54.1", - "@typescript-eslint/types": "5.54.1", - "@typescript-eslint/typescript-estree": "5.54.1", - "debug": "^4.3.4" - }, - "dependencies": { - "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - } - } - }, - "@typescript-eslint/scope-manager": { - "version": "5.54.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.54.1.tgz", - "integrity": "sha512-zWKuGliXxvuxyM71UA/EcPxaviw39dB2504LqAmFDjmkpO8qNLHcmzlh6pbHs1h/7YQ9bnsO8CCcYCSA8sykUg==", - "dev": true, - "requires": { - "@typescript-eslint/types": "5.54.1", - "@typescript-eslint/visitor-keys": "5.54.1" - } - }, - "@typescript-eslint/type-utils": { - "version": "5.54.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.54.1.tgz", - "integrity": "sha512-WREHsTz0GqVYLIbzIZYbmUUr95DKEKIXZNH57W3s+4bVnuF1TKe2jH8ZNH8rO1CeMY3U4j4UQeqPNkHMiGem3g==", - "dev": true, - "requires": { - "@typescript-eslint/typescript-estree": "5.54.1", - "@typescript-eslint/utils": "5.54.1", - "debug": "^4.3.4", - "tsutils": "^3.21.0" - }, - "dependencies": { - "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - } - } - }, - "@typescript-eslint/types": { - "version": "5.54.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.54.1.tgz", - "integrity": "sha512-G9+1vVazrfAfbtmCapJX8jRo2E4MDXxgm/IMOF4oGh3kq7XuK3JRkOg6y2Qu1VsTRmWETyTkWt1wxy7X7/yLkw==", - "dev": true - }, - "@typescript-eslint/typescript-estree": { - "version": "5.54.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.54.1.tgz", - "integrity": "sha512-bjK5t+S6ffHnVwA0qRPTZrxKSaFYocwFIkZx5k7pvWfsB1I57pO/0M0Skatzzw1sCkjJ83AfGTL0oFIFiDX3bg==", - "dev": true, - "requires": { - "@typescript-eslint/types": "5.54.1", - "@typescript-eslint/visitor-keys": "5.54.1", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "semver": "^7.3.7", - "tsutils": "^3.21.0" - }, - "dependencies": { - "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - } - } - }, - "@typescript-eslint/utils": { - "version": "5.54.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.54.1.tgz", - "integrity": "sha512-IY5dyQM8XD1zfDe5X8jegX6r2EVU5o/WJnLu/znLPWCBF7KNGC+adacXnt5jEYS9JixDcoccI6CvE4RCjHMzCQ==", - "dev": true, - "requires": { - "@types/json-schema": "^7.0.9", - "@types/semver": "^7.3.12", - "@typescript-eslint/scope-manager": "5.54.1", - "@typescript-eslint/types": "5.54.1", - "@typescript-eslint/typescript-estree": "5.54.1", - "eslint-scope": "^5.1.1", - "eslint-utils": "^3.0.0", - "semver": "^7.3.7" - }, - "dependencies": { - "eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "requires": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - } - } - } - }, - "@typescript-eslint/visitor-keys": { - "version": "5.54.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.54.1.tgz", - "integrity": "sha512-q8iSoHTgwCfgcRJ2l2x+xCbu8nBlRAlsQ33k24Adj8eoVBE0f8dUeI+bAa8F84Mv05UGbAx57g2zrRsYIooqQg==", - "dev": true, - "requires": { - "@typescript-eslint/types": "5.54.1", - "eslint-visitor-keys": "^3.3.0" - } - }, - "abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" - }, - "accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "requires": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - } - }, - "acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==" - }, - "acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "requires": {} - }, - "acorn-node": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/acorn-node/-/acorn-node-1.8.2.tgz", - "integrity": "sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A==", - "requires": { - "acorn": "^7.0.0", - "acorn-walk": "^7.0.0", - "xtend": "^4.0.2" - } - }, - "acorn-walk": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", - "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==" - }, - "agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "requires": { - "debug": "4" - }, - "dependencies": { - "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "requires": { - "ms": "2.1.2" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - } - } - }, - "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "aproba": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", - "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==" - }, - "are-we-there-yet": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", - "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", - "requires": { - "delegates": "^1.0.0", - "readable-stream": "^3.6.0" - }, - "dependencies": { - "readable-stream": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.1.tgz", - "integrity": "sha512-+rQmrWMYGA90yenhTYsLWAsLsqVC8osOw6PKE1HDYiO0gdPeKe/xDHNzIAIn4C91YQ6oenEhfYqqc1883qHbjQ==", - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, - "string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "requires": { - "safe-buffer": "~5.2.0" - } - } - } - }, - "argparse": { - "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 - }, - "arr-flatten": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==" - }, - "array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" - }, - "array-from": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/array-from/-/array-from-2.1.1.tgz", - "integrity": "sha512-GQTc6Uupx1FCavi5mPzBvVT7nEOeWMmUA9P95wpfpW1XwMSKs+KaymD5C2Up7KAUKg/mYwbsUYzdZWcoajlNZg==" - }, - "array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true - }, - "arraybuffer-to-string": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/arraybuffer-to-string/-/arraybuffer-to-string-1.0.2.tgz", - "integrity": "sha512-WbIYlLVmvIAyUBdQRRuyGOJRriOQy9OAsWcyURmsRQp9+g647hdMSS2VFKXbJLVw0daUu06hqwLXm9etVrXI9A==" - }, - "asn1": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", - "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", - "requires": { - "safer-buffer": "~2.1.0" - } - }, - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==" - }, - "async": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.3.tgz", - "integrity": "sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g==", - "dev": true - }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" - }, - "atob-lite": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/atob-lite/-/atob-lite-2.0.0.tgz", - "integrity": "sha512-LEeSAWeh2Gfa2FtlQE1shxQ8zi5F9GHarrGKz08TMdODD5T4eH6BMsvtnhbWZ+XQn+Gb6om/917ucvRu7l7ukw==" - }, - "available-typed-arrays": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", - "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==" - }, - "aws-sdk": { - "version": "2.1328.0", - "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1328.0.tgz", - "integrity": "sha512-ud8ieE+hGX/cWHkQ9kMxQw6w+onbv71PDrcPmP2j8cmYv5IPlM5Zh8/tpsmXApLYDmQMuZ3TtssiB1KmoSbzgA==", - "requires": { - "buffer": "4.9.2", - "events": "1.1.1", - "ieee754": "1.1.13", - "jmespath": "0.16.0", - "querystring": "0.2.0", - "sax": "1.2.1", - "url": "0.10.3", - "util": "^0.12.4", - "uuid": "8.0.0", - "xml2js": "0.4.19" - } - }, - "aws-sign2": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==" - }, - "aws4": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.12.0.tgz", - "integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==" - }, - "balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" - }, - "base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" - }, - "basic-auth": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", - "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", - "requires": { - "safe-buffer": "5.1.2" - }, - "dependencies": { - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - } - } - }, - "bcrypt": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.1.0.tgz", - "integrity": "sha512-RHBS7HI5N5tEnGTmtR/pppX0mmDSBpQ4aCBsj7CEQfYXDcO74A8sIBYcJMuCsis2E81zDxeENYhv66oZwLiA+Q==", - "requires": { - "@mapbox/node-pre-gyp": "^1.0.10", - "node-addon-api": "^5.0.0" - } - }, - "bcrypt-pbkdf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", - "requires": { - "tweetnacl": "^0.14.3" - } - }, - "bit-buffer": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/bit-buffer/-/bit-buffer-0.2.5.tgz", - "integrity": "sha512-x1yGnmXvFg6e3DiyRztElbcn1bsCTFSoM/ncAzY62uE0JdTl5xlKJd0ooqLYoPbhdsnpehSIQrdIvclcZJYwiA==" - }, - "bmp-js": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/bmp-js/-/bmp-js-0.1.0.tgz", - "integrity": "sha512-vHdS19CnY3hwiNdkaqk93DvjVLfbEcI8mys4UjuWrlX1haDmroo8o4xCzh4wD6DGV6HxRCyauwhHRqMTfERtjw==" - }, - "body-parser": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", - "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", - "requires": { - "bytes": "3.1.2", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.11.0", - "raw-body": "2.5.1", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - } - }, - "bowser": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.11.0.tgz", - "integrity": "sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==", - "optional": true - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "requires": { - "fill-range": "^7.0.1" - } - }, - "brfs": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brfs/-/brfs-2.0.2.tgz", - "integrity": "sha512-IrFjVtwu4eTJZyu8w/V2gxU7iLTtcHih67sgEdzrhjLBMHp2uYefUBfdM4k2UvcuWMgV7PQDZHSLeNWnLFKWVQ==", - "requires": { - "quote-stream": "^1.0.1", - "resolve": "^1.1.5", - "static-module": "^3.0.2", - "through2": "^2.0.0" - } - }, - "bson": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/bson/-/bson-5.0.1.tgz", - "integrity": "sha512-y09gBGusgHtinMon/GVbv1J6FrXhnr/+6hqLlSmEFzkz6PodqF6TxjyvfvY3AfO+oG1mgUtbC86xSbOlwvM62Q==" - }, - "buffer": { - "version": "4.9.2", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", - "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", - "requires": { - "base64-js": "^1.0.2", - "ieee754": "^1.1.4", - "isarray": "^1.0.0" - } - }, - "buffer-equal": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-0.0.1.tgz", - "integrity": "sha512-RgSV6InVQ9ODPdLWJ5UAqBqJBOg370Nz6ZQtRzpt6nUjc8v0St97uJ4PYC6NztqIScrAXafKM3mZPMygSe1ggA==" - }, - "buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" - }, - "buffer-to-arraybuffer": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/buffer-to-arraybuffer/-/buffer-to-arraybuffer-0.0.5.tgz", - "integrity": "sha512-3dthu5CYiVB1DEJp61FtApNnNndTckcqe4pFcLdvHtrpG+kcyekCJKg4MRiDcFW7A6AODnXB9U4dwQiCW5kzJQ==" - }, - "buffer-to-uint8array": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/buffer-to-uint8array/-/buffer-to-uint8array-1.1.0.tgz", - "integrity": "sha512-JVTSbtA6YuOGdu5NL0ffizsBwuwbTXfV7OC91FhazMz9UKP/KlDS+Z7wuiSRClbnTQz52fJgVXI9YDXQRVl2sQ==" - }, - "bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==" - }, - "cacheable-lookup": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", - "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==" - }, - "cacheable-request": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.2.tgz", - "integrity": "sha512-pouW8/FmiPQbuGpkXQ9BAPv/Mo5xDGANgSNXzTzJ8DrKGuXOssM4wIQRjfanNRh3Yu5cfYPvcorqbhg2KIJtew==", - "requires": { - "clone-response": "^1.0.2", - "get-stream": "^5.1.0", - "http-cache-semantics": "^4.0.0", - "keyv": "^4.0.0", - "lowercase-keys": "^2.0.0", - "normalize-url": "^6.0.1", - "responselike": "^2.0.0" - } - }, - "call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "requires": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - } - }, - "callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true - }, - "caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==" - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "chownr": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", - "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==" - }, - "clip-pixels": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/clip-pixels/-/clip-pixels-1.0.1.tgz", - "integrity": "sha512-nJ22fZvCwkJfMppkOEE7GciLX08rDnVzEJ+U46kBFZtwNzH2V4tNxMWa9Tc365WspCxy1c3NtGJ5EeT4SgjmCA==" - }, - "clone-response": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz", - "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==", - "requires": { - "mimic-response": "^1.0.0" - } - }, - "cluster-key-slot": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz", - "integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==" - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "color-support": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", - "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==" - }, - "colors": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", - "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==" - }, - "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", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" - }, - "concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", - "requires": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "console-control-strings": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==" - }, - "content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", - "requires": { - "safe-buffer": "5.2.1" - } - }, - "content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==" - }, - "convert-source-map": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" - }, - "cookie": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", - "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==" - }, - "cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" - }, - "core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" - }, - "cors": { - "version": "2.8.5", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", - "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", - "requires": { - "object-assign": "^4", - "vary": "^1" - } - }, - "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - } - }, - "cycle": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/cycle/-/cycle-1.0.3.tgz", - "integrity": "sha512-TVF6svNzeQCOpjCqsy0/CSy8VgObG3wXusJ73xW2GbG5rGx7lC8zxDSURicsXI2UsGdi2L0QNRCi745/wUDvsA==", - "dev": true - }, - "d": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", - "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", - "requires": { - "es5-ext": "^0.10.50", - "type": "^1.0.1" - } - }, - "dash-ast": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/dash-ast/-/dash-ast-2.0.1.tgz", - "integrity": "sha512-5TXltWJGc+RdnabUGzhRae1TRq6m4gr+3K2wQX0is5/F2yS6MJXJvLyI3ErAnsAXuJoGqvfVD5icRgim07DrxQ==" - }, - "dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", - "requires": { - "assert-plus": "^1.0.0" - } - }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "decompress-response": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", - "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", - "requires": { - "mimic-response": "^3.1.0" - }, - "dependencies": { - "mimic-response": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", - "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==" - } - } - }, - "deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==" - }, - "defer-to-connect": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", - "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==" - }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" - }, - "delegates": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==" - }, - "depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" - }, - "destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==" - }, - "detect-libc": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.1.tgz", - "integrity": "sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w==" - }, - "dicer": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/dicer/-/dicer-0.2.5.tgz", - "integrity": "sha512-FDvbtnq7dzlPz0wyYlOExifDEZcu8h+rErEXgfxqmLfRfC/kJidEFh4+effJRO3P0xmfqyPbSMG0LveNRfTKVg==", - "requires": { - "readable-stream": "1.1.x", - "streamsearch": "0.1.2" - } - }, - "dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "requires": { - "path-type": "^4.0.0" - } - }, - "doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "requires": { - "esutils": "^2.0.2" - } - }, - "dotenv": { - "version": "16.0.3", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz", - "integrity": "sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==" - }, - "dtype": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/dtype/-/dtype-2.0.0.tgz", - "integrity": "sha512-s2YVcLKdFGS0hpFqJaTwscsyt0E8nNFdmo73Ocd81xNPj4URI4rj6D60A+vFMIw7BXWlb4yRkEwfBqcZzPGiZg==" - }, - "duplexer2": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", - "integrity": "sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==", - "requires": { - "readable-stream": "^2.0.2" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "ecc-jsbn": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", - "requires": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" - } - }, - "ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" - }, - "email-validator": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/email-validator/-/email-validator-2.0.4.tgz", - "integrity": "sha512-gYCwo7kh5S3IDyZPLZf6hSS0MnZT8QmJFqYvbqlDZSbwdZlY6QZWxJ4i/6UhITOJ4XzyI647Bm2MXKCLqnJ4nQ==" - }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" - }, - "encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==" - }, - "end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "requires": { - "once": "^1.4.0" - } - }, - "es5-ext": { - "version": "0.10.62", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.62.tgz", - "integrity": "sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA==", - "requires": { - "es6-iterator": "^2.0.3", - "es6-symbol": "^3.1.3", - "next-tick": "^1.1.0" - } - }, - "es6-iterator": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", - "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", - "requires": { - "d": "1", - "es5-ext": "^0.10.35", - "es6-symbol": "^3.1.1" - } - }, - "es6-map": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/es6-map/-/es6-map-0.1.5.tgz", - "integrity": "sha512-mz3UqCh0uPCIqsw1SSAkB/p0rOzF/M0V++vyN7JqlPtSW/VsYgQBvVvqMLmfBuyMzTpLnNqi6JmcSizs4jy19A==", - "requires": { - "d": "1", - "es5-ext": "~0.10.14", - "es6-iterator": "~2.0.1", - "es6-set": "~0.1.5", - "es6-symbol": "~3.1.1", - "event-emitter": "~0.3.5" - } - }, - "es6-set": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/es6-set/-/es6-set-0.1.6.tgz", - "integrity": "sha512-TE3LgGLDIBX332jq3ypv6bcOpkLO0AslAQo7p2VqX/1N46YNsvIWgvjojjSEnWEGWMhr1qUbYeTSir5J6mFHOw==", - "requires": { - "d": "^1.0.1", - "es5-ext": "^0.10.62", - "es6-iterator": "~2.0.3", - "es6-symbol": "^3.1.3", - "event-emitter": "^0.3.5", - "type": "^2.7.2" - }, - "dependencies": { - "type": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/type/-/type-2.7.2.tgz", - "integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==" - } - } - }, - "es6-symbol": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz", - "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", - "requires": { - "d": "^1.0.1", - "ext": "^1.1.2" - } - }, - "es6-weak-map": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.3.tgz", - "integrity": "sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==", - "requires": { - "d": "1", - "es5-ext": "^0.10.46", - "es6-iterator": "^2.0.3", - "es6-symbol": "^3.1.1" - } - }, - "escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" - }, - "escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true - }, - "escodegen": { - "version": "1.14.3", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz", - "integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==", - "requires": { - "esprima": "^4.0.1", - "estraverse": "^4.2.0", - "esutils": "^2.0.2", - "optionator": "^0.8.1", - "source-map": "~0.6.1" - } - }, - "eslint": { - "version": "8.35.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.35.0.tgz", - "integrity": "sha512-BxAf1fVL7w+JLRQhWl2pzGeSiGqbWumV4WNvc9Rhp6tiCtm4oHnyPBSEtMGZwrQgudFQ+otqzWoPB7x+hxoWsw==", - "dev": true, - "requires": { - "@eslint/eslintrc": "^2.0.0", - "@eslint/js": "8.35.0", - "@humanwhocodes/config-array": "^0.11.8", - "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "ajv": "^6.10.0", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.3.2", - "doctrine": "^3.0.0", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.1.1", - "eslint-utils": "^3.0.0", - "eslint-visitor-keys": "^3.3.0", - "espree": "^9.4.0", - "esquery": "^1.4.2", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "grapheme-splitter": "^1.0.4", - "ignore": "^5.2.0", - "import-fresh": "^3.0.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "js-sdsl": "^4.1.4", - "js-yaml": "^4.1.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.1", - "regexpp": "^3.2.0", - "strip-ansi": "^6.0.1", - "strip-json-comments": "^3.1.0", - "text-table": "^0.2.0" - }, - "dependencies": { - "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "requires": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", - "dev": true, - "requires": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" - } - }, - "prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true - }, - "type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "requires": { - "prelude-ls": "^1.2.1" - } - } - } - }, - "eslint-scope": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", - "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", - "dev": true, - "requires": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "dependencies": { - "estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true - } - } - }, - "eslint-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", - "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", - "dev": true, - "requires": { - "eslint-visitor-keys": "^2.0.0" - }, - "dependencies": { - "eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", - "dev": true - } - } - }, - "eslint-visitor-keys": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", - "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", - "dev": true - }, - "espree": { - "version": "9.4.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.4.1.tgz", - "integrity": "sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg==", - "dev": true, - "requires": { - "acorn": "^8.8.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.3.0" - }, - "dependencies": { - "acorn": { - "version": "8.8.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", - "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", - "dev": true - } - } - }, - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" - }, - "esquery": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", - "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", - "dev": true, - "requires": { - "estraverse": "^5.1.0" - }, - "dependencies": { - "estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true - } - } - }, - "esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "requires": { - "estraverse": "^5.2.0" - }, - "dependencies": { - "estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true - } - } - }, - "estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==" - }, - "estree-is-function": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/estree-is-function/-/estree-is-function-1.0.0.tgz", - "integrity": "sha512-nSCWn1jkSq2QAtkaVLJZY2ezwcFO161HVc174zL1KPW3RJ+O6C3eJb8Nx7OXzvhoEv+nLgSR1g71oWUHUDTrJA==" - }, - "esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==" - }, - "etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==" - }, - "event-emitter": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", - "integrity": "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==", - "requires": { - "d": "1", - "es5-ext": "~0.10.14" - } - }, - "events": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", - "integrity": "sha512-kEcvvCBByWXGnZy6JUlgAp2gBIUjfCAV6P6TgT1/aaQKcmuAEC4OZTV1I4EWQLz2gxZw76atuVyvHhTxvi0Flw==" - }, - "express": { - "version": "4.18.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", - "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", - "requires": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "1.20.1", - "content-disposition": "0.5.4", - "content-type": "~1.0.4", - "cookie": "0.5.0", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "1.2.0", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", - "methods": "~1.1.2", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.7", - "qs": "6.11.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - } - }, - "express-rate-limit": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-6.7.0.tgz", - "integrity": "sha512-vhwIdRoqcYB/72TK3tRZI+0ttS8Ytrk24GfmsxDXK9o9IhHNO5bXRiXQSExPQ4GbaE5tvIS7j1SGrxsuWs+sGA==", - "requires": {} - }, - "express-subdomain": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/express-subdomain/-/express-subdomain-1.0.5.tgz", - "integrity": "sha512-tpYy7MPgDoouxA4r+BnGI43yxYakbSSpQn7MjEYM0ssHeipTM1YiIoK3i4pCAgoXoks22Yb5C4QFkOYBYczZcw==" - }, - "ext": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz", - "integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==", - "requires": { - "type": "^2.7.2" - }, - "dependencies": { - "type": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/type/-/type-2.7.2.tgz", - "integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==" - } - } - }, - "extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" - }, - "extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==" - }, - "eyes": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz", - "integrity": "sha512-GipyPsXO1anza0AOZdy69Im7hGFCNB7Y/NGjDlZGJ3GJJLtwNSb2vrzYrTYJRrRloVx7pl+bhUaTB8yiccPvFQ==", - "dev": true - }, - "fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" - }, - "fast-glob": { - "version": "3.2.12", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", - "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", - "dev": true, - "requires": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - }, - "dependencies": { - "glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "requires": { - "is-glob": "^4.0.1" - } - } - } - }, - "fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" - }, - "fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==" - }, - "fast-xml-parser": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.1.2.tgz", - "integrity": "sha512-CDYeykkle1LiA/uqQyNwYpFbyF6Axec6YapmpUP+/RHWIoR1zKjocdvNaTsxCxZzQ6v9MLXaSYm9Qq0thv0DHg==", - "optional": true, - "requires": { - "strnum": "^1.0.5" - } - }, - "fastq": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", - "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", - "dev": true, - "requires": { - "reusify": "^1.0.4" - } - }, - "file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, - "requires": { - "flat-cache": "^3.0.4" - } - }, - "file-type": { - "version": "10.11.0", - "resolved": "https://registry.npmjs.org/file-type/-/file-type-10.11.0.tgz", - "integrity": "sha512-uzk64HRpUZyTGZtVuvrjP0FYxzQrBf4rojot6J65YMEbwBLB0CWm0CLojVpwpmFmxcE/lkvYICgfcGozbBq6rw==" - }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "finalhandler": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", - "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", - "requires": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "statuses": "2.0.1", - "unpipe": "~1.0.0" - } - }, - "find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "requires": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - } - }, - "flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", - "dev": true, - "requires": { - "flatted": "^3.1.0", - "rimraf": "^3.0.2" - } - }, - "flatted": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", - "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", - "dev": true - }, - "flatten-vertex-data": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/flatten-vertex-data/-/flatten-vertex-data-1.0.2.tgz", - "integrity": "sha512-BvCBFK2NZqerFTdMDgqfHBwxYWnxeCkwONsw6PvBMcUXqo8U/KDWwmXhqx1x2kLIg7DqIsJfOaJFOmlua3Lxuw==", - "requires": { - "dtype": "^2.0.0" - } - }, - "flip-pixels": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/flip-pixels/-/flip-pixels-1.0.2.tgz", - "integrity": "sha512-oXbJGbjDnfJRWPC7Va38EFhd+A8JWE5/hCiKcK8qjCdbLj9DTpsq6MEudwpRTH+V4qq+Jw7d3pUgQdSr3x3mTA==" - }, - "for-each": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", - "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", - "requires": { - "is-callable": "^1.1.3" - } - }, - "forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==" - }, - "form-data": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" - } - }, - "forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==" - }, - "fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==" - }, - "fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", - "requires": { - "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - } - }, - "fs-minipass": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", - "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", - "requires": { - "minipass": "^3.0.0" - }, - "dependencies": { - "minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "requires": { - "yallist": "^4.0.0" - } - } - } - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" - }, - "gauge": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", - "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", - "requires": { - "aproba": "^1.0.3 || ^2.0.0", - "color-support": "^1.1.2", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.1", - "object-assign": "^4.1.1", - "signal-exit": "^3.0.0", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1", - "wide-align": "^1.1.2" - } - }, - "generic-pool": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.9.0.tgz", - "integrity": "sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g==" - }, - "get-assigned-identifiers": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/get-assigned-identifiers/-/get-assigned-identifiers-1.2.0.tgz", - "integrity": "sha512-mBBwmeGTrxEMO4pMaaf/uUEFHnYtwr8FTe8Y/mer4rcV/bye0qGm6pw1bGZFGStxC5O76c5ZAVBGnqHmOaJpdQ==" - }, - "get-intrinsic": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz", - "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==", - "requires": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.3" - } - }, - "get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "requires": { - "pump": "^3.0.0" - } - }, - "getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", - "requires": { - "assert-plus": "^1.0.0" - } - }, - "glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "requires": { - "is-glob": "^4.0.3" - } - }, - "globals": { - "version": "13.20.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", - "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", - "dev": true, - "requires": { - "type-fest": "^0.20.2" - } - }, - "globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "dev": true, - "requires": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - } - }, - "gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "requires": { - "get-intrinsic": "^1.1.3" - } - }, - "got": { - "version": "11.8.6", - "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz", - "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==", - "requires": { - "@sindresorhus/is": "^4.0.0", - "@szmarczak/http-timer": "^4.0.5", - "@types/cacheable-request": "^6.0.1", - "@types/responselike": "^1.0.0", - "cacheable-lookup": "^5.0.3", - "cacheable-request": "^7.0.2", - "decompress-response": "^6.0.0", - "http2-wrapper": "^1.0.0-beta.5.2", - "lowercase-keys": "^2.0.0", - "p-cancelable": "^2.0.0", - "responselike": "^2.0.0" - } - }, - "graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" - }, - "grapheme-splitter": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", - "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", - "dev": true - }, - "har-schema": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==" - }, - "har-validator": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", - "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", - "requires": { - "ajv": "^6.12.3", - "har-schema": "^2.0.0" - } - }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "requires": { - "function-bind": "^1.1.1" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" - }, - "has-tostringtag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", - "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", - "requires": { - "has-symbols": "^1.0.2" - } - }, - "has-unicode": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==" - }, - "hcaptcha": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/hcaptcha/-/hcaptcha-0.1.1.tgz", - "integrity": "sha512-iMrDmH2VpIEKOrcKWidVjI89FdDKTEdZ7PfPWkP27sTazIIkob8YfdY2ezaufAnWBiUUcvzsn0qF+dyXtBH2Vw==" - }, - "http-cache-semantics": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", - "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==" - }, - "http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "requires": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - } - }, - "http-signature": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==", - "requires": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" - } - }, - "http2-wrapper": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", - "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", - "requires": { - "quick-lru": "^5.1.1", - "resolve-alpn": "^1.0.0" - } - }, - "https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "requires": { - "agent-base": "6", - "debug": "4" - }, - "dependencies": { - "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "requires": { - "ms": "2.1.2" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - } - } - }, - "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "ieee754": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", - "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==" - }, - "ignore": { - "version": "5.2.4", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", - "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", - "dev": true - }, - "image-pixels": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/image-pixels/-/image-pixels-1.1.1.tgz", - "integrity": "sha512-NQgoJVHiOWfmQnkEPSvOD8iuKUfm3AUXKdelCN8tQWT9NtwOd5qHofUci0Fcua6d28eq4gBXRKz7MQd8QK53rg==", - "requires": { - "arr-flatten": "^1.1.0", - "arraybuffer-to-string": "^1.0.2", - "bmp-js": "^0.1.0", - "brfs": "^2.0.1", - "buffer-to-arraybuffer": "0.0.5", - "buffer-to-uint8array": "^1.1.0", - "clip-pixels": "^1.0.1", - "es6-weak-map": "^2.0.2", - "flip-pixels": "^1.0.1", - "image-type": "^3.0.0", - "is-base64": "0.0.6", - "is-blob": "^2.0.0", - "is-browser": "^2.1.0", - "is-buffer": "^2.0.3", - "is-plain-obj": "^1.1.0", - "is-promise": "^2.1.0", - "is-url": "^1.2.4", - "jpeg-js": "^0.3.4", - "object-assign": "^4.1.1", - "omggif": "^1.0.9", - "parse-rect": "^1.2.0", - "pngjs": "^3.3.3", - "primitive-pool": "^1.1.0", - "request": "^2.88.0", - "string-to-arraybuffer": "^1.0.1", - "to-array-buffer": "^2.1.0" - } - }, - "image-type": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/image-type/-/image-type-3.1.0.tgz", - "integrity": "sha512-edYRXKQ3WD2yHXFGUbwoJVn5v7j1A6Z505uZUYIfzCwOOhPGLYSc3VOucF9fqbsaUbgb37DdjOU+WV4uo7ZooQ==", - "requires": { - "file-type": "^10.9.0" - } - }, - "import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, - "requires": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - } - }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "iota-array": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/iota-array/-/iota-array-1.0.0.tgz", - "integrity": "sha512-pZ2xT+LOHckCatGQ3DcG/a+QuEqvoxqkiL7tvE8nn3uuu+f6i1TtpB5/FtWFbxUuVr5PZCx8KskuGatbJDXOWA==", - "dev": true - }, - "ip": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz", - "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==" - }, - "ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" - }, - "is-arguments": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", - "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", - "requires": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - } - }, - "is-base64": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/is-base64/-/is-base64-0.0.6.tgz", - "integrity": "sha512-u1cuGk5DbKVfoKvqtF+nV4a4VWN+0npvTuOzyEcScqriIJbQrExdEr2lVkqt3pCIgW5aS7hI1Us9qYaK4FXrZQ==" - }, - "is-blob": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-blob/-/is-blob-2.1.0.tgz", - "integrity": "sha512-SZ/fTft5eUhQM6oF/ZaASFDEdbFVe89Imltn9uZr03wdKMcWNVYSMjQPFtg05QuNkt5l5c135ElvXEQG0rk4tw==" - }, - "is-browser": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-browser/-/is-browser-2.1.0.tgz", - "integrity": "sha512-F5rTJxDQ2sW81fcfOR1GnCXT6sVJC104fCyfj+mjpwNEwaPYSn5fte5jiHmBg3DHsIoL/l8Kvw5VN5SsTRcRFQ==" - }, - "is-buffer": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", - "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==" - }, - "is-callable": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", - "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==" - }, - "is-core-module": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", - "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", - "requires": { - "has": "^1.0.3" - } - }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" - }, - "is-generator-function": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", - "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", - "requires": { - "has-tostringtag": "^1.0.0" - } - }, - "is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "requires": { - "is-extglob": "^2.1.1" - } - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true - }, - "is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true - }, - "is-plain-obj": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", - "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==" - }, - "is-promise": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", - "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==" - }, - "is-typed-array": { - "version": "1.1.10", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", - "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==", - "requires": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0" - } - }, - "is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==" - }, - "is-url": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz", - "integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==" - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true - }, - "isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==" - }, - "jmespath": { - "version": "0.16.0", - "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.16.0.tgz", - "integrity": "sha512-9FzQjJ7MATs1tSpnco1K6ayiYE3figslrXA72G2HQ/n76RzvYlofyi5QM+iX4YRs/pu3yzxlVQSST23+dMDknw==" - }, - "joi": { - "version": "17.8.3", - "resolved": "https://registry.npmjs.org/joi/-/joi-17.8.3.tgz", - "integrity": "sha512-q5Fn6Tj/jR8PfrLrx4fpGH4v9qM6o+vDUfD4/3vxxyg34OmKcNqYZ1qn2mpLza96S8tL0p0rIw2gOZX+/cTg9w==", - "requires": { - "@hapi/hoek": "^9.0.0", - "@hapi/topo": "^5.0.0", - "@sideway/address": "^4.1.3", - "@sideway/formula": "^3.0.1", - "@sideway/pinpoint": "^2.0.0" - } - }, - "jpeg-js": { - "version": "0.3.7", - "resolved": "https://registry.npmjs.org/jpeg-js/-/jpeg-js-0.3.7.tgz", - "integrity": "sha512-9IXdWudL61npZjvLuVe/ktHiA41iE8qFyLB+4VDTblEsWBzeg8WQTlktdUK4CdncUqtUgUg0bbOmTE2bKBKaBQ==" - }, - "js-sdsl": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.3.0.tgz", - "integrity": "sha512-mifzlm2+5nZ+lEcLJMoBK0/IH/bDg8XnJfd/Wq6IP+xoCjLZsTOnV2QpxlVbX9bMnkl5PdEjNtBJ9Cj1NjifhQ==", - "dev": true - }, - "js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "requires": { - "argparse": "^2.0.1" - } - }, - "jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==" - }, - "json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==" - }, - "json-schema": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", - "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==" - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" - }, - "json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true - }, - "json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==" - }, - "jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", - "requires": { - "graceful-fs": "^4.1.6" - } - }, - "jsprim": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", - "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", - "requires": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.4.0", - "verror": "1.10.0" - } - }, - "kareem": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.5.1.tgz", - "integrity": "sha512-7jFxRVm+jD+rkq3kY0iZDJfsO2/t4BBPeEb2qKn2lR/9KhuksYk5hxzfRYWMPV8P/x2d0kHD306YyWLzjjH+uA==" - }, - "keyv": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.2.tgz", - "integrity": "sha512-5MHbFaKn8cNSmVW7BYnijeAVlE4cYA/SVkifVgrh7yotnfhKmjuXpDKjrABLnT0SfHWV21P8ow07OGfRrNDg8g==", - "requires": { - "json-buffer": "3.0.1" - } - }, - "levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", - "requires": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - } - }, - "locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "requires": { - "p-locate": "^5.0.0" - } - }, - "lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true - }, - "lodash.foreach": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.foreach/-/lodash.foreach-4.5.0.tgz", - "integrity": "sha512-aEXTF4d+m05rVOAUG3z4vZZ4xVexLKZGF0lIxuHZ1Hplpk/3B6Z1+/ICICYRLm7c41Z2xiejbkCkJoTlypoXhQ==" - }, - "lodash.get": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", - "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==" - }, - "lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" - }, - "lowercase-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", - "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==" - }, - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "requires": { - "yallist": "^4.0.0" - } - }, - "magic-string": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.1.tgz", - "integrity": "sha512-sCuTz6pYom8Rlt4ISPFn6wuFodbKMIHUMv4Qko9P17dpxb7s52KJTmRuZZqHdGmLCK9AOcDare039nRIcfdkEg==", - "requires": { - "sourcemap-codec": "^1.4.1" - } - }, - "make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "requires": { - "semver": "^6.0.0" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" - } - } - }, - "media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==" - }, - "memory-pager": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", - "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", - "optional": true - }, - "merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" - }, - "merge-source-map": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/merge-source-map/-/merge-source-map-1.0.4.tgz", - "integrity": "sha512-PGSmS0kfnTnMJCzJ16BLLCEe6oeYCamKFFdQKshi4BmM6FUwipjVOcBFGxqtQtirtAG4iZvHlqST9CpZKqlRjA==", - "requires": { - "source-map": "^0.5.6" - }, - "dependencies": { - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==" - } - } - }, - "merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true - }, - "methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==" - }, - "micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", - "dev": true, - "requires": { - "braces": "^3.0.2", - "picomatch": "^2.3.1" - } - }, - "mii-js": { - "version": "git+ssh://git@github.com/PretendoNetwork/mii-js.git#5d8eb8013514a13b0df6eb4a5bfd8b5a63fb9861", - "from": "mii-js@github:PretendoNetwork/mii-js", - "requires": { - "bit-buffer": "^0.2.5" - } - }, - "mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" - }, - "mime-db": { - "version": "1.52.0", - "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" - } - }, - "mimic-response": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", - "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==" - }, - "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==" - }, - "minipass": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.4.tgz", - "integrity": "sha512-lwycX3cBMTvcejsHITUgYj6Gy6A7Nh4Q6h9NP4sTHY1ccJlC7yKzDmiShEHsJ16Jf1nKGDEaiHxiltsJEvk0nQ==" - }, - "minizlib": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", - "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", - "requires": { - "minipass": "^3.0.0", - "yallist": "^4.0.0" - }, - "dependencies": { - "minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "requires": { - "yallist": "^4.0.0" - } - } - } - }, - "mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" - }, - "moment": { - "version": "2.29.4", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", - "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==" - }, - "mongodb": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-5.1.0.tgz", - "integrity": "sha512-qgKb7y+EI90y4weY3z5+lIgm8wmexbonz0GalHkSElQXVKtRuwqXuhXKccyvIjXCJVy9qPV82zsinY0W1FBnJw==", - "requires": { - "bson": "^5.0.1", - "mongodb-connection-string-url": "^2.6.0", - "saslprep": "^1.0.3", - "socks": "^2.7.1" - } - }, - "mongodb-connection-string-url": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.6.0.tgz", - "integrity": "sha512-WvTZlI9ab0QYtTYnuMLgobULWhokRjtC7db9LtcVfJ+Hsnyr5eo6ZtNAt3Ly24XZScGMelOcGtm7lSn0332tPQ==", - "requires": { - "@types/whatwg-url": "^8.2.1", - "whatwg-url": "^11.0.0" - } - }, - "mongoose": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-7.0.0.tgz", - "integrity": "sha512-U0YPURDld+k/nvvSG1mRClQSjZMRXwQKSU5yb9PslRnOmVz0UlBD7SjSnjUuGT0yk+7BH+kJNimsKqMxYAKkMA==", - "requires": { - "bson": "^5.0.1", - "kareem": "2.5.1", - "mongodb": "5.1.0", - "mpath": "0.9.0", - "mquery": "5.0.0", - "ms": "2.1.3", - "sift": "16.0.1" - }, - "dependencies": { - "ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - } - } - }, - "mongoose-unique-validator": { - "version": "git+ssh://git@github.com/stenneepro/mongoose-unique-validator.git#1cd976b70ddd8e8e65fed1d9a387677713a8d177", - "from": "mongoose-unique-validator@github:stenneepro/mongoose-unique-validator#bump/mongoose7", - "requires": { - "lodash.foreach": "^4.1.0", - "lodash.get": "^4.0.2", - "lodash.merge": "^4.6.2" - } - }, - "morgan": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz", - "integrity": "sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==", - "requires": { - "basic-auth": "~2.0.1", - "debug": "2.6.9", - "depd": "~2.0.0", - "on-finished": "~2.3.0", - "on-headers": "~1.0.2" - }, - "dependencies": { - "on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", - "requires": { - "ee-first": "1.1.1" - } - } - } - }, - "mpath": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz", - "integrity": "sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew==" - }, - "mquery": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/mquery/-/mquery-5.0.0.tgz", - "integrity": "sha512-iQMncpmEK8R8ncT8HJGsGc9Dsp8xcgYMVSbs5jgnm1lFHTZqMJTUWTDx1LBO8+mK3tPNZWFLBghQEIOULSTHZg==", - "requires": { - "debug": "4.x" - }, - "dependencies": { - "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "requires": { - "ms": "2.1.2" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - } - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "mute-stream": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", - "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", - "dev": true - }, - "natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true - }, - "natural-compare-lite": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", - "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", - "dev": true - }, - "ndarray": { - "version": "1.0.19", - "resolved": "https://registry.npmjs.org/ndarray/-/ndarray-1.0.19.tgz", - "integrity": "sha512-B4JHA4vdyZU30ELBw3g7/p9bZupyew5a7tX1Y/gGeF2hafrPaQZhgrGQfsvgfYbgdFZjYwuEcnaobeM/WMW+HQ==", - "dev": true, - "requires": { - "iota-array": "^1.0.0", - "is-buffer": "^1.0.2" - }, - "dependencies": { - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true - } - } - }, - "negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==" - }, - "nested-error-stacks": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/nested-error-stacks/-/nested-error-stacks-2.1.1.tgz", - "integrity": "sha512-9iN1ka/9zmX1ZvLV9ewJYEk9h7RyRRtqdK0woXcqohu8EWIerfPUjYJPg0ULy0UqP7cslmdGc8xKDJcojlKiaw==" - }, - "next-tick": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", - "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==" - }, - "node-addon-api": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz", - "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==" - }, - "node-fetch": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz", - "integrity": "sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==", - "requires": { - "whatwg-url": "^5.0.0" - }, - "dependencies": { - "tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" - }, - "webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" - }, - "whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "requires": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - } - } - }, - "node-rsa": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/node-rsa/-/node-rsa-1.1.1.tgz", - "integrity": "sha512-Jd4cvbJMryN21r5HgxQOpMEqv+ooke/korixNNK3mGqfGJmy0M77WDDzo/05969+OkMy3XW1UuZsSmW9KQm7Fw==", - "requires": { - "asn1": "^0.2.4" - } - }, - "nodemailer": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.1.tgz", - "integrity": "sha512-qHw7dOiU5UKNnQpXktdgQ1d3OFgRAekuvbJLcdG5dnEo/GtcTHRYM7+UfJARdOFU9WUQO8OiIamgWPmiSFHYAA==" - }, - "nopt": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", - "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", - "requires": { - "abbrev": "1" - } - }, - "normalize-url": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", - "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==" - }, - "npmlog": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", - "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", - "requires": { - "are-we-there-yet": "^2.0.0", - "console-control-strings": "^1.1.0", - "gauge": "^3.0.0", - "set-blocking": "^2.0.0" - } - }, - "oauth-sign": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==" - }, - "object-inspect": { - "version": "1.12.3", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", - "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==" - }, - "omggif": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/omggif/-/omggif-1.0.10.tgz", - "integrity": "sha512-LMJTtvgc/nugXj0Vcrrs68Mn2D1r0zf630VNtqtpI1FEO7e+O9FP4gqs9AcnBaSEeoHIPm28u6qgPR0oyEpGSw==" - }, - "on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "requires": { - "ee-first": "1.1.1" - } - }, - "on-headers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", - "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==" - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "requires": { - "wrappy": "1" - } - }, - "optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", - "requires": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" - } - }, - "p-cancelable": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", - "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==" - }, - "p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "requires": { - "yocto-queue": "^0.1.0" - } - }, - "p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "requires": { - "p-limit": "^3.0.2" - } - }, - "parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "requires": { - "callsites": "^3.0.0" - } - }, - "parse-rect": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/parse-rect/-/parse-rect-1.2.0.tgz", - "integrity": "sha512-4QZ6KYbnE6RTwg9E0HpLchUM9EZt6DnDxajFZZDSV4p/12ZJEvPO702DZpGvRYEPo00yKDys7jASi+/w7aO8LA==", - "requires": { - "pick-by-alias": "^1.2.0" - } - }, - "parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==" - }, - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true - }, - "path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" - }, - "path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" - }, - "path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true - }, - "performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==" - }, - "pick-by-alias": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/pick-by-alias/-/pick-by-alias-1.2.0.tgz", - "integrity": "sha512-ESj2+eBxhGrcA1azgHs7lARG5+5iLakc/6nlfbpjcLl00HuuUOIuORhYXN4D1HfvMSKuVtFQjAlnwi1JHEeDIw==" - }, - "picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true - }, - "pngjs": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-3.4.0.tgz", - "integrity": "sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==" - }, - "prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==" - }, - "primitive-pool": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/primitive-pool/-/primitive-pool-1.1.0.tgz", - "integrity": "sha512-pl4l1R9OWWb4XmUpeX30yGz8ukZ25EiLhwlP2KqOqCb8NkwJt8qUeuCQEsk8CiAS7msICbPD18csu8jcIX7GNg==" - }, - "process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" - }, - "prompt": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/prompt/-/prompt-1.3.0.tgz", - "integrity": "sha512-ZkaRWtaLBZl7KKAKndKYUL8WqNT+cQHKRZnT4RYYms48jQkFw3rrBL+/N5K/KtdEveHkxs982MX2BkDKub2ZMg==", - "dev": true, - "requires": { - "@colors/colors": "1.5.0", - "async": "3.2.3", - "read": "1.0.x", - "revalidator": "0.1.x", - "winston": "2.x" - } - }, - "proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "requires": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - } - }, - "psl": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", - "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==" - }, - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "punycode": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", - "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==" - }, - "qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", - "requires": { - "side-channel": "^1.0.4" - } - }, - "querystring": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", - "integrity": "sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g==" - }, - "queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true - }, - "quick-lru": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", - "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==" - }, - "quote-stream": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/quote-stream/-/quote-stream-1.0.2.tgz", - "integrity": "sha512-kKr2uQ2AokadPjvTyKJQad9xELbZwYzWlNfI3Uz2j/ib5u6H9lDP7fUUR//rMycd0gv4Z5P1qXMfXR8YpIxrjQ==", - "requires": { - "buffer-equal": "0.0.1", - "minimist": "^1.1.3", - "through2": "^2.0.0" - } - }, - "range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" - }, - "raw-body": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", - "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", - "requires": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - } - }, - "read": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz", - "integrity": "sha512-rSOKNYUmaxy0om1BNjMN4ezNT6VKK+2xF4GBhc81mkH7L60i6dp8qPYrkndNLT3QPphoII3maL9PVC9XmhHwVQ==", - "dev": true, - "requires": { - "mute-stream": "~0.0.4" - } - }, - "readable-stream": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", - "integrity": "sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" - }, - "dependencies": { - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==" - } - } - }, - "redis": { - "version": "4.6.5", - "resolved": "https://registry.npmjs.org/redis/-/redis-4.6.5.tgz", - "integrity": "sha512-O0OWA36gDQbswOdUuAhRL6mTZpHFN525HlgZgDaVNgCJIAZR3ya06NTESb0R+TUZ+BFaDpz6NnnVvoMx9meUFg==", - "requires": { - "@redis/bloom": "1.2.0", - "@redis/client": "1.5.6", - "@redis/graph": "1.1.0", - "@redis/json": "1.0.4", - "@redis/search": "1.1.2", - "@redis/time-series": "1.0.4" - } - }, - "reflect-metadata": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", - "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==", - "optional": true - }, - "regexpp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", - "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", - "dev": true - }, - "request": { - "version": "2.88.2", - "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", - "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", - "requires": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "har-validator": "~5.1.3", - "http-signature": "~1.2.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "oauth-sign": "~0.9.0", - "performance-now": "^2.1.0", - "qs": "~6.5.2", - "safe-buffer": "^5.1.2", - "tough-cookie": "~2.5.0", - "tunnel-agent": "^0.6.0", - "uuid": "^3.3.2" - }, - "dependencies": { - "qs": { - "version": "6.5.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", - "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==" - }, - "uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" - } - } - }, - "resolve": { - "version": "1.22.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", - "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", - "requires": { - "is-core-module": "^2.9.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - } - }, - "resolve-alpn": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", - "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==" - }, - "resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true - }, - "responselike": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz", - "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", - "requires": { - "lowercase-keys": "^2.0.0" - } - }, - "restructure": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/restructure/-/restructure-2.0.1.tgz", - "integrity": "sha512-e0dOpjm5DseomnXx2M5lpdZ5zoHqF1+bqdMJUohoYVVQa7cBdnk7fdmeI6byNWP/kiME72EeTiSypTCVnpLiDg==" - }, - "reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true - }, - "revalidator": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/revalidator/-/revalidator-0.1.8.tgz", - "integrity": "sha512-xcBILK2pA9oh4SiinPEZfhP8HfrB/ha+a2fTMyl7Om2WjlDVrOQy99N2MXXlUHqGJz4qEu2duXxHJjDWuK/0xg==", - "dev": true - }, - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "requires": { - "glob": "^7.1.3" - } - }, - "run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "requires": { - "queue-microtask": "^1.2.2" - } - }, - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "saslprep": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.3.tgz", - "integrity": "sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==", - "optional": true, - "requires": { - "sparse-bitfield": "^3.0.3" - } - }, - "sax": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz", - "integrity": "sha512-8I2a3LovHTOpm7NV5yOyO8IHqgVsfK4+UuySrXU8YXkSRX7k6hCV9b3HrkKCr3nMpgj+0bmocaJJWpvp1oc7ZA==" - }, - "scope-analyzer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/scope-analyzer/-/scope-analyzer-2.1.2.tgz", - "integrity": "sha512-5cfCmsTYV/wPaRIItNxatw02ua/MThdIUNnUOCYp+3LSEJvnG804ANw2VLaavNILIfWXF1D1G2KNANkBBvInwQ==", - "requires": { - "array-from": "^2.1.1", - "dash-ast": "^2.0.1", - "es6-map": "^0.1.5", - "es6-set": "^0.1.5", - "es6-symbol": "^3.1.1", - "estree-is-function": "^1.0.0", - "get-assigned-identifiers": "^1.1.0" - } - }, - "semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", - "requires": { - "lru-cache": "^6.0.0" - } - }, - "send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", - "requires": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" - }, - "dependencies": { - "ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - } - } - }, - "serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", - "requires": { - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.18.0" - } - }, - "set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" - }, - "setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" - }, - "shallow-copy": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/shallow-copy/-/shallow-copy-0.0.1.tgz", - "integrity": "sha512-b6i4ZpVuUxB9h5gfCxPiusKYkqTMOjEbBs4wMaFbkfia4yFv92UKZ6Df8WXcKbn08JNL/abvg3FnMAOfakDvUw==" - }, - "shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "requires": { - "shebang-regex": "^3.0.0" - } - }, - "shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true - }, - "side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", - "requires": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" - } - }, - "sift": { - "version": "16.0.1", - "resolved": "https://registry.npmjs.org/sift/-/sift-16.0.1.tgz", - "integrity": "sha512-Wv6BjQ5zbhW7VFefWusVP33T/EM0vYikCaQ2qR8yULbsilAT8/wQaXvuQ3ptGLpoKx+lihJE3y2UTgKDyyNHZQ==" - }, - "signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" - }, - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true - }, - "smart-buffer": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", - "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==" - }, - "socks": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz", - "integrity": "sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==", - "requires": { - "ip": "^2.0.0", - "smart-buffer": "^4.2.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "optional": true - }, - "sourcemap-codec": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", - "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==" - }, - "sparse-bitfield": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", - "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==", - "optional": true, - "requires": { - "memory-pager": "^1.0.2" - } - }, - "sshpk": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.17.0.tgz", - "integrity": "sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ==", - "requires": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jsbn": "~0.1.0", - "safer-buffer": "^2.0.2", - "tweetnacl": "~0.14.0" - } - }, - "stack-trace": { - "version": "0.0.10", - "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", - "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==", - "dev": true - }, - "static-eval": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/static-eval/-/static-eval-2.1.0.tgz", - "integrity": "sha512-agtxZ/kWSsCkI5E4QifRwsaPs0P0JmZV6dkLz6ILYfFYQGn+5plctanRN+IC8dJRiFkyXHrwEE3W9Wmx67uDbw==", - "requires": { - "escodegen": "^1.11.1" - } - }, - "static-module": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/static-module/-/static-module-3.0.4.tgz", - "integrity": "sha512-gb0v0rrgpBkifXCa3yZXxqVmXDVE+ETXj6YlC/jt5VzOnGXR2C15+++eXuMDUYsePnbhf+lwW0pE1UXyOLtGCw==", - "requires": { - "acorn-node": "^1.3.0", - "concat-stream": "~1.6.0", - "convert-source-map": "^1.5.1", - "duplexer2": "~0.1.4", - "escodegen": "^1.11.1", - "has": "^1.0.1", - "magic-string": "0.25.1", - "merge-source-map": "1.0.4", - "object-inspect": "^1.6.0", - "readable-stream": "~2.3.3", - "scope-analyzer": "^2.0.1", - "shallow-copy": "~0.0.1", - "static-eval": "^2.0.5", - "through2": "~2.0.3" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" - }, - "streamsearch": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz", - "integrity": "sha512-jos8u++JKm0ARcSUTAZXOVC0mSox7Bhn6sBgty73P1f3JGf7yG2clTbBNHUdde/kdvP2FESam+vM6l8jBrNxHA==" - }, - "string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==" - }, - "string-to-arraybuffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-to-arraybuffer/-/string-to-arraybuffer-1.0.2.tgz", - "integrity": "sha512-DaGZidzi93dwjQen5I2osxR9ERS/R7B1PFyufNMnzhj+fmlDQAc1DSDIJVJhgI8Oq221efIMbABUBdPHDRt43Q==", - "requires": { - "atob-lite": "^2.0.0", - "is-base64": "^0.1.0" - }, - "dependencies": { - "is-base64": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-base64/-/is-base64-0.1.0.tgz", - "integrity": "sha512-WRRyllsGXJM7ZN7gPTCCQ/6wNPTRDwiWdPK66l5sJzcU/oOzcIcRRf0Rux8bkpox/1yjt0F6VJRsQOIG2qz5sg==" - } - } - }, - "string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "requires": { - "ansi-regex": "^5.0.1" - } - }, - "strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true - }, - "strnum": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz", - "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==", - "optional": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==" - }, - "tar": { - "version": "6.1.13", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.13.tgz", - "integrity": "sha512-jdIBIN6LTIe2jqzay/2vtYLlBHa3JF42ot3h1dW8Q0PaAG4v8rm0cvpVePtau5C6OKXGGcgO9q2AMNSWxiLqKw==", - "requires": { - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "minipass": "^4.0.0", - "minizlib": "^2.1.1", - "mkdirp": "^1.0.3", - "yallist": "^4.0.0" - } - }, - "text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true - }, - "tga": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/tga/-/tga-1.0.7.tgz", - "integrity": "sha512-GFVJwov5aJTMgh8U1QfaRheIELXo+dYc1qYIvQEIqZX4n+S6Fj/SDWsdbelHt7WP08xOR6W1z5aJQ+Ilh5gIeA==", - "requires": { - "debug": "^2.6.1", - "restructure": "^2.0.0" - } - }, - "through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "requires": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "to-array-buffer": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/to-array-buffer/-/to-array-buffer-2.2.2.tgz", - "integrity": "sha512-ZLcA71btdGxBs6WH2559ybYfoQ23756bZACcU9pIUwbBJ4Bwr1DHW96XfXQoKSk87pYo9zp2VsWrcX+At988Bw==", - "requires": { - "flatten-vertex-data": "^1.0.2", - "is-blob": "^2.0.1", - "string-to-arraybuffer": "^1.0.0" - } - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "requires": { - "is-number": "^7.0.0" - } - }, - "toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" - }, - "tough-cookie": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", - "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", - "requires": { - "psl": "^1.1.28", - "punycode": "^2.1.1" - } - }, - "tr46": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", - "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", - "requires": { - "punycode": "^2.1.1" - } - }, - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - }, - "tsutils": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", - "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", - "requires": { - "tslib": "^1.8.1" - } - }, - "tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", - "requires": { - "safe-buffer": "^5.0.1" - } - }, - "tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==" - }, - "type": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", - "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==" - }, - "type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", - "requires": { - "prelude-ls": "~1.1.2" - } - }, - "type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true - }, - "type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "requires": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - } - }, - "typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==" - }, - "typescript": { - "version": "4.9.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", - "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==" - }, - "typescript-is": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/typescript-is/-/typescript-is-0.19.0.tgz", - "integrity": "sha512-SAJEx2cxbQZhfOjDEjPnQJt1qRS1M3wrKbUwvsywVHWGbMgM1dcIf9gPWNDS1/dgTa/7Iexk2mmAHHsP9MeCsA==", - "requires": { - "nested-error-stacks": "^2", - "reflect-metadata": ">=0.1.12", - "tsutils": "^3.17.1" - } - }, - "universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==" - }, - "unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==" - }, - "uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "requires": { - "punycode": "^2.1.0" - } - }, - "url": { - "version": "0.10.3", - "resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz", - "integrity": "sha512-hzSUW2q06EqL1gKM/a+obYHLIO6ct2hwPuviqTTOcfFVc61UbfJ2Q32+uGL/HCPxKqrdGB5QUwIe7UqlDgwsOQ==", - "requires": { - "punycode": "1.3.2", - "querystring": "0.2.0" - }, - "dependencies": { - "punycode": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", - "integrity": "sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw==" - } - } - }, - "util": { - "version": "0.12.5", - "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", - "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", - "requires": { - "inherits": "^2.0.3", - "is-arguments": "^1.0.4", - "is-generator-function": "^1.0.7", - "is-typed-array": "^1.1.3", - "which-typed-array": "^1.1.2" - } - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" - }, - "utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==" - }, - "uuid": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.0.0.tgz", - "integrity": "sha512-jOXGuXZAWdsTH7eZLtyXMqUb9EcWMGZNbL9YcGBJl4MH4nrxHmZJhEHvyLFrkxo+28uLb/NYRcStH48fnD0Vzw==" - }, - "validator": { - "version": "13.9.0", - "resolved": "https://registry.npmjs.org/validator/-/validator-13.9.0.tgz", - "integrity": "sha512-B+dGG8U3fdtM0/aNK4/X8CXq/EcxU2WPrPEkJGslb47qyHsxmbggTWK0yEA4qnYVNF+nxNlN88o14hIcPmSIEA==" - }, - "vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==" - }, - "verror": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", - "requires": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" - }, - "dependencies": { - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==" - } - } - }, - "webidl-conversions": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", - "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==" - }, - "whatwg-url": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", - "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", - "requires": { - "tr46": "^3.0.0", - "webidl-conversions": "^7.0.0" - } - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - }, - "which-typed-array": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz", - "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==", - "requires": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0", - "is-typed-array": "^1.1.10" - } - }, - "wide-align": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", - "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", - "requires": { - "string-width": "^1.0.2 || 2 || 3 || 4" - } - }, - "winston": { - "version": "2.4.7", - "resolved": "https://registry.npmjs.org/winston/-/winston-2.4.7.tgz", - "integrity": "sha512-vLB4BqzCKDnnZH9PHGoS2ycawueX4HLqENXQitvFHczhgW2vFpSOn31LZtVr1KU8YTw7DS4tM+cqyovxo8taVg==", - "dev": true, - "requires": { - "async": "^2.6.4", - "colors": "1.0.x", - "cycle": "1.0.x", - "eyes": "0.1.x", - "isstream": "0.1.x", - "stack-trace": "0.0.x" - }, - "dependencies": { - "async": { - "version": "2.6.4", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", - "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", - "dev": true, - "requires": { - "lodash": "^4.17.14" - } - }, - "colors": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", - "integrity": "sha512-pFGrxThWcWQ2MsAz6RtgeWe4NK2kUE1WfsrvvlctdII745EW9I0yflqhe7++M5LEc7bV2c/9/5zc8sFcpL0Drw==", - "dev": true - } - } - }, - "word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==" - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" - }, - "xml2js": { - "version": "0.4.19", - "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz", - "integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==", - "requires": { - "sax": ">=0.6.0", - "xmlbuilder": "~9.0.1" - }, - "dependencies": { - "xmlbuilder": { - "version": "9.0.7", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", - "integrity": "sha512-7YXTQc3P2l9+0rjaUbLwMKRhtmwg1M1eDf6nag7urC7pIPYLD9W/jmzQ4ptRSUbodw5S0jfoGTflLemQibSpeQ==" - } - } - }, - "xmlbuilder": { - "version": "13.0.2", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-13.0.2.tgz", - "integrity": "sha512-Eux0i2QdDYKbdbA6AM6xE4m6ZTZr4G4xF9kahI2ukSEMCzwce2eX9WlTI5J3s+NU7hpasFsr8hWIONae7LluAQ==" - }, - "xmlbuilder2": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/xmlbuilder2/-/xmlbuilder2-0.0.4.tgz", - "integrity": "sha512-4l+sSxet6EkVooCNlNgcImwrUZrk5BMRlJV6xUH9y19rwbwxB+OMfvlWT0hppDcVLjWM0Mu6FnpV/UGB4y/Feg==", - "requires": { - "@oozcitak/dom": "0.0.11", - "@oozcitak/infra": "1.0.4", - "@oozcitak/util": "1.0.3" - } - }, - "xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - }, - "yesno": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/yesno/-/yesno-0.4.0.tgz", - "integrity": "sha512-tdBxmHvbXPBKYIg81bMCB7bVeDmHkRzk5rVJyYYXurwKkHq/MCd8rz4HSJUP7hW0H2NlXiq8IFiWvYKEHhlotA==", - "dev": true - }, - "yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true - } } } diff --git a/package.json b/package.json index 5409fc7..8a6862c 100644 --- a/package.json +++ b/package.json @@ -43,10 +43,12 @@ "mii-js": "github:PretendoNetwork/mii-js", "moment": "^2.29.4", "mongoose": "^7.0.0", - "mongoose-unique-validator": "github:stenneepro/mongoose-unique-validator#bump/mongoose7", + "mongoose-unique-validator": "^4.0.0", "morgan": "^1.9.1", + "nice-grpc": "^2.1.4", "node-rsa": "^1.0.7", "nodemailer": "^6.4.2", + "pretendo-grpc-ts": "github:PretendoNetwork/grpc-ts", "redis": "^4.3.1", "tga": "^1.0.4", "typescript-is": "^0.19.0", @@ -61,7 +63,6 @@ "@types/dicer": "^0.2.2", "@types/express": "^4.17.17", "@types/fs-extra": "^11.0.1", - "@types/mongoose-unique-validator": "^1.0.7", "@types/morgan": "^1.9.4", "@types/ndarray": "^1.0.11", "@types/node": "^18.14.4", @@ -69,7 +70,6 @@ "@types/nodemailer": "^6.4.7", "@types/qs": "^6.9.7", "@types/validator": "^13.7.14", - "@types/w3c-css-typed-object-model-level-1": "^20180410.0.5", "@typescript-eslint/eslint-plugin": "^5.54.1", "@typescript-eslint/parser": "^5.54.1", "eslint": "^8.35.0", diff --git a/src/config-manager.ts b/src/config-manager.ts index 23c9f84..fa34228 100644 --- a/src/config-manager.ts +++ b/src/config-manager.ts @@ -65,7 +65,8 @@ export const config: Config = { base_url: process.env.PN_ACT_CONFIG_CDN_BASE_URL || '' }, website_base: process.env.PN_ACT_CONFIG_WEBSITE_BASE || '', - aes_key: process.env.PN_ACT_CONFIG_AES_KEY || '' + aes_key: process.env.PN_ACT_CONFIG_AES_KEY || '', + grpc_api_key: process.env.PN_ACT_CONFIG_GRPC_API_KEY || '', }; LOG_INFO('Config loaded, checking integrity'); @@ -168,4 +169,9 @@ if (disabledFeatures.s3) { if (!config.aes_key) { LOG_ERROR('Token AES key is not set. Set the PN_ACT_CONFIG_AES_KEY environment variable to your AES-256-CBC key'); process.exit(0); +} + +if (!config.grpc_api_key) { + LOG_ERROR('gRPC API key is not set. Set the PN_ACT_CONFIG_GRPC_API_KEY environment variable'); + process.exit(0); } \ No newline at end of file diff --git a/src/server.ts b/src/server.ts index 1d9eac4..63e03e0 100644 --- a/src/server.ts +++ b/src/server.ts @@ -10,6 +10,7 @@ import xmlbuilder from 'xmlbuilder'; import xmlparser from '@/middleware/xml-parser'; import { connect as connectCache } from '@/cache'; import { connect as connectDatabase } from '@/database'; +import { startGRPCServer } from '@/services/grpc/server'; import { fullUrl, getValueFromHeaders } from '@/util'; import { LOG_INFO, LOG_SUCCESS, LOG_WARN } from '@/logger'; @@ -100,6 +101,7 @@ async function main(): Promise { await connectDatabase(); await connectCache(); + await startGRPCServer(); app.listen(port, () => { LOG_SUCCESS(`Server started on port ${port}`); diff --git a/src/services/grpc/api-key-middleware.ts b/src/services/grpc/api-key-middleware.ts new file mode 100644 index 0000000..e1a5a62 --- /dev/null +++ b/src/services/grpc/api-key-middleware.ts @@ -0,0 +1,18 @@ +import { Status, ServerMiddlewareCall, CallContext, ServerError } from 'nice-grpc'; +import { config } from '@/config-manager'; + +export async function* apiKeyMiddleware( + call: ServerMiddlewareCall, + context: CallContext, +): AsyncGenerator { + const apiKey = context.metadata.get('X-API-Key'); + + if (!apiKey || apiKey !== config.grpc_api_key) { + throw new ServerError( + Status.UNAUTHENTICATED, + 'Missing or invalid API key', + ); + } + + return yield* call.next(call.request, context); +} \ No newline at end of file diff --git a/src/services/grpc/get-user-data.ts b/src/services/grpc/get-user-data.ts new file mode 100644 index 0000000..be7a606 --- /dev/null +++ b/src/services/grpc/get-user-data.ts @@ -0,0 +1,35 @@ +import { Status, ServerError } from 'nice-grpc'; +import { GetUserDataRequest, GetUserDataResponse, DeepPartial } from 'pretendo-grpc-ts/dist/account/get_user_data_rpc'; +import { getPNIDByPID } from '@/database'; +import { HydratedPNIDDocument } from '@/types/mongoose/pnid'; +import { config } from '@/config-manager'; + +export async function getUserData(request: GetUserDataRequest): Promise> { + const pnid: HydratedPNIDDocument | null = await getPNIDByPID(request.pid); + + if (!pnid) { + throw new ServerError( + Status.INVALID_ARGUMENT, + 'No PNID found', + ); + } + + return { + pid: pnid.pid, + username: pnid.username, + accessLevel: pnid.access_level, + serverAccessLevel: pnid.server_access_level, + mii: { + name: pnid.mii.name, + data: pnid.mii.data, + url: `${config.cdn.base_url}/mii/${pnid.pid}/standard.tga`, + }, + creationDate: pnid.creation_date, + birthdate: pnid.birthdate, + gender: pnid.gender, + country: pnid.country, + language: pnid.language, + emailAddress: pnid.email.address, + tierName: pnid.connections.stripe.tier_name + }; +} \ No newline at end of file diff --git a/src/services/grpc/login.ts b/src/services/grpc/login.ts new file mode 100644 index 0000000..5cd2da3 --- /dev/null +++ b/src/services/grpc/login.ts @@ -0,0 +1,6 @@ +import { Status, ServerError } from 'nice-grpc'; +import { LoginRequest, LoginResponse, DeepPartial } from 'pretendo-grpc-ts/dist/account/login_rpc'; + +export async function login(_request: LoginRequest): Promise> { + throw new ServerError(Status.UNIMPLEMENTED, 'Login method not implemented'); +} \ No newline at end of file diff --git a/src/services/grpc/register-pnid.ts b/src/services/grpc/register-pnid.ts new file mode 100644 index 0000000..1da888e --- /dev/null +++ b/src/services/grpc/register-pnid.ts @@ -0,0 +1,7 @@ +import { Status, ServerError } from 'nice-grpc'; +import { RegisterPNIDRequest, DeepPartial } from 'pretendo-grpc-ts/dist/account/register_pnid_rpc'; +import { LoginResponse } from 'pretendo-grpc-ts/dist/account/login_rpc'; + +export async function registerPNID(_request: RegisterPNIDRequest): Promise> { + throw new ServerError(Status.UNIMPLEMENTED, 'Register PNID method not implemented'); +} \ No newline at end of file diff --git a/src/services/grpc/server.ts b/src/services/grpc/server.ts new file mode 100644 index 0000000..666b553 --- /dev/null +++ b/src/services/grpc/server.ts @@ -0,0 +1,21 @@ +import { createServer } from 'nice-grpc'; +import { AccountServiceImplementation, AccountDefinition } from 'pretendo-grpc-ts/dist/account/account_service'; + +import { apiKeyMiddleware } from '@/services/grpc/api-key-middleware'; +import { getUserData } from '@/services/grpc/get-user-data'; +import { login } from '@/services/grpc/login'; +import { registerPNID } from '@/services/grpc/register-pnid'; + +const accountServiceImplementation: AccountServiceImplementation = { + getUserData, + login, + registerPNID, +}; + +export async function startGRPCServer(): Promise { + const server = createServer().use(apiKeyMiddleware); + + server.add(AccountDefinition, accountServiceImplementation); + + await server.listen('0.0.0.0:50051'); +} \ No newline at end of file diff --git a/src/types/common/config.ts b/src/types/common/config.ts index 4780d00..a10b21f 100644 --- a/src/types/common/config.ts +++ b/src/types/common/config.ts @@ -38,6 +38,7 @@ export interface Config { }; website_base: string; aes_key: string; + grpc_api_key: string; } export interface DisabledFeatures { From 12ad6b39c671b73be3cf2cb0a5d08317cc606999 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Fri, 28 Apr 2023 13:59:19 -0400 Subject: [PATCH 073/219] Made gRPC server port configurable --- SETUP.md | 3 ++- src/config-manager.ts | 12 ++++++++++-- src/services/grpc/api-key-middleware.ts | 2 +- src/services/grpc/server.ts | 3 ++- src/types/common/config.ts | 5 ++++- 5 files changed, 19 insertions(+), 6 deletions(-) diff --git a/SETUP.md b/SETUP.md index 88db0b8..296d729 100644 --- a/SETUP.md +++ b/SETUP.md @@ -75,4 +75,5 @@ Configurations are loaded through environment variables. `.env` files are suppor | `PN_ACT_CONFIG_CDN_BASE_URL` | URL for serving CDN contents (usually the same as s3 endpoint) | No | | `PN_ACT_CONFIG_WEBSITE_BASE` | Website URL | Yes | | `PN_ACT_CONFIG_AES_KEY` | AES-256 key used for encrypting tokens | No | -| `PN_ACT_CONFIG_GRPC_API_KEY` | gRPC API key that other clients use to interact with this server | No | \ No newline at end of file +| `PN_ACT_CONFIG_GRPC_API_KEY` | gRPC API key that other clients use to interact with this server | No | +| `PN_ACT_CONFIG_GRPC_PORT` | gRPC server port | No | \ No newline at end of file diff --git a/src/config-manager.ts b/src/config-manager.ts index fa34228..5a22d3b 100644 --- a/src/config-manager.ts +++ b/src/config-manager.ts @@ -66,7 +66,10 @@ export const config: Config = { }, website_base: process.env.PN_ACT_CONFIG_WEBSITE_BASE || '', aes_key: process.env.PN_ACT_CONFIG_AES_KEY || '', - grpc_api_key: process.env.PN_ACT_CONFIG_GRPC_API_KEY || '', + grpc: { + api_key: process.env.PN_ACT_CONFIG_GRPC_API_KEY || '', + port: Number(process.env.PN_ACT_CONFIG_GRPC_PORT || ''), + } }; LOG_INFO('Config loaded, checking integrity'); @@ -171,7 +174,12 @@ if (!config.aes_key) { process.exit(0); } -if (!config.grpc_api_key) { +if (!config.grpc.api_key) { LOG_ERROR('gRPC API key is not set. Set the PN_ACT_CONFIG_GRPC_API_KEY environment variable'); process.exit(0); +} + +if (!config.grpc.port) { + LOG_ERROR('Failed to find gRPC port. Set the PN_ACT_CONFIG_GRPC_PORT environment variable'); + process.exit(0); } \ No newline at end of file diff --git a/src/services/grpc/api-key-middleware.ts b/src/services/grpc/api-key-middleware.ts index e1a5a62..37d2058 100644 --- a/src/services/grpc/api-key-middleware.ts +++ b/src/services/grpc/api-key-middleware.ts @@ -7,7 +7,7 @@ export async function* apiKeyMiddleware( ): AsyncGenerator { const apiKey = context.metadata.get('X-API-Key'); - if (!apiKey || apiKey !== config.grpc_api_key) { + if (!apiKey || apiKey !== config.grpc.api_key) { throw new ServerError( Status.UNAUTHENTICATED, 'Missing or invalid API key', diff --git a/src/services/grpc/server.ts b/src/services/grpc/server.ts index 666b553..3c804c7 100644 --- a/src/services/grpc/server.ts +++ b/src/services/grpc/server.ts @@ -1,5 +1,6 @@ import { createServer } from 'nice-grpc'; import { AccountServiceImplementation, AccountDefinition } from 'pretendo-grpc-ts/dist/account/account_service'; +import { config } from '@/config-manager'; import { apiKeyMiddleware } from '@/services/grpc/api-key-middleware'; import { getUserData } from '@/services/grpc/get-user-data'; @@ -17,5 +18,5 @@ export async function startGRPCServer(): Promise { server.add(AccountDefinition, accountServiceImplementation); - await server.listen('0.0.0.0:50051'); + await server.listen(`0.0.0.0:${config.grpc.port}`); } \ No newline at end of file diff --git a/src/types/common/config.ts b/src/types/common/config.ts index a10b21f..628dac0 100644 --- a/src/types/common/config.ts +++ b/src/types/common/config.ts @@ -38,7 +38,10 @@ export interface Config { }; website_base: string; aes_key: string; - grpc_api_key: string; + grpc: { + api_key: string; + port: number; + }; } export interface DisabledFeatures { From 379d1895c24ed947647c11f6923f5783b9827c2e Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Fri, 28 Apr 2023 14:15:49 -0400 Subject: [PATCH 074/219] Added more logging when starting server --- src/server.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/server.ts b/src/server.ts index 63e03e0..3e59b8e 100644 --- a/src/server.ts +++ b/src/server.ts @@ -24,7 +24,6 @@ import assets from '@/services/assets'; import { config } from '@/config-manager'; -const { http: { port } } = config; const app = express(); // START APPLICATION @@ -100,11 +99,14 @@ async function main(): Promise { LOG_INFO('Starting server'); await connectDatabase(); + LOG_SUCCESS('Database connected'); await connectCache(); + LOG_SUCCESS('Cache enabled'); await startGRPCServer(); + LOG_SUCCESS(`gRPC server started on port ${config.grpc.port}`); - app.listen(port, () => { - LOG_SUCCESS(`Server started on port ${port}`); + app.listen(config.http.port, () => { + LOG_SUCCESS(`HTTP server started on port ${config.http.port}`); }); } From d46a9413b441b02cc70fd6633c418c7dae41a3f4 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Fri, 28 Apr 2023 16:02:31 -0400 Subject: [PATCH 075/219] Changed decryptToken to always use the account server key --- src/database.ts | 2 +- src/services/api/routes/v1/resetPassword.ts | 3 +-- src/util.ts | 4 ++-- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/database.ts b/src/database.ts index e7f7748..cbb20cc 100644 --- a/src/database.ts +++ b/src/database.ts @@ -104,7 +104,7 @@ export async function getPNIDByBearerAuth(token: string): Promise let unpackedToken: Token; try { - const decryptedToken: Buffer = await decryptToken(config.aes_key, Buffer.from(token, 'base64')); + const decryptedToken: Buffer = await decryptToken(Buffer.from(token, 'base64')); unpackedToken = unpackToken(decryptedToken); } catch (error) { console.log(error); diff --git a/src/util.ts b/src/util.ts index a884cad..d335934 100644 --- a/src/util.ts +++ b/src/util.ts @@ -78,9 +78,9 @@ export function generateToken(key: string, options: TokenOptions): Buffer | null ]); } -export function decryptToken(key: string, token: Buffer): Buffer { +export function decryptToken(token: Buffer): Buffer { const iv: Buffer = Buffer.alloc(16); - const decipher: crypto.Decipher = crypto.createDecipheriv('aes-256-cbc', Buffer.from(key, 'hex'), iv); + const decipher: crypto.Decipher = crypto.createDecipheriv('aes-256-cbc', Buffer.from(config.aes_key, 'hex'), iv); return Buffer.concat([ decipher.update(token), From c0e4395b80b98989ef0e5aba1467f72004a473dd Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Fri, 28 Apr 2023 16:18:47 -0400 Subject: [PATCH 076/219] Removed useless await from getPNIDByBearerAuth --- src/database.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/database.ts b/src/database.ts index cbb20cc..cb122cc 100644 --- a/src/database.ts +++ b/src/database.ts @@ -104,7 +104,7 @@ export async function getPNIDByBearerAuth(token: string): Promise Date: Fri, 28 Apr 2023 19:36:31 -0400 Subject: [PATCH 077/219] Added server maintenance mode check --- src/services/api/routes/v1/resetPassword.ts | 1 - src/services/nasc/routes/ac.ts | 9 +++++++-- src/services/nnid/routes/provider.ts | 22 +++++++++++++++++++++ 3 files changed, 29 insertions(+), 3 deletions(-) diff --git a/src/services/api/routes/v1/resetPassword.ts b/src/services/api/routes/v1/resetPassword.ts index 9b90138..dae8546 100644 --- a/src/services/api/routes/v1/resetPassword.ts +++ b/src/services/api/routes/v1/resetPassword.ts @@ -31,7 +31,6 @@ router.post('/', async (request: express.Request, response: express.Response) => const decryptedToken: Buffer = await decryptToken(Buffer.from(token, 'base64')); unpackedToken = unpackToken(decryptedToken); } catch (error) { - console.log(error); return response.status(400).json({ app: 'api', status: 400, diff --git a/src/services/nasc/routes/ac.ts b/src/services/nasc/routes/ac.ts index cd688d3..2e5ba57 100644 --- a/src/services/nasc/routes/ac.ts +++ b/src/services/nasc/routes/ac.ts @@ -35,13 +35,18 @@ router.post('/', async (request: express.Request, response: express.Response) => const server: HydratedServerDocument | null = await getServerByTitleId(titleID, serverAccessLevel); if (!server || !server.aes_key) { - return response.status(200).send( nascError('110').toString()); + return response.status(200).send(nascError('110').toString()); + } + + if (server.maintenance_mode) { + // TODO - FIND THE REAL UNDER MAINTENANCE ERROR CODE. 110 IS NOT IT + return response.status(200).send(nascError('110').toString()); } if (action === 'LOGIN' && server.port <= 0 && server.ip !== '0.0.0.0') { // * Addresses of 0.0.0.0:0 are allowed // * They are expected for titles with no NEX server - return response.status(200).send( nascError('110').toString()); + return response.status(200).send(nascError('110').toString()); } switch (action) { diff --git a/src/services/nnid/routes/provider.ts b/src/services/nnid/routes/provider.ts index 21160d3..5d83b65 100644 --- a/src/services/nnid/routes/provider.ts +++ b/src/services/nnid/routes/provider.ts @@ -60,6 +60,17 @@ router.get('/service_token/@me', async (request: express.Request, response: expr }).end()); } + if (server.maintenance_mode) { + return response.send(xmlbuilder.create({ + errors: { + error: { + code: '2002', + message: 'The requested game server is under maintenance' + } + } + }).end()); + } + const tokenOptions: TokenOptions = { system_type: server.device, token_type: 0x4, // * Service token @@ -148,6 +159,17 @@ router.get('/nex_token/@me', async (request: express.Request, response: express. }).end()); } + if (server.maintenance_mode) { + return response.send(xmlbuilder.create({ + errors: { + error: { + code: '2002', + message: 'The requested game server is under maintenance' + } + } + }).end()); + } + const titleID: string | undefined = getValueFromHeaders(request.headers, 'x-nintendo-title-id'); if (!titleID) { From 4f19993889130b0d261b34e8ecc421d301349d16 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Sat, 29 Apr 2023 13:27:39 -0400 Subject: [PATCH 078/219] Only use fcdcert as the source of truth for 3DS consoles --- src/middleware/nasc.ts | 4 ---- src/types/mongoose/device.ts | 1 - 2 files changed, 5 deletions(-) diff --git a/src/middleware/nasc.ts b/src/middleware/nasc.ts index 5b4da42..5c0e083 100644 --- a/src/middleware/nasc.ts +++ b/src/middleware/nasc.ts @@ -96,10 +96,6 @@ async function NASCMiddleware(request: express.Request, response: express.Respon } let device: HydratedDeviceDocument | null = await Device.findOne({ - model, - serial: serialNumber, - environment, - mac_hash: macAddressHash, fcdcert_hash: fcdcertHash, }); diff --git a/src/types/mongoose/device.ts b/src/types/mongoose/device.ts index a2b2c78..a17b84e 100644 --- a/src/types/mongoose/device.ts +++ b/src/types/mongoose/device.ts @@ -16,7 +16,6 @@ export interface IDevice { token: string; account_id: number; }; - // * 3DS-specific stuff environment: string; mac_hash: string; fcdcert_hash: string; From 50089e043591c86f4c2b8ec2430d82ad07258928 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Sat, 29 Apr 2023 14:32:04 -0400 Subject: [PATCH 079/219] Added PNID.scrub method to remove personal information --- src/models/pnid.ts | 52 ++++++++++++++++++++++++++++++ src/services/nnid/routes/people.ts | 4 +++ src/types/mongoose/pnid.ts | 1 + 3 files changed, 57 insertions(+) diff --git a/src/models/pnid.ts b/src/models/pnid.ts index dfc01d6..7905ba4 100644 --- a/src/models/pnid.ts +++ b/src/models/pnid.ts @@ -217,4 +217,56 @@ PNIDSchema.method('getServerMode', function getServerMode(): string { return this.get('server_mode') || 'prod'; }); +PNIDSchema.method('scrub', function scrub() { + // * Remove all personal info from a PNID + // * Username and PID remain so thye do not get assigned again + this.creation_date = ''; + this.password = ''; + this.birthdate = ''; + this.gender = ''; + this.country = ''; + this.language = ''; + this.email = { + address: '', + primary: false, + parent: false, + reachable: false, + validated: false, + validated_date: '', + id: 0 + }; + this.region = 0; + this.timezone = { + name: '', + offset: 0 + }; + this.mii = { + name: 'Default', + primary: false, + data: 'AwAAQOlVognnx0GC2/uogAOzuI0n2QAAAEBEAGUAZgBhAHUAbAB0AAAAAAAAAEBAAAAhAQJoRBgmNEYUgRIXaA0AACkAUkhQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGm9', + id: 0, + hash: '', + image_url: '', + image_id: 0 + }; + this.flags = { + active: false, + marketing: false, + off_device: false + }; + this.connections = { + discord: { + id: '' + }, + stripe: { + customer_id: '', + subscription_id: '', + price_id: '', + tier_level: 0, + tier_name: '', + latest_webhook_timestamp: 0 + } + }; +}); + export const PNID: PNIDModel = model('PNID', PNIDSchema); \ No newline at end of file diff --git a/src/services/nnid/routes/people.ts b/src/services/nnid/routes/people.ts index a1c271e..d880641 100644 --- a/src/services/nnid/routes/people.ts +++ b/src/services/nnid/routes/people.ts @@ -523,6 +523,10 @@ router.put('/@me/deletion', async (request: express.Request, response: express.R }).end()); } + pnid.scrub(); + + await pnid.save(); + response.send(''); }); diff --git a/src/types/mongoose/pnid.ts b/src/types/mongoose/pnid.ts index 0b75ee1..a063c9c 100644 --- a/src/types/mongoose/pnid.ts +++ b/src/types/mongoose/pnid.ts @@ -79,6 +79,7 @@ export interface IPNIDMethods { updateMii(mii: { name: string, primary: string, data: string}): Promise; generateMiiImages(): Promise; getServerMode(): string; + scrub(): void; } interface IPNIDQueryHelpers {} From 8b7296543db1904ddb368a8ff174e1d88cf0a016 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Sat, 29 Apr 2023 14:48:19 -0400 Subject: [PATCH 080/219] Cancel Stripe subscription during PNID scrubbing --- SETUP.md | 47 +++++++++++++++++++------------------- package-lock.json | 13 +++++++++++ package.json | 1 + src/config-manager.ts | 10 ++++++++ src/models/pnid.ts | 21 ++++++++++++++++- src/types/common/config.ts | 3 +++ src/types/mongoose/pnid.ts | 2 +- 7 files changed, 72 insertions(+), 25 deletions(-) diff --git a/SETUP.md b/SETUP.md index 296d729..0ff4310 100644 --- a/SETUP.md +++ b/SETUP.md @@ -54,26 +54,27 @@ The Pretendo Network website uses this server as an API for querying user inform Configurations are loaded through environment variables. `.env` files are supported. All configuration options will be gone over, both required and optional. There also exists an example `.env` file -| Name | Description | Optional | -|-----------------------------------------------|------------------------------------------------------------------|----------| -| `PN_ACT_CONFIG_HTTP_PORT` | The HTTP port the server listens on | No | -| `PN_ACT_CONFIG_MONGO_CONNECTION_STRING` | MongoDB connection string | No | -| `PN_ACT_CONFIG_MONGOOSE_CONNECT_OPTIONS_PATH` | Path to a `.json` file containing Mongoose connection options | Yes | -| `PN_ACT_CONFIG_REDIS_URL` | Redis URL | Yes | -| `PN_ACT_CONFIG_EMAIL_HOST` | SMTP host | Yes | -| `PN_ACT_CONFIG_EMAIL_PORT` | SMTP port | Yes | -| `PN_ACT_CONFIG_EMAIL_SECURE` | Is the email server secure | Yes | -| `PN_ACT_CONFIG_EMAIL_USERNAME` | Email account username | Yes | -| `PN_ACT_CONFIG_EMAIL_PASSWORD` | Email account password | Yes | -| `PN_ACT_CONFIG_EMAIL_FROM` | Email "from" address | Yes | -| `PN_ACT_CONFIG_S3_ENDPOINT` | s3 server endpoint | Yes | -| `PN_ACT_CONFIG_S3_ACCESS_KEY` | s3 secret key | Yes | -| `PN_ACT_CONFIG_S3_ACCESS_SECRET` | s3 secret | Yes | -| `PN_ACT_CONFIG_HCAPTCHA_SECRET` | hCaptcha secret (in the form `0x...`) | Yes | -| `PN_ACT_CONFIG_CDN_SUBDOMAIN` | Subdomain used to serve CDN contents if s3 is disabled | Yes | -| `PN_ACT_CONFIG_CDN_DISK_PATH` | File system path used to store CDN contents if s3 is disabled | Yes | -| `PN_ACT_CONFIG_CDN_BASE_URL` | URL for serving CDN contents (usually the same as s3 endpoint) | No | -| `PN_ACT_CONFIG_WEBSITE_BASE` | Website URL | Yes | -| `PN_ACT_CONFIG_AES_KEY` | AES-256 key used for encrypting tokens | No | -| `PN_ACT_CONFIG_GRPC_API_KEY` | gRPC API key that other clients use to interact with this server | No | -| `PN_ACT_CONFIG_GRPC_PORT` | gRPC server port | No | \ No newline at end of file +| Name | Description | Optional | +|-----------------------------------------------|-------------------------------------------------------------------|----------| +| `PN_ACT_CONFIG_HTTP_PORT` | The HTTP port the server listens on | No | +| `PN_ACT_CONFIG_MONGO_CONNECTION_STRING` | MongoDB connection string | No | +| `PN_ACT_CONFIG_MONGOOSE_CONNECT_OPTIONS_PATH` | Path to a `.json` file containing Mongoose connection options | Yes | +| `PN_ACT_CONFIG_REDIS_URL` | Redis URL | Yes | +| `PN_ACT_CONFIG_EMAIL_HOST` | SMTP host | Yes | +| `PN_ACT_CONFIG_EMAIL_PORT` | SMTP port | Yes | +| `PN_ACT_CONFIG_EMAIL_SECURE` | Is the email server secure | Yes | +| `PN_ACT_CONFIG_EMAIL_USERNAME` | Email account username | Yes | +| `PN_ACT_CONFIG_EMAIL_PASSWORD` | Email account password | Yes | +| `PN_ACT_CONFIG_EMAIL_FROM` | Email "from" address | Yes | +| `PN_ACT_CONFIG_S3_ENDPOINT` | s3 server endpoint | Yes | +| `PN_ACT_CONFIG_S3_ACCESS_KEY` | s3 secret key | Yes | +| `PN_ACT_CONFIG_S3_ACCESS_SECRET` | s3 secret | Yes | +| `PN_ACT_CONFIG_HCAPTCHA_SECRET` | hCaptcha secret (in the form `0x...`) | Yes | +| `PN_ACT_CONFIG_CDN_SUBDOMAIN` | Subdomain used to serve CDN contents if s3 is disabled | Yes | +| `PN_ACT_CONFIG_CDN_DISK_PATH` | File system path used to store CDN contents if s3 is disabled | Yes | +| `PN_ACT_CONFIG_CDN_BASE_URL` | URL for serving CDN contents (usually the same as s3 endpoint) | No | +| `PN_ACT_CONFIG_WEBSITE_BASE` | Website URL | Yes | +| `PN_ACT_CONFIG_AES_KEY` | AES-256 key used for encrypting tokens | No | +| `PN_ACT_CONFIG_GRPC_API_KEY` | gRPC API key that other clients use to interact with this server | No | +| `PN_ACT_CONFIG_GRPC_PORT` | gRPC server port | No | +| `PN_ACT_CONFIG_STRIPE_SECRET_KEY` | Stripe API key. Used to cancel subscriptions when scrubbing PNIDs | Yes | \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index e78ea55..98264f2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -34,6 +34,7 @@ "nodemailer": "^6.4.2", "pretendo-grpc-ts": "github:PretendoNetwork/grpc-ts", "redis": "^4.3.1", + "stripe": "^12.3.0", "tga": "^1.0.4", "typescript-is": "^0.19.0", "validator": "^13.7.0", @@ -5067,6 +5068,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/stripe": { + "version": "12.3.0", + "resolved": "https://registry.npmjs.org/stripe/-/stripe-12.3.0.tgz", + "integrity": "sha512-B9Q1b0gbKY/Z4fQc1Y82VpHTFLh8A67D6kdcFtgpGfTovVkI7SamE66vmVaWNHgcUjPqI8x6wVvksdRf/ucTDw==", + "dependencies": { + "@types/node": ">=8.1.0", + "qs": "^6.11.0" + }, + "engines": { + "node": ">=12.*" + } + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", diff --git a/package.json b/package.json index 8a6862c..3b73edd 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,7 @@ "nodemailer": "^6.4.2", "pretendo-grpc-ts": "github:PretendoNetwork/grpc-ts", "redis": "^4.3.1", + "stripe": "^12.3.0", "tga": "^1.0.4", "typescript-is": "^0.19.0", "validator": "^13.7.0", diff --git a/src/config-manager.ts b/src/config-manager.ts index 5a22d3b..3305eb7 100644 --- a/src/config-manager.ts +++ b/src/config-manager.ts @@ -72,6 +72,12 @@ export const config: Config = { } }; +if (process.env.PN_ACT_CONFIG_STRIPE_SECRET_KEY) { + config.stripe = { + secret_key: process.env.PN_ACT_CONFIG_STRIPE_SECRET_KEY + }; +} + LOG_INFO('Config loaded, checking integrity'); if (!config.http.port) { @@ -182,4 +188,8 @@ if (!config.grpc.api_key) { if (!config.grpc.port) { LOG_ERROR('Failed to find gRPC port. Set the PN_ACT_CONFIG_GRPC_PORT environment variable'); process.exit(0); +} + +if (!config.stripe?.secret_key) { + LOG_WARN('Failed to find Stripe api key! If a PNID is deleted with an active subscription, the subscription will *NOT* be canceled! Set the PN_ACT_CONFIG_STRIPE_SECRET_KEY environment variable to enable'); } \ No newline at end of file diff --git a/src/models/pnid.ts b/src/models/pnid.ts index 7905ba4..9a048d0 100644 --- a/src/models/pnid.ts +++ b/src/models/pnid.ts @@ -5,9 +5,21 @@ import imagePixels from 'image-pixels'; import TGA from 'tga'; import got from 'got'; import Mii from 'mii-js'; +import Stripe from 'stripe'; import { DeviceSchema } from '@/models/device'; import { uploadCDNAsset } from '@/util'; +import { LOG_WARN } from '@/logger'; import { HydratedPNIDDocument, IPNID, IPNIDMethods, PNIDModel } from '@/types/mongoose/pnid'; +import { config } from '@/config-manager'; + +let stripe: Stripe; + +if (config.stripe?.secret_key) { + stripe = new Stripe(config.stripe.secret_key, { + apiVersion: '2022-11-15', + typescript: true, + }); +} const PNIDSchema = new Schema({ access_level: { @@ -217,9 +229,16 @@ PNIDSchema.method('getServerMode', function getServerMode(): string { return this.get('server_mode') || 'prod'; }); -PNIDSchema.method('scrub', function scrub() { +PNIDSchema.method('scrub', async function scrub() { // * Remove all personal info from a PNID // * Username and PID remain so thye do not get assigned again + + if (this.connections?.stripe?.subscription_id && stripe) { + await stripe.subscriptions.del(this.connections.stripe.subscription_id); + } else { + LOG_WARN(`SCRUBBING USER DATA FOR USER ${this.username}. HAS STRIPE SUBSCRIPTION ${this.connections.stripe.subscription_id}, BUT STRIPE CLIENT NOT ENABLED. SUBSCRIPTION NOT CANCELED`); + } + this.creation_date = ''; this.password = ''; this.birthdate = ''; diff --git a/src/types/common/config.ts b/src/types/common/config.ts index 628dac0..753322d 100644 --- a/src/types/common/config.ts +++ b/src/types/common/config.ts @@ -42,6 +42,9 @@ export interface Config { api_key: string; port: number; }; + stripe?: { + secret_key: string; + }; } export interface DisabledFeatures { diff --git a/src/types/mongoose/pnid.ts b/src/types/mongoose/pnid.ts index a063c9c..500d2f9 100644 --- a/src/types/mongoose/pnid.ts +++ b/src/types/mongoose/pnid.ts @@ -79,7 +79,7 @@ export interface IPNIDMethods { updateMii(mii: { name: string, primary: string, data: string}): Promise; generateMiiImages(): Promise; getServerMode(): string; - scrub(): void; + scrub(): Promise; } interface IPNIDQueryHelpers {} From 65f057c47a75a9603f3aced7736d6d152fa84a17 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Sat, 29 Apr 2023 14:52:18 -0400 Subject: [PATCH 081/219] Added deleted flag to PNIDs --- src/models/pnid.ts | 5 +++++ src/services/nnid/routes/people.ts | 3 +-- src/types/mongoose/pnid.ts | 1 + 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/models/pnid.ts b/src/models/pnid.ts index 9a048d0..a0dfdf1 100644 --- a/src/models/pnid.ts +++ b/src/models/pnid.ts @@ -22,6 +22,10 @@ if (config.stripe?.secret_key) { } const PNIDSchema = new Schema({ + deleted: { + type: Boolean, + default: false + }, access_level: { type: Number, default: 0 // 0: standard, 1: tester, 2: mod?, 3: dev @@ -239,6 +243,7 @@ PNIDSchema.method('scrub', async function scrub() { LOG_WARN(`SCRUBBING USER DATA FOR USER ${this.username}. HAS STRIPE SUBSCRIPTION ${this.connections.stripe.subscription_id}, BUT STRIPE CLIENT NOT ENABLED. SUBSCRIPTION NOT CANCELED`); } + this.deleted = true; this.creation_date = ''; this.password = ''; this.birthdate = ''; diff --git a/src/services/nnid/routes/people.ts b/src/services/nnid/routes/people.ts index d880641..0826c4f 100644 --- a/src/services/nnid/routes/people.ts +++ b/src/services/nnid/routes/people.ts @@ -523,8 +523,7 @@ router.put('/@me/deletion', async (request: express.Request, response: express.R }).end()); } - pnid.scrub(); - + await pnid.scrub(); await pnid.save(); response.send(''); diff --git a/src/types/mongoose/pnid.ts b/src/types/mongoose/pnid.ts index 500d2f9..db6f7d9 100644 --- a/src/types/mongoose/pnid.ts +++ b/src/types/mongoose/pnid.ts @@ -2,6 +2,7 @@ import { Model, Types, HydratedDocument } from 'mongoose'; import { IDevice } from '@/types/mongoose/device'; export interface IPNID { + deleted: boolean; access_level: number; server_access_level: string; pid: number; From 91e1537b101bd682428e2be78ca5557c5273abbf Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Sat, 29 Apr 2023 15:25:18 -0400 Subject: [PATCH 082/219] Call PNID.updateMii when scrubbing to generate default Mii images --- src/models/pnid.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/models/pnid.ts b/src/models/pnid.ts index a0dfdf1..530feb6 100644 --- a/src/models/pnid.ts +++ b/src/models/pnid.ts @@ -243,6 +243,12 @@ PNIDSchema.method('scrub', async function scrub() { LOG_WARN(`SCRUBBING USER DATA FOR USER ${this.username}. HAS STRIPE SUBSCRIPTION ${this.connections.stripe.subscription_id}, BUT STRIPE CLIENT NOT ENABLED. SUBSCRIPTION NOT CANCELED`); } + await this.updateMii({ + name: 'Default', + primary: false, + data: 'AwAAQOlVognnx0GC2/uogAOzuI0n2QAAAEBEAGUAZgBhAHUAbAB0AAAAAAAAAEBAAAAhAQJoRBgmNEYUgRIXaA0AACkAUkhQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGm9' + }); + this.deleted = true; this.creation_date = ''; this.password = ''; @@ -265,9 +271,6 @@ PNIDSchema.method('scrub', async function scrub() { offset: 0 }; this.mii = { - name: 'Default', - primary: false, - data: 'AwAAQOlVognnx0GC2/uogAOzuI0n2QAAAEBEAGUAZgBhAHUAbAB0AAAAAAAAAEBAAAAhAQJoRBgmNEYUgRIXaA0AACkAUkhQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGm9', id: 0, hash: '', image_url: '', From d82180208806f0250ad5ec8e29017d6f1a456a33 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Sat, 29 Apr 2023 15:27:41 -0400 Subject: [PATCH 083/219] Add types to PNID.updateMii params --- src/models/pnid.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/models/pnid.ts b/src/models/pnid.ts index 530feb6..9a2c6ec 100644 --- a/src/models/pnid.ts +++ b/src/models/pnid.ts @@ -178,7 +178,7 @@ PNIDSchema.method('generateEmailValidationToken', async function generateEmailVa } }); -PNIDSchema.method('updateMii', async function updateMii({name, primary, data}): Promise { +PNIDSchema.method('updateMii', async function updateMii({ name, primary, data }: { name: string; primary: string; data: string; }): Promise { this.mii.name = name; this.mii.primary = primary === 'Y'; this.mii.data = data; @@ -245,7 +245,7 @@ PNIDSchema.method('scrub', async function scrub() { await this.updateMii({ name: 'Default', - primary: false, + primary: 'N', data: 'AwAAQOlVognnx0GC2/uogAOzuI0n2QAAAEBEAGUAZgBhAHUAbAB0AAAAAAAAAEBAAAAhAQJoRBgmNEYUgRIXaA0AACkAUkhQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGm9' }); From cc62c8dad3190aecab79b6786c653246120fa07f Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Sat, 29 Apr 2023 15:30:12 -0400 Subject: [PATCH 084/219] Removed unused PNIDgetServerMode method --- src/models/pnid.ts | 4 ---- src/types/mongoose/pnid.ts | 1 - 2 files changed, 5 deletions(-) diff --git a/src/models/pnid.ts b/src/models/pnid.ts index 9a2c6ec..10964e4 100644 --- a/src/models/pnid.ts +++ b/src/models/pnid.ts @@ -229,10 +229,6 @@ PNIDSchema.method('generateMiiImages', async function generateMiiImages(): Promi await uploadCDNAsset('pn-cdn', `${userMiiKey}/body.png`, miiStudioBodyImageData, 'public-read'); }); -PNIDSchema.method('getServerMode', function getServerMode(): string { - return this.get('server_mode') || 'prod'; -}); - PNIDSchema.method('scrub', async function scrub() { // * Remove all personal info from a PNID // * Username and PID remain so thye do not get assigned again diff --git a/src/types/mongoose/pnid.ts b/src/types/mongoose/pnid.ts index db6f7d9..5b5be65 100644 --- a/src/types/mongoose/pnid.ts +++ b/src/types/mongoose/pnid.ts @@ -79,7 +79,6 @@ export interface IPNIDMethods { generateEmailValidationToken(): Promise; updateMii(mii: { name: string, primary: string, data: string}): Promise; generateMiiImages(): Promise; - getServerMode(): string; scrub(): Promise; } From b9481892e0bc1098271a45f0d5692ff0f7e75ef6 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Sat, 29 Apr 2023 15:39:20 -0400 Subject: [PATCH 085/219] Removed unsafe .get() calls from Mongoose documents --- src/middleware/nasc.ts | 4 ++-- src/models/pnid.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/middleware/nasc.ts b/src/middleware/nasc.ts index 5c0e083..3786908 100644 --- a/src/middleware/nasc.ts +++ b/src/middleware/nasc.ts @@ -100,13 +100,13 @@ async function NASCMiddleware(request: express.Request, response: express.Respon }); if (device) { - if (device.get('access_level') < 0) { + if (device.access_level < 0) { response.status(200).send(nascError('102').toString()); return; } if (pid) { - const linkedPIDs = device.get('linked_pids'); + const linkedPIDs: number[] = device.linked_pids; if (!linkedPIDs.includes(pid)) { response.status(200).send(nascError('102').toString()); diff --git a/src/models/pnid.ts b/src/models/pnid.ts index 10964e4..72504ba 100644 --- a/src/models/pnid.ts +++ b/src/models/pnid.ts @@ -192,7 +192,7 @@ PNIDSchema.method('updateMii', async function updateMii({ name, primary, data }: }); PNIDSchema.method('generateMiiImages', async function generateMiiImages(): Promise { - const miiData: string = this.get('mii.data'); + const miiData: string = this.mii.data; const mii: Mii = new Mii(Buffer.from(miiData, 'base64')); const miiStudioUrl: string = mii.studioUrl({ type: 'face', @@ -203,7 +203,7 @@ PNIDSchema.method('generateMiiImages', async function generateMiiImages(): Promi const pngData: ImageData = await imagePixels(miiStudioNormalFaceImageData); const tga: Buffer = TGA.createTgaBuffer(pngData.width, pngData.height, Uint8Array.from(pngData.data), false); - const userMiiKey: string = `mii/${this.get('pid')}`; + const userMiiKey: string = `mii/${this.pid}`; await uploadCDNAsset('pn-cdn', `${userMiiKey}/standard.tga`, tga, 'public-read'); await uploadCDNAsset('pn-cdn', `${userMiiKey}/normal_face.png`, miiStudioNormalFaceImageData, 'public-read'); From 60b6392153a9a7aba1271368bbd6d177216c21e9 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Sat, 29 Apr 2023 17:40:54 -0400 Subject: [PATCH 086/219] Added return types to route handlers --- src/middleware/ratelimit.ts | 2 +- src/server.ts | 4 +- src/services/api/routes/v1/connections.ts | 24 +++-- src/services/api/routes/v1/email.ts | 10 +- src/services/api/routes/v1/forgotPassword.ts | 6 +- src/services/api/routes/v1/login.ts | 30 ++++-- src/services/api/routes/v1/register.ts | 90 +++++++++++++----- src/services/api/routes/v1/resetPassword.ts | 46 ++++++--- src/services/api/routes/v1/user.ts | 20 ++-- src/services/conntest/index.ts | 2 +- src/services/datastore/routes/upload.ts | 11 ++- src/services/grpc/api-key-middleware.ts | 2 +- src/services/grpc/server.ts | 4 +- src/services/local-cdn/routes/get.ts | 2 +- src/services/nasc/routes/ac.ts | 14 ++- src/services/nnid/routes/admin.ts | 6 +- src/services/nnid/routes/content.ts | 4 +- src/services/nnid/routes/devices.ts | 2 +- src/services/nnid/routes/miis.ts | 6 +- src/services/nnid/routes/oauth.ts | 22 +++-- src/services/nnid/routes/people.ts | 98 +++++++++++++------- src/services/nnid/routes/provider.ts | 44 ++++++--- src/services/nnid/routes/support.ts | 27 ++++-- 23 files changed, 332 insertions(+), 144 deletions(-) diff --git a/src/middleware/ratelimit.ts b/src/middleware/ratelimit.ts index 0cc8b38..7538724 100644 --- a/src/middleware/ratelimit.ts +++ b/src/middleware/ratelimit.ts @@ -6,7 +6,7 @@ import { getValueFromHeaders } from '@/util'; export default ratelimit({ windowMs: 60 * 1000, max: 1, - keyGenerator: (request: express.Request) => { + keyGenerator: (request: express.Request): string => { let data: string | undefined = getValueFromHeaders(request.headers, 'x-nintendo-device-cert'); if (!data) { diff --git a/src/server.ts b/src/server.ts index 3e59b8e..2103ec4 100644 --- a/src/server.ts +++ b/src/server.ts @@ -48,7 +48,7 @@ app.use(assets); // 404 handler LOG_INFO('Creating 404 status handler'); -app.use((request: express.Request, response: express.Response) => { +app.use((request: express.Request, response: express.Response): void => { const url: string = fullUrl(request); let deviceId: string | undefined = getValueFromHeaders(request.headers, 'X-Nintendo-Device-ID'); @@ -75,7 +75,7 @@ app.use((request: express.Request, response: express.Response) => { // non-404 error handler LOG_INFO('Creating non-404 status handler'); -app.use((error: any, request: express.Request, response: express.Response, _next: express.NextFunction) => { +app.use((error: any, request: express.Request, response: express.Response, _next: express.NextFunction): void => { const status: number = error.status || 500; const url: string = fullUrl(request); let deviceId: string | undefined = getValueFromHeaders(request.headers, 'X-Nintendo-Device-ID'); diff --git a/src/services/api/routes/v1/connections.ts b/src/services/api/routes/v1/connections.ts index 5bf79b4..92c3fd8 100644 --- a/src/services/api/routes/v1/connections.ts +++ b/src/services/api/routes/v1/connections.ts @@ -15,33 +15,39 @@ const VALID_CONNECTION_TYPES: string[] = [ * Implementation of for: https://api.pretendo.cc/v1/connections/add/TYPE * Description: Adds an account connection to the users PNID */ -router.post('/add/:type', async (request: express.Request, response: express.Response) => { +router.post('/add/:type', async (request: express.Request, response: express.Response): Promise => { const data: ConnectionData = request.body?.data; const pnid: HydratedPNIDDocument | null = request.pnid; const type: string = request.params.type; if (!pnid) { - return response.status(400).json({ + response.status(400).json({ app: 'api', status: 400, error: 'Invalid or missing access token' }); + + return; } if (!data) { - return response.status(400).json({ + response.status(400).json({ app: 'api', status: 400, error: 'Invalid or missing connection data' }); + + return; } if (!VALID_CONNECTION_TYPES.includes(type)) { - return response.status(400).json({ + response.status(400).json({ app: 'api', status: 400, error: 'Invalid or missing connection type' }); + + return; } let result: ConnectionResponse | undefined = await addPNIDConnection(pnid, data, type); @@ -62,24 +68,28 @@ router.post('/add/:type', async (request: express.Request, response: express.Res * Implementation of for: https://api.pretendo.cc/v1/connections/remove/TYPE * Description: Removes an account connection from the users PNID */ -router.delete('/remove/:type', async (request: express.Request, response: express.Response) => { +router.delete('/remove/:type', async (request: express.Request, response: express.Response): Promise => { const pnid: HydratedPNIDDocument | null = request.pnid; const type: string = request.params.type; if (!pnid) { - return response.status(400).json({ + response.status(400).json({ app: 'api', status: 400, error: 'Invalid or missing access token' }); + + return; } if (!VALID_CONNECTION_TYPES.includes(type)) { - return response.status(400).json({ + response.status(400).json({ app: 'api', status: 400, error: 'Invalid or missing connection type' }); + + return; } let result: ConnectionResponse | undefined = await removePNIDConnection(pnid, type); diff --git a/src/services/api/routes/v1/email.ts b/src/services/api/routes/v1/email.ts index 7a2efed..b0b41f3 100644 --- a/src/services/api/routes/v1/email.ts +++ b/src/services/api/routes/v1/email.ts @@ -6,15 +6,17 @@ import { HydratedPNIDDocument } from '@/types/mongoose/pnid'; const router: express.Router = express.Router(); -router.get('/verify', async (request: express.Request, response: express.Response) => { +router.get('/verify', async (request: express.Request, response: express.Response): Promise => { const token: string | undefined = getValueFromQueryString(request.query, 'token'); if (!token || token.trim() == '') { - return response.status(400).json({ + response.status(400).json({ app: 'api', status: 400, error: 'Missing email token' }); + + return; } const pnid: HydratedPNIDDocument | null = await PNID.findOne({ @@ -22,11 +24,13 @@ router.get('/verify', async (request: express.Request, response: express.Respons }); if (!pnid) { - return response.status(400).json({ + response.status(400).json({ app: 'api', status: 400, error: 'Invalid email token' }); + + return; } const validatedDate: string = moment().format('YYYY-MM-DDTHH:MM:SS'); diff --git a/src/services/api/routes/v1/forgotPassword.ts b/src/services/api/routes/v1/forgotPassword.ts index 1cb3283..521bc56 100644 --- a/src/services/api/routes/v1/forgotPassword.ts +++ b/src/services/api/routes/v1/forgotPassword.ts @@ -6,15 +6,17 @@ import { HydratedPNIDDocument } from '@/types/mongoose/pnid'; const router: express.Router = express.Router(); -router.post('/', async (request: express.Request, response: express.Response) => { +router.post('/', async (request: express.Request, response: express.Response): Promise => { const input: string = request.body?.input; if (!input || input.trim() === '') { - return response.status(400).json({ + response.status(400).json({ app: 'api', status: 400, error: 'Invalid or missing input' }); + + return; } let pnid: HydratedPNIDDocument | null; diff --git a/src/services/api/routes/v1/login.ts b/src/services/api/routes/v1/login.ts index 400b3fe..ea51eff 100644 --- a/src/services/api/routes/v1/login.ts +++ b/src/services/api/routes/v1/login.ts @@ -14,42 +14,50 @@ const router: express.Router = express.Router(); * Description: Generates an access token for an API user * TODO: Replace this with a more robust OAuth2 implementation */ -router.post('/', async (request: express.Request, response: express.Response) => { +router.post('/', async (request: express.Request, response: express.Response): Promise => { const grantType: string = request.body?.grantType; const username: string = request.body?.username; const password: string = request.body?.password; const refreshToken: string = request.body?.refresh_token; if (!['password', 'refresh_token'].includes(grantType)) { - return response.status(400).json({ + response.status(400).json({ app: 'api', status: 400, error: 'Invalid grant type' }); + + return; } if (grantType === 'password' && (!username || username.trim() === '')) { - return response.status(400).json({ + response.status(400).json({ app: 'api', status: 400, error: 'Invalid or missing username' }); + + return; } if (grantType === 'password' && (!password || password.trim() === '')) { - return response.status(400).json({ + response.status(400).json({ app: 'api', status: 400, error: 'Invalid or missing password' }); + + return; } if (grantType === 'refresh_token' && (!refreshToken || refreshToken.trim() === '')) { - return response.status(400).json({ + response.status(400).json({ app: 'api', status: 400, error: 'Invalid or missing refresh token' }); + + return; } let pnid: HydratedPNIDDocument | null; @@ -58,31 +66,37 @@ router.post('/', async (request: express.Request, response: express.Response) => pnid = await getPNIDByUsername(username); if (!pnid) { - return response.status(400).json({ + response.status(400).json({ app: 'api', status: 400, error: 'User not found' }); + + return; } const hashedPassword: string = nintendoPasswordHash(password, pnid.pid); if (!pnid || !bcrypt.compareSync(hashedPassword, pnid.password)) { - return response.status(400).json({ + response.status(400).json({ app: 'api', status: 400, error: 'Invalid or missing password' }); + + return; } } else { pnid = await getPNIDByBearerAuth(refreshToken); if (!pnid) { - return response.status(400).json({ + response.status(400).json({ app: 'api', status: 400, error: 'Invalid or missing refresh token' }); + + return; } } diff --git a/src/services/api/routes/v1/register.ts b/src/services/api/routes/v1/register.ts index 25d30ca..b724e25 100644 --- a/src/services/api/routes/v1/register.ts +++ b/src/services/api/routes/v1/register.ts @@ -37,7 +37,7 @@ const DEFAULT_MII_DATA: Buffer = Buffer.from('AwAAQOlVognnx0GC2/uogAOzuI0n2QAAAE * Implementation of: https://api.pretendo.cc/v1/register * Description: Creates a new user PNID */ -router.post('/', async (request: express.Request, response: express.Response) => { +router.post('/', async (request: express.Request, response: express.Response): Promise => { const email: string = request.body.email?.trim(); const username: string = request.body.username?.trim(); const miiName: string = request.body.mii_name?.trim(); @@ -47,179 +47,221 @@ router.post('/', async (request: express.Request, response: express.Response) => if (!disabledFeatures.captcha) { if (!hCaptchaResponse || hCaptchaResponse === '') { - return response.status(400).json({ + response.status(400).json({ app: 'api', status: 400, error: 'Must fill in captcha' }); + + return; } const captchaVerify: VerifyResponse = await hcaptcha.verify(config.hcaptcha.secret, hCaptchaResponse); if (!captchaVerify.success) { - return response.status(400).json({ + response.status(400).json({ app: 'api', status: 400, error: 'Captcha verification failed' }); + + return; } } if (!email || email === '') { - return response.status(400).json({ + response.status(400).json({ app: 'api', status: 400, error: 'Must enter an email address' }); + + return; } if (!emailvalidator.validate(email)) { - return response.status(400).json({ + response.status(400).json({ app: 'api', status: 400, error: 'Invalid email address' }); + + return; } if (!username || username === '') { - return response.status(400).json({ + response.status(400).json({ app: 'api', status: 400, error: 'Must enter a username' }); + + return; } if (username.length < 6) { - return response.status(400).json({ + response.status(400).json({ app: 'api', status: 400, error: 'Username is too short' }); + + return; } if (username.length > 16) { - return response.status(400).json({ + response.status(400).json({ app: 'api', status: 400, error: 'Username is too long' }); + + return; } if (!PNID_VALID_CHARACTERS_REGEX.test(username)) { console.log(Buffer.from(username)); - return response.status(400).json({ + response.status(400).json({ app: 'api', status: 400, error: 'Username contains invalid characters' }); + + return; } if (PNID_PUNCTUATION_START_REGEX.test(username)) { - return response.status(400).json({ + response.status(400).json({ app: 'api', status: 400, error: 'Username cannot begin with punctuation characters' }); + + return; } if (PNID_PUNCTUATION_END_REGEX.test(username)) { - return response.status(400).json({ + response.status(400).json({ app: 'api', status: 400, error: 'Username cannot end with punctuation characters' }); + + return; } if (PNID_PUNCTUATION_DUPLICATE_REGEX.test(username)) { - return response.status(400).json({ + response.status(400).json({ app: 'api', status: 400, error: 'Two or more punctuation characters cannot be used in a row' }); + + return; } const userExists: boolean = await doesPNIDExist(username); if (userExists) { - return response.status(400).json({ + response.status(400).json({ app: 'api', status: 400, error: 'PNID already in use' }); + + return; } if (!miiName || miiName === '') { - return response.status(400).json({ + response.status(400).json({ app: 'api', status: 400, error: 'Must enter a Mii name' }); + + return; } if (!password || password === '') { - return response.status(400).json({ + response.status(400).json({ app: 'api', status: 400, error: 'Must enter a password' }); + + return; } if (password.length < 6) { - return response.status(400).json({ + response.status(400).json({ app: 'api', status: 400, error: 'Password is too short' }); + + return; } if (password.length > 16) { - return response.status(400).json({ + response.status(400).json({ app: 'api', status: 400, error: 'Password is too long' }); + + return; } if (password.toLowerCase() === username.toLowerCase()) { - return response.status(400).json({ + response.status(400).json({ app: 'api', status: 400, error: 'Password cannot be the same as username' }); + + return; } if (!PASSWORD_WORD_OR_NUMBER_REGEX.test(password) && !PASSWORD_WORD_OR_PUNCTUATION_REGEX.test(password) && !PASSWORD_NUMBER_OR_PUNCTUATION_REGEX.test(password)) { - return response.status(400).json({ + response.status(400).json({ app: 'api', status: 400, error: 'Password must have combination of letters, numbers, and/or punctuation characters' }); + + return; } if (PASSWORD_REPEATED_CHARACTER_REGEX.test(password)) { - return response.status(400).json({ + response.status(400).json({ app: 'api', status: 400, error: 'Password may not have 3 repeating characters' }); + + return; } if (password !== passwordConfirm) { - return response.status(400).json({ + response.status(400).json({ app: 'api', status: 400, error: 'Passwords do not match' }); + + return; } const miiNameBuffer: Buffer = Buffer.from(miiName, 'utf16le'); // UTF8 to UTF16 if (miiNameBuffer.length > 0x14) { - return response.status(400).json({ + response.status(400).json({ app: 'api', status: 400, error: 'Mii name too long' }); + + return; } const mii: Mii = new Mii(DEFAULT_MII_DATA); @@ -310,11 +352,13 @@ router.post('/', async (request: express.Request, response: express.Response) => await session.abortTransaction(); - return response.status(400).json({ + response.status(400).json({ app: 'api', status: 400, error: 'Password must have combination of letters, numbers, and/or punctuation characters' }); + + return; } finally { // * This runs regardless of failure // * Returning on catch will not prevent this from running diff --git a/src/services/api/routes/v1/resetPassword.ts b/src/services/api/routes/v1/resetPassword.ts index dae8546..c86d40c 100644 --- a/src/services/api/routes/v1/resetPassword.ts +++ b/src/services/api/routes/v1/resetPassword.ts @@ -13,17 +13,19 @@ const PASSWORD_WORD_OR_PUNCTUATION_REGEX: RegExp = /(?=.*[a-zA-Z])(?=.*[_\-.]).* const PASSWORD_NUMBER_OR_PUNCTUATION_REGEX: RegExp = /(?=.*\d)(?=.*[_\-.]).*/; const PASSWORD_REPEATED_CHARACTER_REGEX: RegExp = /(.)\1\1/; -router.post('/', async (request: express.Request, response: express.Response) => { +router.post('/', async (request: express.Request, response: express.Response): Promise => { const password: string = request.body.password?.trim(); const passwordConfirm: string = request.body.password_confirm?.trim(); const token: string = request.body.token?.trim(); if (!token || token === '') { - return response.status(400).json({ + response.status(400).json({ app: 'api', status: 400, error: 'Missing token' }); + + return; } let unpackedToken: Token; @@ -31,86 +33,106 @@ router.post('/', async (request: express.Request, response: express.Response) => const decryptedToken: Buffer = await decryptToken(Buffer.from(token, 'base64')); unpackedToken = unpackToken(decryptedToken); } catch (error) { - return response.status(400).json({ + response.status(400).json({ app: 'api', status: 400, error: 'Invalid token' }); + + return; } if (unpackedToken.expire_time < Date.now()) { - return response.status(400).json({ + response.status(400).json({ app: 'api', status: 400, error: 'Token expired' }); + + return; } const pnid: HydratedPNIDDocument | null = await PNID.findOne({ pid: unpackedToken.pid }); if (!pnid) { - return response.status(400).json({ + response.status(400).json({ app: 'api', status: 400, error: 'Invalid token. No user found' }); + + return; } if (!password || password === '') { - return response.status(400).json({ + response.status(400).json({ app: 'api', status: 400, error: 'Must enter a password' }); + + return; } if (password.length < 6) { - return response.status(400).json({ + response.status(400).json({ app: 'api', status: 400, error: 'Password is too short' }); + + return; } if (password.length > 16) { - return response.status(400).json({ + response.status(400).json({ app: 'api', status: 400, error: 'Password is too long' }); + + return; } if (password.toLowerCase() === pnid.usernameLower) { - return response.status(400).json({ + response.status(400).json({ app: 'api', status: 400, error: 'Password cannot be the same as username' }); + + return; } if (!PASSWORD_WORD_OR_NUMBER_REGEX.test(password) && !PASSWORD_WORD_OR_PUNCTUATION_REGEX.test(password) && !PASSWORD_NUMBER_OR_PUNCTUATION_REGEX.test(password)) { - return response.status(400).json({ + response.status(400).json({ app: 'api', status: 400, error: 'Password must have combination of letters, numbers, and/or punctuation characters' }); + + return; } if (PASSWORD_REPEATED_CHARACTER_REGEX.test(password)) { - return response.status(400).json({ + response.status(400).json({ app: 'api', status: 400, error: 'Password may not have 3 repeating characters' }); + + return; } if (password !== passwordConfirm) { - return response.status(400).json({ + response.status(400).json({ app: 'api', status: 400, error: 'Passwords do not match' }); + + return; } const primaryPasswordHash: string = nintendoPasswordHash(password, pnid.pid); diff --git a/src/services/api/routes/v1/user.ts b/src/services/api/routes/v1/user.ts index 28eac4e..c15bfd9 100644 --- a/src/services/api/routes/v1/user.ts +++ b/src/services/api/routes/v1/user.ts @@ -21,18 +21,20 @@ const userSchema: joi.ObjectSchema = joi.object({ * Implementation of for: https://api.pretendo.cc/v1/user * Description: Gets PNID details about the current user */ -router.get('/', async (request: express.Request, response: express.Response) => { +router.get('/', async (request: express.Request, response: express.Response): Promise => { const pnid: HydratedPNIDDocument | null = request.pnid; if (!pnid) { - return response.status(400).json({ + response.status(400).json({ app: 'api', status: 400, error: 'Invalid or missing access token' }); + + return; } - return response.json({ + response.json({ access_level: pnid.access_level, server_access_level: pnid.server_access_level, pid: pnid.pid, @@ -69,26 +71,30 @@ router.get('/', async (request: express.Request, response: express.Response) => * Implementation of for: https://api.pretendo.cc/v1/user * Description: Updates PNID certain details about the current user */ -router.post('/', async (request: express.Request, response: express.Response) => { +router.post('/', async (request: express.Request, response: express.Response): Promise => { const pnid: HydratedPNIDDocument | null = request.pnid; const updateUserRequest: UpdateUserRequest = request.body; if (!pnid) { - return response.status(400).json({ + response.status(400).json({ app: 'api', status: 400, error: 'Invalid or missing access token' }); + + return; } const valid: joi.ValidationResult = userSchema.validate(updateUserRequest); if (valid.error) { - return response.status(400).json({ + response.status(400).json({ app: 'api', status: 400, error: valid.error }); + + return; } // TODO - Make this do something @@ -99,7 +105,7 @@ router.post('/', async (request: express.Request, response: express.Response) => //await PNID.updateOne({ pid }, { $set: updateData }).exec(); - return response.json({ + response.json({ access_level: pnid.access_level, server_access_level: pnid.server_access_level, pid: pnid.pid, diff --git a/src/services/conntest/index.ts b/src/services/conntest/index.ts index c016eca..75784cc 100644 --- a/src/services/conntest/index.ts +++ b/src/services/conntest/index.ts @@ -9,7 +9,7 @@ const conntest: express.Router = express.Router(); // Setup route LOG_INFO('[conntest] Applying imported routes'); -conntest.get('/', async (request: express.Request, response: express.Response) => { +conntest.get('/', (request: express.Request, response: express.Response): void => { response.set('Content-Type', 'text/html'); response.set('X-Organization', 'Nintendo'); diff --git a/src/services/datastore/routes/upload.ts b/src/services/datastore/routes/upload.ts index 6bd3a5b..3ceccda 100644 --- a/src/services/datastore/routes/upload.ts +++ b/src/services/datastore/routes/upload.ts @@ -61,9 +61,10 @@ function multipartParser(request: express.Request, response: express.Response, n request.pipe(dicer); } -router.post('/upload', multipartParser, async (request: express.Request, response: express.Response) => { +router.post('/upload', multipartParser, async (request: express.Request, response: express.Response): Promise => { if (!request.files) { - return response.sendStatus(500); + response.sendStatus(500); + return; } const bucket: string = request.files.bucket.toString(); @@ -79,7 +80,8 @@ router.post('/upload', multipartParser, async (request: express.Request, respons const minuteAgo: number = Date.now() - minute; if (Number(date) < Math.floor(minuteAgo / 1000)) { - return response.sendStatus(400); + response.sendStatus(400); + return; } const data: string = `${pid}${bucket}${key}${date}`; @@ -89,7 +91,8 @@ router.post('/upload', multipartParser, async (request: express.Request, respons console.log(hmac, signature); if (hmac !== signature) { - return response.sendStatus(400); + response.sendStatus(400); + return; } await uploadCDNAsset(bucket, key, file, acl); diff --git a/src/services/grpc/api-key-middleware.ts b/src/services/grpc/api-key-middleware.ts index 37d2058..4fffca3 100644 --- a/src/services/grpc/api-key-middleware.ts +++ b/src/services/grpc/api-key-middleware.ts @@ -5,7 +5,7 @@ export async function* apiKeyMiddleware( call: ServerMiddlewareCall, context: CallContext, ): AsyncGenerator { - const apiKey = context.metadata.get('X-API-Key'); + const apiKey: string | undefined = context.metadata.get('X-API-Key'); if (!apiKey || apiKey !== config.grpc.api_key) { throw new ServerError( diff --git a/src/services/grpc/server.ts b/src/services/grpc/server.ts index 3c804c7..f568114 100644 --- a/src/services/grpc/server.ts +++ b/src/services/grpc/server.ts @@ -1,4 +1,4 @@ -import { createServer } from 'nice-grpc'; +import { createServer, Server } from 'nice-grpc'; import { AccountServiceImplementation, AccountDefinition } from 'pretendo-grpc-ts/dist/account/account_service'; import { config } from '@/config-manager'; @@ -14,7 +14,7 @@ const accountServiceImplementation: AccountServiceImplementation = { }; export async function startGRPCServer(): Promise { - const server = createServer().use(apiKeyMiddleware); + const server: Server = createServer().use(apiKeyMiddleware); server.add(AccountDefinition, accountServiceImplementation); diff --git a/src/services/local-cdn/routes/get.ts b/src/services/local-cdn/routes/get.ts index 6c98b71..acd4570 100644 --- a/src/services/local-cdn/routes/get.ts +++ b/src/services/local-cdn/routes/get.ts @@ -3,7 +3,7 @@ import { getLocalCDNFile } from '@/cache'; const router: express.Router = express.Router(); -router.get('/*', async (request: express.Request, response: express.Response) => { +router.get('/*', async (request: express.Request, response: express.Response): Promise => { const filePath: string = request.params[0]; const file: Buffer = await getLocalCDNFile(filePath); diff --git a/src/services/nasc/routes/ac.ts b/src/services/nasc/routes/ac.ts index 2e5ba57..326ab4f 100644 --- a/src/services/nasc/routes/ac.ts +++ b/src/services/nasc/routes/ac.ts @@ -13,7 +13,7 @@ const router: express.Router = express.Router(); * Replacement for: https://nasc.nintendowifi.net/ac * Description: Gets a NEX server address and token */ -router.post('/', async (request: express.Request, response: express.Response) => { +router.post('/', async (request: express.Request, response: express.Response): Promise => { const requestParams: NASCRequestParams = request.body; const action: string = nintendoBase64Decode(requestParams.action).toString(); const titleID: string = nintendoBase64Decode(requestParams.titleid).toString(); @@ -21,7 +21,8 @@ router.post('/', async (request: express.Request, response: express.Response) => let responseData: URLSearchParams = nascError('null'); if (!nexAccount) { - return response.status(200).send(responseData.toString()); + response.status(200).send(responseData.toString()); + return; } // TODO: REMOVE AFTER PUBLIC LAUNCH @@ -35,18 +36,21 @@ router.post('/', async (request: express.Request, response: express.Response) => const server: HydratedServerDocument | null = await getServerByTitleId(titleID, serverAccessLevel); if (!server || !server.aes_key) { - return response.status(200).send(nascError('110').toString()); + response.status(200).send(nascError('110').toString()); + return; } if (server.maintenance_mode) { // TODO - FIND THE REAL UNDER MAINTENANCE ERROR CODE. 110 IS NOT IT - return response.status(200).send(nascError('110').toString()); + response.status(200).send(nascError('110').toString()); + return; } if (action === 'LOGIN' && server.port <= 0 && server.ip !== '0.0.0.0') { // * Addresses of 0.0.0.0:0 are allowed // * They are expected for titles with no NEX server - return response.status(200).send(nascError('110').toString()); + response.status(200).send(nascError('110').toString()); + return; } switch (action) { diff --git a/src/services/nnid/routes/admin.ts b/src/services/nnid/routes/admin.ts index f1b63b4..87dbd40 100644 --- a/src/services/nnid/routes/admin.ts +++ b/src/services/nnid/routes/admin.ts @@ -11,13 +11,13 @@ const router: express.Router = express.Router(); * Replacement for: https://account.nintendo.net/v1/api/admin/mapped_ids * Description: Maps between NNID usernames and PIDs */ -router.get('/mapped_ids', async (request: express.Request, response: express.Response) => { +router.get('/mapped_ids', async (request: express.Request, response: express.Response): Promise => { const inputType: string | undefined = getValueFromQueryString(request.query, 'input_type'); const outputType: string | undefined = getValueFromQueryString(request.query, 'output_type'); const input: string | undefined = getValueFromQueryString(request.query, 'input'); if (!inputType || !outputType || !input) { - return response.status(400).send(xmlbuilder.create({ + response.status(400).send(xmlbuilder.create({ errors: { error: { cause: 'Bad Request', @@ -26,6 +26,8 @@ router.get('/mapped_ids', async (request: express.Request, response: express.Res } } }).end()); + + return; } let inputList: string[] = input.split(','); diff --git a/src/services/nnid/routes/content.ts b/src/services/nnid/routes/content.ts index 56dc2f5..b4d1ac3 100644 --- a/src/services/nnid/routes/content.ts +++ b/src/services/nnid/routes/content.ts @@ -11,7 +11,7 @@ const router: express.Router = express.Router(); * Replacement for: https://account.nintendo.net/v1/api/content/agreements/TYPE/REGION/VERSION * Description: Sends the client requested agreement */ -router.get('/agreements/:type/:region/:version', (request: express.Request, response: express.Response) => { +router.get('/agreements/:type/:region/:version', (request: express.Request, response: express.Response): void => { response.set('Content-Type', 'text/xml'); response.set('Server', 'Nintendo 3DS (http)'); response.set('X-Nintendo-Date', new Date().getTime().toString()); @@ -128,7 +128,7 @@ router.get('/agreements/:type/:region/:version', (request: express.Request, resp * Replacement for: https://account.nintendo.net/v1/api/content/time_zones/COUNTRY/LANGUAGE * Description: Sends the client the requested timezones */ -router.get('/time_zones/:countryCode/:language', (request: express.Request, response: express.Response) => { +router.get('/time_zones/:countryCode/:language', (request: express.Request, response: express.Response): void => { response.set('Content-Type', 'text/xml'); response.set('Server', 'Nintendo 3DS (http)'); response.set('X-Nintendo-Date', new Date().getTime().toString()); diff --git a/src/services/nnid/routes/devices.ts b/src/services/nnid/routes/devices.ts index 3626fb1..c770795 100644 --- a/src/services/nnid/routes/devices.ts +++ b/src/services/nnid/routes/devices.ts @@ -8,7 +8,7 @@ const router: express.Router = express.Router(); * Replacement for: https://account.nintendo.net/v1/api/devices/@current/status * Description: Unknown use */ -router.get('/@current/status', async (request: express.Request, response: express.Response) => { +router.get('/@current/status', async (request: express.Request, response: express.Response): Promise => { // TODO - Finish this response.send(xmlbuilder.create({ device: '' diff --git a/src/services/nnid/routes/miis.ts b/src/services/nnid/routes/miis.ts index dd58978..e6982f9 100644 --- a/src/services/nnid/routes/miis.ts +++ b/src/services/nnid/routes/miis.ts @@ -13,11 +13,11 @@ const router: express.Router = express.Router(); * Replacement for: https://account.nintendo.net/v1/api/miis * Description: Returns a list of NNID miis */ -router.get('/', async (request: express.Request, response: express.Response) => { +router.get('/', async (request: express.Request, response: express.Response): Promise => { const input: string | undefined = getValueFromQueryString(request.query, 'pids'); if (!input) { - return response.status(400).send(xmlbuilder.create({ + response.status(400).send(xmlbuilder.create({ errors: { error: { cause: 'Bad Request', @@ -26,6 +26,8 @@ router.get('/', async (request: express.Request, response: express.Response) => } } }).end()); + + return; } const pids: number[] = input.split(',').map(pid => Number(pid)); diff --git a/src/services/nnid/routes/oauth.ts b/src/services/nnid/routes/oauth.ts index c276b9f..95caab9 100644 --- a/src/services/nnid/routes/oauth.ts +++ b/src/services/nnid/routes/oauth.ts @@ -14,58 +14,66 @@ const router: express.Router = express.Router(); * Replacement for: https://account.nintendo.net/v1/api/oauth20/access_token/generate * Description: Generates an access token for a user */ -router.post('/access_token/generate', async (request: express.Request, response: express.Response) => { +router.post('/access_token/generate', async (request: express.Request, response: express.Response): Promise => { const grantType: string = request.body?.grant_type; const username: string = request.body?.user_id; const password: string = request.body?.password; if (!['password', 'refresh_token'].includes(grantType)) { response.status(400); - return response.send(xmlbuilder.create({ + response.send(xmlbuilder.create({ error: { cause: 'grant_type', code: '0004', message: 'Invalid Grant Type' } }).end()); + + return; } if (!username || username.trim() === '') { response.status(400); - return response.send(xmlbuilder.create({ + response.send(xmlbuilder.create({ error: { cause: 'user_id', code: '0002', message: 'user_id format is invalid' } }).end()); + + return; } if (!password || password.trim() === '') { response.status(400); - return response.send(xmlbuilder.create({ + response.send(xmlbuilder.create({ error: { cause: 'password', code: '0002', message: 'password format is invalid' } }).end()); + + return; } const pnid: HydratedPNIDDocument | null = await getPNIDByUsername(username); if (!pnid || !await bcrypt.compare(password, pnid.password)) { response.status(400); - return response.send(xmlbuilder.create({ + response.send(xmlbuilder.create({ error: { code: '0106', message: 'Invalid account ID or password' } }).end({ pretty: true })); + + return; } if (pnid.access_level < 0) { - return response.status(400).send(xmlbuilder.create({ + response.status(400).send(xmlbuilder.create({ errors: { error: { code: '0122', @@ -73,6 +81,8 @@ router.post('/access_token/generate', async (request: express.Request, response: } } }).end()); + + return; } const accessTokenOptions: TokenOptions = { diff --git a/src/services/nnid/routes/people.ts b/src/services/nnid/routes/people.ts index 0826c4f..57ce457 100644 --- a/src/services/nnid/routes/people.ts +++ b/src/services/nnid/routes/people.ts @@ -28,7 +28,7 @@ const router: express.Router = express.Router(); * Replacement for: https://account.nintendo.net/v1/api/people/:USERNAME * Description: Checks if a username is in use */ -router.get('/:username', async (request: express.Request, response: express.Response) => { +router.get('/:username', async (request: express.Request, response: express.Response): Promise => { const username: string = request.params.username; const userExists: boolean = await doesPNIDExist(username); @@ -36,7 +36,7 @@ router.get('/:username', async (request: express.Request, response: express.Resp if (userExists) { response.status(400); - return response.end(xmlbuilder.create({ + response.end(xmlbuilder.create({ errors: { error: { code: '0100', @@ -44,6 +44,8 @@ router.get('/:username', async (request: express.Request, response: express.Resp } } }).end()); + + return; } response.status(200); @@ -55,18 +57,20 @@ router.get('/:username', async (request: express.Request, response: express.Resp * Replacement for: https://account.nintendo.net/v1/api/people * Description: Registers a new NNID */ -router.post('/', ratelimit, deviceCertificateMiddleware, async (request: express.Request, response: express.Response) => { +router.post('/', ratelimit, deviceCertificateMiddleware, async (request: express.Request, response: express.Response): Promise => { if (!request.certificate || !request.certificate.valid) { // TODO: Change this to a different error response.status(400); - return response.send(xmlbuilder.create({ + response.send(xmlbuilder.create({ error: { cause: 'Bad Request', code: '1600', message: 'Unable to process request' } }).end()); + + return; } const person: Person = request.body.person; @@ -76,7 +80,7 @@ router.post('/', ratelimit, deviceCertificateMiddleware, async (request: express if (userExists) { response.status(400); - return response.end(xmlbuilder.create({ + response.end(xmlbuilder.create({ errors: { error: { code: '0100', @@ -84,6 +88,8 @@ router.post('/', ratelimit, deviceCertificateMiddleware, async (request: express } } }).end()); + + return; } const creationDate: string = moment().format('YYYY-MM-DDTHH:MM:SS'); @@ -190,13 +196,15 @@ router.post('/', ratelimit, deviceCertificateMiddleware, async (request: express response.status(400); - return response.send(xmlbuilder.create({ + response.send(xmlbuilder.create({ error: { cause: 'Bad Request', code: '1600', message: 'Unable to process request' } }).end()); + + return; } finally { // * This runs regardless of failure // * Returning on catch will not prevent this from running @@ -217,7 +225,7 @@ router.post('/', ratelimit, deviceCertificateMiddleware, async (request: express * Replacement for: https://account.nintendo.net/v1/api/people/@me/profile * Description: Gets a users profile */ -router.get('/@me/profile', async (request: express.Request, response: express.Response) => { +router.get('/@me/profile', async (request: express.Request, response: express.Response): Promise => { response.set('Content-Type', 'text/xml'); response.set('Server', 'Nintendo 3DS (http)'); response.set('X-Nintendo-Date', new Date().getTime().toString()); @@ -226,7 +234,7 @@ router.get('/@me/profile', async (request: express.Request, response: express.Re if (!pnid) { // TODO - Research this error more - return response.status(404).send(xmlbuilder.create({ + response.status(404).send(xmlbuilder.create({ errors: { error: { cause: '', @@ -235,13 +243,15 @@ router.get('/@me/profile', async (request: express.Request, response: express.Re } } }).end()); + + return; } const person: PNIDProfile | null = await getPNIDProfileJSONByPID(pnid.pid); if (!person) { // TODO - Research this error more - return response.status(404).send(xmlbuilder.create({ + response.status(404).send(xmlbuilder.create({ errors: { error: { cause: '', @@ -250,6 +260,8 @@ router.get('/@me/profile', async (request: express.Request, response: express.Re } } }).end()); + + return; } response.send(xmlbuilder.create({ @@ -262,7 +274,7 @@ router.get('/@me/profile', async (request: express.Request, response: express.Re * Replacement for: https://account.nintendo.net/v1/api/people/@me/devices * Description: Gets user profile, seems to be the same as https://account.nintendo.net/v1/api/people/@me/profile */ -router.post('/@me/devices', async (request: express.Request, response: express.Response) => { +router.post('/@me/devices', async (request: express.Request, response: express.Response): Promise => { response.set('Content-Type', 'text/xml'); response.set('Server', 'Nintendo 3DS (http)'); response.set('X-Nintendo-Date', new Date().getTime().toString()); @@ -277,7 +289,7 @@ router.post('/@me/devices', async (request: express.Request, response: express.R if (!pnid) { // TODO - Research this error more - return response.status(404).send(xmlbuilder.create({ + response.status(404).send(xmlbuilder.create({ errors: { error: { cause: '', @@ -286,13 +298,15 @@ router.post('/@me/devices', async (request: express.Request, response: express.R } } }).end()); + + return; } const person: PNIDProfile | null = await getPNIDProfileJSONByPID(pnid.pid); if (!person) { // TODO - Research this error more - return response.status(404).send(xmlbuilder.create({ + response.status(404).send(xmlbuilder.create({ errors: { error: { cause: '', @@ -301,6 +315,8 @@ router.post('/@me/devices', async (request: express.Request, response: express.R } } }).end()); + + return; } response.send(xmlbuilder.create({ @@ -313,7 +329,7 @@ router.post('/@me/devices', async (request: express.Request, response: express.R * Replacement for: https://account.nintendo.net/v1/api/people/@me/devices * Description: Returns only user devices */ -router.get('/@me/devices', async (request: express.Request, response: express.Response) => { +router.get('/@me/devices', async (request: express.Request, response: express.Response): Promise => { response.set('Content-Type', 'text/xml'); response.set('Server', 'Nintendo 3DS (http)'); response.set('X-Nintendo-Date', new Date().getTime().toString()); @@ -328,7 +344,7 @@ router.get('/@me/devices', async (request: express.Request, response: express.Re if (!deviceId || !acceptLanguage || !platformId || !region || !serialNumber || !systemVersion) { // TODO - Research these error more - return response.status(400).send(xmlbuilder.create({ + response.status(400).send(xmlbuilder.create({ errors: { error: { cause: 'Bad Request', @@ -337,11 +353,13 @@ router.get('/@me/devices', async (request: express.Request, response: express.Re } } }).end()); + + return; } if (!pnid) { // TODO - Research this error more - return response.status(404).send(xmlbuilder.create({ + response.status(404).send(xmlbuilder.create({ errors: { error: { cause: '', @@ -350,6 +368,8 @@ router.get('/@me/devices', async (request: express.Request, response: express.Re } } }).end()); + + return; } response.send(xmlbuilder.create({ @@ -378,7 +398,7 @@ router.get('/@me/devices', async (request: express.Request, response: express.Re * Replacement for: https://account.nintendo.net/v1/api/people/@me/devices/owner * Description: Gets user profile, seems to be the same as https://account.nintendo.net/v1/api/people/@me/profile */ -router.get('/@me/devices/owner', async (request: express.Request, response: express.Response) => { +router.get('/@me/devices/owner', async (request: express.Request, response: express.Response): Promise => { response.set('Content-Type', 'text/xml'); response.set('Server', 'Nintendo 3DS (http)'); response.set('X-Nintendo-Date', moment().add(5, 'h').toString()); @@ -387,7 +407,7 @@ router.get('/@me/devices/owner', async (request: express.Request, response: expr if (!pnid) { // TODO - Research this error more - return response.status(404).send(xmlbuilder.create({ + response.status(404).send(xmlbuilder.create({ errors: { error: { cause: '', @@ -396,13 +416,15 @@ router.get('/@me/devices/owner', async (request: express.Request, response: expr } } }).end()); + + return; } const person: PNIDProfile | null = await getPNIDProfileJSONByPID(pnid.pid); if (!person) { // TODO - Research this error more - return response.status(404).send(xmlbuilder.create({ + response.status(404).send(xmlbuilder.create({ errors: { error: { cause: '', @@ -411,6 +433,8 @@ router.get('/@me/devices/owner', async (request: express.Request, response: expr } } }).end()); + + return; } response.send(xmlbuilder.create({ @@ -423,7 +447,7 @@ router.get('/@me/devices/owner', async (request: express.Request, response: expr * Replacement for: https://account.nintendo.net/v1/api/people/@me/devices/status * Description: Unknown use */ -router.get('/@me/devices/status', async (_request: express.Request, response: express.Response) => { +router.get('/@me/devices/status', async (_request: express.Request, response: express.Response): Promise => { response.set('Content-Type', 'text/xml'); response.set('Server', 'Nintendo 3DS (http)'); response.set('X-Nintendo-Date', moment().add(5, 'h').toString()); @@ -439,12 +463,12 @@ router.get('/@me/devices/status', async (_request: express.Request, response: ex * Replacement for: https://account.nintendo.net/v1/api/people/@me/miis/@primary * Description: Updates a users Mii */ -router.put('/@me/miis/@primary', async (request: express.Request, response: express.Response) => { +router.put('/@me/miis/@primary', async (request: express.Request, response: express.Response): Promise => { const pnid: HydratedPNIDDocument | null = request.pnid; if (!pnid) { // TODO - Research this error more - return response.status(404).send(xmlbuilder.create({ + response.status(404).send(xmlbuilder.create({ errors: { error: { cause: '', @@ -453,6 +477,8 @@ router.put('/@me/miis/@primary', async (request: express.Request, response: expr } } }).end()); + + return; } const mii: { @@ -477,7 +503,7 @@ router.put('/@me/miis/@primary', async (request: express.Request, response: expr * Replacement for: https://account.nintendo.net/v1/api/people/@me/devices/@current/inactivate * Description: Deactivates a user from a console */ -router.put('/@me/devices/@current/inactivate', async (request: express.Request, response: express.Response) => { +router.put('/@me/devices/@current/inactivate', async (request: express.Request, response: express.Response): Promise => { response.set('Server', 'Nintendo 3DS (http)'); response.set('X-Nintendo-Date', new Date().getTime().toString()); @@ -486,7 +512,7 @@ router.put('/@me/devices/@current/inactivate', async (request: express.Request, if (!pnid) { response.status(400); - return response.end(xmlbuilder.create({ + response.end(xmlbuilder.create({ errors: { error: { cause: 'access_token', @@ -495,6 +521,8 @@ router.put('/@me/devices/@current/inactivate', async (request: express.Request, } } }).end()); + + return; } response.status(200); @@ -506,13 +534,13 @@ router.put('/@me/devices/@current/inactivate', async (request: express.Request, * Replacement for: https://account.nintendo.net/v1/api/people/@me/deletion * Description: Deletes a NNID */ -router.put('/@me/deletion', async (request: express.Request, response: express.Response) => { +router.put('/@me/deletion', async (request: express.Request, response: express.Response): Promise => { const pnid: HydratedPNIDDocument | null = request.pnid; if (!pnid) { response.status(400); - return response.end(xmlbuilder.create({ + response.end(xmlbuilder.create({ errors: { error: { cause: 'access_token', @@ -521,6 +549,8 @@ router.put('/@me/deletion', async (request: express.Request, response: express.R } } }).end()); + + return; } await pnid.scrub(); @@ -534,14 +564,14 @@ router.put('/@me/deletion', async (request: express.Request, response: express.R * Replacement for: https://account.nintendo.net/v1/api/people/@me/ * Description: Updates a PNIDs account details */ -router.put('/@me', async (request: express.Request, response: express.Response) => { +router.put('/@me', async (request: express.Request, response: express.Response): Promise => { const pnid: HydratedPNIDDocument | null = request.pnid; const person: Person = request.body.person; if (!pnid) { response.status(400); - return response.end(xmlbuilder.create({ + response.end(xmlbuilder.create({ errors: { error: { cause: 'access_token', @@ -550,6 +580,8 @@ router.put('/@me', async (request: express.Request, response: express.Response) } } }).end()); + + return; } const gender: string = person.gender ? person.gender : pnid.gender; @@ -599,13 +631,13 @@ router.put('/@me', async (request: express.Request, response: express.Response) * Replacement for: https://account.nintendo.net/v1/api/people/@me/emails/ * Description: Gets a list (why?) of PNID emails */ -router.get('/@me/emails', async (request: express.Request, response: express.Response) => { +router.get('/@me/emails', async (request: express.Request, response: express.Response): Promise => { const pnid: HydratedPNIDDocument | null = request.pnid; if (!pnid) { response.status(400); - return response.end(xmlbuilder.create({ + response.end(xmlbuilder.create({ errors: { error: { cause: 'access_token', @@ -614,6 +646,8 @@ router.get('/@me/emails', async (request: express.Request, response: express.Res } } }).end()); + + return; } response.send(xmlbuilder.create({ @@ -640,7 +674,7 @@ router.get('/@me/emails', async (request: express.Request, response: express.Res * Replacement for: https://account.nintendo.net/v1/api/people/@me/emails/@primary * Description: Updates a users email address */ -router.put('/@me/emails/@primary', async (request: express.Request, response: express.Response) => { +router.put('/@me/emails/@primary', async (request: express.Request, response: express.Response): Promise => { const pnid: HydratedPNIDDocument | null = request.pnid; const email: { @@ -650,7 +684,7 @@ router.put('/@me/emails/@primary', async (request: express.Request, response: ex if (!pnid || !email || !email.address) { response.status(400); - return response.end(xmlbuilder.create({ + response.end(xmlbuilder.create({ errors: { error: { cause: 'access_token', @@ -659,6 +693,8 @@ router.put('/@me/emails/@primary', async (request: express.Request, response: ex } } }).end()); + + return; } // TODO - Better email check diff --git a/src/services/nnid/routes/provider.ts b/src/services/nnid/routes/provider.ts index 5d83b65..4e5f818 100644 --- a/src/services/nnid/routes/provider.ts +++ b/src/services/nnid/routes/provider.ts @@ -15,13 +15,13 @@ const router: express.Router = express.Router(); * Replacement for: https://account.nintendo.net/v1/api/provider/service_token/@me * Description: Gets a service token */ -router.get('/service_token/@me', async (request: express.Request, response: express.Response) => { +router.get('/service_token/@me', async (request: express.Request, response: express.Response): Promise => { const pnid: HydratedPNIDDocument | null = request.pnid; if (!pnid) { response.status(400); - return response.end(xmlbuilder.create({ + response.end(xmlbuilder.create({ errors: { error: { cause: 'access_token', @@ -30,13 +30,15 @@ router.get('/service_token/@me', async (request: express.Request, response: expr } } }).end()); + + return; } const titleID: string | undefined = getValueFromHeaders(request.headers, 'x-nintendo-title-id'); if (!titleID) { // TODO - Research this error more - return response.send(xmlbuilder.create({ + response.send(xmlbuilder.create({ errors: { error: { code: '1021', @@ -44,13 +46,15 @@ router.get('/service_token/@me', async (request: express.Request, response: expr } } }).end()); + + return; } const serverAccessLevel: string = pnid.server_access_level; const server: HydratedServerDocument | null = await getServerByTitleId(titleID, serverAccessLevel); if (!server || !server.aes_key) { - return response.send(xmlbuilder.create({ + response.send(xmlbuilder.create({ errors: { error: { code: '1021', @@ -58,10 +62,12 @@ router.get('/service_token/@me', async (request: express.Request, response: expr } } }).end()); + + return; } if (server.maintenance_mode) { - return response.send(xmlbuilder.create({ + response.send(xmlbuilder.create({ errors: { error: { code: '2002', @@ -69,6 +75,8 @@ router.get('/service_token/@me', async (request: express.Request, response: expr } } }).end()); + + return; } const tokenOptions: TokenOptions = { @@ -99,13 +107,13 @@ router.get('/service_token/@me', async (request: express.Request, response: expr * Replacement for: https://account.nintendo.net/v1/api/provider/nex_token/@me * Description: Gets a NEX server address and token */ -router.get('/nex_token/@me', async (request: express.Request, response: express.Response) => { +router.get('/nex_token/@me', async (request: express.Request, response: express.Response): Promise => { const pnid: HydratedPNIDDocument | null = request.pnid; if (!pnid) { response.status(400); - return response.end(xmlbuilder.create({ + response.end(xmlbuilder.create({ errors: { error: { cause: 'access_token', @@ -114,6 +122,8 @@ router.get('/nex_token/@me', async (request: express.Request, response: express. } } }).end()); + + return; } const nexAccount: HydratedNEXAccountDocument | null = await NEXAccount.findOne({ @@ -121,7 +131,7 @@ router.get('/nex_token/@me', async (request: express.Request, response: express. }); if (!nexAccount) { - return response.status(404).send(xmlbuilder.create({ + response.status(404).send(xmlbuilder.create({ errors: { error: { cause: '', @@ -130,12 +140,14 @@ router.get('/nex_token/@me', async (request: express.Request, response: express. } } }).end()); + + return; } const gameServerID: string | undefined = getValueFromQueryString(request.query, 'game_server_id'); if (!gameServerID) { - return response.send(xmlbuilder.create({ + response.send(xmlbuilder.create({ errors: { error: { code: '0118', @@ -143,13 +155,15 @@ router.get('/nex_token/@me', async (request: express.Request, response: express. } } }).end()); + + return; } const serverAccessLevel: string = pnid.server_access_level; const server: HydratedServerDocument | null = await getServerByGameServerId(gameServerID, serverAccessLevel); if (!server || !server.aes_key) { - return response.send(xmlbuilder.create({ + response.send(xmlbuilder.create({ errors: { error: { code: '1021', @@ -157,10 +171,12 @@ router.get('/nex_token/@me', async (request: express.Request, response: express. } } }).end()); + + return; } if (server.maintenance_mode) { - return response.send(xmlbuilder.create({ + response.send(xmlbuilder.create({ errors: { error: { code: '2002', @@ -168,13 +184,15 @@ router.get('/nex_token/@me', async (request: express.Request, response: express. } } }).end()); + + return; } const titleID: string | undefined = getValueFromHeaders(request.headers, 'x-nintendo-title-id'); if (!titleID) { // TODO - Research this error more - return response.send(xmlbuilder.create({ + response.send(xmlbuilder.create({ errors: { error: { code: '1021', @@ -182,6 +200,8 @@ router.get('/nex_token/@me', async (request: express.Request, response: express. } } }).end()); + + return; } const tokenOptions: TokenOptions = { diff --git a/src/services/nnid/routes/support.ts b/src/services/nnid/routes/support.ts index be795d6..149ab31 100644 --- a/src/services/nnid/routes/support.ts +++ b/src/services/nnid/routes/support.ts @@ -13,11 +13,11 @@ const router: express.Router = express.Router(); * Replacement for: https://account.nintendo.net/v1/api/support/validate/email * Description: Verifies a provided email address is valid */ -router.post('/validate/email', async (request: express.Request, response: express.Response) => { +router.post('/validate/email', async (request: express.Request, response: express.Response): Promise => { const email: string = request.body.email; if (!email) { - return response.send(xmlbuilder.create({ + response.send(xmlbuilder.create({ errors: { error: { cause: 'email', @@ -26,6 +26,8 @@ router.post('/validate/email', async (request: express.Request, response: expres } } }).end()); + + return; } const domain: string = email.split('@')[1]; @@ -52,14 +54,14 @@ router.post('/validate/email', async (request: express.Request, response: expres * Replacement for: https://account.nintendo.net/v1/api/support/email_confirmation/:pid/:code * Description: Verifies a users email via 6 digit code */ -router.put('/email_confirmation/:pid/:code', async (request: express.Request, response: express.Response) => { +router.put('/email_confirmation/:pid/:code', async (request: express.Request, response: express.Response): Promise => { const code: string = request.params.code; const pid: number = Number(request.params.pid); const pnid: HydratedPNIDDocument | null = await getPNIDByPID(pid); if (!pnid) { - return response.status(400).send(xmlbuilder.create({ + response.status(400).send(xmlbuilder.create({ errors: { error: { code: '0130', @@ -67,10 +69,12 @@ router.put('/email_confirmation/:pid/:code', async (request: express.Request, re } } }).end()); + + return; } if (pnid.identification.email_code !== code) { - return response.status(400).send(xmlbuilder.create({ + response.status(400).send(xmlbuilder.create({ errors: { error: { code: '0116', @@ -78,6 +82,7 @@ router.put('/email_confirmation/:pid/:code', async (request: express.Request, re } } }).end()); + return; } const validatedDate: string = moment().format('YYYY-MM-DDTHH:MM:SS'); @@ -98,14 +103,14 @@ router.put('/email_confirmation/:pid/:code', async (request: express.Request, re * Replacement for: https://account.nintendo.net/v1/api/support/resend_confirmation * Description: Resends a users confirmation email */ -router.get('/resend_confirmation', async (request: express.Request, response: express.Response) => { +router.get('/resend_confirmation', async (request: express.Request, response: express.Response): Promise => { const pid: number = Number(request.headers['x-nintendo-pid']); const pnid: HydratedPNIDDocument | null = await getPNIDByPID(pid); if (!pnid) { // TODO - Unsure if this is the right error - return response.status(400).send(xmlbuilder.create({ + response.status(400).send(xmlbuilder.create({ errors: { error: { code: '0130', @@ -113,6 +118,8 @@ router.get('/resend_confirmation', async (request: express.Request, response: ex } } }).end()); + + return; } await sendConfirmationEmail(pnid); @@ -126,14 +133,14 @@ router.get('/resend_confirmation', async (request: express.Request, response: ex * Description: Sends the user a password reset email * NOTE: On NN this was a temp password that expired after 24 hours. We do not do that */ -router.get('/forgotten_password/:pid', async (request: express.Request, response: express.Response) => { +router.get('/forgotten_password/:pid', async (request: express.Request, response: express.Response): Promise => { const pid: number = Number(request.params.pid); const pnid: HydratedPNIDDocument | null = await getPNIDByPID(pid); if (!pnid) { // TODO - Better errors - return response.status(400).send(xmlbuilder.create({ + response.status(400).send(xmlbuilder.create({ errors: { error: { cause: 'device_id', @@ -142,6 +149,8 @@ router.get('/forgotten_password/:pid', async (request: express.Request, response } } }).end()); + + return; } await sendForgotPasswordEmail(pnid); From 43432902c4962800003fbcb893582a780c006242 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Sat, 29 Apr 2023 17:42:27 -0400 Subject: [PATCH 087/219] Replaced all instances of response.end with response.send --- src/services/nnid/routes/people.ts | 18 +++++++++--------- src/services/nnid/routes/provider.ts | 4 ++-- src/services/nnid/routes/support.ts | 2 +- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/services/nnid/routes/people.ts b/src/services/nnid/routes/people.ts index 57ce457..e667089 100644 --- a/src/services/nnid/routes/people.ts +++ b/src/services/nnid/routes/people.ts @@ -36,7 +36,7 @@ router.get('/:username', async (request: express.Request, response: express.Resp if (userExists) { response.status(400); - response.end(xmlbuilder.create({ + response.send(xmlbuilder.create({ errors: { error: { code: '0100', @@ -49,7 +49,7 @@ router.get('/:username', async (request: express.Request, response: express.Resp } response.status(200); - response.end(); + response.send(); }); /** @@ -80,7 +80,7 @@ router.post('/', ratelimit, deviceCertificateMiddleware, async (request: express if (userExists) { response.status(400); - response.end(xmlbuilder.create({ + response.send(xmlbuilder.create({ errors: { error: { code: '0100', @@ -512,7 +512,7 @@ router.put('/@me/devices/@current/inactivate', async (request: express.Request, if (!pnid) { response.status(400); - response.end(xmlbuilder.create({ + response.send(xmlbuilder.create({ errors: { error: { cause: 'access_token', @@ -526,7 +526,7 @@ router.put('/@me/devices/@current/inactivate', async (request: express.Request, } response.status(200); - response.end(); + response.send(); }); /** @@ -540,7 +540,7 @@ router.put('/@me/deletion', async (request: express.Request, response: express.R if (!pnid) { response.status(400); - response.end(xmlbuilder.create({ + response.send(xmlbuilder.create({ errors: { error: { cause: 'access_token', @@ -571,7 +571,7 @@ router.put('/@me', async (request: express.Request, response: express.Response): if (!pnid) { response.status(400); - response.end(xmlbuilder.create({ + response.send(xmlbuilder.create({ errors: { error: { cause: 'access_token', @@ -637,7 +637,7 @@ router.get('/@me/emails', async (request: express.Request, response: express.Res if (!pnid) { response.status(400); - response.end(xmlbuilder.create({ + response.send(xmlbuilder.create({ errors: { error: { cause: 'access_token', @@ -684,7 +684,7 @@ router.put('/@me/emails/@primary', async (request: express.Request, response: ex if (!pnid || !email || !email.address) { response.status(400); - response.end(xmlbuilder.create({ + response.send(xmlbuilder.create({ errors: { error: { cause: 'access_token', diff --git a/src/services/nnid/routes/provider.ts b/src/services/nnid/routes/provider.ts index 4e5f818..5918fa0 100644 --- a/src/services/nnid/routes/provider.ts +++ b/src/services/nnid/routes/provider.ts @@ -21,7 +21,7 @@ router.get('/service_token/@me', async (request: express.Request, response: expr if (!pnid) { response.status(400); - response.end(xmlbuilder.create({ + response.send(xmlbuilder.create({ errors: { error: { cause: 'access_token', @@ -113,7 +113,7 @@ router.get('/nex_token/@me', async (request: express.Request, response: express. if (!pnid) { response.status(400); - response.end(xmlbuilder.create({ + response.send(xmlbuilder.create({ errors: { error: { cause: 'access_token', diff --git a/src/services/nnid/routes/support.ts b/src/services/nnid/routes/support.ts index 149ab31..f4b226a 100644 --- a/src/services/nnid/routes/support.ts +++ b/src/services/nnid/routes/support.ts @@ -45,7 +45,7 @@ router.post('/validate/email', async (request: express.Request, response: expres } response.status(200); - response.end(); + response.send(); }); }); From 20ddbe7552cd6ee26fcdec8a2ae2821a391194e8 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Sat, 29 Apr 2023 17:50:56 -0400 Subject: [PATCH 088/219] Explicitly set each non-200 status on each response --- src/middleware/pnid.ts | 6 ++--- src/middleware/xml-parser.ts | 4 +--- src/server.ts | 3 +-- src/services/nnid/routes/oauth.ts | 12 ++++------ src/services/nnid/routes/people.ts | 36 +++++++--------------------- src/services/nnid/routes/provider.ts | 8 ++----- 6 files changed, 19 insertions(+), 50 deletions(-) diff --git a/src/middleware/pnid.ts b/src/middleware/pnid.ts index 3ab6d96..f260fed 100644 --- a/src/middleware/pnid.ts +++ b/src/middleware/pnid.ts @@ -27,10 +27,8 @@ async function PNIDMiddleware(request: express.Request, response: express.Respon } if (!pnid) { - response.status(401); - if (type === 'Bearer') { - response.send(xmlbuilder.create({ + response.status(401).send(xmlbuilder.create({ errors: { error: { cause: 'access_token', @@ -43,7 +41,7 @@ async function PNIDMiddleware(request: express.Request, response: express.Respon return; } - response.send(xmlbuilder.create({ + response.status(401).send(xmlbuilder.create({ errors: { error: { code: '1105', diff --git a/src/middleware/xml-parser.ts b/src/middleware/xml-parser.ts index 4f639d5..435b1e3 100644 --- a/src/middleware/xml-parser.ts +++ b/src/middleware/xml-parser.ts @@ -34,10 +34,8 @@ function XMLMiddleware(request: express.Request, response: express.Response, nex request.body = request.body.toObject(); request.body = mapToObject(request.body); } catch (error) { - response.status(401); - // TODO: This is not a real error code, check to see if better one exists - return response.send(xmlbuilder.create({ + return response.status(401).send(xmlbuilder.create({ errors: { error: { code: '0004', diff --git a/src/server.ts b/src/server.ts index 2103ec4..80b67cd 100644 --- a/src/server.ts +++ b/src/server.ts @@ -86,8 +86,7 @@ app.use((error: any, request: express.Request, response: express.Response, _next LOG_WARN(`HTTP ${status} at ${url} from ${deviceId}: ${error.message}`); - response.status(status); - response.json({ + response.status(status).json({ app: 'api', status, error: error.message diff --git a/src/services/nnid/routes/oauth.ts b/src/services/nnid/routes/oauth.ts index 95caab9..aa93ea6 100644 --- a/src/services/nnid/routes/oauth.ts +++ b/src/services/nnid/routes/oauth.ts @@ -20,8 +20,7 @@ router.post('/access_token/generate', async (request: express.Request, response: const password: string = request.body?.password; if (!['password', 'refresh_token'].includes(grantType)) { - response.status(400); - response.send(xmlbuilder.create({ + response.status(400).send(xmlbuilder.create({ error: { cause: 'grant_type', code: '0004', @@ -33,8 +32,7 @@ router.post('/access_token/generate', async (request: express.Request, response: } if (!username || username.trim() === '') { - response.status(400); - response.send(xmlbuilder.create({ + response.status(400).send(xmlbuilder.create({ error: { cause: 'user_id', code: '0002', @@ -46,8 +44,7 @@ router.post('/access_token/generate', async (request: express.Request, response: } if (!password || password.trim() === '') { - response.status(400); - response.send(xmlbuilder.create({ + response.status(400).send(xmlbuilder.create({ error: { cause: 'password', code: '0002', @@ -61,8 +58,7 @@ router.post('/access_token/generate', async (request: express.Request, response: const pnid: HydratedPNIDDocument | null = await getPNIDByUsername(username); if (!pnid || !await bcrypt.compare(password, pnid.password)) { - response.status(400); - response.send(xmlbuilder.create({ + response.status(400).send(xmlbuilder.create({ error: { code: '0106', message: 'Invalid account ID or password' diff --git a/src/services/nnid/routes/people.ts b/src/services/nnid/routes/people.ts index e667089..fa4fa09 100644 --- a/src/services/nnid/routes/people.ts +++ b/src/services/nnid/routes/people.ts @@ -34,9 +34,7 @@ router.get('/:username', async (request: express.Request, response: express.Resp const userExists: boolean = await doesPNIDExist(username); if (userExists) { - response.status(400); - - response.send(xmlbuilder.create({ + response.status(400).send(xmlbuilder.create({ errors: { error: { code: '0100', @@ -60,9 +58,7 @@ router.get('/:username', async (request: express.Request, response: express.Resp router.post('/', ratelimit, deviceCertificateMiddleware, async (request: express.Request, response: express.Response): Promise => { if (!request.certificate || !request.certificate.valid) { // TODO: Change this to a different error - response.status(400); - - response.send(xmlbuilder.create({ + response.status(400).send(xmlbuilder.create({ error: { cause: 'Bad Request', code: '1600', @@ -78,9 +74,7 @@ router.post('/', ratelimit, deviceCertificateMiddleware, async (request: express const userExists: boolean = await doesPNIDExist(person.user_id); if (userExists) { - response.status(400); - - response.send(xmlbuilder.create({ + response.status(400).send(xmlbuilder.create({ errors: { error: { code: '0100', @@ -194,9 +188,7 @@ router.post('/', ratelimit, deviceCertificateMiddleware, async (request: express await session.abortTransaction(); - response.status(400); - - response.send(xmlbuilder.create({ + response.status(400).send(xmlbuilder.create({ error: { cause: 'Bad Request', code: '1600', @@ -510,9 +502,7 @@ router.put('/@me/devices/@current/inactivate', async (request: express.Request, const pnid: HydratedPNIDDocument | null = request.pnid; if (!pnid) { - response.status(400); - - response.send(xmlbuilder.create({ + response.status(400).send(xmlbuilder.create({ errors: { error: { cause: 'access_token', @@ -538,9 +528,7 @@ router.put('/@me/deletion', async (request: express.Request, response: express.R const pnid: HydratedPNIDDocument | null = request.pnid; if (!pnid) { - response.status(400); - - response.send(xmlbuilder.create({ + response.status(400).send(xmlbuilder.create({ errors: { error: { cause: 'access_token', @@ -569,9 +557,7 @@ router.put('/@me', async (request: express.Request, response: express.Response): const person: Person = request.body.person; if (!pnid) { - response.status(400); - - response.send(xmlbuilder.create({ + response.status(400).send(xmlbuilder.create({ errors: { error: { cause: 'access_token', @@ -635,9 +621,7 @@ router.get('/@me/emails', async (request: express.Request, response: express.Res const pnid: HydratedPNIDDocument | null = request.pnid; if (!pnid) { - response.status(400); - - response.send(xmlbuilder.create({ + response.status(400).send(xmlbuilder.create({ errors: { error: { cause: 'access_token', @@ -682,9 +666,7 @@ router.put('/@me/emails/@primary', async (request: express.Request, response: ex } = request.body.email; if (!pnid || !email || !email.address) { - response.status(400); - - response.send(xmlbuilder.create({ + response.status(400).send(xmlbuilder.create({ errors: { error: { cause: 'access_token', diff --git a/src/services/nnid/routes/provider.ts b/src/services/nnid/routes/provider.ts index 5918fa0..6756221 100644 --- a/src/services/nnid/routes/provider.ts +++ b/src/services/nnid/routes/provider.ts @@ -19,9 +19,7 @@ router.get('/service_token/@me', async (request: express.Request, response: expr const pnid: HydratedPNIDDocument | null = request.pnid; if (!pnid) { - response.status(400); - - response.send(xmlbuilder.create({ + response.status(400).send(xmlbuilder.create({ errors: { error: { cause: 'access_token', @@ -111,9 +109,7 @@ router.get('/nex_token/@me', async (request: express.Request, response: express. const pnid: HydratedPNIDDocument | null = request.pnid; if (!pnid) { - response.status(400); - - response.send(xmlbuilder.create({ + response.status(400).send(xmlbuilder.create({ errors: { error: { cause: 'access_token', From eeab60ae26382d2c02a1ec6aef112ff06852a18d Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Sat, 29 Apr 2023 17:51:36 -0400 Subject: [PATCH 089/219] Removed useless calls to response.status(200) (200 is default) --- src/services/nnid/routes/people.ts | 2 -- src/services/nnid/routes/support.ts | 1 - 2 files changed, 3 deletions(-) diff --git a/src/services/nnid/routes/people.ts b/src/services/nnid/routes/people.ts index fa4fa09..4e17a94 100644 --- a/src/services/nnid/routes/people.ts +++ b/src/services/nnid/routes/people.ts @@ -46,7 +46,6 @@ router.get('/:username', async (request: express.Request, response: express.Resp return; } - response.status(200); response.send(); }); @@ -515,7 +514,6 @@ router.put('/@me/devices/@current/inactivate', async (request: express.Request, return; } - response.status(200); response.send(); }); diff --git a/src/services/nnid/routes/support.ts b/src/services/nnid/routes/support.ts index f4b226a..a7e163d 100644 --- a/src/services/nnid/routes/support.ts +++ b/src/services/nnid/routes/support.ts @@ -44,7 +44,6 @@ router.post('/validate/email', async (request: express.Request, response: expres }).end()); } - response.status(200); response.send(); }); }); From a4bc671957efb2583d862000673ad1d4582f35bc Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Sat, 29 Apr 2023 17:58:20 -0400 Subject: [PATCH 090/219] Added deleted field to gRPC getUserData method --- package-lock.json | 14 +++++++------- src/services/grpc/get-user-data.ts | 1 + 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index 98264f2..ecb6675 100644 --- a/package-lock.json +++ b/package-lock.json @@ -674,9 +674,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "18.16.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.16.2.tgz", - "integrity": "sha512-GQW/JL/5Fz/0I8RpeBG9lKp0+aNcXEaVL71c0D2Q0QHDTFvlYKT7an0onCUXj85anv7b4/WesqdfchLc0jtsCg==" + "version": "18.16.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.16.3.tgz", + "integrity": "sha512-OPs5WnnT1xkCBiuQrZA4+YAV4HEJejmHneyraIaxsbev5yCEr6KMwINNFP9wQeFIw8FWcoTqF3vQsa5CDaI+8Q==" }, "node_modules/@types/node-rsa": { "version": "1.1.1", @@ -1190,9 +1190,9 @@ } }, "node_modules/aws-sdk": { - "version": "2.1367.0", - "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1367.0.tgz", - "integrity": "sha512-ZlN3iXazEVPwjmQzC1TfkRUPOKruF6RkAFnVz4hOPjQQT91RYi2lCRWtipWk4ZoONBLX7gFLGUgIfiHjf/A+iA==", + "version": "2.1368.0", + "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1368.0.tgz", + "integrity": "sha512-Yc3s8PqdcYG4wyCOpDj4TwXacGZGDgZBJ/XAtzMLKW2wN2c4uu7GwSosLxZ8ejzbAbcqjf080odPuD8P0819tw==", "dependencies": { "buffer": "4.9.2", "events": "1.1.1", @@ -4275,7 +4275,7 @@ }, "node_modules/pretendo-grpc-ts": { "version": "1.0.0", - "resolved": "git+ssh://git@github.com/PretendoNetwork/grpc-ts.git#d2703693db67c2c19d07abac5b3410542b74d855", + "resolved": "git+ssh://git@github.com/PretendoNetwork/grpc-ts.git#028bc63602eb3cf33ea5e775face04b4c586399c", "hasInstallScript": true, "license": "ISC", "dependencies": { diff --git a/src/services/grpc/get-user-data.ts b/src/services/grpc/get-user-data.ts index be7a606..bc4c7fb 100644 --- a/src/services/grpc/get-user-data.ts +++ b/src/services/grpc/get-user-data.ts @@ -15,6 +15,7 @@ export async function getUserData(request: GetUserDataRequest): Promise Date: Mon, 1 May 2023 15:02:04 -0400 Subject: [PATCH 091/219] Added token checksum to non-access/refresh tokens --- package-lock.json | 52 ++++++++++++++++++++++++++++++++++++------- package.json | 3 +++ src/middleware/api.ts | 10 ++++++--- src/util.ts | 40 ++++++++++++++++++++++++++++++--- 4 files changed, 91 insertions(+), 14 deletions(-) diff --git a/package-lock.json b/package-lock.json index ecb6675..80029e5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,8 +11,10 @@ "dependencies": { "aws-sdk": "^2.978.0", "bcrypt": "^5.0.0", + "buffer-crc32": "^0.2.13", "colors": "^1.4.0", "cors": "^2.8.5", + "crc": "^4.3.2", "dicer": "^0.2.5", "dotenv": "^16.0.3", "email-validator": "^2.0.4", @@ -44,6 +46,7 @@ "devDependencies": { "@hcaptcha/types": "^1.0.3", "@types/bcrypt": "^5.0.0", + "@types/buffer-crc32": "^0.2.2", "@types/cors": "^2.8.13", "@types/dicer": "^0.2.2", "@types/express": "^4.17.17", @@ -547,6 +550,15 @@ "@types/node": "*" } }, + "node_modules/@types/buffer-crc32": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@types/buffer-crc32/-/buffer-crc32-0.2.2.tgz", + "integrity": "sha512-UpJyUKgG33LVehYtv9k2x4HUEY5ThV62YNGFBbQNBgtoky/0tQCceh8BPI9r3XL5hQ1tGmq34jGWNRBKf2P1UQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/cacheable-request": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz", @@ -1209,6 +1221,16 @@ "node": ">= 10.0.0" } }, + "node_modules/aws-sdk/node_modules/buffer": { + "version": "4.9.2", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", + "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", + "dependencies": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4", + "isarray": "^1.0.0" + } + }, "node_modules/aws-sign2": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", @@ -1372,14 +1394,12 @@ "node": ">=14.20.1" } }, - "node_modules/buffer": { - "version": "4.9.2", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", - "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", - "dependencies": { - "base64-js": "^1.0.2", - "ieee754": "^1.1.4", - "isarray": "^1.0.0" + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "engines": { + "node": "*" } }, "node_modules/buffer-equal": { @@ -1670,6 +1690,22 @@ "node": ">= 0.10" } }, + "node_modules/crc": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/crc/-/crc-4.3.2.tgz", + "integrity": "sha512-uGDHf4KLLh2zsHa8D8hIQ1H/HtFQhyHrc0uhHBcoKGol/Xnb+MPYfUMw7cvON6ze/GUESTudKayDcJC5HnJv1A==", + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "buffer": ">=6.0.3" + }, + "peerDependenciesMeta": { + "buffer": { + "optional": true + } + } + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", diff --git a/package.json b/package.json index 3b73edd..fefab26 100644 --- a/package.json +++ b/package.json @@ -27,8 +27,10 @@ "dependencies": { "aws-sdk": "^2.978.0", "bcrypt": "^5.0.0", + "buffer-crc32": "^0.2.13", "colors": "^1.4.0", "cors": "^2.8.5", + "crc": "^4.3.2", "dicer": "^0.2.5", "dotenv": "^16.0.3", "email-validator": "^2.0.4", @@ -60,6 +62,7 @@ "devDependencies": { "@hcaptcha/types": "^1.0.3", "@types/bcrypt": "^5.0.0", + "@types/buffer-crc32": "^0.2.2", "@types/cors": "^2.8.13", "@types/dicer": "^0.2.2", "@types/express": "^4.17.17", diff --git a/src/middleware/api.ts b/src/middleware/api.ts index 8645559..a3e97f8 100644 --- a/src/middleware/api.ts +++ b/src/middleware/api.ts @@ -10,10 +10,14 @@ async function APIMiddleware(request: express.Request, _response: express.Respon return next(); } - const token: string = authHeader.split(' ')[1]; - const pnid: HydratedPNIDDocument | null = await getPNIDByBearerAuth(token); + try { + const token: string = authHeader.split(' ')[1]; + const pnid: HydratedPNIDDocument | null = await getPNIDByBearerAuth(token); - request.pnid = pnid; + request.pnid = pnid; + } catch (error) { + // TODO - Log error + } return next(); } diff --git a/src/util.ts b/src/util.ts index d335934..080c6bd 100644 --- a/src/util.ts +++ b/src/util.ts @@ -5,6 +5,8 @@ import fs from 'fs-extra'; import express from 'express'; import mongoose from 'mongoose'; import { ParsedQs } from 'qs'; +import crc32 from 'buffer-crc32'; +import crc from 'crc'; import { sendMail } from '@/mailer'; import { config, disabledFeatures } from '@/config-manager'; import { TokenOptions } from '@/types/common/token-options'; @@ -56,6 +58,7 @@ export function generateToken(key: string, options: TokenOptions): Buffer | null dataBuffer.writeBigUInt64LE(options.expire_time, 0x6); if (options.token_type !== 0x1 && options.token_type !== 0x2) { + // * Access and refresh tokens have smaller bodies due to size constraints if (options.access_level === undefined || options.title_id === undefined) { return null; } @@ -72,20 +75,51 @@ export function generateToken(key: string, options: TokenOptions): Buffer | null const iv: Buffer = Buffer.alloc(16); const cipher: crypto.Cipher = crypto.createCipheriv('aes-256-cbc', Buffer.from(key, 'hex'), iv); - return Buffer.concat([ + const encrypted = Buffer.concat([ cipher.update(dataBuffer), cipher.final() ]); + + let final: Buffer = encrypted; + + if (options.token_type !== 0x1 && options.token_type !== 0x2) { + // * Access and refresh tokens don't get a checksum due to size constraints + const checksum: Buffer = crc32(dataBuffer); + + final = Buffer.concat([ + checksum, + final + ]); + } + + return final; } export function decryptToken(token: Buffer): Buffer { + let encryptedBody: Buffer; + let expectedChecksum: number = 0; + + if (token.length === 16) { + // * Token is an access/refresh token, no checksum + encryptedBody = token; + } else { + expectedChecksum = token.readUint32BE(); + encryptedBody = token.subarray(4); + } + const iv: Buffer = Buffer.alloc(16); const decipher: crypto.Decipher = crypto.createDecipheriv('aes-256-cbc', Buffer.from(config.aes_key, 'hex'), iv); - return Buffer.concat([ - decipher.update(token), + const decrypted: Buffer = Buffer.concat([ + decipher.update(encryptedBody), decipher.final() ]); + + if (expectedChecksum && (expectedChecksum !== crc.crc32(decrypted))) { + throw new Error('Checksum did not match. Failed decrypt. Are you using the right key?'); + } + + return decrypted; } export function unpackToken(token: Buffer): Token { From 9f6882733763e3f19d7aa63538ac9968b3c350ef Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Tue, 2 May 2023 14:37:11 -0400 Subject: [PATCH 092/219] Generate 3DS friend code when registering 3DS NEX account --- src/middleware/nasc.ts | 14 ++++++++++++++ src/models/nex-account.ts | 3 ++- src/types/mongoose/nex-account.ts | 1 + 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/middleware/nasc.ts b/src/middleware/nasc.ts index 3786908..18e8f62 100644 --- a/src/middleware/nasc.ts +++ b/src/middleware/nasc.ts @@ -135,6 +135,20 @@ async function NASCMiddleware(request: express.Request, response: express.Respon pid = nexAccount.pid; + const pidBuffer: Buffer = Buffer.alloc(4); + pidBuffer.writeUInt32LE(pid); + + const hash: crypto.Hash = crypto.createHash('sha1').update(pidBuffer); + const pidHash: Buffer = hash.digest(); + const checksum: number = pidHash[0] >> 1; + const hex: string = checksum.toString(16) + pid.toString(16); + const int: number = parseInt(hex, 16); + const friendCode: string = int.toString().padStart(12, '0').match(/.{1,4}/g)!.join('-'); + + nexAccount.friend_code = friendCode; + + await nexAccount.save({ session }); + // Set password if (!device) { diff --git a/src/models/nex-account.ts b/src/models/nex-account.ts index c302c4b..3f7c0dd 100644 --- a/src/models/nex-account.ts +++ b/src/models/nex-account.ts @@ -24,7 +24,8 @@ const NEXAccountSchema = new Schema Date: Tue, 2 May 2023 15:03:24 -0400 Subject: [PATCH 093/219] Added friend code migration --- ....js => add-console-type-to-nex-account.js} | 4 +-- migrations/add-missing-friendcodes.js | 36 +++++++++++++++++++ 2 files changed, 38 insertions(+), 2 deletions(-) rename migrations/{add-console-type-to-nex-account/migration.js => add-console-type-to-nex-account.js} (86%) create mode 100644 migrations/add-missing-friendcodes.js diff --git a/migrations/add-console-type-to-nex-account/migration.js b/migrations/add-console-type-to-nex-account.js similarity index 86% rename from migrations/add-console-type-to-nex-account/migration.js rename to migrations/add-console-type-to-nex-account.js index af581b4..d6ac621 100644 --- a/migrations/add-console-type-to-nex-account/migration.js +++ b/migrations/add-console-type-to-nex-account.js @@ -1,5 +1,5 @@ -const database = require('../../src/database'); -const { NEXAccount } = require('../../src/models/nex-account'); +const database = require('../dist/database'); +const { NEXAccount } = require('../dist/models/nex-account'); database.connect().then(async function () { const nexAccounts = await NEXAccount.find({}); diff --git a/migrations/add-missing-friendcodes.js b/migrations/add-missing-friendcodes.js new file mode 100644 index 0000000..b67c7f9 --- /dev/null +++ b/migrations/add-missing-friendcodes.js @@ -0,0 +1,36 @@ +const crypto = require('node:crypto'); +const database = require('../dist/database'); +const { NEXAccount } = require('../dist/models/nex-account'); + +database.connect().then(async function () { + const nexAccounts3DS = await NEXAccount.find({ + device_type: '3ds', + friend_code: { + $exists: false + } + }); + + for (const nexAccount of nexAccounts3DS) { + + if (!nexAccount.friend_code) { + const pid = nexAccount.pid; + const pidBuffer = Buffer.alloc(4); + pidBuffer.writeUInt32LE(pid); + + const hash = crypto.createHash('sha1').update(pidBuffer); + const pidHash = hash.digest(); + const checksum = pidHash[0] >> 1; + const hex = checksum.toString(16) + pid.toString(16); + const int = parseInt(hex, 16); + const friendCode = int.toString().padStart(12, '0').match(/.{1,4}/g).join('-'); + + nexAccount.friend_code = friendCode; + + await nexAccount.save(); + } + } + + console.log('Migrated accounts'); + + process.exit(0); +}); \ No newline at end of file From fc994bf84de34734e8f99123dddc6a91419a7a43 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Tue, 2 May 2023 15:28:43 -0400 Subject: [PATCH 094/219] Added migration to add missing server AES keys --- migrations/add-missing-server-aes-keys.js | 24 +++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 migrations/add-missing-server-aes-keys.js diff --git a/migrations/add-missing-server-aes-keys.js b/migrations/add-missing-server-aes-keys.js new file mode 100644 index 0000000..7a47379 --- /dev/null +++ b/migrations/add-missing-server-aes-keys.js @@ -0,0 +1,24 @@ +const crypto = require('node:crypto'); +const database = require('../dist/database'); +const { Server } = require('../dist/models/server'); + +database.connect().then(async function () { + const servers = await Server.find({ + aes_key: { + $exists: false + } + }); + + for (const server of servers) { + + if (!server.aes_key) { + server.aes_key = crypto.randomBytes(32).toString('hex'); + + await server.save(); + } + } + + console.log('Migrated accounts'); + + process.exit(0); +}); \ No newline at end of file From 298bcf7b45750cd948925169b592aa46d30e650b Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Tue, 2 May 2023 22:01:39 -0400 Subject: [PATCH 095/219] updated migrations --- migrations/add-console-type-to-nex-account.js | 20 ++++++++++--------- migrations/add-missing-friendcodes.js | 4 ++-- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/migrations/add-console-type-to-nex-account.js b/migrations/add-console-type-to-nex-account.js index d6ac621..b92f208 100644 --- a/migrations/add-console-type-to-nex-account.js +++ b/migrations/add-console-type-to-nex-account.js @@ -2,27 +2,29 @@ const database = require('../dist/database'); const { NEXAccount } = require('../dist/models/nex-account'); database.connect().then(async function () { - const nexAccounts = await NEXAccount.find({}); + const nexAccountsTotal = await NEXAccount.find({}); const nexAccounts3DS = await NEXAccount.find({ device_type: '3ds' }); const nexAccountsWiiU = await NEXAccount.find({ device_type: 'wiiu' }); + const nexAccountsToBeChanged = await NEXAccount.find({ + device_type: { + $exists: false + } + }); - console.log('NEX accounts:', nexAccounts.length); + console.log('NEX accounts (Total):', nexAccountsTotal.length); console.log('NEX accounts (3DS):', nexAccounts3DS.length); console.log('NEX accounts (WiiU):', nexAccountsWiiU.length); + console.log('NEX accounts (To be changed):', nexAccountsToBeChanged.length); - for (const nexAccount of nexAccounts) { - let deviceType = ''; - + for (const nexAccount of nexAccountsToBeChanged) { if (nexAccount.owning_pid !== nexAccount.pid) { // 3DS account - deviceType = '3ds'; + nexAccount.device_type = '3ds'; } else { // WiiU account - deviceType = 'wiiu'; + nexAccount.device_type = 'wiiu'; } - nexAccount.device_type = deviceType; - await nexAccount.save(); } diff --git a/migrations/add-missing-friendcodes.js b/migrations/add-missing-friendcodes.js index b67c7f9..4ac5aff 100644 --- a/migrations/add-missing-friendcodes.js +++ b/migrations/add-missing-friendcodes.js @@ -3,14 +3,14 @@ const database = require('../dist/database'); const { NEXAccount } = require('../dist/models/nex-account'); database.connect().then(async function () { - const nexAccounts3DS = await NEXAccount.find({ + const nexAccountsToBeChanged = await NEXAccount.find({ device_type: '3ds', friend_code: { $exists: false } }); - for (const nexAccount of nexAccounts3DS) { + for (const nexAccount of nexAccountsToBeChanged) { if (!nexAccount.friend_code) { const pid = nexAccount.pid; From 8b578c750a55ec58a43aff26703532636fe91acb Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Wed, 3 May 2023 12:19:55 -0400 Subject: [PATCH 096/219] Removed password change from PNID.scrub and added 0112 error on deleted accounts --- src/middleware/pnid.ts | 13 +++++++++++++ src/models/pnid.ts | 1 - 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/middleware/pnid.ts b/src/middleware/pnid.ts index f260fed..9d53ecd 100644 --- a/src/middleware/pnid.ts +++ b/src/middleware/pnid.ts @@ -53,6 +53,19 @@ async function PNIDMiddleware(request: express.Request, response: express.Respon return; } + if (pnid.deleted) { + response.status(400).send(xmlbuilder.create({ + errors: { + error: { + code: '0112', + message: pnid.username + } + } + }).end()); + + return; + } + if (pnid.access_level < 0) { response.status(400).send(xmlbuilder.create({ errors: { diff --git a/src/models/pnid.ts b/src/models/pnid.ts index 72504ba..758fb5f 100644 --- a/src/models/pnid.ts +++ b/src/models/pnid.ts @@ -247,7 +247,6 @@ PNIDSchema.method('scrub', async function scrub() { this.deleted = true; this.creation_date = ''; - this.password = ''; this.birthdate = ''; this.gender = ''; this.country = ''; From 3c03ce850945e565e66d98e141800bef69781ba2 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Wed, 3 May 2023 17:40:26 -0400 Subject: [PATCH 097/219] fixed website grant type check --- src/services/api/routes/v1/login.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/api/routes/v1/login.ts b/src/services/api/routes/v1/login.ts index ea51eff..9126df1 100644 --- a/src/services/api/routes/v1/login.ts +++ b/src/services/api/routes/v1/login.ts @@ -15,7 +15,7 @@ const router: express.Router = express.Router(); * TODO: Replace this with a more robust OAuth2 implementation */ router.post('/', async (request: express.Request, response: express.Response): Promise => { - const grantType: string = request.body?.grantType; + const grantType: string = request.body?.grant_type; const username: string = request.body?.username; const password: string = request.body?.password; const refreshToken: string = request.body?.refresh_token; From 0db626a2b5d37b674a40328c6119167b16eed73c Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Wed, 3 May 2023 19:38:49 -0400 Subject: [PATCH 098/219] Updated API token format --- src/services/api/routes/v1/login.ts | 4 ++-- src/services/api/routes/v1/register.ts | 4 ++-- src/util.ts | 5 +++-- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/services/api/routes/v1/login.ts b/src/services/api/routes/v1/login.ts index 9126df1..0ff53c3 100644 --- a/src/services/api/routes/v1/login.ts +++ b/src/services/api/routes/v1/login.ts @@ -101,14 +101,14 @@ router.post('/', async (request: express.Request, response: express.Response): P } const accessTokenOptions: TokenOptions = { - system_type: 0x1, // * WiiU + system_type: 0x3, // * API token_type: 0x1, // * OAuth Access pid: pnid.pid, expire_time: BigInt(Date.now() + (3600 * 1000)) }; const refreshTokenOptions: TokenOptions = { - system_type: 0x1, // * WiiU + system_type: 0x3, // * API token_type: 0x2, // * OAuth Refresh pid: pnid.pid, expire_time: BigInt(Date.now() + (3600 * 1000)) diff --git a/src/services/api/routes/v1/register.ts b/src/services/api/routes/v1/register.ts index b724e25..4644189 100644 --- a/src/services/api/routes/v1/register.ts +++ b/src/services/api/routes/v1/register.ts @@ -368,14 +368,14 @@ router.post('/', async (request: express.Request, response: express.Response): P await sendConfirmationEmail(pnid); const accessTokenOptions: TokenOptions = { - system_type: 0x1, // * WiiU + system_type: 0x3, // * API token_type: 0x1, // * OAuth Access pid: pnid.pid, expire_time: BigInt(Date.now() + (3600 * 1000)) }; const refreshTokenOptions: TokenOptions = { - system_type: 0x1, // * WiiU + system_type: 0x3, // * API token_type: 0x2, // * OAuth Refresh pid: pnid.pid, expire_time: BigInt(Date.now() + (3600 * 1000)) diff --git a/src/util.ts b/src/util.ts index 080c6bd..afc17f1 100644 --- a/src/util.ts +++ b/src/util.ts @@ -57,9 +57,10 @@ export function generateToken(key: string, options: TokenOptions): Buffer | null dataBuffer.writeUInt32LE(options.pid, 0x2); dataBuffer.writeBigUInt64LE(options.expire_time, 0x6); - if (options.token_type !== 0x1 && options.token_type !== 0x2) { + if ((options.token_type !== 0x1 && options.token_type !== 0x2) || options.system_type === 0x3) { // * Access and refresh tokens have smaller bodies due to size constraints - if (options.access_level === undefined || options.title_id === undefined) { + // * The API does not have this restraint, however + if (options.title_id === undefined || options.access_level === undefined) { return null; } From 11984e12a4b47084a0a45f8264023198fe908cc1 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Wed, 3 May 2023 19:49:51 -0400 Subject: [PATCH 099/219] Added missing fields to API tokens --- src/services/api/routes/v1/login.ts | 4 ++++ src/services/api/routes/v1/register.ts | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/src/services/api/routes/v1/login.ts b/src/services/api/routes/v1/login.ts index 0ff53c3..d102ab6 100644 --- a/src/services/api/routes/v1/login.ts +++ b/src/services/api/routes/v1/login.ts @@ -104,6 +104,8 @@ router.post('/', async (request: express.Request, response: express.Response): P system_type: 0x3, // * API token_type: 0x1, // * OAuth Access pid: pnid.pid, + access_level: pnid.access_level, + title_id: BigInt(0), expire_time: BigInt(Date.now() + (3600 * 1000)) }; @@ -111,6 +113,8 @@ router.post('/', async (request: express.Request, response: express.Response): P system_type: 0x3, // * API token_type: 0x2, // * OAuth Refresh pid: pnid.pid, + access_level: pnid.access_level, + title_id: BigInt(0), expire_time: BigInt(Date.now() + (3600 * 1000)) }; diff --git a/src/services/api/routes/v1/register.ts b/src/services/api/routes/v1/register.ts index 4644189..56a99f4 100644 --- a/src/services/api/routes/v1/register.ts +++ b/src/services/api/routes/v1/register.ts @@ -371,6 +371,8 @@ router.post('/', async (request: express.Request, response: express.Response): P system_type: 0x3, // * API token_type: 0x1, // * OAuth Access pid: pnid.pid, + access_level: pnid.access_level, + title_id: BigInt(0), expire_time: BigInt(Date.now() + (3600 * 1000)) }; @@ -378,6 +380,8 @@ router.post('/', async (request: express.Request, response: express.Response): P system_type: 0x3, // * API token_type: 0x2, // * OAuth Refresh pid: pnid.pid, + access_level: pnid.access_level, + title_id: BigInt(0), expire_time: BigInt(Date.now() + (3600 * 1000)) }; From 9c16196435f2bc4d0dfe1ed95b99a84c2090ceda Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Wed, 31 May 2023 20:55:41 -0400 Subject: [PATCH 100/219] Fixed missing crc on website tokens --- src/util.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/util.ts b/src/util.ts index afc17f1..7767716 100644 --- a/src/util.ts +++ b/src/util.ts @@ -1,5 +1,6 @@ import crypto from 'node:crypto'; import path from 'node:path'; +import { IncomingHttpHeaders } from 'node:http'; import aws from 'aws-sdk'; import fs from 'fs-extra'; import express from 'express'; @@ -14,7 +15,6 @@ import { Token } from '@/types/common/token'; import { IPNID, IPNIDMethods } from '@/types/mongoose/pnid'; import { MailerOptions } from '@/types/common/mailer-options'; import { SafeQs } from '@/types/common/safe-qs'; -import { IncomingHttpHeaders } from 'node:http'; let s3: aws.S3; @@ -83,7 +83,7 @@ export function generateToken(key: string, options: TokenOptions): Buffer | null let final: Buffer = encrypted; - if (options.token_type !== 0x1 && options.token_type !== 0x2) { + if ((options.token_type !== 0x1 && options.token_type !== 0x2) || options.system_type === 0x3) { // * Access and refresh tokens don't get a checksum due to size constraints const checksum: Buffer = crc32(dataBuffer); From dee432b4a0e564ca4dede0044965a44985ff0773 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Wed, 31 May 2023 21:29:41 -0400 Subject: [PATCH 101/219] Added gRPC service for the API (for future use) --- SETUP.md | 3 +- package-lock.json | 321 +++++++++++------- src/config-manager.ts | 14 +- src/server.ts | 2 +- .../grpc/account/api-key-middleware.ts | 16 + .../grpc/{ => account}/get-user-data.ts | 0 src/services/grpc/account/implementation.ts | 6 + .../grpc/{ => api}/api-key-middleware.ts | 7 +- .../grpc/api/authentication-middleware.ts | 55 +++ src/services/grpc/api/forgot-password.ts | 29 ++ src/services/grpc/api/get-user-data.ts | 46 +++ src/services/grpc/api/implementation.ts | 20 ++ src/services/grpc/api/login.ts | 90 +++++ src/services/grpc/api/register.ts | 266 +++++++++++++++ src/services/grpc/api/reset-password.ts | 79 +++++ .../grpc/api/set-discord-connection-data.ts | 26 ++ .../grpc/api/set-stripe-connection-data.ts | 91 +++++ src/services/grpc/api/update-user-data.ts | 48 +++ src/services/grpc/login.ts | 6 - src/services/grpc/register-pnid.ts | 7 - src/services/grpc/server.ts | 27 +- src/types/common/config.ts | 5 +- 22 files changed, 1000 insertions(+), 164 deletions(-) create mode 100644 src/services/grpc/account/api-key-middleware.ts rename src/services/grpc/{ => account}/get-user-data.ts (100%) create mode 100644 src/services/grpc/account/implementation.ts rename src/services/grpc/{ => api}/api-key-middleware.ts (75%) create mode 100644 src/services/grpc/api/authentication-middleware.ts create mode 100644 src/services/grpc/api/forgot-password.ts create mode 100644 src/services/grpc/api/get-user-data.ts create mode 100644 src/services/grpc/api/implementation.ts create mode 100644 src/services/grpc/api/login.ts create mode 100644 src/services/grpc/api/register.ts create mode 100644 src/services/grpc/api/reset-password.ts create mode 100644 src/services/grpc/api/set-discord-connection-data.ts create mode 100644 src/services/grpc/api/set-stripe-connection-data.ts create mode 100644 src/services/grpc/api/update-user-data.ts delete mode 100644 src/services/grpc/login.ts delete mode 100644 src/services/grpc/register-pnid.ts diff --git a/SETUP.md b/SETUP.md index 0ff4310..1aeaba2 100644 --- a/SETUP.md +++ b/SETUP.md @@ -75,6 +75,7 @@ Configurations are loaded through environment variables. `.env` files are suppor | `PN_ACT_CONFIG_CDN_BASE_URL` | URL for serving CDN contents (usually the same as s3 endpoint) | No | | `PN_ACT_CONFIG_WEBSITE_BASE` | Website URL | Yes | | `PN_ACT_CONFIG_AES_KEY` | AES-256 key used for encrypting tokens | No | -| `PN_ACT_CONFIG_GRPC_API_KEY` | gRPC API key that other clients use to interact with this server | No | +| `PN_ACT_CONFIG_GRPC_MASTER_API_KEY_ACCOUNT` | Master API key to interact with the account gRPC service | No | +| `PN_ACT_CONFIG_GRPC_MASTER_API_KEY_API` | Master API key to interact with the API gRPC service | No | | `PN_ACT_CONFIG_GRPC_PORT` | gRPC server port | No | | `PN_ACT_CONFIG_STRIPE_SECRET_KEY` | Stripe API key. Used to cancel subscriptions when scrubbing PNIDs | Yes | \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 80029e5..f1ab5ad 100644 --- a/package-lock.json +++ b/package-lock.json @@ -92,23 +92,23 @@ } }, "node_modules/@eslint-community/regexpp": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.5.0.tgz", - "integrity": "sha512-vITaYzIcNmjn5tF5uxcZ/ft7/RXGrMUIS9HalWckEOF6ESiwXKoMzAQf2UW0aVd6rnOeExTJVd5hmWXucBKGXQ==", + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.5.1.tgz", + "integrity": "sha512-Z5ba73P98O1KUYCCJTUeVpja9RcGoMdncZ6T49FCUl2lN38JtCJ+3WgIDBv0AuY4WChU5PmtJmOCTlN6FZTFKQ==", "dev": true, "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, "node_modules/@eslint/eslintrc": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.2.tgz", - "integrity": "sha512-3W4f5tDUra+pA+FzgugqL2pRimUTDJWKr7BINqOpkZrC0uYI0NIc0/JFgBROCU07HR6GieA5m3/rsPIhDmCXTQ==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.3.tgz", + "integrity": "sha512-+5gy6OQfk+xx3q0d6jGZZC3f3KzAkXc/IanVxd1is/VIIziRqqt3ongQz0FiTUXqTk0c7aDB3OaFuKnuSoJicQ==", "dev": true, "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", - "espree": "^9.5.1", + "espree": "^9.5.2", "globals": "^13.19.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", @@ -124,9 +124,9 @@ } }, "node_modules/@eslint/js": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.39.0.tgz", - "integrity": "sha512-kf9RB0Fg7NZfap83B3QOqOGg9QmD9yBudqQXzzOtn3i4y7ZUXe5ONeW34Gwi+TxhH4mvj72R1Zc300KUMa9Bng==", + "version": "8.40.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.40.0.tgz", + "integrity": "sha512-ElyB54bJIhXQYVKjDSvCkPO1iU1tSAeVQJbllWJq1XQSmmA4dgFk8CbiBGpiOPxleE48vDogxCtmMYku4HSVLA==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -145,15 +145,15 @@ } }, "node_modules/@grpc/proto-loader": { - "version": "0.7.6", - "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.6.tgz", - "integrity": "sha512-QyAXR8Hyh7uMDmveWxDSUcJr9NAWaZ2I6IXgAYvQmfflwouTM+rArE2eEaCtLlRqO81j7pRLCt81IefUei6Zbw==", + "version": "0.7.7", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.7.tgz", + "integrity": "sha512-1TIeXOi8TuSCQprPItwoMymZXxWT0CPxUhkrkeCUH+D8U7QDwQ6b7SUz2MaLuWM2llT+J/TVFLmQI5KtML3BhQ==", "dependencies": { "@types/long": "^4.0.1", "lodash.camelcase": "^4.3.0", "long": "^4.0.0", "protobufjs": "^7.0.0", - "yargs": "^16.2.0" + "yargs": "^17.7.2" }, "bin": { "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" @@ -686,9 +686,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "18.16.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.16.3.tgz", - "integrity": "sha512-OPs5WnnT1xkCBiuQrZA4+YAV4HEJejmHneyraIaxsbev5yCEr6KMwINNFP9wQeFIw8FWcoTqF3vQsa5CDaI+8Q==" + "version": "18.16.7", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.16.7.tgz", + "integrity": "sha512-MFg7ua/bRtnA1hYE3pVyWxGd/r7aMqjNOdHvlSsXV3n8iaeGKkOaPzpJh6/ovf4bEXWcojkeMJpTsq3mzXW4IQ==" }, "node_modules/@types/node-rsa": { "version": "1.1.1", @@ -729,9 +729,9 @@ } }, "node_modules/@types/semver": { - "version": "7.3.13", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz", - "integrity": "sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.0.tgz", + "integrity": "sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==", "dev": true }, "node_modules/@types/send": { @@ -755,9 +755,9 @@ } }, "node_modules/@types/validator": { - "version": "13.7.15", - "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.7.15.tgz", - "integrity": "sha512-yeinDVQunb03AEP8luErFcyf/7Lf7AzKCD0NXfgVoGCCQDNpZET8Jgq74oBgqKld3hafLbfzt/3inUdQvaFeXQ==", + "version": "13.7.17", + "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.7.17.tgz", + "integrity": "sha512-aqayTNmeWrZcvnG2MG9eGYI6b7S5fl+yKgPs6bAjOTwPS316R5SxBGKvtSExfyoJU7pIeHJfsHI0Ji41RVMkvQ==", "dev": true }, "node_modules/@types/webidl-conversions": { @@ -775,15 +775,15 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "5.59.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.59.1.tgz", - "integrity": "sha512-AVi0uazY5quFB9hlp2Xv+ogpfpk77xzsgsIEWyVS7uK/c7MZ5tw7ZPbapa0SbfkqE0fsAMkz5UwtgMLVk2BQAg==", + "version": "5.59.5", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.59.5.tgz", + "integrity": "sha512-feA9xbVRWJZor+AnLNAr7A8JRWeZqHUf4T9tlP+TN04b05pFVhO5eN7/O93Y/1OUlLMHKbnJisgDURs/qvtqdg==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.4.0", - "@typescript-eslint/scope-manager": "5.59.1", - "@typescript-eslint/type-utils": "5.59.1", - "@typescript-eslint/utils": "5.59.1", + "@typescript-eslint/scope-manager": "5.59.5", + "@typescript-eslint/type-utils": "5.59.5", + "@typescript-eslint/utils": "5.59.5", "debug": "^4.3.4", "grapheme-splitter": "^1.0.4", "ignore": "^5.2.0", @@ -809,14 +809,14 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "5.59.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.59.1.tgz", - "integrity": "sha512-nzjFAN8WEu6yPRDizIFyzAfgK7nybPodMNFGNH0M9tei2gYnYszRDqVA0xlnRjkl7Hkx2vYrEdb6fP2a21cG1g==", + "version": "5.59.5", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.59.5.tgz", + "integrity": "sha512-NJXQC4MRnF9N9yWqQE2/KLRSOLvrrlZb48NGVfBa+RuPMN6B7ZcK5jZOvhuygv4D64fRKnZI4L4p8+M+rfeQuw==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "5.59.1", - "@typescript-eslint/types": "5.59.1", - "@typescript-eslint/typescript-estree": "5.59.1", + "@typescript-eslint/scope-manager": "5.59.5", + "@typescript-eslint/types": "5.59.5", + "@typescript-eslint/typescript-estree": "5.59.5", "debug": "^4.3.4" }, "engines": { @@ -836,13 +836,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "5.59.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.59.1.tgz", - "integrity": "sha512-mau0waO5frJctPuAzcxiNWqJR5Z8V0190FTSqRw1Q4Euop6+zTwHAf8YIXNwDOT29tyUDrQ65jSg9aTU/H0omA==", + "version": "5.59.5", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.59.5.tgz", + "integrity": "sha512-jVecWwnkX6ZgutF+DovbBJirZcAxgxC0EOHYt/niMROf8p4PwxxG32Qdhj/iIQQIuOflLjNkxoXyArkcIP7C3A==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.59.1", - "@typescript-eslint/visitor-keys": "5.59.1" + "@typescript-eslint/types": "5.59.5", + "@typescript-eslint/visitor-keys": "5.59.5" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -853,13 +853,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "5.59.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.59.1.tgz", - "integrity": "sha512-ZMWQ+Oh82jWqWzvM3xU+9y5U7MEMVv6GLioM3R5NJk6uvP47kZ7YvlgSHJ7ERD6bOY7Q4uxWm25c76HKEwIjZw==", + "version": "5.59.5", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.59.5.tgz", + "integrity": "sha512-4eyhS7oGym67/pSxA2mmNq7X164oqDYNnZCUayBwJZIRVvKpBCMBzFnFxjeoDeShjtO6RQBHBuwybuX3POnDqg==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "5.59.1", - "@typescript-eslint/utils": "5.59.1", + "@typescript-eslint/typescript-estree": "5.59.5", + "@typescript-eslint/utils": "5.59.5", "debug": "^4.3.4", "tsutils": "^3.21.0" }, @@ -880,9 +880,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "5.59.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.59.1.tgz", - "integrity": "sha512-dg0ICB+RZwHlysIy/Dh1SP+gnXNzwd/KS0JprD3Lmgmdq+dJAJnUPe1gNG34p0U19HvRlGX733d/KqscrGC1Pg==", + "version": "5.59.5", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.59.5.tgz", + "integrity": "sha512-xkfRPHbqSH4Ggx4eHRIO/eGL8XL4Ysb4woL8c87YuAo8Md7AUjyWKa9YMwTL519SyDPrfEgKdewjkxNCVeJW7w==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -893,13 +893,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "5.59.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.1.tgz", - "integrity": "sha512-lYLBBOCsFltFy7XVqzX0Ju+Lh3WPIAWxYpmH/Q7ZoqzbscLiCW00LeYCdsUnnfnj29/s1WovXKh2gwCoinHNGA==", + "version": "5.59.5", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.5.tgz", + "integrity": "sha512-+XXdLN2CZLZcD/mO7mQtJMvCkzRfmODbeSKuMY/yXbGkzvA9rJyDY5qDYNoiz2kP/dmyAxXquL2BvLQLJFPQIg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.59.1", - "@typescript-eslint/visitor-keys": "5.59.1", + "@typescript-eslint/types": "5.59.5", + "@typescript-eslint/visitor-keys": "5.59.5", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -920,17 +920,17 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "5.59.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.59.1.tgz", - "integrity": "sha512-MkTe7FE+K1/GxZkP5gRj3rCztg45bEhsd8HYjczBuYm+qFHP5vtZmjx3B0yUCDotceQ4sHgTyz60Ycl225njmA==", + "version": "5.59.5", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.59.5.tgz", + "integrity": "sha512-sCEHOiw+RbyTii9c3/qN74hYDPNORb8yWCoPLmB7BIflhplJ65u2PBpdRla12e3SSTJ2erRkPjz7ngLHhUegxA==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@types/json-schema": "^7.0.9", "@types/semver": "^7.3.12", - "@typescript-eslint/scope-manager": "5.59.1", - "@typescript-eslint/types": "5.59.1", - "@typescript-eslint/typescript-estree": "5.59.1", + "@typescript-eslint/scope-manager": "5.59.5", + "@typescript-eslint/types": "5.59.5", + "@typescript-eslint/typescript-estree": "5.59.5", "eslint-scope": "^5.1.1", "semver": "^7.3.7" }, @@ -946,12 +946,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "5.59.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.1.tgz", - "integrity": "sha512-6waEYwBTCWryx0VJmP7JaM4FpipLsFl9CvYf2foAE8Qh/Y0s+bxWysciwOs0LTBED4JCaNxTZ5rGadB14M6dwA==", + "version": "5.59.5", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.5.tgz", + "integrity": "sha512-qL+Oz+dbeBRTeyJTIy0eniD3uvqU7x+y1QceBismZ41hd4aBSRh8UAw4pZP0+XzLuPZmx4raNMq/I+59W2lXKA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.59.1", + "@typescript-eslint/types": "5.59.5", "eslint-visitor-keys": "^3.3.0" }, "engines": { @@ -1202,9 +1202,9 @@ } }, "node_modules/aws-sdk": { - "version": "2.1368.0", - "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1368.0.tgz", - "integrity": "sha512-Yc3s8PqdcYG4wyCOpDj4TwXacGZGDgZBJ/XAtzMLKW2wN2c4uu7GwSosLxZ8ejzbAbcqjf080odPuD8P0819tw==", + "version": "2.1376.0", + "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1376.0.tgz", + "integrity": "sha512-ja/Xnft8BDcDEz786VJFPrWpuWpOgsA+QzBAwzsjYeIolQ/vEs/bbXkoS085fOoeAPEhYWQh9wog7cVvrQPJFQ==", "dependencies": { "buffer": "4.9.2", "events": "1.1.1", @@ -1231,6 +1231,11 @@ "isarray": "^1.0.0" } }, + "node_modules/aws-sdk/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + }, "node_modules/aws-sign2": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", @@ -1387,13 +1392,38 @@ } }, "node_modules/bson": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/bson/-/bson-5.2.0.tgz", - "integrity": "sha512-HevkSpDbpUfsrHWmWiAsNavANKYIErV2ePXllp1bwq5CDreAaFVj6RVlZpJnxK4WWDCJ/5jMUpaY6G526q3Hjg==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/bson/-/bson-5.3.0.tgz", + "integrity": "sha512-ukmCZMneMlaC5ebPHXIkP8YJzNl5DC41N5MAIvKDqLggdao342t4McltoJBQfQya/nHBWAcSsYRqlXPoQkTJag==", "engines": { "node": ">=14.20.1" } }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "optional": true, + "peer": true, + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, "node_modules/buffer-crc32": { "version": "0.2.13", "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", @@ -1425,6 +1455,27 @@ "resolved": "https://registry.npmjs.org/buffer-to-uint8array/-/buffer-to-uint8array-1.1.0.tgz", "integrity": "sha512-JVTSbtA6YuOGdu5NL0ffizsBwuwbTXfV7OC91FhazMz9UKP/KlDS+Z7wuiSRClbnTQz52fJgVXI9YDXQRVl2sQ==" }, + "node_modules/buffer/node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "optional": true, + "peer": true + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -1514,13 +1565,16 @@ "integrity": "sha512-nJ22fZvCwkJfMppkOEE7GciLX08rDnVzEJ+U46kBFZtwNzH2V4tNxMWa9Tc365WspCxy1c3NtGJ5EeT4SgjmCA==" }, "node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", "dependencies": { "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", + "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" } }, "node_modules/clone-response": { @@ -1604,6 +1658,11 @@ "typedarray": "^0.0.6" } }, + "node_modules/concat-stream/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + }, "node_modules/concat-stream/node_modules/readable-stream": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", @@ -1906,6 +1965,11 @@ "readable-stream": "^2.0.2" } }, + "node_modules/duplexer2/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + }, "node_modules/duplexer2/node_modules/readable-stream": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", @@ -2148,15 +2212,15 @@ } }, "node_modules/eslint": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.39.0.tgz", - "integrity": "sha512-mwiok6cy7KTW7rBpo05k6+p4YVZByLNjAZ/ACB9DRCu4YDRwjXI01tWHp6KAUWelsBetTxKK/2sHB0vdS8Z2Og==", + "version": "8.40.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.40.0.tgz", + "integrity": "sha512-bvR+TsP9EHL3TqNtj9sCNJVAFK3fBN8Q7g5waghxyRsPLIMwL73XSKnZFK0hk/O2ANC+iAoq6PWMQ+IfBAJIiQ==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.4.0", - "@eslint/eslintrc": "^2.0.2", - "@eslint/js": "8.39.0", + "@eslint/eslintrc": "^2.0.3", + "@eslint/js": "8.40.0", "@humanwhocodes/config-array": "^0.11.8", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", @@ -2167,8 +2231,8 @@ "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", "eslint-scope": "^7.2.0", - "eslint-visitor-keys": "^3.4.0", - "espree": "^9.5.1", + "eslint-visitor-keys": "^3.4.1", + "espree": "^9.5.2", "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", @@ -2218,9 +2282,9 @@ } }, "node_modules/eslint-visitor-keys": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.0.tgz", - "integrity": "sha512-HPpKPUBQcAsZOsHAFwTtIKcYlCje62XB7SEAcxjtmW6TD1WVpkS6i6/hOVtTZIl4zGj/mBqpFVGvaDneik+VoQ==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz", + "integrity": "sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -2255,14 +2319,14 @@ } }, "node_modules/espree": { - "version": "9.5.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.1.tgz", - "integrity": "sha512-5yxtHSZXRSW5pvv3hAlXM5+/Oswi1AUFqBmbibKb5s6bp3rGIDkyXU6xCoyuuLhijr4SFwPrXRoZjz0AZDN9tg==", + "version": "9.5.2", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.2.tgz", + "integrity": "sha512-7OASN1Wma5fum5SrNhFMAMJxOUAbhyfQ8dQ//PJaJbNw0URTPWqIghHWt1MmAANKhHZIYOHruW4Kw4ruUWOdGw==", "dev": true, "dependencies": { "acorn": "^8.8.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.0" + "eslint-visitor-keys": "^3.4.1" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -3359,9 +3423,9 @@ "integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==" }, "node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==" }, "node_modules/isexe": { "version": "2.0.0", @@ -3729,9 +3793,9 @@ } }, "node_modules/minipass": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.8.tgz", - "integrity": "sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", "engines": { "node": ">=8" } @@ -3820,9 +3884,9 @@ } }, "node_modules/mongoose": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-7.1.0.tgz", - "integrity": "sha512-shoo9z/7o96Ojx69wpJn65+EC+Mt3q1SWTducW+F2Y4ieCXo0lZwpCZedgC841MIvJ7V8o6gmzoN1NfcnOTOuw==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-7.1.1.tgz", + "integrity": "sha512-AIxaWwGY+td7QOMk4NgK6fbRuGovFyDzv65nU1uj1DsUh3lpjfP3iFYHSR+sUKrs7nbp19ksLlRXkmInBteSCA==", "dependencies": { "bson": "^5.2.0", "kareem": "2.5.1", @@ -3997,9 +4061,9 @@ "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==" }, "node_modules/node-fetch": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz", - "integrity": "sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==", + "version": "2.6.11", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.11.tgz", + "integrity": "sha512-4I6pdBY1EthSqDmJkiNk3JIT8cswwR9nfeW/cPdUagJYEQG7R95WRH74wpz7ma8Gh/9dI9FP+OU+0E4FvtA55w==", "dependencies": { "whatwg-url": "^5.0.0" }, @@ -4043,9 +4107,9 @@ } }, "node_modules/nodemailer": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.1.tgz", - "integrity": "sha512-qHw7dOiU5UKNnQpXktdgQ1d3OFgRAekuvbJLcdG5dnEo/GtcTHRYM7+UfJARdOFU9WUQO8OiIamgWPmiSFHYAA==", + "version": "6.9.2", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.2.tgz", + "integrity": "sha512-4+TYaa/e1nIxQfyw/WzNPYTEZ5OvHIDEnmjs4LPmIfccPQN+2CYKmGHjWixn/chzD3bmUTu5FMfpltizMxqzdg==", "engines": { "node": ">=6.0.0" } @@ -4311,7 +4375,7 @@ }, "node_modules/pretendo-grpc-ts": { "version": "1.0.0", - "resolved": "git+ssh://git@github.com/PretendoNetwork/grpc-ts.git#028bc63602eb3cf33ea5e775face04b4c586399c", + "resolved": "git+ssh://git@github.com/PretendoNetwork/grpc-ts.git#b7bcb0d691cec3f88ff0627cb7431c14dd2d2d1d", "hasInstallScript": true, "license": "ISC", "dependencies": { @@ -4524,11 +4588,6 @@ "string_decoder": "~0.10.x" } }, - "node_modules/readable-stream/node_modules/isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==" - }, "node_modules/redis": { "version": "4.6.6", "resolved": "https://registry.npmjs.org/redis/-/redis-4.6.6.tgz", @@ -5006,6 +5065,11 @@ "through2": "~2.0.3" } }, + "node_modules/static-module/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + }, "node_modules/static-module/node_modules/readable-stream": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", @@ -5105,9 +5169,9 @@ } }, "node_modules/stripe": { - "version": "12.3.0", - "resolved": "https://registry.npmjs.org/stripe/-/stripe-12.3.0.tgz", - "integrity": "sha512-B9Q1b0gbKY/Z4fQc1Y82VpHTFLh8A67D6kdcFtgpGfTovVkI7SamE66vmVaWNHgcUjPqI8x6wVvksdRf/ucTDw==", + "version": "12.4.0", + "resolved": "https://registry.npmjs.org/stripe/-/stripe-12.4.0.tgz", + "integrity": "sha512-QjZRzKi3wf8TsuJf/fd6/ejfPgwNptDIzFogRWaRzP3oMJnSD73I2YxR0Eje5zfrU8FmddYWZYawoUejqN+o1w==", "dependencies": { "@types/node": ">=8.1.0", "qs": "^6.11.0" @@ -5140,13 +5204,13 @@ } }, "node_modules/tar": { - "version": "6.1.13", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.13.tgz", - "integrity": "sha512-jdIBIN6LTIe2jqzay/2vtYLlBHa3JF42ot3h1dW8Q0PaAG4v8rm0cvpVePtau5C6OKXGGcgO9q2AMNSWxiLqKw==", + "version": "6.1.14", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.14.tgz", + "integrity": "sha512-piERznXu0U7/pW7cdSn7hjqySIVTYT6F76icmFk7ptU7dDYlXTm5r9A6K04R2vU3olYgoKeo1Cg3eeu5nhftAw==", "dependencies": { "chownr": "^2.0.0", "fs-minipass": "^2.0.0", - "minipass": "^4.0.0", + "minipass": "^5.0.0", "minizlib": "^2.1.1", "mkdirp": "^1.0.3", "yallist": "^4.0.0" @@ -5195,6 +5259,11 @@ "xtend": "~4.0.1" } }, + "node_modules/through2/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + }, "node_modules/through2/node_modules/readable-stream": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", @@ -5685,28 +5754,28 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "node_modules/yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", "dependencies": { - "cliui": "^7.0.2", + "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", - "string-width": "^4.2.0", + "string-width": "^4.2.3", "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" + "yargs-parser": "^21.1.1" }, "engines": { - "node": ">=10" + "node": ">=12" } }, "node_modules/yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", "engines": { - "node": ">=10" + "node": ">=12" } }, "node_modules/yesno": { diff --git a/src/config-manager.ts b/src/config-manager.ts index 3305eb7..7bad4ac 100644 --- a/src/config-manager.ts +++ b/src/config-manager.ts @@ -67,7 +67,10 @@ export const config: Config = { website_base: process.env.PN_ACT_CONFIG_WEBSITE_BASE || '', aes_key: process.env.PN_ACT_CONFIG_AES_KEY || '', grpc: { - api_key: process.env.PN_ACT_CONFIG_GRPC_API_KEY || '', + master_api_keys: { + account: process.env.PN_ACT_CONFIG_GRPC_MASTER_API_KEY_ACCOUNT || '', + api: process.env.PN_ACT_CONFIG_GRPC_MASTER_API_KEY_API || '', + }, port: Number(process.env.PN_ACT_CONFIG_GRPC_PORT || ''), } }; @@ -180,8 +183,13 @@ if (!config.aes_key) { process.exit(0); } -if (!config.grpc.api_key) { - LOG_ERROR('gRPC API key is not set. Set the PN_ACT_CONFIG_GRPC_API_KEY environment variable'); +if (!config.grpc.master_api_keys.account) { + LOG_ERROR('Master gRPC API key for the account service is not set. Set the PN_ACT_CONFIG_GRPC_MASTER_API_KEY_ACCOUNT environment variable'); + process.exit(0); +} + +if (!config.grpc.master_api_keys.api) { + LOG_ERROR('Master gRPC API key for the api service is not set. Set the PN_ACT_CONFIG_GRPC_MASTER_API_KEY_API environment variable'); process.exit(0); } diff --git a/src/server.ts b/src/server.ts index 80b67cd..d8fdb63 100644 --- a/src/server.ts +++ b/src/server.ts @@ -24,7 +24,7 @@ import assets from '@/services/assets'; import { config } from '@/config-manager'; -const app = express(); +const app: express.Express = express(); // START APPLICATION diff --git a/src/services/grpc/account/api-key-middleware.ts b/src/services/grpc/account/api-key-middleware.ts new file mode 100644 index 0000000..63e03c0 --- /dev/null +++ b/src/services/grpc/account/api-key-middleware.ts @@ -0,0 +1,16 @@ +import { Status, ServerMiddlewareCall, CallContext, ServerError } from 'nice-grpc'; +import { config } from '@/config-manager'; + +export async function* apiKeyMiddleware( + call: ServerMiddlewareCall, + context: CallContext, +): AsyncGenerator { + console.log(call.method); + const apiKey: string | undefined = context.metadata.get('X-API-Key'); + + if (!apiKey || apiKey !== config.grpc.master_api_keys.account) { + throw new ServerError(Status.UNAUTHENTICATED, 'Missing or invalid API key'); + } + + return yield* call.next(call.request, context); +} \ No newline at end of file diff --git a/src/services/grpc/get-user-data.ts b/src/services/grpc/account/get-user-data.ts similarity index 100% rename from src/services/grpc/get-user-data.ts rename to src/services/grpc/account/get-user-data.ts diff --git a/src/services/grpc/account/implementation.ts b/src/services/grpc/account/implementation.ts new file mode 100644 index 0000000..266e157 --- /dev/null +++ b/src/services/grpc/account/implementation.ts @@ -0,0 +1,6 @@ +import { AccountServiceImplementation } from 'pretendo-grpc-ts/dist/account/account_service'; +import { getUserData } from '@/services/grpc/account/get-user-data'; + +export const accountServiceImplementation: AccountServiceImplementation = { + getUserData, +}; \ No newline at end of file diff --git a/src/services/grpc/api-key-middleware.ts b/src/services/grpc/api/api-key-middleware.ts similarity index 75% rename from src/services/grpc/api-key-middleware.ts rename to src/services/grpc/api/api-key-middleware.ts index 4fffca3..19c4e3b 100644 --- a/src/services/grpc/api-key-middleware.ts +++ b/src/services/grpc/api/api-key-middleware.ts @@ -7,11 +7,8 @@ export async function* apiKeyMiddleware( ): AsyncGenerator { const apiKey: string | undefined = context.metadata.get('X-API-Key'); - if (!apiKey || apiKey !== config.grpc.api_key) { - throw new ServerError( - Status.UNAUTHENTICATED, - 'Missing or invalid API key', - ); + if (!apiKey || apiKey !== config.grpc.master_api_keys.api) { + throw new ServerError(Status.UNAUTHENTICATED, 'Missing or invalid API key'); } return yield* call.next(call.request, context); diff --git a/src/services/grpc/api/authentication-middleware.ts b/src/services/grpc/api/authentication-middleware.ts new file mode 100644 index 0000000..c650e6d --- /dev/null +++ b/src/services/grpc/api/authentication-middleware.ts @@ -0,0 +1,55 @@ +import { Status, ServerMiddlewareCall, CallContext, ServerError } from 'nice-grpc'; +import { getPNIDByBearerAuth } from '@/database'; +import type { HydratedPNIDDocument } from '@/types/mongoose/pnid'; + +// * These paths require that a token be present +const TOKEN_REQUIRED_PATHS: string[] = [ + '/api.API/GetUserData', + '/api.API/UpdateUserData', + '/api.API/ResetPassword', // * This paths token is not an authentication token, it is a password reset token + '/api.API/SetDiscordConnectionData', + '/api.API/SetStripeConnectionData', + '/api.API/RemoveConnection' +]; + +export type AuthenticationCallContextExt = { + pnid: HydratedPNIDDocument | null; +}; + +export async function* authenticationMiddleware( + call: ServerMiddlewareCall, + context: CallContext, +): AsyncGenerator { + const token: string | undefined = context.metadata.get('X-Token')?.trim(); + + if (!token && TOKEN_REQUIRED_PATHS.includes(call.method.path)) { + throw new ServerError(Status.UNAUTHENTICATED, 'Missing or invalid authentication token'); + } + + try { + let pnid: HydratedPNIDDocument | null = null; + + if (token) { + pnid = await getPNIDByBearerAuth(token); + } + + if (!pnid && TOKEN_REQUIRED_PATHS.includes(call.method.path)) { + throw new ServerError(Status.UNAUTHENTICATED, 'Missing or invalid authentication token'); + } + + return yield* call.next(call.request, { + ...context, + pnid + }); + } catch (error) { + let message: string = 'Unknown server error'; + + console.log(error); + + if (error instanceof Error) { + message = error.message; + } + + throw new ServerError(Status.INVALID_ARGUMENT, message); + } +} \ No newline at end of file diff --git a/src/services/grpc/api/forgot-password.ts b/src/services/grpc/api/forgot-password.ts new file mode 100644 index 0000000..d9a887f --- /dev/null +++ b/src/services/grpc/api/forgot-password.ts @@ -0,0 +1,29 @@ +import { Status, ServerError } from 'nice-grpc'; +import validator from 'validator'; +import { ForgotPasswordRequest } from 'pretendo-grpc-ts/dist/api/forgot_password_rpc'; +import { getPNIDByEmailAddress, getPNIDByUsername } from '@/database'; +import { sendForgotPasswordEmail } from '@/util'; +import type { Empty } from 'pretendo-grpc-ts/dist/api/google/protobuf/empty'; +import type { HydratedPNIDDocument } from '@/types/mongoose/pnid'; + +export async function forgotPassword(request: ForgotPasswordRequest): Promise { + const input: string = request.emailAddressOrUsername.trim(); + + if (!input) { + throw new ServerError(Status.INVALID_ARGUMENT, 'Invalid or missing input'); + } + + let pnid: HydratedPNIDDocument | null; + + if (validator.isEmail(input)) { + pnid = await getPNIDByEmailAddress(input); + } else { + pnid = await getPNIDByUsername(input); + } + + if (pnid) { + await sendForgotPasswordEmail(pnid); + } + + return {}; +} \ No newline at end of file diff --git a/src/services/grpc/api/get-user-data.ts b/src/services/grpc/api/get-user-data.ts new file mode 100644 index 0000000..beeb92e --- /dev/null +++ b/src/services/grpc/api/get-user-data.ts @@ -0,0 +1,46 @@ +import { CallContext } from 'nice-grpc'; +import { GetUserDataResponse, DeepPartial } from 'pretendo-grpc-ts/dist/api/get_user_data_rpc'; +import { config } from '@/config-manager'; +import type { Empty } from 'pretendo-grpc-ts/dist/api/google/protobuf/empty'; +import type { AuthenticationCallContextExt } from '@/services/grpc/api/authentication-middleware'; +import type { HydratedPNIDDocument } from '@/types/mongoose/pnid'; + +export async function getUserData(_request: Empty, context: CallContext & AuthenticationCallContextExt): Promise> { + // * This is asserted in authentication-middleware, we know this is never null + const pnid: HydratedPNIDDocument = context.pnid!; + + return { + deleted: pnid.deleted, + creationDate: pnid.creation_date, + updatedDate: pnid.updated, + pid: pnid.pid, + username: pnid.username, + accessLevel: pnid.access_level, + serverAccessLevel: pnid.server_access_level, + mii: { + name: pnid.mii.name, + data: pnid.mii.data, + url: `${config.cdn.base_url}/mii/${pnid.pid}/standard.tga`, + }, + birthday: pnid.birthdate, + gender: pnid.gender, + country: pnid.country, + timezone: pnid.timezone.name, + language: pnid.language, + emailAddress: pnid.email.address, + connections: { + discord: { + id: pnid.connections.discord.id + }, + stripe: { + customerId: pnid.connections.stripe.customer_id, + subscriptionId: pnid.connections.stripe.subscription_id, + priceId: pnid.connections.stripe.price_id, + tierLevel: pnid.connections.stripe.tier_level, + tierName: pnid.connections.stripe.tier_name, + latestWebhookTimestamp: pnid.connections.stripe.latest_webhook_timestamp + } + }, + marketingFlag: pnid.flags.marketing + }; +} \ No newline at end of file diff --git a/src/services/grpc/api/implementation.ts b/src/services/grpc/api/implementation.ts new file mode 100644 index 0000000..337c513 --- /dev/null +++ b/src/services/grpc/api/implementation.ts @@ -0,0 +1,20 @@ +import { APIServiceImplementation } from 'pretendo-grpc-ts/dist/api/api_service'; +import { register } from '@/services/grpc/api/register'; +import { login } from '@/services/grpc/api/login'; +import { getUserData } from '@/services/grpc/api/get-user-data'; +import { updateUserData } from '@/services/grpc/api/update-user-data'; +import { forgotPassword } from '@/services/grpc/api/forgot-password'; +import { resetPassword } from '@/services/grpc/api/reset-password'; +import { setDiscordConnectionData } from '@/services/grpc/api/set-discord-connection-data'; +import { setStripeConnectionData } from '@/services/grpc/api/set-stripe-connection-data'; + +export const apiServiceImplementation: APIServiceImplementation = { + register, + login, + getUserData, + updateUserData, + forgotPassword, + resetPassword, + setDiscordConnectionData, + setStripeConnectionData +}; \ No newline at end of file diff --git a/src/services/grpc/api/login.ts b/src/services/grpc/api/login.ts new file mode 100644 index 0000000..2069305 --- /dev/null +++ b/src/services/grpc/api/login.ts @@ -0,0 +1,90 @@ +import { Status, ServerError } from 'nice-grpc'; +import { LoginRequest, LoginResponse, DeepPartial } from 'pretendo-grpc-ts/dist/api/login_rpc'; +import bcrypt from 'bcrypt'; +import { getPNIDByUsername, getPNIDByBearerAuth } from '@/database'; +import { nintendoPasswordHash, generateToken} from '@/util'; +import { config } from '@/config-manager'; +import type { TokenOptions } from '@/types/common/token-options'; +import type { HydratedPNIDDocument } from '@/types/mongoose/pnid'; + +export async function login(request: LoginRequest): Promise> { + const grantType: string = request.grantType?.trim(); + const username: string | undefined = request.username?.trim(); + const password: string | undefined = request.password?.trim(); + const refreshToken: string | undefined = request.refreshToken?.trim(); + + if (!['password', 'refresh_token'].includes(grantType)) { + throw new ServerError(Status.INVALID_ARGUMENT, 'Invalid grant type'); + } + + if (grantType === 'password' && !username) { + throw new ServerError(Status.INVALID_ARGUMENT, 'Invalid or missing username'); + } + + if (grantType === 'password' && !password) { + throw new ServerError(Status.INVALID_ARGUMENT, 'Invalid or missing password'); + } + + if (grantType === 'refresh_token' && !refreshToken) { + throw new ServerError(Status.INVALID_ARGUMENT, 'Invalid or missing refresh token'); + } + + let pnid: HydratedPNIDDocument | null; + + if (grantType === 'password') { + pnid = await getPNIDByUsername(username!); // * We know username will never be null here + + if (!pnid) { + throw new ServerError(Status.INVALID_ARGUMENT, 'User not found'); + } + + const hashedPassword: string = nintendoPasswordHash(password!, pnid.pid); // * We know password will never be null here + + if (!bcrypt.compareSync(hashedPassword, pnid.password)) { + throw new ServerError(Status.INVALID_ARGUMENT, 'Password is incorrect'); + } + } else { + pnid = await getPNIDByBearerAuth(refreshToken!); // * We know refreshToken will never be null here + + if (!pnid) { + throw new ServerError(Status.INVALID_ARGUMENT, 'Invalid or missing refresh token'); + } + } + + if (pnid.deleted) { + throw new ServerError(Status.UNAUTHENTICATED, 'Account has been deleted'); + } + + const accessTokenOptions: TokenOptions = { + system_type: 0x3, // * API + token_type: 0x1, // * OAuth Access + pid: pnid.pid, + access_level: pnid.access_level, + title_id: BigInt(0), + expire_time: BigInt(Date.now() + (3600 * 1000)) + }; + + const refreshTokenOptions: TokenOptions = { + system_type: 0x3, // * API + token_type: 0x2, // * OAuth Refresh + pid: pnid.pid, + access_level: pnid.access_level, + title_id: BigInt(0), + expire_time: BigInt(Date.now() + (3600 * 1000)) + }; + + const accessTokenBuffer: Buffer | null = await generateToken(config.aes_key, accessTokenOptions); + const refreshTokenBuffer: Buffer | null = await generateToken(config.aes_key, refreshTokenOptions); + + const accessToken: string = accessTokenBuffer ? accessTokenBuffer.toString('hex') : ''; + const newRefreshToken: string = refreshTokenBuffer ? refreshTokenBuffer.toString('hex') : ''; + + // TODO - Handle null tokens + + return { + accessToken: accessToken, + tokenType: 'Bearer', + expiresIn: 3600, + refreshToken: newRefreshToken + }; +} \ No newline at end of file diff --git a/src/services/grpc/api/register.ts b/src/services/grpc/api/register.ts new file mode 100644 index 0000000..8800d9c --- /dev/null +++ b/src/services/grpc/api/register.ts @@ -0,0 +1,266 @@ +import crypto from 'node:crypto'; +import { Status, ServerError } from 'nice-grpc'; +import { RegisterRequest, DeepPartial } from 'pretendo-grpc-ts/dist/api/register_rpc'; +import { LoginResponse } from 'pretendo-grpc-ts/dist/api/login_rpc'; +import emailvalidator from 'email-validator'; +import bcrypt from 'bcrypt'; +import moment from 'moment'; +import hcaptcha from 'hcaptcha'; +import Mii from 'mii-js'; +import mongoose from 'mongoose'; +import { doesPNIDExist, connection as databaseConnection } from '@/database'; +import { nintendoPasswordHash, sendConfirmationEmail, generateToken } from '@/util'; +import { LOG_ERROR } from '@/logger'; +import { PNID } from '@/models/pnid'; +import { NEXAccount } from '@/models/nex-account'; +import { config, disabledFeatures } from '@/config-manager'; +import type { TokenOptions } from '@/types/common/token-options'; +import type { HydratedNEXAccountDocument } from '@/types/mongoose/nex-account'; +import type { HydratedPNIDDocument } from '@/types/mongoose/pnid'; + +const PNID_VALID_CHARACTERS_REGEX: RegExp = /^[\w\-.]*$/; +const PNID_PUNCTUATION_START_REGEX: RegExp = /^[_\-.]/; +const PNID_PUNCTUATION_END_REGEX: RegExp = /[_\-.]$/; +const PNID_PUNCTUATION_DUPLICATE_REGEX: RegExp = /[_\-.]{2,}/; + +// This sucks +const PASSWORD_WORD_OR_NUMBER_REGEX: RegExp = /(?=.*[a-zA-Z])(?=.*\d).*/; +const PASSWORD_WORD_OR_PUNCTUATION_REGEX: RegExp = /(?=.*[a-zA-Z])(?=.*[_\-.]).*/; +const PASSWORD_NUMBER_OR_PUNCTUATION_REGEX: RegExp = /(?=.*\d)(?=.*[_\-.]).*/; +const PASSWORD_REPEATED_CHARACTER_REGEX: RegExp = /(.)\1\1/; + +const DEFAULT_MII_DATA: Buffer = Buffer.from('AwAAQOlVognnx0GC2/uogAOzuI0n2QAAAEBEAGUAZgBhAHUAbAB0AAAAAAAAAEBAAAAhAQJoRBgmNEYUgRIXaA0AACkAUkhQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGm9', 'base64'); + +export async function register(request: RegisterRequest): Promise> { + const email: string = request.email?.trim(); + const username: string = request.username?.trim(); + const miiName: string = request.miiName?.trim(); + const password: string = request.password?.trim(); + const passwordConfirm: string = request.passwordConfirm?.trim(); + const captchaResponse: string | undefined = request.captchaResponse?.trim(); + + // * Only validate the captcha if that's enabled + if (!disabledFeatures.captcha) { + if (!captchaResponse) { + throw new ServerError(Status.INVALID_ARGUMENT, 'Must fill in captcha'); + } + + const captchaVerify: VerifyResponse = await hcaptcha.verify(config.hcaptcha.secret, captchaResponse); + + if (!captchaVerify.success) { + throw new ServerError(Status.INVALID_ARGUMENT, 'Captcha verification failed'); + } + } + + if (!email) { + throw new ServerError(Status.INVALID_ARGUMENT, 'Must enter an email address'); + } + + if (!emailvalidator.validate(email)) { + throw new ServerError(Status.INVALID_ARGUMENT, 'Invalid email address'); + } + + if (!username) { + throw new ServerError(Status.INVALID_ARGUMENT, 'Must enter a username'); + } + + if (username.length < 6) { + throw new ServerError(Status.INVALID_ARGUMENT, 'Username is too short'); + } + + if (username.length > 16) { + throw new ServerError(Status.INVALID_ARGUMENT, 'Username is too long'); + } + + if (!PNID_VALID_CHARACTERS_REGEX.test(username)) { + throw new ServerError(Status.INVALID_ARGUMENT, 'Username contains invalid characters'); + } + + if (PNID_PUNCTUATION_START_REGEX.test(username)) { + throw new ServerError(Status.INVALID_ARGUMENT, 'Username cannot begin with punctuation characters'); + } + + if (PNID_PUNCTUATION_END_REGEX.test(username)) { + throw new ServerError(Status.INVALID_ARGUMENT, 'Username cannot end with punctuation characters'); + } + + if (PNID_PUNCTUATION_DUPLICATE_REGEX.test(username)) { + throw new ServerError(Status.INVALID_ARGUMENT, 'Two or more punctuation characters cannot be used in a row'); + } + + const userExists: boolean = await doesPNIDExist(username); + + if (userExists) { + throw new ServerError(Status.INVALID_ARGUMENT, 'PNID already in use'); + } + + if (!miiName) { + throw new ServerError(Status.INVALID_ARGUMENT, 'Must enter a Mii name'); + } + + const miiNameBuffer: Buffer = Buffer.from(miiName, 'utf16le'); // * UTF8 to UTF16 + + if (miiNameBuffer.length > 0x14) { + throw new ServerError(Status.INVALID_ARGUMENT, 'Mii name too long'); + } + + if (!password) { + throw new ServerError(Status.INVALID_ARGUMENT, 'Must enter a password'); + } + + if (password.length < 6 || password.length > 16) { + throw new ServerError(Status.INVALID_ARGUMENT, 'Password must be between 6 and 16 characters long'); + } + + if (password.toLowerCase() === username.toLowerCase()) { + throw new ServerError(Status.INVALID_ARGUMENT, 'Password cannot be the same as username'); + } + + if (!PASSWORD_WORD_OR_NUMBER_REGEX.test(password) && !PASSWORD_WORD_OR_PUNCTUATION_REGEX.test(password) && !PASSWORD_NUMBER_OR_PUNCTUATION_REGEX.test(password)) { + throw new ServerError(Status.INVALID_ARGUMENT, 'Password must have combination of letters, numbers, and/or punctuation characters'); + } + + if (PASSWORD_REPEATED_CHARACTER_REGEX.test(password)) { + throw new ServerError(Status.INVALID_ARGUMENT, 'Password may not have 3 repeating characters'); + } + + if (password !== passwordConfirm) { + throw new ServerError(Status.INVALID_ARGUMENT, 'Passwords do not match'); + } + + const mii: Mii = new Mii(DEFAULT_MII_DATA); + mii.miiName = miiName; + + const creationDate: string = moment().format('YYYY-MM-DDTHH:MM:SS'); + let pnid: HydratedPNIDDocument; + let nexAccount: HydratedNEXAccountDocument; + + const session: mongoose.ClientSession = await databaseConnection().startSession(); + await session.startTransaction(); + + try { + // * PNIDs can only be registered from a Wii U + // * So assume website users are WiiU NEX accounts + nexAccount = new NEXAccount({ + device_type: 'wiiu', + }); + + await nexAccount.generatePID(); + await nexAccount.generatePassword(); + + // Quick hack to get the PIDs to match + // TODO: Change this maybe? + // NN with a NNID will always use the NNID PID + // even if the provided NEX PID is different + // To fix this we make them the same PID + nexAccount.owning_pid = nexAccount.pid; + + await nexAccount.save({ session }); + + const primaryPasswordHash: string = nintendoPasswordHash(password, nexAccount.pid); + const passwordHash: string = await bcrypt.hash(primaryPasswordHash, 10); + + pnid = new PNID({ + pid: nexAccount.pid, + creation_date: creationDate, + updated: creationDate, + username: username, + usernameLower: username.toLowerCase(), + password: passwordHash, + birthdate: '1990-01-01', // TODO: Change this + gender: 'M', // TODO: Change this + country: 'US', // TODO: Change this + language: 'en', // TODO: Change this + email: { + address: email.toLowerCase(), + primary: true, // TODO: Change this + parent: true, // TODO: Change this + reachable: false, // TODO: Change this + validated: false, // TODO: Change this + id: crypto.randomBytes(4).readUInt32LE() + }, + region: 0x310B0000, // TODO: Change this + timezone: { + name: 'America/New_York', // TODO: Change this + offset: -14400 // TODO: Change this + }, + mii: { + name: miiName, + primary: true, // TODO: Change this + data: mii.encode().toString('base64'), + id: crypto.randomBytes(4).readUInt32LE(), + hash: crypto.randomBytes(7).toString('hex'), + image_url: '', // deprecated, will be removed in the future + image_id: crypto.randomBytes(4).readUInt32LE() + }, + flags: { + active: true, // TODO: Change this + marketing: true, // TODO: Change this + off_device: true // TODO: Change this + }, + identification: { + email_code: 1, // will be overwritten before saving + email_token: '' // will be overwritten before saving + } + }); + + await pnid.generateEmailValidationCode(); + await pnid.generateEmailValidationToken(); + await pnid.generateMiiImages(); + + await pnid.save({ session }); + + await session.commitTransaction(); + } catch (error) { + let message: string = 'Unknown Mongo error'; + + if (error instanceof Error) { + message = error.message; + } + + LOG_ERROR(`[gRPC] /api.API/Register: ${message}`); + + await session.abortTransaction(); + + throw new ServerError(Status.INVALID_ARGUMENT, message); + } finally { + // * This runs regardless of failure + // * Returning on catch will not prevent this from running + await session.endSession(); + } + + await sendConfirmationEmail(pnid); + + const accessTokenOptions: TokenOptions = { + system_type: 0x3, // * API + token_type: 0x1, // * OAuth Access + pid: pnid.pid, + access_level: pnid.access_level, + title_id: BigInt(0), + expire_time: BigInt(Date.now() + (3600 * 1000)) + }; + + const refreshTokenOptions: TokenOptions = { + system_type: 0x3, // * API + token_type: 0x2, // * OAuth Refresh + pid: pnid.pid, + access_level: pnid.access_level, + title_id: BigInt(0), + expire_time: BigInt(Date.now() + (3600 * 1000)) + }; + + const accessTokenBuffer: Buffer | null = await generateToken(config.aes_key, accessTokenOptions); + const refreshTokenBuffer: Buffer | null = await generateToken(config.aes_key, refreshTokenOptions); + + const accessToken: string = accessTokenBuffer ? accessTokenBuffer.toString('hex') : ''; + const refreshToken: string = refreshTokenBuffer ? refreshTokenBuffer.toString('hex') : ''; + + // TODO - Handle null tokens + + return { + accessToken: accessToken, + tokenType: 'Bearer', + expiresIn: 3600, + refreshToken: refreshToken + }; +} \ No newline at end of file diff --git a/src/services/grpc/api/reset-password.ts b/src/services/grpc/api/reset-password.ts new file mode 100644 index 0000000..d01111c --- /dev/null +++ b/src/services/grpc/api/reset-password.ts @@ -0,0 +1,79 @@ +import bcrypt from 'bcrypt'; +import { Status, ServerError } from 'nice-grpc'; +import { ResetPasswordRequest } from 'pretendo-grpc-ts/dist/api/reset_password_rpc'; +import { decryptToken, unpackToken, nintendoPasswordHash } from '@/util'; +import { getPNIDByPID } from '@/database'; +import type { Empty } from 'pretendo-grpc-ts/dist/api/google/protobuf/empty'; +import type { Token } from '@/types/common/token'; +import type { HydratedPNIDDocument } from '@/types/mongoose/pnid'; + +// This sucks +const PASSWORD_WORD_OR_NUMBER_REGEX: RegExp = /(?=.*[a-zA-Z])(?=.*\d).*/; +const PASSWORD_WORD_OR_PUNCTUATION_REGEX: RegExp = /(?=.*[a-zA-Z])(?=.*[_\-.]).*/; +const PASSWORD_NUMBER_OR_PUNCTUATION_REGEX: RegExp = /(?=.*\d)(?=.*[_\-.]).*/; +const PASSWORD_REPEATED_CHARACTER_REGEX: RegExp = /(.)\1\1/; + +export async function resetPassword(request: ResetPasswordRequest): Promise { + const password: string = request.password.trim(); + const passwordConfirm: string = request.passwordConfirm.trim(); + const token: string = request.token.trim(); + + if (!token) { + throw new ServerError(Status.INVALID_ARGUMENT, 'Missing token'); + } + + let unpackedToken: Token; + try { + const decryptedToken: Buffer = await decryptToken(Buffer.from(token, 'base64')); + unpackedToken = unpackToken(decryptedToken); + } catch (error) { + throw new ServerError(Status.INVALID_ARGUMENT, 'Invalid token'); + } + + if (unpackedToken.expire_time < Date.now()) { + throw new ServerError(Status.INVALID_ARGUMENT, 'Token expired'); + } + + const pnid: HydratedPNIDDocument | null = await getPNIDByPID(unpackedToken.pid); + + if (!pnid) { + throw new ServerError(Status.INVALID_ARGUMENT, 'Invalid token. No user found'); + } + + if (!password) { + throw new ServerError(Status.INVALID_ARGUMENT, 'Must enter a password'); + } + + if (password.length < 6) { + throw new ServerError(Status.INVALID_ARGUMENT, 'Password is too short'); + } + + if (password.length > 16) { + throw new ServerError(Status.INVALID_ARGUMENT, 'Password is too long'); + } + + if (password.toLowerCase() === pnid.usernameLower) { + throw new ServerError(Status.INVALID_ARGUMENT, 'Password cannot be the same as username'); + } + + if (!PASSWORD_WORD_OR_NUMBER_REGEX.test(password) && !PASSWORD_WORD_OR_PUNCTUATION_REGEX.test(password) && !PASSWORD_NUMBER_OR_PUNCTUATION_REGEX.test(password)) { + throw new ServerError(Status.INVALID_ARGUMENT, 'Password must have combination of letters, numbers, and/or punctuation characters'); + } + + if (PASSWORD_REPEATED_CHARACTER_REGEX.test(password)) { + throw new ServerError(Status.INVALID_ARGUMENT, 'Password may not have 3 repeating characters'); + } + + if (password !== passwordConfirm) { + throw new ServerError(Status.INVALID_ARGUMENT, 'Passwords do not match'); + } + + const primaryPasswordHash: string = nintendoPasswordHash(password, pnid.pid); + const passwordHash: string = await bcrypt.hash(primaryPasswordHash, 10); + + pnid.password = passwordHash; + + await pnid.save(); + + return {}; +} \ No newline at end of file diff --git a/src/services/grpc/api/set-discord-connection-data.ts b/src/services/grpc/api/set-discord-connection-data.ts new file mode 100644 index 0000000..2f3dbed --- /dev/null +++ b/src/services/grpc/api/set-discord-connection-data.ts @@ -0,0 +1,26 @@ +import { Status, ServerError, CallContext } from 'nice-grpc'; +import { SetDiscordConnectionDataRequest } from 'pretendo-grpc-ts/dist/api/set_discord_connection_data_rpc'; +import type { Empty } from 'pretendo-grpc-ts/dist/api/google/protobuf/empty'; +import type { AuthenticationCallContextExt } from '@/services/grpc/api/authentication-middleware'; +import type { HydratedPNIDDocument } from '@/types/mongoose/pnid'; + +export async function setDiscordConnectionData(request: SetDiscordConnectionDataRequest, context: CallContext & AuthenticationCallContextExt): Promise{ + // * This is asserted in authentication-middleware, we know this is never null + const pnid: HydratedPNIDDocument = context.pnid!; + + try { + pnid.connections.discord.id = request.id; + + await pnid.save(); + } catch (error) { + let message: string = 'Unknown Mongo error'; + + if (error instanceof Error) { + message = error.message; + } + + throw new ServerError(Status.INVALID_ARGUMENT, message); + } + + return {}; +} \ No newline at end of file diff --git a/src/services/grpc/api/set-stripe-connection-data.ts b/src/services/grpc/api/set-stripe-connection-data.ts new file mode 100644 index 0000000..238bc08 --- /dev/null +++ b/src/services/grpc/api/set-stripe-connection-data.ts @@ -0,0 +1,91 @@ +import { Status, ServerError, CallContext } from 'nice-grpc'; +import { SetStripeConnectionDataRequest } from 'pretendo-grpc-ts/dist/api/set_stripe_connection_data_rpc'; +import { PNID } from '@/models/pnid'; +import type { Empty } from 'pretendo-grpc-ts/dist/api/google/protobuf/empty'; +import type { AuthenticationCallContextExt } from '@/services/grpc/api/authentication-middleware'; +import type { HydratedPNIDDocument } from '@/types/mongoose/pnid'; + +type StripeMongoUpdateScheme = { + access_level?: number; + server_access_level?: string; + 'connections.stripe.customer_id'?: string; + 'connections.stripe.subscription_id'?: string; + 'connections.stripe.price_id'?: string; + 'connections.stripe.tier_level'?: number; + 'connections.stripe.tier_name'?: string; + 'connections.stripe.latest_webhook_timestamp': number; +}; + +export async function setStripeConnectionData(request: SetStripeConnectionDataRequest, context: CallContext & AuthenticationCallContextExt): Promise{ + // * This is asserted in authentication-middleware, we know this is never null + const pnid: HydratedPNIDDocument = context.pnid!; + + const updateData: StripeMongoUpdateScheme = { + 'connections.stripe.latest_webhook_timestamp': request.timestamp + }; + + if (request.customerId && !pnid.connections.stripe.customer_id) { + updateData['connections.stripe.customer_id'] = request.customerId; + } + + // * These checks allow for null/0 values in order to reset data if needed + + if (request.accessLevel !== undefined) { + updateData.access_level = request.accessLevel; + } + + if (request.serverAccessLevel !== undefined) { + updateData.server_access_level = request.serverAccessLevel; + } + + if (request.subscriptionId !== undefined) { + updateData['connections.stripe.subscription_id'] = request.subscriptionId; + } + + if (request.subscriptionId !== undefined) { + updateData['connections.stripe.subscription_id'] = request.subscriptionId; + } + + if (request.priceId !== undefined) { + updateData['connections.stripe.price_id'] = request.priceId; + } + + if (request.tierLevel !== undefined) { + updateData['connections.stripe.tier_level'] = request.tierLevel; + } + + if (request.tierName !== undefined) { + updateData['connections.stripe.tier_name'] = request.tierName; + } + + try { + if (pnid.connections.stripe.latest_webhook_timestamp && pnid.connections.stripe.customer_id) { + // * Stripe customer data has already been initialized, update it + await PNID.updateOne({ + pid: pnid.pid, + 'connections.stripe.latest_webhook_timestamp': { + $lte: request.timestamp + } + }, { $set: updateData }).exec(); + } else { + // * Initialize a new Stripe user + if (!request.customerId) { + throw new ServerError(Status.INVALID_ARGUMENT, 'No Stripe user data found and no custom ID provided'); + } + + PNID.updateOne({ pid: pnid.pid }, { + $set: updateData + }, { upsert: true }).exec(); + } + } catch (error) { + let message: string = 'Unknown Mongo error'; + + if (error instanceof Error) { + message = error.message; + } + + throw new ServerError(Status.INVALID_ARGUMENT, message); + } + + return {}; +} \ No newline at end of file diff --git a/src/services/grpc/api/update-user-data.ts b/src/services/grpc/api/update-user-data.ts new file mode 100644 index 0000000..3747144 --- /dev/null +++ b/src/services/grpc/api/update-user-data.ts @@ -0,0 +1,48 @@ +import { CallContext } from 'nice-grpc'; +import { UpdateUserDataRequest, DeepPartial } from 'pretendo-grpc-ts/dist/api/update_user_data_rpc'; +import { GetUserDataResponse } from 'pretendo-grpc-ts/dist/api/get_user_data_rpc'; +import { config } from '@/config-manager'; +import type { HydratedPNIDDocument } from '@/types/mongoose/pnid'; +import type { AuthenticationCallContextExt } from '@/services/grpc/api/authentication-middleware'; + +export async function updateUserData(_request: UpdateUserDataRequest, context: CallContext & AuthenticationCallContextExt): Promise> { + // * This is asserted in authentication-middleware, we know this is never null + const pnid: HydratedPNIDDocument = context.pnid!; + + // TODO - STUBBED, DO SOMETHING HERE + + return { + deleted: pnid.deleted, + creationDate: pnid.creation_date, + updatedDate: pnid.updated, + pid: pnid.pid, + username: pnid.username, + accessLevel: pnid.access_level, + serverAccessLevel: pnid.server_access_level, + mii: { + name: pnid.mii.name, + data: pnid.mii.data, + url: `${config.cdn.base_url}/mii/${pnid.pid}/standard.tga`, + }, + birthday: pnid.birthdate, + gender: pnid.gender, + country: pnid.country, + timezone: pnid.timezone.name, + language: pnid.language, + emailAddress: pnid.email.address, + connections: { + discord: { + id: pnid.connections.discord.id + }, + stripe: { + customerId: pnid.connections.stripe.customer_id, + subscriptionId: pnid.connections.stripe.subscription_id, + priceId: pnid.connections.stripe.price_id, + tierLevel: pnid.connections.stripe.tier_level, + tierName: pnid.connections.stripe.tier_name, + latestWebhookTimestamp: pnid.connections.stripe.latest_webhook_timestamp + } + }, + marketingFlag: pnid.flags.marketing + }; +} \ No newline at end of file diff --git a/src/services/grpc/login.ts b/src/services/grpc/login.ts deleted file mode 100644 index 5cd2da3..0000000 --- a/src/services/grpc/login.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { Status, ServerError } from 'nice-grpc'; -import { LoginRequest, LoginResponse, DeepPartial } from 'pretendo-grpc-ts/dist/account/login_rpc'; - -export async function login(_request: LoginRequest): Promise> { - throw new ServerError(Status.UNIMPLEMENTED, 'Login method not implemented'); -} \ No newline at end of file diff --git a/src/services/grpc/register-pnid.ts b/src/services/grpc/register-pnid.ts deleted file mode 100644 index 1da888e..0000000 --- a/src/services/grpc/register-pnid.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Status, ServerError } from 'nice-grpc'; -import { RegisterPNIDRequest, DeepPartial } from 'pretendo-grpc-ts/dist/account/register_pnid_rpc'; -import { LoginResponse } from 'pretendo-grpc-ts/dist/account/login_rpc'; - -export async function registerPNID(_request: RegisterPNIDRequest): Promise> { - throw new ServerError(Status.UNIMPLEMENTED, 'Register PNID method not implemented'); -} \ No newline at end of file diff --git a/src/services/grpc/server.ts b/src/services/grpc/server.ts index f568114..5e4bc8d 100644 --- a/src/services/grpc/server.ts +++ b/src/services/grpc/server.ts @@ -1,22 +1,21 @@ import { createServer, Server } from 'nice-grpc'; -import { AccountServiceImplementation, AccountDefinition } from 'pretendo-grpc-ts/dist/account/account_service'; +import { AccountDefinition } from 'pretendo-grpc-ts/dist/account/account_service'; +import { APIDefinition } from 'pretendo-grpc-ts/dist/api/api_service'; + +import { apiKeyMiddleware as accountApiKeyMiddleware } from '@/services/grpc/account/api-key-middleware'; +import { apiKeyMiddleware as apiApiKeyMiddleware } from '@/services/grpc/api/api-key-middleware'; +import { authenticationMiddleware as apiAuthenticationMiddleware } from '@/services/grpc/api/authentication-middleware'; + +import { accountServiceImplementation } from '@/services/grpc/account/implementation'; +import { apiServiceImplementation } from '@/services/grpc/api/implementation'; + import { config } from '@/config-manager'; -import { apiKeyMiddleware } from '@/services/grpc/api-key-middleware'; -import { getUserData } from '@/services/grpc/get-user-data'; -import { login } from '@/services/grpc/login'; -import { registerPNID } from '@/services/grpc/register-pnid'; - -const accountServiceImplementation: AccountServiceImplementation = { - getUserData, - login, - registerPNID, -}; - export async function startGRPCServer(): Promise { - const server: Server = createServer().use(apiKeyMiddleware); + const server: Server = createServer(); - server.add(AccountDefinition, accountServiceImplementation); + server.with(accountApiKeyMiddleware).add(AccountDefinition, accountServiceImplementation); + server.with(apiApiKeyMiddleware).with(apiAuthenticationMiddleware).add(APIDefinition, apiServiceImplementation); await server.listen(`0.0.0.0:${config.grpc.port}`); } \ No newline at end of file diff --git a/src/types/common/config.ts b/src/types/common/config.ts index 753322d..ce328d9 100644 --- a/src/types/common/config.ts +++ b/src/types/common/config.ts @@ -39,7 +39,10 @@ export interface Config { website_base: string; aes_key: string; grpc: { - api_key: string; + master_api_keys: { + account: string; + api: string; + }; port: number; }; stripe?: { From 15b6f1f4a354826be5fca10b553b36553d5e57ea Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Wed, 31 May 2023 21:31:48 -0400 Subject: [PATCH 102/219] Renamed getPNIDByBearerAuth to getPNIDByTokenAuth (not all tokens are bearer) --- src/database.ts | 2 +- src/middleware/api.ts | 4 ++-- src/middleware/pnid.ts | 4 ++-- src/services/api/routes/v1/login.ts | 4 ++-- src/services/grpc/api/authentication-middleware.ts | 4 ++-- src/services/grpc/api/login.ts | 4 ++-- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/database.ts b/src/database.ts index cb122cc..84d6024 100644 --- a/src/database.ts +++ b/src/database.ts @@ -100,7 +100,7 @@ export async function getPNIDByBasicAuth(token: string): Promise { +export async function getPNIDByTokenAuth(token: string): Promise { verifyConnected(); try { diff --git a/src/middleware/api.ts b/src/middleware/api.ts index a3e97f8..6e03492 100644 --- a/src/middleware/api.ts +++ b/src/middleware/api.ts @@ -1,6 +1,6 @@ import express from 'express'; import { getValueFromHeaders } from '@/util'; -import { getPNIDByBearerAuth } from '@/database'; +import { getPNIDByTokenAuth } from '@/database'; import { HydratedPNIDDocument } from '@/types/mongoose/pnid'; async function APIMiddleware(request: express.Request, _response: express.Response, next: express.NextFunction): Promise { @@ -12,7 +12,7 @@ async function APIMiddleware(request: express.Request, _response: express.Respon try { const token: string = authHeader.split(' ')[1]; - const pnid: HydratedPNIDDocument | null = await getPNIDByBearerAuth(token); + const pnid: HydratedPNIDDocument | null = await getPNIDByTokenAuth(token); request.pnid = pnid; } catch (error) { diff --git a/src/middleware/pnid.ts b/src/middleware/pnid.ts index 9d53ecd..780dc74 100644 --- a/src/middleware/pnid.ts +++ b/src/middleware/pnid.ts @@ -1,7 +1,7 @@ import express from 'express'; import xmlbuilder from 'xmlbuilder'; import { getValueFromHeaders } from '@/util'; -import { getPNIDByBasicAuth, getPNIDByBearerAuth } from '@/database'; +import { getPNIDByBasicAuth, getPNIDByTokenAuth } from '@/database'; import { HydratedPNIDDocument } from '@/types/mongoose/pnid'; async function PNIDMiddleware(request: express.Request, response: express.Response, next: express.NextFunction): Promise { @@ -23,7 +23,7 @@ async function PNIDMiddleware(request: express.Request, response: express.Respon if (type === 'Basic') { pnid = await getPNIDByBasicAuth(token); } else { - pnid = await getPNIDByBearerAuth(token); + pnid = await getPNIDByTokenAuth(token); } if (!pnid) { diff --git a/src/services/api/routes/v1/login.ts b/src/services/api/routes/v1/login.ts index d102ab6..03e47bb 100644 --- a/src/services/api/routes/v1/login.ts +++ b/src/services/api/routes/v1/login.ts @@ -1,6 +1,6 @@ import express from 'express'; import bcrypt from 'bcrypt'; -import { getPNIDByUsername, getPNIDByBearerAuth } from '@/database'; +import { getPNIDByUsername, getPNIDByTokenAuth } from '@/database'; import { nintendoPasswordHash, generateToken} from '@/util'; import { config } from '@/config-manager'; import { TokenOptions } from '@/types/common/token-options'; @@ -87,7 +87,7 @@ router.post('/', async (request: express.Request, response: express.Response): P return; } } else { - pnid = await getPNIDByBearerAuth(refreshToken); + pnid = await getPNIDByTokenAuth(refreshToken); if (!pnid) { response.status(400).json({ diff --git a/src/services/grpc/api/authentication-middleware.ts b/src/services/grpc/api/authentication-middleware.ts index c650e6d..d0a4405 100644 --- a/src/services/grpc/api/authentication-middleware.ts +++ b/src/services/grpc/api/authentication-middleware.ts @@ -1,5 +1,5 @@ import { Status, ServerMiddlewareCall, CallContext, ServerError } from 'nice-grpc'; -import { getPNIDByBearerAuth } from '@/database'; +import { getPNIDByTokenAuth } from '@/database'; import type { HydratedPNIDDocument } from '@/types/mongoose/pnid'; // * These paths require that a token be present @@ -30,7 +30,7 @@ export async function* authenticationMiddleware( let pnid: HydratedPNIDDocument | null = null; if (token) { - pnid = await getPNIDByBearerAuth(token); + pnid = await getPNIDByTokenAuth(token); } if (!pnid && TOKEN_REQUIRED_PATHS.includes(call.method.path)) { diff --git a/src/services/grpc/api/login.ts b/src/services/grpc/api/login.ts index 2069305..6ff7d3b 100644 --- a/src/services/grpc/api/login.ts +++ b/src/services/grpc/api/login.ts @@ -1,7 +1,7 @@ import { Status, ServerError } from 'nice-grpc'; import { LoginRequest, LoginResponse, DeepPartial } from 'pretendo-grpc-ts/dist/api/login_rpc'; import bcrypt from 'bcrypt'; -import { getPNIDByUsername, getPNIDByBearerAuth } from '@/database'; +import { getPNIDByUsername, getPNIDByTokenAuth } from '@/database'; import { nintendoPasswordHash, generateToken} from '@/util'; import { config } from '@/config-manager'; import type { TokenOptions } from '@/types/common/token-options'; @@ -44,7 +44,7 @@ export async function login(request: LoginRequest): Promise Date: Sat, 10 Jun 2023 20:08:31 -0400 Subject: [PATCH 103/219] Token access level is now a signed int to allow negative numbers --- src/services/api/index.ts | 2 -- src/services/nnid/index.ts | 2 +- src/util.ts | 4 ++-- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/services/api/index.ts b/src/services/api/index.ts index 18b7cde..b849f3e 100644 --- a/src/services/api/index.ts +++ b/src/services/api/index.ts @@ -1,5 +1,3 @@ -// handles "api.nintendo.cc" endpoints - import express from 'express'; import subdomain from 'express-subdomain'; import cors from 'cors'; diff --git a/src/services/nnid/index.ts b/src/services/nnid/index.ts index 8fbcf6b..726b95c 100644 --- a/src/services/nnid/index.ts +++ b/src/services/nnid/index.ts @@ -17,7 +17,7 @@ import provider from '@/services/nnid/routes/provider'; import support from '@/services/nnid/routes/support'; // Router to handle the subdomain restriction -const nnid = express.Router(); +const nnid: express.Router = express.Router(); LOG_INFO('[NNID] Importing middleware'); nnid.use(clientHeaderCheck); diff --git a/src/util.ts b/src/util.ts index 7767716..53ddf4b 100644 --- a/src/util.ts +++ b/src/util.ts @@ -70,7 +70,7 @@ export function generateToken(key: string, options: TokenOptions): Buffer | null ]); dataBuffer.writeBigUInt64LE(options.title_id, 0xE); - dataBuffer.writeUInt8(options.access_level, 0x16); + dataBuffer.writeInt8(options.access_level, 0x16); } const iv: Buffer = Buffer.alloc(16); @@ -133,7 +133,7 @@ export function unpackToken(token: Buffer): Token { if (unpacked.token_type !== 0x1 && unpacked.token_type !== 0x2) { unpacked.title_id = token.readBigUInt64LE(0xE); - unpacked.access_level = token.readUInt8(0x16); + unpacked.access_level = token.readInt8(0x16); } return unpacked; From b36c63a0b31519722cbf397173575f47cbefc45b Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Sun, 11 Jun 2023 07:46:27 -0400 Subject: [PATCH 104/219] Added better console tracking --- src/middleware/client-header.ts | 1 - src/middleware/console-status-verification.ts | 135 ++++++++++++++++++ src/models/device.ts | 22 +-- src/nintendo-certificate.ts | 15 +- src/services/nnid/routes/oauth.ts | 13 +- src/types/express.d.ts | 2 + src/types/mongoose/device.ts | 7 +- 7 files changed, 176 insertions(+), 19 deletions(-) create mode 100644 src/middleware/console-status-verification.ts diff --git a/src/middleware/client-header.ts b/src/middleware/client-header.ts index fc9c1e5..2288c80 100644 --- a/src/middleware/client-header.ts +++ b/src/middleware/client-header.ts @@ -9,7 +9,6 @@ const VALID_CLIENT_ID_SECRET_PAIRS: { [key: string]: string } = { 'ea25c66c26b403376b4c5ed94ab9cdea': 'd137be62cb6a2b831cad8c013b92fb55', // * Possibly 3DS exclusive? }; - function nintendoClientHeaderCheck(request: express.Request, response: express.Response, next: express.NextFunction): void { response.set('Content-Type', 'text/xml'); response.set('Server', 'Nintendo 3DS (http)'); diff --git a/src/middleware/console-status-verification.ts b/src/middleware/console-status-verification.ts new file mode 100644 index 0000000..a261952 --- /dev/null +++ b/src/middleware/console-status-verification.ts @@ -0,0 +1,135 @@ +import crypto from 'node:crypto'; +import express from 'express'; +import xmlbuilder from 'xmlbuilder'; +import { Device } from '@/models/device'; +import { getValueFromHeaders } from '@/util'; +import { HydratedDeviceDocument } from '@/types/mongoose/device'; + +async function consoleStatusVerificationMiddleware(request: express.Request, response: express.Response, next: express.NextFunction): Promise { + if (!request.certificate || !request.certificate.valid) { + // TODO - Change this to a different error + response.status(400).send(xmlbuilder.create({ + error: { + cause: 'Bad Request', + code: '1600', + message: 'Unable to process request' + } + }).end()); + + return; + } + + const deviceIDHeader: string | undefined = getValueFromHeaders(request.headers, 'x-nintendo-device-id'); + + if (!deviceIDHeader) { + // TODO - Change this to a different error + response.status(400).send(xmlbuilder.create({ + error: { + cause: 'Bad Request', + code: '1600', + message: 'Unable to process request' + } + }).end()); + + return; + } + + const deviceID: number = Number(deviceIDHeader); + + if (isNaN(deviceID)) { + // TODO - Change this to a different error + response.status(400).send(xmlbuilder.create({ + error: { + cause: 'Bad Request', + code: '1600', + message: 'Unable to process request' + } + }).end()); + + return; + } + + const serialNumber: string | undefined = getValueFromHeaders(request.headers, 'x-nintendo-serial-number'); + + if (!serialNumber) { + // TODO - Change this to a different error + response.status(400).send(xmlbuilder.create({ + error: { + cause: 'Bad Request', + code: '1600', + message: 'Unable to process request' + } + }).end()); + + return; + } + + // * This is kinda temp for now. Needs to be redone to handle linking this data to existing 3DS devices in the DB + // TODO - 3DS consoles are created in the NASC middleware. They need special handling to link them up with the data in the NNID API! + if (request.certificate.consoleType === 'wiiu') { + const certificateDeviceID: number = parseInt(request.certificate.certificateName.slice(2), 16); + + if (deviceID !== certificateDeviceID) { + // TODO - Change this to a different error + response.status(400).send(xmlbuilder.create({ + error: { + cause: 'Bad Request', + code: '1600', + message: 'Unable to process request' + } + }).end()); + + return; + } + + // * Only store a hash of the certificate in case of a breach + const certificateHash: string = crypto.createHash('sha256').update(request.certificate._certificate).digest('base64'); + + let device: HydratedDeviceDocument | null = await Device.findOne({ + certificate_hash: certificateHash, + }); + + if (!device) { + device = await Device.create({ + is_emulator: false, + model: 'wup', + device_id: deviceID, + serial: serialNumber, + linked_pids: [], + certificate_hash: certificateHash + }); + } + + if (device.serial !== serialNumber) { + // TODO - Change this to a different error + response.status(400).send(xmlbuilder.create({ + error: { + cause: 'Bad Request', + code: '1600', + message: 'Unable to process request' + } + }).end()); + + return; + } + + if (device.access_level < 0) { + response.status(400).send(xmlbuilder.create({ + errors: { + error: { + code: '0012', + message: 'Device has been banned by game server' // TODO - This is not the right error message + } + } + }).end()); + + return; + } + + request.device = device; + } + + return next(); +} + +export default consoleStatusVerificationMiddleware; \ No newline at end of file diff --git a/src/models/device.ts b/src/models/device.ts index 1866e7f..ee0dc57 100644 --- a/src/models/device.ts +++ b/src/models/device.ts @@ -16,13 +16,13 @@ export const DeviceSchema = new Schema({ model: { type: String, enum: [ - 'wup', // Nintendo Wii U - 'ctr', // Nintendo 3DS - 'spr', // Nintendo 3DS XL - 'ftr', // Nintendo 2DS - 'ktr', // New Nintendo 3DS - 'red', // New Nintendo 3DS XL - 'jan' // New Nintendo 2DS XL + 'wup', // * Nintendo Wii U + 'ctr', // * Nintendo 3DS + 'spr', // * Nintendo 3DS XL + 'ftr', // * Nintendo 2DS + 'ktr', // * New Nintendo 3DS + 'red', // * New Nintendo 3DS XL + 'jan' // * New Nintendo 2DS XL ] }, device_id: Number, @@ -33,10 +33,9 @@ export const DeviceSchema = new Schema({ token: String, account_id: Number, }, - // 3DS-specific stuff environment: String, - mac_hash: String, - fcdcert_hash: String, + mac_hash: String, // * 3DS-specific + fcdcert_hash: String, // * 3DS-specific linked_pids: [Number], access_level: { type: Number, @@ -45,7 +44,8 @@ export const DeviceSchema = new Schema({ server_access_level: { type: String, default: 'prod' // everyone is in production by default - } + }, + certificate_hash: String }); export const Device: DeviceModel = model('Device', DeviceSchema); \ No newline at end of file diff --git a/src/nintendo-certificate.ts b/src/nintendo-certificate.ts index a371c8f..ba15762 100644 --- a/src/nintendo-certificate.ts +++ b/src/nintendo-certificate.ts @@ -81,6 +81,7 @@ class NintendoCertificate { publicKey: Buffer; valid: boolean; publicKeyData: Buffer; + consoleType: string; constructor(certificate: string | Buffer) { this._certificate = Buffer.alloc(0); @@ -94,6 +95,7 @@ class NintendoCertificate { this.publicKey = Buffer.alloc(0); this.valid = false; this.publicKeyData = Buffer.alloc(0); + this.consoleType = ''; if (certificate) { if (certificate instanceof Buffer) { @@ -108,13 +110,14 @@ class NintendoCertificate { _parseCertificateData(): void { if (this._certificate.length === 0x110) { - // Assume fcdcert (3DS LFCS) + // * Assume fcdcert (3DS LFCS) + this.consoleType = '3ds'; this.signature = this._certificate.subarray(0x0, 0x100); this._certificateBody = this._certificate.subarray(0x100); this._verifySignatureLFCS(); } else { - // Assume regular certificate + // * Assume regular certificate this.signatureType = this._certificate.readUInt32BE(0x00); const signatureTypeSizes: SignatureSize = this._signatureTypeSizes(this.signatureType); @@ -128,6 +131,12 @@ class NintendoCertificate { this.ngKeyId = this._certificate.readUInt32BE(0x104); this.publicKeyData = this._certificate.subarray(0x108); + if (this.issuer === 'Root-CA00000003-MS00000012') { + this.consoleType = 'wiiu'; + } else { + this.consoleType = '3ds'; + } + this._verifySignature(); } } @@ -197,7 +206,7 @@ class NintendoCertificate { // from bytes to PEM! // https://github.com/Myriachan _verifySignatureECDSA(): void { - const pem: string = this.issuer == 'Root-CA00000003-MS00000012' ? WIIU_DEVICE_PUB_PEM : CTR_DEVICE_PUB_PEM; + const pem: string = this.consoleType === 'wiiu' ? WIIU_DEVICE_PUB_PEM : CTR_DEVICE_PUB_PEM; const key: crypto.VerifyPublicKeyInput = { key: pem, dsaEncoding: 'ieee-p1363' as crypto.DSAEncoding diff --git a/src/services/nnid/routes/oauth.ts b/src/services/nnid/routes/oauth.ts index aa93ea6..68f7082 100644 --- a/src/services/nnid/routes/oauth.ts +++ b/src/services/nnid/routes/oauth.ts @@ -1,6 +1,8 @@ import express from 'express'; import xmlbuilder from 'xmlbuilder'; import bcrypt from 'bcrypt'; +import deviceCertificateMiddleware from '@/middleware/device-certificate'; +import consoleStatusVerificationMiddleware from '@/middleware/console-status-verification'; import { getPNIDByUsername } from '@/database'; import { generateToken } from '@/util'; import { config } from '@/config-manager'; @@ -14,7 +16,7 @@ const router: express.Router = express.Router(); * Replacement for: https://account.nintendo.net/v1/api/oauth20/access_token/generate * Description: Generates an access token for a user */ -router.post('/access_token/generate', async (request: express.Request, response: express.Response): Promise => { +router.post('/access_token/generate', deviceCertificateMiddleware, consoleStatusVerificationMiddleware, async (request: express.Request, response: express.Response): Promise => { const grantType: string = request.body?.grant_type; const username: string = request.body?.user_id; const password: string = request.body?.password; @@ -68,6 +70,15 @@ router.post('/access_token/generate', async (request: express.Request, response: return; } + // * These are set/validated in consoleStatusVerificationMiddleware + // * They are always set, despite what Express might think + if (request.certificate?.consoleType === 'wiiu') { + if (!request.device?.linked_pids.includes(pnid.pid)) { + request.device?.linked_pids.push(pnid.pid); + await request.device?.save(); + } + } + if (pnid.access_level < 0) { response.status(400).send(xmlbuilder.create({ errors: { diff --git a/src/types/express.d.ts b/src/types/express.d.ts index 4a14525..d6980f3 100644 --- a/src/types/express.d.ts +++ b/src/types/express.d.ts @@ -1,6 +1,7 @@ import NintendoCertificate from '@/nintendo-certificate'; import { HydratedPNIDDocument } from '@/types/mongoose/pnid'; import { HydratedNEXAccountDocument } from '@/types/mongoose/nex-account'; +import { HydratedDeviceDocument } from '@/types/mongoose/device'; declare global { namespace Express { @@ -10,6 +11,7 @@ declare global { isCemu?: boolean; files?: Record; certificate?: NintendoCertificate; + device?: HydratedDeviceDocument; } } } \ No newline at end of file diff --git a/src/types/mongoose/device.ts b/src/types/mongoose/device.ts index a17b84e..6955f39 100644 --- a/src/types/mongoose/device.ts +++ b/src/types/mongoose/device.ts @@ -2,7 +2,7 @@ import { Model, Types, HydratedDocument } from 'mongoose'; import { IDeviceAttribute } from '@/types/mongoose/device-attribute'; type MODEL = 'wup' | 'ctr' | 'spr' | 'ftr' | 'ktr' | 'red' | 'jan'; -type ACCESS_LEVEL = 0 | 1 | 2 | 3; +type ACCESS_LEVEL = -1 | 0 | 1 | 2 | 3; type SERVER_ACCESS_LEVEL = 'prod' | 'test' | 'dev'; export interface IDevice { @@ -17,11 +17,12 @@ export interface IDevice { account_id: number; }; environment: string; - mac_hash: string; - fcdcert_hash: string; + mac_hash: string; // * 3DS-specific + fcdcert_hash: string; // * 3DS-specific linked_pids: number[]; access_level: ACCESS_LEVEL; server_access_level: SERVER_ACCESS_LEVEL; + certificate_hash: string; } export interface IDeviceMethods {} From 26e87b133c59725f5ed8a2afdb2ceb8ffc2219cd Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Sun, 11 Jun 2023 09:51:13 -0400 Subject: [PATCH 105/219] Improved PID tracking --- src/services/nnid/routes/oauth.ts | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/services/nnid/routes/oauth.ts b/src/services/nnid/routes/oauth.ts index 68f7082..9162d56 100644 --- a/src/services/nnid/routes/oauth.ts +++ b/src/services/nnid/routes/oauth.ts @@ -8,6 +8,7 @@ import { generateToken } from '@/util'; import { config } from '@/config-manager'; import { TokenOptions } from '@/types/common/token-options'; import { HydratedPNIDDocument } from '@/types/mongoose/pnid'; +import { Device } from '@/models/device'; const router: express.Router = express.Router(); @@ -70,13 +71,16 @@ router.post('/access_token/generate', deviceCertificateMiddleware, consoleStatus return; } - // * These are set/validated in consoleStatusVerificationMiddleware - // * They are always set, despite what Express might think - if (request.certificate?.consoleType === 'wiiu') { - if (!request.device?.linked_pids.includes(pnid.pid)) { - request.device?.linked_pids.push(pnid.pid); - await request.device?.save(); - } + // * This are set/validated in consoleStatusVerificationMiddleware + // * It is always set, despite what Express might think + if (request.device?.model === 'wup') { + await Device.updateOne({ + _id: request.device?._id + }, { + $addToSet: { + linked_pids: pnid.pid + } + }); } if (pnid.access_level < 0) { From 9c3a80be4aa5dafcfdd4a6a812413c6fd3c749a5 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Sun, 11 Jun 2023 21:07:39 -0400 Subject: [PATCH 106/219] Added support for comma separated headers --- src/util.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/util.ts b/src/util.ts index 53ddf4b..e3bb22e 100644 --- a/src/util.ts +++ b/src/util.ts @@ -274,11 +274,11 @@ export function getValueFromHeaders(headers: IncomingHttpHeaders, key: string): let value: string | undefined; if (header) { - if (Array.isArray(header)) { - header = header[0]; + if (!Array.isArray(header)) { + header = header.split(', '); } - value = header; + value = header[0]; } return value; From b69349f24820cdbbe1ef3655befabd4fa28ef9e6 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Mon, 12 Jun 2023 21:19:45 -0400 Subject: [PATCH 107/219] v1/api/people/@me/deletion is POST not PUT --- src/services/nnid/routes/people.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/nnid/routes/people.ts b/src/services/nnid/routes/people.ts index 4e17a94..e2f6d95 100644 --- a/src/services/nnid/routes/people.ts +++ b/src/services/nnid/routes/people.ts @@ -522,7 +522,7 @@ router.put('/@me/devices/@current/inactivate', async (request: express.Request, * Replacement for: https://account.nintendo.net/v1/api/people/@me/deletion * Description: Deletes a NNID */ -router.put('/@me/deletion', async (request: express.Request, response: express.Response): Promise => { +router.post('/@me/deletion', async (request: express.Request, response: express.Response): Promise => { const pnid: HydratedPNIDDocument | null = request.pnid; if (!pnid) { From dbaa154d47e6add964910b4b31282c054de9ddcd Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Tue, 13 Jun 2023 18:41:35 -0400 Subject: [PATCH 108/219] Added getNEXPassword gRPC method --- package-lock.json | 286 +++++++++--------- .../grpc/account/api-key-middleware.ts | 1 - src/services/grpc/account/get-nex-password.ts | 19 ++ src/services/grpc/account/implementation.ts | 2 + 4 files changed, 172 insertions(+), 136 deletions(-) create mode 100644 src/services/grpc/account/get-nex-password.ts diff --git a/package-lock.json b/package-lock.json index f1ab5ad..e0c0320 100644 --- a/package-lock.json +++ b/package-lock.json @@ -124,18 +124,18 @@ } }, "node_modules/@eslint/js": { - "version": "8.40.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.40.0.tgz", - "integrity": "sha512-ElyB54bJIhXQYVKjDSvCkPO1iU1tSAeVQJbllWJq1XQSmmA4dgFk8CbiBGpiOPxleE48vDogxCtmMYku4HSVLA==", + "version": "8.42.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.42.0.tgz", + "integrity": "sha512-6SWlXpWU5AvId8Ac7zjzmIOqMOba/JWY8XZ4A7q7Gn1Vlfg/SFFIlrtHXt9nPn4op9ZPAkl91Jao+QQv3r/ukw==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, "node_modules/@grpc/grpc-js": { - "version": "1.8.14", - "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.8.14.tgz", - "integrity": "sha512-w84maJ6CKl5aApCMzFll0hxtFNT6or9WwMslobKaqWUEf1K+zhlL43bSQhFreyYWIWR+Z0xnVFC1KtLm4ZpM/A==", + "version": "1.8.15", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.8.15.tgz", + "integrity": "sha512-H2Bu/w6+oQ58DsRbQol66ERBk3V5ZIak/z/MDx0T4EgDnJWps807I6BvTjq0v6UvZtOcLO+ur+Q9wvniqu3OJA==", "dependencies": { "@grpc/proto-loader": "^0.7.0", "@types/node": ">=12.12.47" @@ -182,9 +182,9 @@ "dev": true }, "node_modules/@humanwhocodes/config-array": { - "version": "0.11.8", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", - "integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==", + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.10.tgz", + "integrity": "sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==", "dev": true, "dependencies": { "@humanwhocodes/object-schema": "^1.2.1", @@ -447,9 +447,9 @@ } }, "node_modules/@redis/client": { - "version": "1.5.7", - "resolved": "https://registry.npmjs.org/@redis/client/-/client-1.5.7.tgz", - "integrity": "sha512-gaOBOuJPjK5fGtxSseaKgSvjiZXQCdLlGg9WYQst+/GRUjmXaiB5kVkeQMRtPc7Q2t93XZcJfBMSwzs/XS9UZw==", + "version": "1.5.8", + "resolved": "https://registry.npmjs.org/@redis/client/-/client-1.5.8.tgz", + "integrity": "sha512-xzElwHIO6rBAqzPeVnCzgvrnBEcFL1P0w8P65VNLRkdVW8rOE58f52hdj0BDgmsdOm4f1EoXPZtH4Fh7M/qUpw==", "dependencies": { "cluster-key-slot": "1.1.2", "generic-pool": "3.9.0", @@ -476,9 +476,9 @@ } }, "node_modules/@redis/search": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@redis/search/-/search-1.1.2.tgz", - "integrity": "sha512-/cMfstG/fOh/SsE+4/BQGeuH/JJloeWuH+qJzM8dbxuWvdWibWAOAHHCZTMPhV3xIlH4/cUEIA8OV5QnYpaVoA==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@redis/search/-/search-1.1.3.tgz", + "integrity": "sha512-4Dg1JjvCevdiCBTZqjhKkGoC5/BcB7k9j99kdMnaXFXg8x4eyOIVg9487CMv7/BUVkFLZCaIh8ead9mU15DNng==", "peerDependencies": { "@redis/client": "^1.0.0" } @@ -610,9 +610,9 @@ } }, "node_modules/@types/express-serve-static-core": { - "version": "4.17.34", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.34.tgz", - "integrity": "sha512-fvr49XlCGoUj2Pp730AItckfjat4WNb0lb3kfrLWffd+RLeoGAMsq7UOy04PAPtoL01uKwcp6u8nhzpgpDYr3w==", + "version": "4.17.35", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.35.tgz", + "integrity": "sha512-wALWQwrgiB2AWTT91CB62b6Yt0sNHpznUXeZEcnPU3DRdlDIz74x8Qg1UUYKSVFi+va5vKOLYRBI1bRKiLLKIg==", "dev": true, "dependencies": { "@types/node": "*", @@ -637,9 +637,9 @@ "integrity": "sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==" }, "node_modules/@types/json-schema": { - "version": "7.0.11", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", - "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", + "version": "7.0.12", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz", + "integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==", "dev": true }, "node_modules/@types/jsonfile": { @@ -686,9 +686,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "18.16.7", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.16.7.tgz", - "integrity": "sha512-MFg7ua/bRtnA1hYE3pVyWxGd/r7aMqjNOdHvlSsXV3n8iaeGKkOaPzpJh6/ovf4bEXWcojkeMJpTsq3mzXW4IQ==" + "version": "18.16.18", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.16.18.tgz", + "integrity": "sha512-/aNaQZD0+iSBAGnvvN2Cx92HqE5sZCPZtx2TsK+4nvV23fFe09jVDvpArXr2j9DnYlzuU9WuoykDDc6wqvpNcw==" }, "node_modules/@types/node-rsa": { "version": "1.1.1", @@ -700,9 +700,9 @@ } }, "node_modules/@types/nodemailer": { - "version": "6.4.7", - "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.7.tgz", - "integrity": "sha512-f5qCBGAn/f0qtRcd4SEn88c8Fp3Swct1731X4ryPKqS61/A3LmmzN8zaEz7hneJvpjFbUUgY7lru/B/7ODTazg==", + "version": "6.4.8", + "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.8.tgz", + "integrity": "sha512-oVsJSCkqViCn8/pEu2hfjwVO+Gb3e+eTWjg3PcjeFKRItfKpKwHphQqbYmPQrlMk+op7pNNWPbsJIEthpFN/OQ==", "dev": true, "dependencies": { "@types/node": "*" @@ -775,15 +775,15 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "5.59.5", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.59.5.tgz", - "integrity": "sha512-feA9xbVRWJZor+AnLNAr7A8JRWeZqHUf4T9tlP+TN04b05pFVhO5eN7/O93Y/1OUlLMHKbnJisgDURs/qvtqdg==", + "version": "5.59.11", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.59.11.tgz", + "integrity": "sha512-XxuOfTkCUiOSyBWIvHlUraLw/JT/6Io1365RO6ZuI88STKMavJZPNMU0lFcUTeQXEhHiv64CbxYxBNoDVSmghg==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.4.0", - "@typescript-eslint/scope-manager": "5.59.5", - "@typescript-eslint/type-utils": "5.59.5", - "@typescript-eslint/utils": "5.59.5", + "@typescript-eslint/scope-manager": "5.59.11", + "@typescript-eslint/type-utils": "5.59.11", + "@typescript-eslint/utils": "5.59.11", "debug": "^4.3.4", "grapheme-splitter": "^1.0.4", "ignore": "^5.2.0", @@ -809,14 +809,14 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "5.59.5", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.59.5.tgz", - "integrity": "sha512-NJXQC4MRnF9N9yWqQE2/KLRSOLvrrlZb48NGVfBa+RuPMN6B7ZcK5jZOvhuygv4D64fRKnZI4L4p8+M+rfeQuw==", + "version": "5.59.11", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.59.11.tgz", + "integrity": "sha512-s9ZF3M+Nym6CAZEkJJeO2TFHHDsKAM3ecNkLuH4i4s8/RCPnF5JRip2GyviYkeEAcwGMJxkqG9h2dAsnA1nZpA==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "5.59.5", - "@typescript-eslint/types": "5.59.5", - "@typescript-eslint/typescript-estree": "5.59.5", + "@typescript-eslint/scope-manager": "5.59.11", + "@typescript-eslint/types": "5.59.11", + "@typescript-eslint/typescript-estree": "5.59.11", "debug": "^4.3.4" }, "engines": { @@ -836,13 +836,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "5.59.5", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.59.5.tgz", - "integrity": "sha512-jVecWwnkX6ZgutF+DovbBJirZcAxgxC0EOHYt/niMROf8p4PwxxG32Qdhj/iIQQIuOflLjNkxoXyArkcIP7C3A==", + "version": "5.59.11", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.59.11.tgz", + "integrity": "sha512-dHFOsxoLFtrIcSj5h0QoBT/89hxQONwmn3FOQ0GOQcLOOXm+MIrS8zEAhs4tWl5MraxCY3ZJpaXQQdFMc2Tu+Q==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.59.5", - "@typescript-eslint/visitor-keys": "5.59.5" + "@typescript-eslint/types": "5.59.11", + "@typescript-eslint/visitor-keys": "5.59.11" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -853,13 +853,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "5.59.5", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.59.5.tgz", - "integrity": "sha512-4eyhS7oGym67/pSxA2mmNq7X164oqDYNnZCUayBwJZIRVvKpBCMBzFnFxjeoDeShjtO6RQBHBuwybuX3POnDqg==", + "version": "5.59.11", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.59.11.tgz", + "integrity": "sha512-LZqVY8hMiVRF2a7/swmkStMYSoXMFlzL6sXV6U/2gL5cwnLWQgLEG8tjWPpaE4rMIdZ6VKWwcffPlo1jPfk43g==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "5.59.5", - "@typescript-eslint/utils": "5.59.5", + "@typescript-eslint/typescript-estree": "5.59.11", + "@typescript-eslint/utils": "5.59.11", "debug": "^4.3.4", "tsutils": "^3.21.0" }, @@ -880,9 +880,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "5.59.5", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.59.5.tgz", - "integrity": "sha512-xkfRPHbqSH4Ggx4eHRIO/eGL8XL4Ysb4woL8c87YuAo8Md7AUjyWKa9YMwTL519SyDPrfEgKdewjkxNCVeJW7w==", + "version": "5.59.11", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.59.11.tgz", + "integrity": "sha512-epoN6R6tkvBYSc+cllrz+c2sOFWkbisJZWkOE+y3xHtvYaOE6Wk6B8e114McRJwFRjGvYdJwLXQH5c9osME/AA==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -893,13 +893,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "5.59.5", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.5.tgz", - "integrity": "sha512-+XXdLN2CZLZcD/mO7mQtJMvCkzRfmODbeSKuMY/yXbGkzvA9rJyDY5qDYNoiz2kP/dmyAxXquL2BvLQLJFPQIg==", + "version": "5.59.11", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.11.tgz", + "integrity": "sha512-YupOpot5hJO0maupJXixi6l5ETdrITxeo5eBOeuV7RSKgYdU3G5cxO49/9WRnJq9EMrB7AuTSLH/bqOsXi7wPA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.59.5", - "@typescript-eslint/visitor-keys": "5.59.5", + "@typescript-eslint/types": "5.59.11", + "@typescript-eslint/visitor-keys": "5.59.11", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -920,17 +920,17 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "5.59.5", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.59.5.tgz", - "integrity": "sha512-sCEHOiw+RbyTii9c3/qN74hYDPNORb8yWCoPLmB7BIflhplJ65u2PBpdRla12e3SSTJ2erRkPjz7ngLHhUegxA==", + "version": "5.59.11", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.59.11.tgz", + "integrity": "sha512-didu2rHSOMUdJThLk4aZ1Or8IcO3HzCw/ZvEjTTIfjIrcdd5cvSIwwDy2AOlE7htSNp7QIZ10fLMyRCveesMLg==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@types/json-schema": "^7.0.9", "@types/semver": "^7.3.12", - "@typescript-eslint/scope-manager": "5.59.5", - "@typescript-eslint/types": "5.59.5", - "@typescript-eslint/typescript-estree": "5.59.5", + "@typescript-eslint/scope-manager": "5.59.11", + "@typescript-eslint/types": "5.59.11", + "@typescript-eslint/typescript-estree": "5.59.11", "eslint-scope": "^5.1.1", "semver": "^7.3.7" }, @@ -946,12 +946,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "5.59.5", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.5.tgz", - "integrity": "sha512-qL+Oz+dbeBRTeyJTIy0eniD3uvqU7x+y1QceBismZ41hd4aBSRh8UAw4pZP0+XzLuPZmx4raNMq/I+59W2lXKA==", + "version": "5.59.11", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.11.tgz", + "integrity": "sha512-KGYniTGG3AMTuKF9QBD7EIrvufkB6O6uX3knP73xbKLMpH+QRPcgnCxjWXSHjMRuOxFLovljqQgQpR0c7GvjoA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.59.5", + "@typescript-eslint/types": "5.59.11", "eslint-visitor-keys": "^3.3.0" }, "engines": { @@ -968,9 +968,9 @@ "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" }, "node_modules/abort-controller-x": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/abort-controller-x/-/abort-controller-x-0.4.1.tgz", - "integrity": "sha512-lJ2ssrl3FoTK3cX/g15lRCkXFWKiwRTRtBjfwounO2EM/Q65rI/MEZsfsch1juWU2pH2aLSaq0HGowlDP/imrw==" + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/abort-controller-x/-/abort-controller-x-0.4.2.tgz", + "integrity": "sha512-5bgqvgogTIxTkQpkgDoFZHt2YS8uireyOkC4snQG2kaEKs7DI7Tgsu5xysjp9MxG+6OLICBOAL8TtKHzxQrlqw==" }, "node_modules/accepts": { "version": "1.3.8", @@ -1175,10 +1175,9 @@ } }, "node_modules/async": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.3.tgz", - "integrity": "sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g==", - "dev": true + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", + "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==" }, "node_modules/asynckit": { "version": "0.4.0", @@ -1202,9 +1201,9 @@ } }, "node_modules/aws-sdk": { - "version": "2.1376.0", - "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1376.0.tgz", - "integrity": "sha512-ja/Xnft8BDcDEz786VJFPrWpuWpOgsA+QzBAwzsjYeIolQ/vEs/bbXkoS085fOoeAPEhYWQh9wog7cVvrQPJFQ==", + "version": "2.1397.0", + "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1397.0.tgz", + "integrity": "sha512-Km+jUscV6vW3vuurSsGrTjEqaNfrE9ykA3IJmJb85V2z5tklJvoBU+7JcCiF6h3BDKegNjiFOM7uoL3cGVGz4g==", "dependencies": { "buffer": "4.9.2", "events": "1.1.1", @@ -1493,9 +1492,9 @@ } }, "node_modules/cacheable-request": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.2.tgz", - "integrity": "sha512-pouW8/FmiPQbuGpkXQ9BAPv/Mo5xDGANgSNXzTzJ8DrKGuXOssM4wIQRjfanNRh3Yu5cfYPvcorqbhg2KIJtew==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.4.tgz", + "integrity": "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==", "dependencies": { "clone-response": "^1.0.2", "get-stream": "^5.1.0", @@ -1942,11 +1941,14 @@ } }, "node_modules/dotenv": { - "version": "16.0.3", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz", - "integrity": "sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==", + "version": "16.1.4", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.1.4.tgz", + "integrity": "sha512-m55RtE8AsPeJBpOIFKihEmqUcoVncQIwo7x9U8ZwLEZw9ZpXboz2c+rvog+jUaJvVrZ5kBOeYQBX5+8Aa/OZQw==", "engines": { "node": ">=12" + }, + "funding": { + "url": "https://github.com/motdotla/dotenv?sponsor=1" } }, "node_modules/dtype": { @@ -2212,16 +2214,16 @@ } }, "node_modules/eslint": { - "version": "8.40.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.40.0.tgz", - "integrity": "sha512-bvR+TsP9EHL3TqNtj9sCNJVAFK3fBN8Q7g5waghxyRsPLIMwL73XSKnZFK0hk/O2ANC+iAoq6PWMQ+IfBAJIiQ==", + "version": "8.42.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.42.0.tgz", + "integrity": "sha512-ulg9Ms6E1WPf67PHaEY4/6E2tEn5/f7FXGzr3t9cBMugOmf1INYvuUwwh1aXQN4MfJ6a5K2iNwP3w4AColvI9A==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.4.0", "@eslint/eslintrc": "^2.0.3", - "@eslint/js": "8.40.0", - "@humanwhocodes/config-array": "^0.11.8", + "@eslint/js": "8.42.0", + "@humanwhocodes/config-array": "^0.11.10", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", "ajv": "^6.10.0", @@ -2240,13 +2242,12 @@ "find-up": "^5.0.0", "glob-parent": "^6.0.2", "globals": "^13.19.0", - "grapheme-splitter": "^1.0.4", + "graphemer": "^1.4.0", "ignore": "^5.2.0", "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "is-path-inside": "^3.0.3", - "js-sdsl": "^4.1.4", "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", @@ -2833,12 +2834,13 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz", - "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", + "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", "dependencies": { "function-bind": "^1.1.1", "has": "^1.0.3", + "has-proto": "^1.0.1", "has-symbols": "^1.0.3" }, "funding": { @@ -2979,6 +2981,12 @@ "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", "dev": true }, + "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/har-schema": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", @@ -3020,6 +3028,17 @@ "node": ">=8" } }, + "node_modules/has-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/has-symbols": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", @@ -3310,9 +3329,9 @@ } }, "node_modules/is-core-module": { - "version": "2.12.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.0.tgz", - "integrity": "sha512-RECHCBCd/viahWmwj6enj19sKbHfJrddi/6cBDsNTKbNq0f7VeaUkBo60BqzvPqo/W54ChS62Z5qyun7cfOMqQ==", + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.1.tgz", + "integrity": "sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==", "dependencies": { "has": "^1.0.3" }, @@ -3463,16 +3482,6 @@ "resolved": "https://registry.npmjs.org/jpeg-js/-/jpeg-js-0.3.7.tgz", "integrity": "sha512-9IXdWudL61npZjvLuVe/ktHiA41iE8qFyLB+4VDTblEsWBzeg8WQTlktdUK4CdncUqtUgUg0bbOmTE2bKBKaBQ==" }, - "node_modules/js-sdsl": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.4.0.tgz", - "integrity": "sha512-FfVSdx6pJ41Oa+CF7RDaFmTnCaFhua+SNYQX74riGOpl96x+2jQCqEfQ2bnXu/5DPCqlRuiqyvTJM0Qjz26IVg==", - "dev": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/js-sdsl" - } - }, "node_modules/js-yaml": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", @@ -3843,11 +3852,11 @@ } }, "node_modules/mongodb": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-5.3.0.tgz", - "integrity": "sha512-Wy/sbahguL8c3TXQWXmuBabiLD+iVmz+tOgQf+FwkCjhUIorqbAxRbbz00g4ZoN4sXIPwpAlTANMaGRjGGTikQ==", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-5.5.0.tgz", + "integrity": "sha512-XgrkUgAAdfnZKQfk5AsYL8j7O99WHd4YXPxYxnh8dZxD+ekYWFRA3JktUsBnfg+455Smf75/+asoU/YLwNGoQQ==", "dependencies": { - "bson": "^5.2.0", + "bson": "^5.3.0", "mongodb-connection-string-url": "^2.6.0", "socks": "^2.7.1" }, @@ -3884,20 +3893,20 @@ } }, "node_modules/mongoose": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-7.1.1.tgz", - "integrity": "sha512-AIxaWwGY+td7QOMk4NgK6fbRuGovFyDzv65nU1uj1DsUh3lpjfP3iFYHSR+sUKrs7nbp19ksLlRXkmInBteSCA==", + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-7.2.4.tgz", + "integrity": "sha512-BWcgShV2WH1rspICiJKLPi7QssTebpGJ23Nyk7qG0TMEE/OEAlsQKEhI7VlrXg4ZnoOcHgG+N+upW9tj17TTQg==", "dependencies": { - "bson": "^5.2.0", + "bson": "^5.3.0", "kareem": "2.5.1", - "mongodb": "5.3.0", + "mongodb": "5.5.0", "mpath": "0.9.0", "mquery": "5.0.0", "ms": "2.1.3", "sift": "16.0.1" }, "engines": { - "node": ">=14.0.0" + "node": ">=14.20.1" }, "funding": { "type": "opencollective", @@ -4107,9 +4116,9 @@ } }, "node_modules/nodemailer": { - "version": "6.9.2", - "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.2.tgz", - "integrity": "sha512-4+TYaa/e1nIxQfyw/WzNPYTEZ5OvHIDEnmjs4LPmIfccPQN+2CYKmGHjWixn/chzD3bmUTu5FMfpltizMxqzdg==", + "version": "6.9.3", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.3.tgz", + "integrity": "sha512-fy9v3NgTzBngrMFkDsKEj0r02U7jm6XfC3b52eoNV+GCrGj+s8pt5OqhiJdWKuw51zCTdiNR/IUD1z33LIIGpg==", "engines": { "node": ">=6.0.0" } @@ -4374,11 +4383,12 @@ } }, "node_modules/pretendo-grpc-ts": { - "version": "1.0.0", - "resolved": "git+ssh://git@github.com/PretendoNetwork/grpc-ts.git#b7bcb0d691cec3f88ff0627cb7431c14dd2d2d1d", + "version": "1.0.1", + "resolved": "git+ssh://git@github.com/PretendoNetwork/grpc-ts.git#64c83180d1f2d0829fd8d5e0e6305c6c019b1f03", "hasInstallScript": true, "license": "ISC", "dependencies": { + "async": "^3.2.4", "long": "^5.2.1", "protobufjs": "^7.2.3" } @@ -4414,6 +4424,12 @@ "node": ">= 6.0.0" } }, + "node_modules/prompt/node_modules/async": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.3.tgz", + "integrity": "sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g==", + "dev": true + }, "node_modules/protobufjs": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.2.3.tgz", @@ -4589,15 +4605,15 @@ } }, "node_modules/redis": { - "version": "4.6.6", - "resolved": "https://registry.npmjs.org/redis/-/redis-4.6.6.tgz", - "integrity": "sha512-aLs2fuBFV/VJ28oLBqYykfnhGGkFxvx0HdCEBYdJ99FFbSEMZ7c1nVKwR6ZRv+7bb7JnC0mmCzaqu8frgOYhpA==", + "version": "4.6.7", + "resolved": "https://registry.npmjs.org/redis/-/redis-4.6.7.tgz", + "integrity": "sha512-KrkuNJNpCwRm5vFJh0tteMxW8SaUzkm5fBH7eL5hd/D0fAkzvapxbfGPP/r+4JAXdQuX7nebsBkBqA2RHB7Usw==", "dependencies": { "@redis/bloom": "1.2.0", - "@redis/client": "1.5.7", + "@redis/client": "1.5.8", "@redis/graph": "1.1.0", "@redis/json": "1.0.4", - "@redis/search": "1.1.2", + "@redis/search": "1.1.3", "@redis/time-series": "1.0.4" } }, @@ -4821,9 +4837,9 @@ } }, "node_modules/semver": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.0.tgz", - "integrity": "sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==", + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz", + "integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==", "dependencies": { "lru-cache": "^6.0.0" }, @@ -5169,9 +5185,9 @@ } }, "node_modules/stripe": { - "version": "12.4.0", - "resolved": "https://registry.npmjs.org/stripe/-/stripe-12.4.0.tgz", - "integrity": "sha512-QjZRzKi3wf8TsuJf/fd6/ejfPgwNptDIzFogRWaRzP3oMJnSD73I2YxR0Eje5zfrU8FmddYWZYawoUejqN+o1w==", + "version": "12.9.0", + "resolved": "https://registry.npmjs.org/stripe/-/stripe-12.9.0.tgz", + "integrity": "sha512-stYtrWetRYUsEbsUVyJaPG9Sppt0ds2szBqXsuDG6KZPPuUmCccbpceLrhoOBwNl1RziEfNB7oG9wg1n2eW+EQ==", "dependencies": { "@types/node": ">=8.1.0", "qs": "^6.11.0" @@ -5204,9 +5220,9 @@ } }, "node_modules/tar": { - "version": "6.1.14", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.14.tgz", - "integrity": "sha512-piERznXu0U7/pW7cdSn7hjqySIVTYT6F76icmFk7ptU7dDYlXTm5r9A6K04R2vU3olYgoKeo1Cg3eeu5nhftAw==", + "version": "6.1.15", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.15.tgz", + "integrity": "sha512-/zKt9UyngnxIT/EAGYuxaMYgOIJiP81ab9ZfkILq4oNLPFX50qyYmu7jRj9qeXoxmJHjGlbH0+cm2uy1WCs10A==", "dependencies": { "chownr": "^2.0.0", "fs-minipass": "^2.0.0", diff --git a/src/services/grpc/account/api-key-middleware.ts b/src/services/grpc/account/api-key-middleware.ts index 63e03c0..ee42a13 100644 --- a/src/services/grpc/account/api-key-middleware.ts +++ b/src/services/grpc/account/api-key-middleware.ts @@ -5,7 +5,6 @@ export async function* apiKeyMiddleware( call: ServerMiddlewareCall, context: CallContext, ): AsyncGenerator { - console.log(call.method); const apiKey: string | undefined = context.metadata.get('X-API-Key'); if (!apiKey || apiKey !== config.grpc.master_api_keys.account) { diff --git a/src/services/grpc/account/get-nex-password.ts b/src/services/grpc/account/get-nex-password.ts new file mode 100644 index 0000000..49102c9 --- /dev/null +++ b/src/services/grpc/account/get-nex-password.ts @@ -0,0 +1,19 @@ +import { Status, ServerError } from 'nice-grpc'; +import {GetNEXPasswordRequest,GetNEXPasswordResponse, DeepPartial } from 'pretendo-grpc-ts/dist/account/get_nex_password_rpc'; +import { NEXAccount } from '@/models/nex-account'; +import { HydratedNEXAccountDocument } from '@/types/mongoose/nex-account'; + +export async function getNEXPassword(request: GetNEXPasswordRequest): Promise> { + const nexAccount: HydratedNEXAccountDocument | null = await NEXAccount.findOne({ pid: request.pid }); + + if (!nexAccount) { + throw new ServerError( + Status.INVALID_ARGUMENT, + 'No NEX account found found', + ); + } + + return { + password: nexAccount.password + }; +} \ No newline at end of file diff --git a/src/services/grpc/account/implementation.ts b/src/services/grpc/account/implementation.ts index 266e157..d6290d0 100644 --- a/src/services/grpc/account/implementation.ts +++ b/src/services/grpc/account/implementation.ts @@ -1,6 +1,8 @@ import { AccountServiceImplementation } from 'pretendo-grpc-ts/dist/account/account_service'; import { getUserData } from '@/services/grpc/account/get-user-data'; +import { getNEXPassword } from '@/services/grpc/account/get-nex-password'; export const accountServiceImplementation: AccountServiceImplementation = { getUserData, + getNEXPassword }; \ No newline at end of file From 4051a3107f0a898943beff13ede78b10b3f9c97d Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Tue, 13 Jun 2023 19:01:23 -0400 Subject: [PATCH 109/219] Implemented https://account.nintendo.net/v1/api/admin/time --- src/services/nnid/routes/admin.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/services/nnid/routes/admin.ts b/src/services/nnid/routes/admin.ts index 87dbd40..3476e6b 100644 --- a/src/services/nnid/routes/admin.ts +++ b/src/services/nnid/routes/admin.ts @@ -105,4 +105,18 @@ router.get('/mapped_ids', async (request: express.Request, response: express.Res }).end()); }); +/** + * [GET] + * Replacement for: https://account.nintendo.net/v1/api/admin/time + * Description: Gets the current server time + */ +router.get('/time', async (request: express.Request, response: express.Response): Promise => { + response.set('X-Nintendo-Date', Date.now().toString()); + response.set('Server', 'Nintendo 3DS (http)'); + response.set('Date', new Date().toUTCString()); + + response.send(''); +}); + + export default router; \ No newline at end of file From cb89fb925b62296c1f1922481b9fa02023ce1f5b Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Tue, 13 Jun 2023 23:21:01 -0400 Subject: [PATCH 110/219] Removed is_emulator field from device document schema --- src/middleware/console-status-verification.ts | 1 - src/middleware/nasc.ts | 1 - src/models/device.ts | 4 ---- src/types/mongoose/device.ts | 1 - 4 files changed, 7 deletions(-) diff --git a/src/middleware/console-status-verification.ts b/src/middleware/console-status-verification.ts index a261952..af01446 100644 --- a/src/middleware/console-status-verification.ts +++ b/src/middleware/console-status-verification.ts @@ -91,7 +91,6 @@ async function consoleStatusVerificationMiddleware(request: express.Request, res if (!device) { device = await Device.create({ - is_emulator: false, model: 'wup', device_id: deviceID, serial: serialNumber, diff --git a/src/middleware/nasc.ts b/src/middleware/nasc.ts index 18e8f62..7a79e85 100644 --- a/src/middleware/nasc.ts +++ b/src/middleware/nasc.ts @@ -153,7 +153,6 @@ async function NASCMiddleware(request: express.Request, response: express.Respon if (!device) { device = new Device({ - is_emulator: false, model, serial: serialNumber, environment, diff --git a/src/models/device.ts b/src/models/device.ts index ee0dc57..cf4ff10 100644 --- a/src/models/device.ts +++ b/src/models/device.ts @@ -9,10 +9,6 @@ const DeviceAttributeSchema = new Schema({ - is_emulator: { - type: Boolean, - default: false - }, model: { type: String, enum: [ diff --git a/src/types/mongoose/device.ts b/src/types/mongoose/device.ts index 6955f39..dfc9d89 100644 --- a/src/types/mongoose/device.ts +++ b/src/types/mongoose/device.ts @@ -6,7 +6,6 @@ type ACCESS_LEVEL = -1 | 0 | 1 | 2 | 3; type SERVER_ACCESS_LEVEL = 'prod' | 'test' | 'dev'; export interface IDevice { - is_emulator: boolean; model: MODEL; device_id: number; device_type: number; From 08e0d9c43673f3bc29c2f72821bee93724da285b Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Wed, 14 Jun 2023 11:54:57 -0400 Subject: [PATCH 111/219] Invalid password error is wrapped in errors tag --- src/services/nnid/routes/oauth.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/services/nnid/routes/oauth.ts b/src/services/nnid/routes/oauth.ts index 9162d56..c9a345d 100644 --- a/src/services/nnid/routes/oauth.ts +++ b/src/services/nnid/routes/oauth.ts @@ -62,9 +62,11 @@ router.post('/access_token/generate', deviceCertificateMiddleware, consoleStatus if (!pnid || !await bcrypt.compare(password, pnid.password)) { response.status(400).send(xmlbuilder.create({ - error: { - code: '0106', - message: 'Invalid account ID or password' + errors: { + error: { + code: '0106', + message: 'Invalid account ID or password' + } } }).end({ pretty: true })); From 976c1bf21f5329adf5b43f069ecbd85a3cc39a45 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Wed, 14 Jun 2023 12:58:09 -0400 Subject: [PATCH 112/219] NNID service tokens now check client_id for generation --- src/database.ts | 16 ++++++++++++---- src/models/server.ts | 1 + src/services/nnid/routes/provider.ts | 22 +++++++++++++++++++--- src/types/mongoose/server.ts | 1 + 4 files changed, 33 insertions(+), 7 deletions(-) diff --git a/src/database.ts b/src/database.ts index 84d6024..a301b92 100644 --- a/src/database.ts +++ b/src/database.ts @@ -208,20 +208,28 @@ export async function getPNIDProfileJSONByPID(pid: number): Promise { +export async function getServerByGameServerID(gameServerID: string, accessMode: string): Promise { return await Server.findOne({ - game_server_id: gameServerId, + game_server_id: gameServerID, access_mode: accessMode }); } -export async function getServerByTitleId(titleId: string, accessMode: string): Promise { +export async function getServerByTitleID(titleID: string, accessMode: string): Promise { return await Server.findOne({ - title_ids: titleId, + title_ids: titleID, access_mode: accessMode }); } +export async function getServerByClientID(clientID: string, accessMode: string): Promise { + return await Server.findOne({ + client_id: clientID, + access_mode: accessMode + }); +} + + export async function addPNIDConnection(pnid: HydratedPNIDDocument, data: ConnectionData, type: string): Promise { if (type === 'discord') { return await addPNIDConnectionDiscord(pnid, data); diff --git a/src/models/server.ts b/src/models/server.ts index c953323..afdd9af 100644 --- a/src/models/server.ts +++ b/src/models/server.ts @@ -3,6 +3,7 @@ import uniqueValidator from 'mongoose-unique-validator'; import { IServer, IServerMethods, ServerModel } from '@/types/mongoose/server'; const ServerSchema = new Schema({ + client_id: String, ip: String, port: Number, service_name: String, diff --git a/src/services/nnid/routes/provider.ts b/src/services/nnid/routes/provider.ts index 6756221..6b7f69f 100644 --- a/src/services/nnid/routes/provider.ts +++ b/src/services/nnid/routes/provider.ts @@ -1,6 +1,6 @@ import express from 'express'; import xmlbuilder from 'xmlbuilder'; -import { getServerByTitleId, getServerByGameServerId } from '@/database'; +import { getServerByClientID, getServerByGameServerID } from '@/database'; import { generateToken, getValueFromHeaders, getValueFromQueryString } from '@/util'; import { NEXAccount } from '@/models/nex-account'; import { TokenOptions } from '@/types/common/token-options'; @@ -32,6 +32,22 @@ router.get('/service_token/@me', async (request: express.Request, response: expr return; } + const clientID: string | undefined = getValueFromQueryString(request.query, 'client_id'); + + if (!clientID) { + // TODO - Research this error more + response.send(xmlbuilder.create({ + errors: { + error: { + code: '1021', + message: 'The requested game server was not found' + } + } + }).end()); + + return; + } + const titleID: string | undefined = getValueFromHeaders(request.headers, 'x-nintendo-title-id'); if (!titleID) { @@ -49,7 +65,7 @@ router.get('/service_token/@me', async (request: express.Request, response: expr } const serverAccessLevel: string = pnid.server_access_level; - const server: HydratedServerDocument | null = await getServerByTitleId(titleID, serverAccessLevel); + const server: HydratedServerDocument | null = await getServerByClientID(clientID, serverAccessLevel); if (!server || !server.aes_key) { response.send(xmlbuilder.create({ @@ -156,7 +172,7 @@ router.get('/nex_token/@me', async (request: express.Request, response: express. } const serverAccessLevel: string = pnid.server_access_level; - const server: HydratedServerDocument | null = await getServerByGameServerId(gameServerID, serverAccessLevel); + const server: HydratedServerDocument | null = await getServerByGameServerID(gameServerID, serverAccessLevel); if (!server || !server.aes_key) { response.send(xmlbuilder.create({ diff --git a/src/types/mongoose/server.ts b/src/types/mongoose/server.ts index 02fc372..a7c8f6e 100644 --- a/src/types/mongoose/server.ts +++ b/src/types/mongoose/server.ts @@ -1,6 +1,7 @@ import { Model, HydratedDocument } from 'mongoose'; export interface IServer { + client_id: string; ip: string; port: number; service_name: string; From ca20208041055f518bc5f3c4a2a09df211ea233e Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Wed, 14 Jun 2023 13:02:42 -0400 Subject: [PATCH 113/219] Fixed PNID scrubbing not saving default Mii data --- src/models/pnid.ts | 62 ++++++++++++++++++---------------------------- 1 file changed, 24 insertions(+), 38 deletions(-) diff --git a/src/models/pnid.ts b/src/models/pnid.ts index 758fb5f..36b1830 100644 --- a/src/models/pnid.ts +++ b/src/models/pnid.ts @@ -241,7 +241,7 @@ PNIDSchema.method('scrub', async function scrub() { await this.updateMii({ name: 'Default', - primary: 'N', + primary: 'Y', data: 'AwAAQOlVognnx0GC2/uogAOzuI0n2QAAAEBEAGUAZgBhAHUAbAB0AAAAAAAAAEBAAAAhAQJoRBgmNEYUgRIXaA0AACkAUkhQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGm9' }); @@ -251,44 +251,30 @@ PNIDSchema.method('scrub', async function scrub() { this.gender = ''; this.country = ''; this.language = ''; - this.email = { - address: '', - primary: false, - parent: false, - reachable: false, - validated: false, - validated_date: '', - id: 0 - }; + this.email.address = ''; + this.email.primary = false; + this.email.parent = false; + this.email.reachable = false; + this.email.validated = false; + this.email.validated_date = ''; + this.email.id = 0; this.region = 0; - this.timezone = { - name: '', - offset: 0 - }; - this.mii = { - id: 0, - hash: '', - image_url: '', - image_id: 0 - }; - this.flags = { - active: false, - marketing: false, - off_device: false - }; - this.connections = { - discord: { - id: '' - }, - stripe: { - customer_id: '', - subscription_id: '', - price_id: '', - tier_level: 0, - tier_name: '', - latest_webhook_timestamp: 0 - } - }; + this.timezone.name = ''; + this.timezone.offset = 0; + this.mii.id = 0; + this.mii.hash = ''; + this.mii.image_url = ''; + this.mii.image_id = 0; + this.flags.active = false; + this.flags.marketing = false; + this.flags.off_device = false; + this.connections.discord.id = ''; + this.connections.stripe.customer_id = ''; + this.connections.stripe.subscription_id = ''; + this.connections.stripe.price_id = ''; + this.connections.stripe.tier_level = ''; + this.connections.stripe.tier_name = ''; + this.connections.stripe.latest_webhook_timestamp = ''; }); export const PNID: PNIDModel = model('PNID', PNIDSchema); \ No newline at end of file From e408189f77e4369633187727018ef455abb00064 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Wed, 14 Jun 2023 13:12:12 -0400 Subject: [PATCH 114/219] Fixed NASC import name change --- src/services/nasc/routes/ac.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/services/nasc/routes/ac.ts b/src/services/nasc/routes/ac.ts index 326ab4f..e556434 100644 --- a/src/services/nasc/routes/ac.ts +++ b/src/services/nasc/routes/ac.ts @@ -1,6 +1,6 @@ import express from 'express'; import { nintendoBase64Encode, nintendoBase64Decode, nascError, generateToken } from '@/util'; -import { getServerByTitleId } from '@/database'; +import { getServerByTitleID } from '@/database'; import { TokenOptions } from '@/types/common/token-options'; import { NASCRequestParams } from '@/types/services/nasc/request-params'; import { HydratedNEXAccountDocument } from '@/types/mongoose/nex-account'; @@ -33,7 +33,7 @@ router.post('/', async (request: express.Request, response: express.Response): P serverAccessLevel = nexAccount.server_access_level; } - const server: HydratedServerDocument | null = await getServerByTitleId(titleID, serverAccessLevel); + const server: HydratedServerDocument | null = await getServerByTitleID(titleID, serverAccessLevel); if (!server || !server.aes_key) { response.status(200).send(nascError('110').toString()); From 70a00bb1393771718e50b092b49ef7c2a657a6fc Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Thu, 15 Jun 2023 14:55:13 -0400 Subject: [PATCH 115/219] Removed useless unique fields on PNID model --- src/models/pnid.ts | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/src/models/pnid.ts b/src/models/pnid.ts index 36b1830..f8f19cf 100644 --- a/src/models/pnid.ts +++ b/src/models/pnid.ts @@ -62,10 +62,7 @@ const PNIDSchema = new Schema({ reachable: Boolean, validated: Boolean, validated_date: String, - id: { - type: Number, - unique: true - } + id: Number }, region: Number, timezone: { @@ -76,19 +73,10 @@ const PNIDSchema = new Schema({ name: String, primary: Boolean, data: String, - id: { - type: Number, - unique: true - }, - hash: { - type: String, - unique: true - }, + id: Number, + hash: String, image_url: String, - image_id: { - type: Number, - unique: true - }, + image_id: Number, }, flags: { active: Boolean, From 664562f344cb105f46cc4c87a075f15e71a500f6 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Wed, 5 Jul 2023 16:30:26 -0400 Subject: [PATCH 116/219] Update Stripe checks in PNID scrub method --- src/models/pnid.ts | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/models/pnid.ts b/src/models/pnid.ts index f8f19cf..0b7cb79 100644 --- a/src/models/pnid.ts +++ b/src/models/pnid.ts @@ -8,7 +8,7 @@ import Mii from 'mii-js'; import Stripe from 'stripe'; import { DeviceSchema } from '@/models/device'; import { uploadCDNAsset } from '@/util'; -import { LOG_WARN } from '@/logger'; +import { LOG_ERROR, LOG_WARN } from '@/logger'; import { HydratedPNIDDocument, IPNID, IPNIDMethods, PNIDModel } from '@/types/mongoose/pnid'; import { config } from '@/config-manager'; @@ -221,10 +221,16 @@ PNIDSchema.method('scrub', async function scrub() { // * Remove all personal info from a PNID // * Username and PID remain so thye do not get assigned again - if (this.connections?.stripe?.subscription_id && stripe) { - await stripe.subscriptions.del(this.connections.stripe.subscription_id); - } else { - LOG_WARN(`SCRUBBING USER DATA FOR USER ${this.username}. HAS STRIPE SUBSCRIPTION ${this.connections.stripe.subscription_id}, BUT STRIPE CLIENT NOT ENABLED. SUBSCRIPTION NOT CANCELED`); + if (this.connections?.stripe?.subscription_id) { + try { + if (stripe) { + await stripe.subscriptions.del(this.connections.stripe.subscription_id); + } else { + LOG_WARN(`SCRUBBING USER DATA FOR USER ${this.username}. HAS STRIPE SUBSCRIPTION ${this.connections.stripe.subscription_id}, BUT STRIPE CLIENT NOT ENABLED. SUBSCRIPTION NOT CANCELED`); + } + } catch (error) { + LOG_ERROR(`ERROR REMOVING ${this.username} STRIPE SUBSCRIPTION. ${error}`); + } } await this.updateMii({ @@ -234,6 +240,8 @@ PNIDSchema.method('scrub', async function scrub() { }); this.deleted = true; + this.access_level = 0; + this.server_access_level = 'prod'; this.creation_date = ''; this.birthdate = ''; this.gender = ''; From 65beb3d78b429b00eee16226f27ac2063e5e86f7 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Wed, 5 Jul 2023 16:38:59 -0400 Subject: [PATCH 117/219] Send email when PNID is deleted --- src/services/nnid/routes/people.ts | 10 +++++++++- src/util.ts | 15 +++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/src/services/nnid/routes/people.ts b/src/services/nnid/routes/people.ts index e2f6d95..c437ea3 100644 --- a/src/services/nnid/routes/people.ts +++ b/src/services/nnid/routes/people.ts @@ -7,7 +7,7 @@ import mongoose from 'mongoose'; import deviceCertificateMiddleware from '@/middleware/device-certificate'; import ratelimit from '@/middleware/ratelimit'; import { connection as databaseConnection, doesPNIDExist, getPNIDProfileJSONByPID } from '@/database'; -import { getValueFromHeaders, nintendoPasswordHash, sendConfirmationEmail } from '@/util'; +import { getValueFromHeaders, nintendoPasswordHash, sendConfirmationEmail, sendPNIDDeletedEmail } from '@/util'; import { PNID } from '@/models/pnid'; import { NEXAccount } from '@/models/nex-account'; import { LOG_ERROR } from '@/logger'; @@ -539,9 +539,17 @@ router.post('/@me/deletion', async (request: express.Request, response: express. return; } + const email: string = pnid.email.address; + await pnid.scrub(); await pnid.save(); + try { + await sendPNIDDeletedEmail(email, pnid.username); + } catch (error) { + LOG_ERROR(error); + } + response.send(''); }); diff --git a/src/util.ts b/src/util.ts index e3bb22e..25558cf 100644 --- a/src/util.ts +++ b/src/util.ts @@ -233,6 +233,21 @@ export async function sendForgotPasswordEmail(pnid: mongoose.HydratedDocument { + const options: MailerOptions = { + to: email, + subject: '[Pretendo Network] PNID Deleted', + username: username, + link: { + text: 'Discord Server', + href: 'https://discord.com/invite/pretendo' + }, + text: `Your PNID ${username} has successfully been deleted. If you had a tier subscription, a separate cancellation email will be sent. If you do not receive this cancellation email, or your subscription is still being charged, please contact @jon on our Discord server` + }; + + await sendMail(options); +} + export function makeSafeQs(query: ParsedQs): SafeQs { const entries = Object.entries(query); const output: SafeQs = {}; From f3040d69e81fc45273fe96379eb64000bba95d07 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Wed, 5 Jul 2023 16:42:32 -0400 Subject: [PATCH 118/219] Update email error log --- src/services/nnid/routes/people.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/nnid/routes/people.ts b/src/services/nnid/routes/people.ts index c437ea3..7499bab 100644 --- a/src/services/nnid/routes/people.ts +++ b/src/services/nnid/routes/people.ts @@ -547,7 +547,7 @@ router.post('/@me/deletion', async (request: express.Request, response: express. try { await sendPNIDDeletedEmail(email, pnid.username); } catch (error) { - LOG_ERROR(error); + LOG_ERROR(`Error sending deletion email ${error}`); } response.send(''); From cf1f95cb33156126d5f1a54b00f541927871bbda Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Sat, 8 Jul 2023 11:39:23 -0400 Subject: [PATCH 119/219] Password reset token is hex, not base64 --- src/services/api/routes/v1/resetPassword.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/api/routes/v1/resetPassword.ts b/src/services/api/routes/v1/resetPassword.ts index c86d40c..7b1aa18 100644 --- a/src/services/api/routes/v1/resetPassword.ts +++ b/src/services/api/routes/v1/resetPassword.ts @@ -30,7 +30,7 @@ router.post('/', async (request: express.Request, response: express.Response): P let unpackedToken: Token; try { - const decryptedToken: Buffer = await decryptToken(Buffer.from(token, 'base64')); + const decryptedToken: Buffer = await decryptToken(Buffer.from(token, 'hex')); unpackedToken = unpackToken(decryptedToken); } catch (error) { response.status(400).json({ From f584a5b499ac2cefc3dfc8f0786a70b0dadd89d9 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Wed, 12 Jul 2023 18:17:15 -0400 Subject: [PATCH 120/219] Make user data update endpoint do something --- package-lock.json | 11 ++- package.json | 3 +- src/services/api/routes/v1/user.ts | 121 +++++++++++++++++++++++++---- src/types/mii-js.d.ts | 1 + 4 files changed, 118 insertions(+), 18 deletions(-) diff --git a/package-lock.json b/package-lock.json index e0c0320..fde4b19 100644 --- a/package-lock.json +++ b/package-lock.json @@ -41,7 +41,8 @@ "typescript-is": "^0.19.0", "validator": "^13.7.0", "xmlbuilder": "^13.0.2", - "xmlbuilder2": "0.0.4" + "xmlbuilder2": "0.0.4", + "zod": "^3.21.4" }, "devDependencies": { "@hcaptcha/types": "^1.0.3", @@ -5811,6 +5812,14 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zod": { + "version": "3.21.4", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.21.4.tgz", + "integrity": "sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw==", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } } } } diff --git a/package.json b/package.json index fefab26..a94aaa7 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,8 @@ "typescript-is": "^0.19.0", "validator": "^13.7.0", "xmlbuilder": "^13.0.2", - "xmlbuilder2": "0.0.4" + "xmlbuilder2": "0.0.4", + "zod": "^3.21.4" }, "devDependencies": { "@hcaptcha/types": "^1.0.3", diff --git a/src/services/api/routes/v1/user.ts b/src/services/api/routes/v1/user.ts index c15bfd9..0622763 100644 --- a/src/services/api/routes/v1/user.ts +++ b/src/services/api/routes/v1/user.ts @@ -1,19 +1,21 @@ import express from 'express'; -import joi from 'joi'; -//import { PNID } from '@/models/pnid'; +import { z } from 'zod'; +import Mii from 'mii-js'; import { config } from '@/config-manager'; +import { PNID } from '@/models/pnid'; import { HydratedPNIDDocument } from '@/types/mongoose/pnid'; import { UpdateUserRequest } from '@/types/services/api/update-user-request'; const router: express.Router = express.Router(); -// TODO: Extend this later with more settings -const userSchema: joi.ObjectSchema = joi.object({ - mii: joi.object({ - name: joi.string(), - primary: joi.string(), - data: joi.string(), - }) +// TODO - Extend this later with more settings +const userSchema = z.object({ + mii: z.object({ + name: z.string().trim(), + primary: z.enum(['Y', 'N']), + data: z.string(), + }).optional(), + environment: z.enum(['prod', 'test', 'dev']).optional() }); /** @@ -85,25 +87,112 @@ router.post('/', async (request: express.Request, response: express.Response): P return; } - const valid: joi.ValidationResult = userSchema.validate(updateUserRequest); + const result = userSchema.safeParse(updateUserRequest); - if (valid.error) { + if (!result.success) { response.status(400).json({ app: 'api', status: 400, - error: valid.error + error: result.error }); return; } - // TODO - Make this do something + if (result.data.mii) { + const miiNameBuffer: Buffer = Buffer.from(result.data.mii.name, 'utf16le'); // * UTF8 to UTF16 - //const pid: number = pnid.pid; + if (miiNameBuffer.length < 1) { + response.status(400).json({ + app: 'api', + status: 400, + error: 'Mii name too short' + }); - //const updateData = {}; + return; + } - //await PNID.updateOne({ pid }, { $set: updateData }).exec(); + if (miiNameBuffer.length > 0x14) { + response.status(400).json({ + app: 'api', + status: 400, + error: 'Mii name too long' + }); + + return; + } + + try { + const miiDataBuffer: Buffer = Buffer.from(result.data.mii.data, 'base64'); + + if (miiDataBuffer.length < 0x60) { + response.status(400).json({ + app: 'api', + status: 400, + error: 'Mii data too short' + }); + + return; + } + + if (miiDataBuffer.length > 0x60) { + response.status(400).json({ + app: 'api', + status: 400, + error: 'Mii data too long' + }); + + return; + } + + const mii: Mii = new Mii(miiDataBuffer); + mii.validate(); + } catch (_) { + response.status(400).json({ + app: 'api', + status: 400, + error: 'Failed to decode Mii data' + }); + + return; + } + + await pnid.updateMii({ + name: result.data.mii.name, + primary: result.data.mii.primary, + data: result.data.mii.data + }); + } + + const updateData: Record = {}; + + if (result.data.environment) { + const environment: string = result.data.environment; + + if (environment === 'test' && pnid.access_level < 1) { + response.status(400).json({ + app: 'api', + status: 400, + error: 'Do not have permission to enter this environment' + }); + + return; + } + + if (environment === 'dev' && pnid.access_level < 3) { + response.status(400).json({ + app: 'api', + status: 400, + error: 'Do not have permission to enter this environment' + }); + + return; + } + + updateData.server_access_level = environment; + } + + await PNID.updateOne({ pid: pnid.pid }, { $set: updateData }).exec(); response.json({ access_level: pnid.access_level, diff --git a/src/types/mii-js.d.ts b/src/types/mii-js.d.ts index a63a5b8..55cc57f 100644 --- a/src/types/mii-js.d.ts +++ b/src/types/mii-js.d.ts @@ -96,4 +96,5 @@ type Mii = { studioUrl: (options: MiiStudioURLOptions) => string; encode: () => Buffer; + validate: () => void; }; \ No newline at end of file From e42329cb735adad41ddfa09a0d1d8f1cd67e850a Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Thu, 13 Jul 2023 13:12:11 -0400 Subject: [PATCH 121/219] [NNID] /v1/api/miis - Send 404 when no valid PIDs are given --- src/services/nnid/routes/miis.ts | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/services/nnid/routes/miis.ts b/src/services/nnid/routes/miis.ts index e6982f9..ca241e8 100644 --- a/src/services/nnid/routes/miis.ts +++ b/src/services/nnid/routes/miis.ts @@ -123,11 +123,15 @@ router.get('/', async (request: express.Request, response: express.Response): Pr }); } - response.send(xmlbuilder.create({ - miis: { - mii: miis - } - }).end()); + if (miis.length === 0) { + response.status(404).end(); + } else { + response.send(xmlbuilder.create({ + miis: { + mii: miis + } + }).end()); + } }); export default router; \ No newline at end of file From c704d9af551ac57ed1ea2d5dfa265fa153636e1e Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Thu, 13 Jul 2023 13:25:16 -0400 Subject: [PATCH 122/219] [NNID] /v1/api/miis - Filter invalid PIDs --- src/services/nnid/routes/miis.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/nnid/routes/miis.ts b/src/services/nnid/routes/miis.ts index ca241e8..ecbecb1 100644 --- a/src/services/nnid/routes/miis.ts +++ b/src/services/nnid/routes/miis.ts @@ -30,7 +30,7 @@ router.get('/', async (request: express.Request, response: express.Response): Pr return; } - const pids: number[] = input.split(',').map(pid => Number(pid)); + const pids: number[] = input.split(',').map(pid => Number(pid)).filter(pid => !isNaN(pid)); const results: HydratedPNIDDocument[] = await PNID.where('pid', pids); const miis: { From 648e15c9e1895109f9d6232337a67ea8ada0270b Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Thu, 13 Jul 2023 14:00:16 -0400 Subject: [PATCH 123/219] [NNID] /v1/api/miis - Return duplicate results for duplicate inputs --- src/services/nnid/routes/miis.ts | 136 +++++++++++++++---------------- 1 file changed, 65 insertions(+), 71 deletions(-) diff --git a/src/services/nnid/routes/miis.ts b/src/services/nnid/routes/miis.ts index ecbecb1..3496a13 100644 --- a/src/services/nnid/routes/miis.ts +++ b/src/services/nnid/routes/miis.ts @@ -32,7 +32,6 @@ router.get('/', async (request: express.Request, response: express.Response): Pr const pids: number[] = input.split(',').map(pid => Number(pid)).filter(pid => !isNaN(pid)); - const results: HydratedPNIDDocument[] = await PNID.where('pid', pids); const miis: { data: string; id: number; @@ -50,77 +49,72 @@ router.get('/', async (request: express.Request, response: express.Response): Pr user_id: string; }[] = []; - for (const user of results) { - const mii: { - name: string; - primary: boolean; - data: string; - id: number; - hash: string; - image_url: string; - image_id: number; - } = user.mii; + for (const pid of pids) { + // TODO - Replace this with a single query again somehow? Maybe aggregation? + const pnid: HydratedPNIDDocument | null = await PNID.findOne({ pid }); - miis.push({ - data: mii.data.replace(/(\r\n|\n|\r)/gm, ''), - id: mii.id, - images: { - image: [ - { - cached_url: `${config.cdn.base_url}/mii/${user.pid}/normal_face.png`, - id: mii.id, - url: `${config.cdn.base_url}/mii/${user.pid}/normal_face.png`, - type: 'standard' - }, - { - cached_url: `${config.cdn.base_url}/mii/${user.pid}/frustrated.png`, - id: mii.id, - url: `${config.cdn.base_url}/mii/${user.pid}/frustrated.png`, - type: 'frustrated_face' - }, - { - cached_url: `${config.cdn.base_url}/mii/${user.pid}/smile_open_mouth.png`, - id: mii.id, - url: `${config.cdn.base_url}/mii/${user.pid}/smile_open_mouth.png`, - type: 'happy_face' - }, - { - cached_url: `${config.cdn.base_url}/mii/${user.pid}/wink_left.png`, - id: mii.id, - url: `${config.cdn.base_url}/mii/${user.pid}/wink_left.png`, - type: 'like_face' - }, - { - cached_url: `${config.cdn.base_url}/mii/${user.pid}/normal_face.png`, - id: mii.id, - url: `${config.cdn.base_url}/mii/${user.pid}/normal_face.png`, - type: 'normal_face' - }, - { - cached_url: `${config.cdn.base_url}/mii/${user.pid}/sorrow.png`, - id: mii.id, - url: `${config.cdn.base_url}/mii/${user.pid}/sorrow.png`, - type: 'puzzled_face' - }, - { - cached_url: `${config.cdn.base_url}/mii/${user.pid}/surprised_open_mouth.png`, - id: mii.id, - url: `${config.cdn.base_url}/mii/${user.pid}/surprised_open_mouth.png`, - type: 'surprised_face' - }, - { - cached_url: `${config.cdn.base_url}/mii/${user.pid}/body.png`, - id: mii.id, - url: `${config.cdn.base_url}/mii/${user.pid}/body.png`, - type: 'whole_body' - } - ] - }, - name: mii.name, - pid: user.pid, - primary: mii.primary ? 'Y' : 'N', - user_id: user.username - }); + if (pnid) { + miis.push({ + data: pnid.mii.data.replace(/(\r\n|\n|\r)/gm, ''), + id: pnid.mii.id, + images: { + image: [ + { + cached_url: `${config.cdn.base_url}/mii/${pnid.pid}/normal_face.png`, + id: pnid.mii.id, + url: `${config.cdn.base_url}/mii/${pnid.pid}/normal_face.png`, + type: 'standard' + }, + { + cached_url: `${config.cdn.base_url}/mii/${pnid.pid}/frustrated.png`, + id: pnid.mii.id, + url: `${config.cdn.base_url}/mii/${pnid.pid}/frustrated.png`, + type: 'frustrated_face' + }, + { + cached_url: `${config.cdn.base_url}/mii/${pnid.pid}/smile_open_mouth.png`, + id: pnid.mii.id, + url: `${config.cdn.base_url}/mii/${pnid.pid}/smile_open_mouth.png`, + type: 'happy_face' + }, + { + cached_url: `${config.cdn.base_url}/mii/${pnid.pid}/wink_left.png`, + id: pnid.mii.id, + url: `${config.cdn.base_url}/mii/${pnid.pid}/wink_left.png`, + type: 'like_face' + }, + { + cached_url: `${config.cdn.base_url}/mii/${pnid.pid}/normal_face.png`, + id: pnid.mii.id, + url: `${config.cdn.base_url}/mii/${pnid.pid}/normal_face.png`, + type: 'normal_face' + }, + { + cached_url: `${config.cdn.base_url}/mii/${pnid.pid}/sorrow.png`, + id: pnid.mii.id, + url: `${config.cdn.base_url}/mii/${pnid.pid}/sorrow.png`, + type: 'puzzled_face' + }, + { + cached_url: `${config.cdn.base_url}/mii/${pnid.pid}/surprised_open_mouth.png`, + id: pnid.mii.id, + url: `${config.cdn.base_url}/mii/${pnid.pid}/surprised_open_mouth.png`, + type: 'surprised_face' + }, + { + cached_url: `${config.cdn.base_url}/mii/${pnid.pid}/body.png`, + id: pnid.mii.id, + url: `${config.cdn.base_url}/mii/${pnid.pid}/body.png`, + type: 'whole_body' + } + ] + }, + name: pnid.mii.name, + pid: pnid.pid, + primary: pnid.mii.primary ? 'Y' : 'N', + user_id: pnid.username + }); + } } if (miis.length === 0) { From ee9c1f7821c5de98bf66d2c46fd8a5fe4a9b1a04 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Thu, 13 Jul 2023 14:17:21 -0400 Subject: [PATCH 124/219] [API] - Fix invalid characters error when registering. Closes #41 --- src/services/api/routes/v1/register.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/services/api/routes/v1/register.ts b/src/services/api/routes/v1/register.ts index 56a99f4..7dd89ba 100644 --- a/src/services/api/routes/v1/register.ts +++ b/src/services/api/routes/v1/register.ts @@ -19,10 +19,10 @@ import { HydratedPNIDDocument } from '@/types/mongoose/pnid'; const router: express.Router = express.Router(); -const PNID_VALID_CHARACTERS_REGEX: RegExp = /^[\w\-.]*$/gm; -const PNID_PUNCTUATION_START_REGEX: RegExp = /^[_\-.]/gm; -const PNID_PUNCTUATION_END_REGEX: RegExp = /[_\-.]$/gm; -const PNID_PUNCTUATION_DUPLICATE_REGEX: RegExp = /[_\-.]{2,}/gm; +const PNID_VALID_CHARACTERS_REGEX: RegExp = /^[\w\-.]*$/; +const PNID_PUNCTUATION_START_REGEX: RegExp = /^[_\-.]/; +const PNID_PUNCTUATION_END_REGEX: RegExp = /[_\-.]$/; +const PNID_PUNCTUATION_DUPLICATE_REGEX: RegExp = /[_\-.]{2,}/; // This sucks const PASSWORD_WORD_OR_NUMBER_REGEX: RegExp = /(?=.*[a-zA-Z])(?=.*\d).*/; From 06c9cc9c01e89999e6214d4cb75fcdf849395589 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Thu, 13 Jul 2023 14:52:41 -0400 Subject: [PATCH 125/219] [NNID] - Update certificate middleware header checks. Closes #22 --- src/middleware/console-status-verification.ts | 37 +++++++++++-------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/src/middleware/console-status-verification.ts b/src/middleware/console-status-verification.ts index af01446..cb1b7d3 100644 --- a/src/middleware/console-status-verification.ts +++ b/src/middleware/console-status-verification.ts @@ -7,12 +7,10 @@ import { HydratedDeviceDocument } from '@/types/mongoose/device'; async function consoleStatusVerificationMiddleware(request: express.Request, response: express.Response, next: express.NextFunction): Promise { if (!request.certificate || !request.certificate.valid) { - // TODO - Change this to a different error response.status(400).send(xmlbuilder.create({ error: { - cause: 'Bad Request', - code: '1600', - message: 'Unable to process request' + code: '0110', + message: 'Unlinked device' } }).end()); @@ -22,12 +20,10 @@ async function consoleStatusVerificationMiddleware(request: express.Request, res const deviceIDHeader: string | undefined = getValueFromHeaders(request.headers, 'x-nintendo-device-id'); if (!deviceIDHeader) { - // TODO - Change this to a different error response.status(400).send(xmlbuilder.create({ error: { - cause: 'Bad Request', - code: '1600', - message: 'Unable to process request' + code: '0002', + message: 'deviceId format is invalid' } }).end()); @@ -37,12 +33,10 @@ async function consoleStatusVerificationMiddleware(request: express.Request, res const deviceID: number = Number(deviceIDHeader); if (isNaN(deviceID)) { - // TODO - Change this to a different error response.status(400).send(xmlbuilder.create({ error: { - cause: 'Bad Request', - code: '1600', - message: 'Unable to process request' + code: '0002', + message: 'deviceId format is invalid' } }).end()); @@ -51,13 +45,24 @@ async function consoleStatusVerificationMiddleware(request: express.Request, res const serialNumber: string | undefined = getValueFromHeaders(request.headers, 'x-nintendo-serial-number'); + // TODO - Verify serial numbers somehow? + // * This is difficult to do safely because serial numbers are + // * inherently insecure. + // * Information about their structure can be found here: + // * https://www.3dbrew.org/wiki/Serials + // * Given this, anyone can generate a valid serial number which + // * passes these checks, even if the serial number isn't real. + // * The 3DS also futher complicates things, as it never sends + // * the complete serial number. The 3DS omits the check digit, + // * meaning any attempt to verify the serial number of a 3DS + // * family of console will ALWAYS fail. Nintendo likely just + // * has a database of all known serials which they are able to + // * compare against. We are not so lucky if (!serialNumber) { - // TODO - Change this to a different error response.status(400).send(xmlbuilder.create({ error: { - cause: 'Bad Request', - code: '1600', - message: 'Unable to process request' + code: '0002', + message: 'serialNumber format is invalid' } }).end()); From 3844531191b38fc6b0e3dfaf780b8d0c15629b59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20L=C3=B3pez=20Guimaraes?= Date: Mon, 17 Jul 2023 00:20:26 +0100 Subject: [PATCH 126/219] Add GetNEXData GRPC method Returns all details of a NEX account. --- package-lock.json | 10 ++------- src/services/grpc/account/get-nex-data.ts | 24 +++++++++++++++++++++ src/services/grpc/account/implementation.ts | 6 ++++-- 3 files changed, 30 insertions(+), 10 deletions(-) create mode 100644 src/services/grpc/account/get-nex-data.ts diff --git a/package-lock.json b/package-lock.json index fde4b19..e26f7d2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1175,11 +1175,6 @@ "node": ">=0.8" } }, - "node_modules/async": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", - "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==" - }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -4384,12 +4379,11 @@ } }, "node_modules/pretendo-grpc-ts": { - "version": "1.0.1", - "resolved": "git+ssh://git@github.com/PretendoNetwork/grpc-ts.git#64c83180d1f2d0829fd8d5e0e6305c6c019b1f03", + "version": "1.0.3", + "resolved": "git+ssh://git@github.com/PretendoNetwork/grpc-ts.git#8047b4d49c9a27cc7edd3786b465c3b0939b6181", "hasInstallScript": true, "license": "ISC", "dependencies": { - "async": "^3.2.4", "long": "^5.2.1", "protobufjs": "^7.2.3" } diff --git a/src/services/grpc/account/get-nex-data.ts b/src/services/grpc/account/get-nex-data.ts new file mode 100644 index 0000000..dfb5462 --- /dev/null +++ b/src/services/grpc/account/get-nex-data.ts @@ -0,0 +1,24 @@ +import { Status, ServerError } from 'nice-grpc'; +import {GetNEXDataRequest,GetNEXDataResponse, DeepPartial } from 'pretendo-grpc-ts/dist/account/get_nex_data_rpc'; +import { NEXAccount } from '@/models/nex-account'; +import { HydratedNEXAccountDocument } from '@/types/mongoose/nex-account'; + +export async function getNEXData(request: GetNEXDataRequest): Promise> { + const nexAccount: HydratedNEXAccountDocument | null = await NEXAccount.findOne({ pid: request.pid }); + + if (!nexAccount) { + throw new ServerError( + Status.INVALID_ARGUMENT, + 'No NEX account found found', + ); + } + + return { + pid: nexAccount.pid, + password: nexAccount.password, + owningPid: nexAccount.owning_pid, + accessLevel: nexAccount.access_level, + serverAccessLevel: nexAccount.server_access_level, + friendCode: nexAccount.friend_code + }; +} diff --git a/src/services/grpc/account/implementation.ts b/src/services/grpc/account/implementation.ts index d6290d0..607d640 100644 --- a/src/services/grpc/account/implementation.ts +++ b/src/services/grpc/account/implementation.ts @@ -1,8 +1,10 @@ import { AccountServiceImplementation } from 'pretendo-grpc-ts/dist/account/account_service'; import { getUserData } from '@/services/grpc/account/get-user-data'; import { getNEXPassword } from '@/services/grpc/account/get-nex-password'; +import { getNEXData } from '@/services/grpc/account/get-nex-data'; export const accountServiceImplementation: AccountServiceImplementation = { getUserData, - getNEXPassword -}; \ No newline at end of file + getNEXPassword, + getNEXData +}; From 5658be404e415366b848fba7739122ccaf8979fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20L=C3=B3pez=20Guimaraes?= Date: Mon, 17 Jul 2023 10:33:38 +0100 Subject: [PATCH 127/219] Fix typos --- src/services/grpc/account/get-nex-data.ts | 2 +- src/services/grpc/account/get-nex-password.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/services/grpc/account/get-nex-data.ts b/src/services/grpc/account/get-nex-data.ts index dfb5462..fbf12ff 100644 --- a/src/services/grpc/account/get-nex-data.ts +++ b/src/services/grpc/account/get-nex-data.ts @@ -9,7 +9,7 @@ export async function getNEXData(request: GetNEXDataRequest): Promise Date: Thu, 10 Aug 2023 20:28:55 -0400 Subject: [PATCH 128/219] Actually handle refresh tokens --- src/services/nnid/routes/oauth.ts | 121 ++++++++++++++++++++---------- 1 file changed, 82 insertions(+), 39 deletions(-) diff --git a/src/services/nnid/routes/oauth.ts b/src/services/nnid/routes/oauth.ts index c9a345d..20f92ad 100644 --- a/src/services/nnid/routes/oauth.ts +++ b/src/services/nnid/routes/oauth.ts @@ -3,7 +3,7 @@ import xmlbuilder from 'xmlbuilder'; import bcrypt from 'bcrypt'; import deviceCertificateMiddleware from '@/middleware/device-certificate'; import consoleStatusVerificationMiddleware from '@/middleware/console-status-verification'; -import { getPNIDByUsername } from '@/database'; +import { getPNIDByTokenAuth, getPNIDByUsername } from '@/database'; import { generateToken } from '@/util'; import { config } from '@/config-manager'; import { TokenOptions } from '@/types/common/token-options'; @@ -18,9 +18,10 @@ const router: express.Router = express.Router(); * Description: Generates an access token for a user */ router.post('/access_token/generate', deviceCertificateMiddleware, consoleStatusVerificationMiddleware, async (request: express.Request, response: express.Response): Promise => { - const grantType: string = request.body?.grant_type; - const username: string = request.body?.user_id; - const password: string = request.body?.password; + const grantType: string = request.body.grant_type; + const username: string | undefined = request.body.user_id; + const password: string | undefined = request.body.password; + const refreshToken: string | undefined = request.body.refresh_token; if (!['password', 'refresh_token'].includes(grantType)) { response.status(400).send(xmlbuilder.create({ @@ -34,43 +35,85 @@ router.post('/access_token/generate', deviceCertificateMiddleware, consoleStatus return; } - if (!username || username.trim() === '') { - response.status(400).send(xmlbuilder.create({ - error: { - cause: 'user_id', - code: '0002', - message: 'user_id format is invalid' - } - }).end()); + let pnid: HydratedPNIDDocument | null = null; - return; - } - - if (!password || password.trim() === '') { - response.status(400).send(xmlbuilder.create({ - error: { - cause: 'password', - code: '0002', - message: 'password format is invalid' - } - }).end()); - - return; - } - - const pnid: HydratedPNIDDocument | null = await getPNIDByUsername(username); - - if (!pnid || !await bcrypt.compare(password, pnid.password)) { - response.status(400).send(xmlbuilder.create({ - errors: { + if (grantType === 'password') { + if (!username || username.trim() === '') { + response.status(400).send(xmlbuilder.create({ error: { - code: '0106', - message: 'Invalid account ID or password' + cause: 'user_id', + code: '0002', + message: 'user_id format is invalid' } - } - }).end({ pretty: true })); + }).end()); - return; + return; + } + + if (!password || password.trim() === '') { + response.status(400).send(xmlbuilder.create({ + error: { + cause: 'password', + code: '0002', + message: 'password format is invalid' + } + }).end()); + + return; + } + + pnid = await getPNIDByUsername(username); + + if (!pnid || !await bcrypt.compare(password, pnid.password)) { + response.status(400).send(xmlbuilder.create({ + errors: { + error: { + code: '0106', + message: 'Invalid account ID or password' + } + } + }).end({ pretty: true })); + + return; + } + } else { + if (!refreshToken || refreshToken.trim() === '') { + response.status(400).send(xmlbuilder.create({ + error: { + cause: 'refresh_token', + code: '0106', + message: 'Invalid Refresh Token' + } + }).end()); + + return; + } + + try { + pnid = await getPNIDByTokenAuth(refreshToken); + + if (!pnid) { + response.status(400).send(xmlbuilder.create({ + error: { + cause: 'refresh_token', + code: '0106', + message: 'Invalid Refresh Token' + } + }).end()); + + return; + } + } catch (error) { + response.status(400).send(xmlbuilder.create({ + error: { + cause: 'refresh_token', + code: '0106', + message: 'Invalid Refresh Token' + } + }).end()); + + return; + } } // * This are set/validated in consoleStatusVerificationMiddleware @@ -116,7 +159,7 @@ router.post('/access_token/generate', deviceCertificateMiddleware, consoleStatus const refreshTokenBuffer: Buffer | null = await generateToken(config.aes_key, refreshTokenOptions); const accessToken: string = accessTokenBuffer ? accessTokenBuffer.toString('hex') : ''; - const refreshToken: string = refreshTokenBuffer ? refreshTokenBuffer.toString('hex') : ''; + const newRefreshToken: string = refreshTokenBuffer ? refreshTokenBuffer.toString('hex') : ''; // TODO - Handle null tokens @@ -124,7 +167,7 @@ router.post('/access_token/generate', deviceCertificateMiddleware, consoleStatus OAuth20: { access_token: { token: accessToken, - refresh_token: refreshToken, + refresh_token: newRefreshToken, expires_in: 3600 } } From 93ab2511769efb8799bc67ff0545c6de18ca8c16 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Sat, 19 Aug 2023 09:46:10 -0400 Subject: [PATCH 129/219] Move gRPC to new NPM package and begin migration to new permissions system --- package-lock.json | 464 +++++++++--------- package.json | 1 + src/models/pnid.ts | 17 + src/services/grpc/account/get-nex-data.ts | 2 +- src/services/grpc/account/get-nex-password.ts | 2 +- src/services/grpc/account/get-user-data.ts | 24 +- src/services/grpc/account/implementation.ts | 6 +- .../grpc/account/update-pnid-permissions.ts | 118 +++++ src/services/grpc/api/forgot-password.ts | 4 +- src/services/grpc/api/get-user-data.ts | 4 +- src/services/grpc/api/implementation.ts | 2 +- src/services/grpc/api/login.ts | 2 +- src/services/grpc/api/register.ts | 4 +- src/services/grpc/api/reset-password.ts | 4 +- .../grpc/api/set-discord-connection-data.ts | 4 +- .../grpc/api/set-stripe-connection-data.ts | 4 +- src/services/grpc/api/update-user-data.ts | 4 +- src/services/grpc/server.ts | 4 +- src/types/common/permission-flags.ts | 19 + src/types/mongoose/pnid.ts | 5 + 20 files changed, 445 insertions(+), 249 deletions(-) create mode 100644 src/services/grpc/account/update-pnid-permissions.ts create mode 100644 src/types/common/permission-flags.ts diff --git a/package-lock.json b/package-lock.json index e26f7d2..9ce47af 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "2.0.0", "license": "ISC", "dependencies": { + "@pretendonetwork/grpc": "^1.0.1", "aws-sdk": "^2.978.0", "bcrypt": "^5.0.0", "buffer-crc32": "^0.2.13", @@ -68,6 +69,15 @@ "yesno": "^0.4.0" } }, + "node_modules/@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", + "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/@colors/colors": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", @@ -93,23 +103,23 @@ } }, "node_modules/@eslint-community/regexpp": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.5.1.tgz", - "integrity": "sha512-Z5ba73P98O1KUYCCJTUeVpja9RcGoMdncZ6T49FCUl2lN38JtCJ+3WgIDBv0AuY4WChU5PmtJmOCTlN6FZTFKQ==", + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.6.2.tgz", + "integrity": "sha512-pPTNuaAG3QMH+buKyBIGJs3g/S5y0caxw0ygM3YyE6yJFySwiGGSzA+mM3KJ8QQvzeLh3blwgSonkFjgQdxzMw==", "dev": true, "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, "node_modules/@eslint/eslintrc": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.3.tgz", - "integrity": "sha512-+5gy6OQfk+xx3q0d6jGZZC3f3KzAkXc/IanVxd1is/VIIziRqqt3ongQz0FiTUXqTk0c7aDB3OaFuKnuSoJicQ==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.2.tgz", + "integrity": "sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==", "dev": true, "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", - "espree": "^9.5.2", + "espree": "^9.6.0", "globals": "^13.19.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", @@ -125,18 +135,18 @@ } }, "node_modules/@eslint/js": { - "version": "8.42.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.42.0.tgz", - "integrity": "sha512-6SWlXpWU5AvId8Ac7zjzmIOqMOba/JWY8XZ4A7q7Gn1Vlfg/SFFIlrtHXt9nPn4op9ZPAkl91Jao+QQv3r/ukw==", + "version": "8.47.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.47.0.tgz", + "integrity": "sha512-P6omY1zv5MItm93kLM8s2vr1HICJH8v0dvddDhysbIuZ+vcjOHg5Zbkf1mTkcmi2JA9oBG2anOkRnW8WJTS8Og==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, "node_modules/@grpc/grpc-js": { - "version": "1.8.15", - "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.8.15.tgz", - "integrity": "sha512-H2Bu/w6+oQ58DsRbQol66ERBk3V5ZIak/z/MDx0T4EgDnJWps807I6BvTjq0v6UvZtOcLO+ur+Q9wvniqu3OJA==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.9.0.tgz", + "integrity": "sha512-H8+iZh+kCE6VR/Krj6W28Y/ZlxoZ1fOzsNt77nrdE3knkbSelW1Uus192xOFCxHyeszLj8i4APQkSIXjAoOxXg==", "dependencies": { "@grpc/proto-loader": "^0.7.0", "@types/node": ">=12.12.47" @@ -146,14 +156,14 @@ } }, "node_modules/@grpc/proto-loader": { - "version": "0.7.7", - "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.7.tgz", - "integrity": "sha512-1TIeXOi8TuSCQprPItwoMymZXxWT0CPxUhkrkeCUH+D8U7QDwQ6b7SUz2MaLuWM2llT+J/TVFLmQI5KtML3BhQ==", + "version": "0.7.8", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.8.tgz", + "integrity": "sha512-GU12e2c8dmdXb7XUlOgYWZ2o2i+z9/VeACkxTA/zzAe2IjclC5PnVL0lpgjhrqfpDYHzM8B1TF6pqWegMYAzlA==", "dependencies": { "@types/long": "^4.0.1", "lodash.camelcase": "^4.3.0", "long": "^4.0.0", - "protobufjs": "^7.0.0", + "protobufjs": "^7.2.4", "yargs": "^17.7.2" }, "bin": { @@ -163,6 +173,11 @@ "node": ">=6" } }, + "node_modules/@grpc/proto-loader/node_modules/long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" + }, "node_modules/@hapi/hoek": { "version": "9.3.0", "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", @@ -216,9 +231,9 @@ "dev": true }, "node_modules/@mapbox/node-pre-gyp": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.10.tgz", - "integrity": "sha512-4ySo4CjzStuprMwk35H5pPbkymjv1SF3jGLj6rAHp/xT/RF7TL7bd9CTm1xDY49K2qF7jmR/g7k+SkLETP6opA==", + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", + "integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==", "dependencies": { "detect-libc": "^2.0.0", "https-proxy-agent": "^5.0.0", @@ -385,6 +400,15 @@ "node": ">=6" } }, + "node_modules/@pretendonetwork/grpc": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@pretendonetwork/grpc/-/grpc-1.0.1.tgz", + "integrity": "sha512-qNsZ//+U22KYTYZyjb4J2CnGUyGfYF35bG1D334WNvq4M2ubvVutWZp98ur5/fVW3g5Af6Eq2IuBwFO9xRhQ1w==", + "dependencies": { + "long": "^5.2.1", + "protobufjs": "^7.2.3" + } + }, "node_modules/@protobufjs/aspromise": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", @@ -637,6 +661,12 @@ "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz", "integrity": "sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==" }, + "node_modules/@types/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-/K3ds8TRAfBvi5vfjuz8y6+GiAYBZ0x4tXv1Av6CWBWn0IlADc+ZX9pMq7oU0fNQPnBwIZl3rmeLp6SBApbxSQ==", + "dev": true + }, "node_modules/@types/json-schema": { "version": "7.0.12", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz", @@ -687,9 +717,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "18.16.18", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.16.18.tgz", - "integrity": "sha512-/aNaQZD0+iSBAGnvvN2Cx92HqE5sZCPZtx2TsK+4nvV23fFe09jVDvpArXr2j9DnYlzuU9WuoykDDc6wqvpNcw==" + "version": "18.17.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.17.6.tgz", + "integrity": "sha512-fGmT/P7z7ecA6bv/ia5DlaWCH4YeZvAQMNpUhrJjtAhOhZfoxS1VLUgU2pdk63efSjQaOJWdXMuAJsws+8I6dg==" }, "node_modules/@types/node-rsa": { "version": "1.1.1", @@ -701,9 +731,9 @@ } }, "node_modules/@types/nodemailer": { - "version": "6.4.8", - "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.8.tgz", - "integrity": "sha512-oVsJSCkqViCn8/pEu2hfjwVO+Gb3e+eTWjg3PcjeFKRItfKpKwHphQqbYmPQrlMk+op7pNNWPbsJIEthpFN/OQ==", + "version": "6.4.9", + "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.9.tgz", + "integrity": "sha512-XYG8Gv+sHjaOtUpiuytahMy2mM3rectgroNbs6R3djZEKmPNiIJwe9KqOJBGzKKnNZNKvnuvmugBgpq3w/S0ig==", "dev": true, "dependencies": { "@types/node": "*" @@ -746,19 +776,20 @@ } }, "node_modules/@types/serve-static": { - "version": "1.15.1", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.1.tgz", - "integrity": "sha512-NUo5XNiAdULrJENtJXZZ3fHtfMolzZwczzBbnAeBbqBwG+LaG6YaJtuwzwGSQZ2wsCrxjEhNNjAkKigy3n8teQ==", + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.2.tgz", + "integrity": "sha512-J2LqtvFYCzaj8pVYKw8klQXrLLk7TBZmQ4ShlcdkELFKGwGMfevMLneMMRkMgZxotOD9wg497LpC7O8PcvAmfw==", "dev": true, "dependencies": { + "@types/http-errors": "*", "@types/mime": "*", "@types/node": "*" } }, "node_modules/@types/validator": { - "version": "13.7.17", - "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.7.17.tgz", - "integrity": "sha512-aqayTNmeWrZcvnG2MG9eGYI6b7S5fl+yKgPs6bAjOTwPS316R5SxBGKvtSExfyoJU7pIeHJfsHI0Ji41RVMkvQ==", + "version": "13.11.1", + "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.11.1.tgz", + "integrity": "sha512-d/MUkJYdOeKycmm75Arql4M5+UuXmf4cHdHKsyw1GcvnNgL6s77UkgSgJ8TE/rI5PYsnwYq5jkcWBLuN/MpQ1A==", "dev": true }, "node_modules/@types/webidl-conversions": { @@ -776,17 +807,17 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "5.59.11", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.59.11.tgz", - "integrity": "sha512-XxuOfTkCUiOSyBWIvHlUraLw/JT/6Io1365RO6ZuI88STKMavJZPNMU0lFcUTeQXEhHiv64CbxYxBNoDVSmghg==", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz", + "integrity": "sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.4.0", - "@typescript-eslint/scope-manager": "5.59.11", - "@typescript-eslint/type-utils": "5.59.11", - "@typescript-eslint/utils": "5.59.11", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/type-utils": "5.62.0", + "@typescript-eslint/utils": "5.62.0", "debug": "^4.3.4", - "grapheme-splitter": "^1.0.4", + "graphemer": "^1.4.0", "ignore": "^5.2.0", "natural-compare-lite": "^1.4.0", "semver": "^7.3.7", @@ -810,14 +841,14 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "5.59.11", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.59.11.tgz", - "integrity": "sha512-s9ZF3M+Nym6CAZEkJJeO2TFHHDsKAM3ecNkLuH4i4s8/RCPnF5JRip2GyviYkeEAcwGMJxkqG9h2dAsnA1nZpA==", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz", + "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "5.59.11", - "@typescript-eslint/types": "5.59.11", - "@typescript-eslint/typescript-estree": "5.59.11", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", "debug": "^4.3.4" }, "engines": { @@ -837,13 +868,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "5.59.11", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.59.11.tgz", - "integrity": "sha512-dHFOsxoLFtrIcSj5h0QoBT/89hxQONwmn3FOQ0GOQcLOOXm+MIrS8zEAhs4tWl5MraxCY3ZJpaXQQdFMc2Tu+Q==", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", + "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.59.11", - "@typescript-eslint/visitor-keys": "5.59.11" + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -854,13 +885,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "5.59.11", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.59.11.tgz", - "integrity": "sha512-LZqVY8hMiVRF2a7/swmkStMYSoXMFlzL6sXV6U/2gL5cwnLWQgLEG8tjWPpaE4rMIdZ6VKWwcffPlo1jPfk43g==", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz", + "integrity": "sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "5.59.11", - "@typescript-eslint/utils": "5.59.11", + "@typescript-eslint/typescript-estree": "5.62.0", + "@typescript-eslint/utils": "5.62.0", "debug": "^4.3.4", "tsutils": "^3.21.0" }, @@ -881,9 +912,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "5.59.11", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.59.11.tgz", - "integrity": "sha512-epoN6R6tkvBYSc+cllrz+c2sOFWkbisJZWkOE+y3xHtvYaOE6Wk6B8e114McRJwFRjGvYdJwLXQH5c9osME/AA==", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", + "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -894,13 +925,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "5.59.11", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.11.tgz", - "integrity": "sha512-YupOpot5hJO0maupJXixi6l5ETdrITxeo5eBOeuV7RSKgYdU3G5cxO49/9WRnJq9EMrB7AuTSLH/bqOsXi7wPA==", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", + "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.59.11", - "@typescript-eslint/visitor-keys": "5.59.11", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -921,17 +952,17 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "5.59.11", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.59.11.tgz", - "integrity": "sha512-didu2rHSOMUdJThLk4aZ1Or8IcO3HzCw/ZvEjTTIfjIrcdd5cvSIwwDy2AOlE7htSNp7QIZ10fLMyRCveesMLg==", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz", + "integrity": "sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@types/json-schema": "^7.0.9", "@types/semver": "^7.3.12", - "@typescript-eslint/scope-manager": "5.59.11", - "@typescript-eslint/types": "5.59.11", - "@typescript-eslint/typescript-estree": "5.59.11", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", "eslint-scope": "^5.1.1", "semver": "^7.3.7" }, @@ -947,12 +978,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "5.59.11", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.11.tgz", - "integrity": "sha512-KGYniTGG3AMTuKF9QBD7EIrvufkB6O6uX3knP73xbKLMpH+QRPcgnCxjWXSHjMRuOxFLovljqQgQpR0c7GvjoA==", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", + "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.59.11", + "@typescript-eslint/types": "5.62.0", "eslint-visitor-keys": "^3.3.0" }, "engines": { @@ -969,9 +1000,9 @@ "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" }, "node_modules/abort-controller-x": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/abort-controller-x/-/abort-controller-x-0.4.2.tgz", - "integrity": "sha512-5bgqvgogTIxTkQpkgDoFZHt2YS8uireyOkC4snQG2kaEKs7DI7Tgsu5xysjp9MxG+6OLICBOAL8TtKHzxQrlqw==" + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/abort-controller-x/-/abort-controller-x-0.4.3.tgz", + "integrity": "sha512-VtUwTNU8fpMwvWGn4xE93ywbogTYsuT+AUxAXOeelbXuQVIwNmC5YLeho9sH4vZ4ITW8414TTAOG1nW6uIVHCA==" }, "node_modules/accepts": { "version": "1.3.8", @@ -986,9 +1017,9 @@ } }, "node_modules/acorn": { - "version": "8.8.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", - "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", + "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", "dev": true, "bin": { "acorn": "bin/acorn" @@ -1175,6 +1206,12 @@ "node": ">=0.8" } }, + "node_modules/async": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.3.tgz", + "integrity": "sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g==", + "dev": true + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -1197,9 +1234,9 @@ } }, "node_modules/aws-sdk": { - "version": "2.1397.0", - "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1397.0.tgz", - "integrity": "sha512-Km+jUscV6vW3vuurSsGrTjEqaNfrE9ykA3IJmJb85V2z5tklJvoBU+7JcCiF6h3BDKegNjiFOM7uoL3cGVGz4g==", + "version": "2.1440.0", + "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1440.0.tgz", + "integrity": "sha512-ijHaRFZIKLYUDqOGTNrwncXF5vzJPU6VJpbRr7eNBAvo+nFtEHY4BZkldWYuhELCbWz0U5/+qMtF2T/JgPjfWQ==", "dependencies": { "buffer": "4.9.2", "events": "1.1.1", @@ -1285,12 +1322,12 @@ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "node_modules/bcrypt": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.1.0.tgz", - "integrity": "sha512-RHBS7HI5N5tEnGTmtR/pppX0mmDSBpQ4aCBsj7CEQfYXDcO74A8sIBYcJMuCsis2E81zDxeENYhv66oZwLiA+Q==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.1.1.tgz", + "integrity": "sha512-AGBHOG5hPYZ5Xl9KXzU5iKq9516yEmvCKDg3ecP5kX2aB6UqTeXZxk2ELnDgDm6BQSMlLt9rDB4LoSMx0rYwww==", "hasInstallScript": true, "dependencies": { - "@mapbox/node-pre-gyp": "^1.0.10", + "@mapbox/node-pre-gyp": "^1.0.11", "node-addon-api": "^5.0.0" }, "engines": { @@ -1387,9 +1424,9 @@ } }, "node_modules/bson": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/bson/-/bson-5.3.0.tgz", - "integrity": "sha512-ukmCZMneMlaC5ebPHXIkP8YJzNl5DC41N5MAIvKDqLggdao342t4McltoJBQfQya/nHBWAcSsYRqlXPoQkTJag==", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/bson/-/bson-5.4.0.tgz", + "integrity": "sha512-WRZ5SQI5GfUuKnPTNmAYPiKIof3ORXAF4IRU5UcgmivNIon01rWQlw5RUH954dpu8yGL8T59YShVddIPaU/gFA==", "engines": { "node": ">=14.20.1" } @@ -1893,9 +1930,9 @@ } }, "node_modules/detect-libc": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.1.tgz", - "integrity": "sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz", + "integrity": "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==", "engines": { "node": ">=8" } @@ -1937,9 +1974,9 @@ } }, "node_modules/dotenv": { - "version": "16.1.4", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.1.4.tgz", - "integrity": "sha512-m55RtE8AsPeJBpOIFKihEmqUcoVncQIwo7x9U8ZwLEZw9ZpXboz2c+rvog+jUaJvVrZ5kBOeYQBX5+8Aa/OZQw==", + "version": "16.3.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz", + "integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==", "engines": { "node": ">=12" }, @@ -2210,27 +2247,27 @@ } }, "node_modules/eslint": { - "version": "8.42.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.42.0.tgz", - "integrity": "sha512-ulg9Ms6E1WPf67PHaEY4/6E2tEn5/f7FXGzr3t9cBMugOmf1INYvuUwwh1aXQN4MfJ6a5K2iNwP3w4AColvI9A==", + "version": "8.47.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.47.0.tgz", + "integrity": "sha512-spUQWrdPt+pRVP1TTJLmfRNJJHHZryFmptzcafwSvHsceV81djHOdnEeDmkdotZyLNjDhrOasNK8nikkoG1O8Q==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.4.0", - "@eslint/eslintrc": "^2.0.3", - "@eslint/js": "8.42.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.2", + "@eslint/js": "^8.47.0", "@humanwhocodes/config-array": "^0.11.10", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", - "ajv": "^6.10.0", + "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", "debug": "^4.3.2", "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.2.0", - "eslint-visitor-keys": "^3.4.1", - "espree": "^9.5.2", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", @@ -2240,7 +2277,6 @@ "globals": "^13.19.0", "graphemer": "^1.4.0", "ignore": "^5.2.0", - "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "is-path-inside": "^3.0.3", @@ -2250,9 +2286,8 @@ "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", - "optionator": "^0.9.1", + "optionator": "^0.9.3", "strip-ansi": "^6.0.1", - "strip-json-comments": "^3.1.0", "text-table": "^0.2.0" }, "bin": { @@ -2279,9 +2314,9 @@ } }, "node_modules/eslint-visitor-keys": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz", - "integrity": "sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==", + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -2291,9 +2326,9 @@ } }, "node_modules/eslint/node_modules/eslint-scope": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.0.tgz", - "integrity": "sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw==", + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", "dev": true, "dependencies": { "esrecurse": "^4.3.0", @@ -2316,12 +2351,12 @@ } }, "node_modules/espree": { - "version": "9.5.2", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.2.tgz", - "integrity": "sha512-7OASN1Wma5fum5SrNhFMAMJxOUAbhyfQ8dQ//PJaJbNw0URTPWqIghHWt1MmAANKhHZIYOHruW4Kw4ruUWOdGw==", + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", "dev": true, "dependencies": { - "acorn": "^8.8.0", + "acorn": "^8.9.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^3.4.1" }, @@ -2474,11 +2509,11 @@ } }, "node_modules/express-rate-limit": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-6.7.0.tgz", - "integrity": "sha512-vhwIdRoqcYB/72TK3tRZI+0ttS8Ytrk24GfmsxDXK9o9IhHNO5bXRiXQSExPQ4GbaE5tvIS7j1SGrxsuWs+sGA==", + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-6.9.0.tgz", + "integrity": "sha512-AnISR3V8qy4gpKM62/TzYdoFO9NV84fBx0POXzTryHU/qGUJBWuVGd+JhbvtVmKBv37t8/afmqdnv16xWoQxag==", "engines": { - "node": ">= 12.9.0" + "node": ">= 14.0.0" }, "peerDependencies": { "express": "^4 || ^5" @@ -2543,9 +2578,9 @@ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, "node_modules/fast-glob": { - "version": "3.2.12", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", - "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", + "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", "dev": true, "dependencies": { "@nodelib/fs.stat": "^2.0.2", @@ -2897,9 +2932,9 @@ } }, "node_modules/globals": { - "version": "13.20.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", - "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", + "version": "13.21.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.21.0.tgz", + "integrity": "sha512-ybyme3s4yy/t/3s35bewwXKOf7cvzfreG2lH0lZl0JB7I4GxRP2ghxOK/Nb9EkRXdbBXZLfq/p/0W2JUONB/Gg==", "dev": true, "dependencies": { "type-fest": "^0.20.2" @@ -2971,12 +3006,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/grapheme-splitter": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", - "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", - "dev": true - }, "node_modules/graphemer": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", @@ -3325,9 +3354,9 @@ } }, "node_modules/is-core-module": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.1.tgz", - "integrity": "sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==", + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.0.tgz", + "integrity": "sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==", "dependencies": { "has": "^1.0.3" }, @@ -3410,15 +3439,11 @@ "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==" }, "node_modules/is-typed-array": { - "version": "1.1.10", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", - "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.12.tgz", + "integrity": "sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==", "dependencies": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0" + "which-typed-array": "^1.1.11" }, "engines": { "node": ">= 0.4" @@ -3552,9 +3577,9 @@ } }, "node_modules/keyv": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.2.tgz", - "integrity": "sha512-5MHbFaKn8cNSmVW7BYnijeAVlE4cYA/SVkifVgrh7yotnfhKmjuXpDKjrABLnT0SfHWV21P8ow07OGfRrNDg8g==", + "version": "4.5.3", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.3.tgz", + "integrity": "sha512-QCiSav9WaX1PgETJ+SpNnx2PRRapJ/oRSXM4VO5OGYGSjrxbKPVFVhB3l2OCbLCk329N8qyAtsJjSjvVBWzEug==", "dependencies": { "json-buffer": "3.0.1" } @@ -3614,9 +3639,9 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" }, "node_modules/long": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", - "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", + "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==" }, "node_modules/lowercase-keys": { "version": "2.0.0", @@ -3660,9 +3685,9 @@ } }, "node_modules/make-dir/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "bin": { "semver": "bin/semver.js" } @@ -3848,11 +3873,11 @@ } }, "node_modules/mongodb": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-5.5.0.tgz", - "integrity": "sha512-XgrkUgAAdfnZKQfk5AsYL8j7O99WHd4YXPxYxnh8dZxD+ekYWFRA3JktUsBnfg+455Smf75/+asoU/YLwNGoQQ==", + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-5.7.0.tgz", + "integrity": "sha512-zm82Bq33QbqtxDf58fLWBwTjARK3NSvKYjyz997KSy6hpat0prjeX/kxjbPVyZY60XYPDNETaHkHJI2UCzSLuw==", "dependencies": { - "bson": "^5.3.0", + "bson": "^5.4.0", "mongodb-connection-string-url": "^2.6.0", "socks": "^2.7.1" }, @@ -3864,6 +3889,8 @@ }, "peerDependencies": { "@aws-sdk/credential-providers": "^3.201.0", + "@mongodb-js/zstd": "^1.1.0", + "kerberos": "^2.0.1", "mongodb-client-encryption": ">=2.3.0 <3", "snappy": "^7.2.2" }, @@ -3871,6 +3898,12 @@ "@aws-sdk/credential-providers": { "optional": true }, + "@mongodb-js/zstd": { + "optional": true + }, + "kerberos": { + "optional": true + }, "mongodb-client-encryption": { "optional": true }, @@ -3889,13 +3922,13 @@ } }, "node_modules/mongoose": { - "version": "7.2.4", - "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-7.2.4.tgz", - "integrity": "sha512-BWcgShV2WH1rspICiJKLPi7QssTebpGJ23Nyk7qG0TMEE/OEAlsQKEhI7VlrXg4ZnoOcHgG+N+upW9tj17TTQg==", + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-7.4.3.tgz", + "integrity": "sha512-eok0lW6mZJHK2vVSWyJb9tUfPMUuRF3h7YC4pU2K2/YSZBlNDUwvKsHgftMOANbokP2Ry+4ylvzAdW4KjkRFjw==", "dependencies": { - "bson": "^5.3.0", + "bson": "^5.4.0", "kareem": "2.5.1", - "mongodb": "5.5.0", + "mongodb": "5.7.0", "mpath": "0.9.0", "mquery": "5.0.0", "ms": "2.1.3", @@ -4043,11 +4076,11 @@ "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==" }, "node_modules/nice-grpc": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/nice-grpc/-/nice-grpc-2.1.4.tgz", - "integrity": "sha512-ZCSnFxg/k6PM1zZ2u/SbuySTrpK7q4klwRE4ymAdiMfZM3Rl1LRUdqUslKSbSjd9XQHzi80Y5JJL5fE58lSrVA==", + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/nice-grpc/-/nice-grpc-2.1.5.tgz", + "integrity": "sha512-xqtgW+9VpKD+J3G3sl9+ggR//DmYGrNgjfuEznTU3eL/zRLhn/SwSzl2Elw7TiPknEQ54hDtaOP6VQ8ktv+6fA==", "dependencies": { - "@grpc/grpc-js": "^1.7.3", + "@grpc/grpc-js": "^1.8.18", "abort-controller-x": "^0.4.0", "nice-grpc-common": "^2.0.2" } @@ -4066,9 +4099,9 @@ "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==" }, "node_modules/node-fetch": { - "version": "2.6.11", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.11.tgz", - "integrity": "sha512-4I6pdBY1EthSqDmJkiNk3JIT8cswwR9nfeW/cPdUagJYEQG7R95WRH74wpz7ma8Gh/9dI9FP+OU+0E4FvtA55w==", + "version": "2.6.13", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.13.tgz", + "integrity": "sha512-StxNAxh15zr77QvvkmveSQ8uCQ4+v5FkvNTj0OESmiHu+VRi/gXArXtkWMElOsOUNLtUEvI4yS+rdtOHZTwlQA==", "dependencies": { "whatwg-url": "^5.0.0" }, @@ -4112,9 +4145,9 @@ } }, "node_modules/nodemailer": { - "version": "6.9.3", - "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.3.tgz", - "integrity": "sha512-fy9v3NgTzBngrMFkDsKEj0r02U7jm6XfC3b52eoNV+GCrGj+s8pt5OqhiJdWKuw51zCTdiNR/IUD1z33LIIGpg==", + "version": "6.9.4", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.4.tgz", + "integrity": "sha512-CXjQvrQZV4+6X5wP6ZIgdehJamI63MFoYFGGPtHudWym9qaEHDNdPzaj5bfMCvxG1vhAileSWW90q7nL0N36mA==", "engines": { "node": ">=6.0.0" } @@ -4212,17 +4245,17 @@ } }, "node_modules/optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", "dev": true, "dependencies": { + "@aashutoshrathi/word-wrap": "^1.2.3", "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" + "type-check": "^0.4.0" }, "engines": { "node": ">= 0.8.0" @@ -4379,20 +4412,15 @@ } }, "node_modules/pretendo-grpc-ts": { - "version": "1.0.3", - "resolved": "git+ssh://git@github.com/PretendoNetwork/grpc-ts.git#8047b4d49c9a27cc7edd3786b465c3b0939b6181", - "hasInstallScript": true, + "name": "@pretendonetwork/grpc", + "version": "1.0.1", + "resolved": "git+ssh://git@github.com/PretendoNetwork/grpc-ts.git#f0c54a7c17617ca4da84a05f7c5c25e2708c2879", "license": "ISC", "dependencies": { "long": "^5.2.1", "protobufjs": "^7.2.3" } }, - "node_modules/pretendo-grpc-ts/node_modules/long": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", - "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==" - }, "node_modules/primitive-pool": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/primitive-pool/-/primitive-pool-1.1.0.tgz", @@ -4419,16 +4447,10 @@ "node": ">= 6.0.0" } }, - "node_modules/prompt/node_modules/async": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.3.tgz", - "integrity": "sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g==", - "dev": true - }, "node_modules/protobufjs": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.2.3.tgz", - "integrity": "sha512-TtpvOqwB5Gdz/PQmOjgsrGH1nHjAQVCN7JG4A6r1sXRWESL5rNMAiRcBQlCAdKxZcAbstExQePYG8xof/JVRgg==", + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.2.4.tgz", + "integrity": "sha512-AT+RJgD2sH8phPmCf7OUZR8xGdcJRga4+1cOaXJ64hvcSkVhNcRHOwIxUatPH15+nj59WAGTDv3LSGZPEQbJaQ==", "hasInstallScript": true, "dependencies": { "@protobufjs/aspromise": "^1.1.2", @@ -4448,11 +4470,6 @@ "node": ">=12.0.0" } }, - "node_modules/protobufjs/node_modules/long": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", - "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==" - }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -4675,11 +4692,11 @@ } }, "node_modules/resolve": { - "version": "1.22.2", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", - "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==", + "version": "1.22.4", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.4.tgz", + "integrity": "sha512-PXNdCiPqDqeUou+w1C2eTQbNfxKSuMxqTCuvlmmMsk1NWHL5fRrhY6Pl0qEYYc6+QqGClco1Qj8XnjPego4wfg==", "dependencies": { - "is-core-module": "^2.11.0", + "is-core-module": "^2.13.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, @@ -4832,9 +4849,9 @@ } }, "node_modules/semver": { - "version": "7.5.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz", - "integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dependencies": { "lru-cache": "^6.0.0" }, @@ -5180,9 +5197,9 @@ } }, "node_modules/stripe": { - "version": "12.9.0", - "resolved": "https://registry.npmjs.org/stripe/-/stripe-12.9.0.tgz", - "integrity": "sha512-stYtrWetRYUsEbsUVyJaPG9Sppt0ds2szBqXsuDG6KZPPuUmCccbpceLrhoOBwNl1RziEfNB7oG9wg1n2eW+EQ==", + "version": "12.18.0", + "resolved": "https://registry.npmjs.org/stripe/-/stripe-12.18.0.tgz", + "integrity": "sha512-cYjgBM2SY/dTm8Lr6eMyyONaHTZHA/QjHxFUIW5WH8FevSRIGAVtXEmBkUXF1fsqe7QvvRgQSGSJZmjDacegGg==", "dependencies": { "@types/node": ">=8.1.0", "qs": "^6.11.0" @@ -5543,9 +5560,9 @@ } }, "node_modules/validator": { - "version": "13.9.0", - "resolved": "https://registry.npmjs.org/validator/-/validator-13.9.0.tgz", - "integrity": "sha512-B+dGG8U3fdtM0/aNK4/X8CXq/EcxU2WPrPEkJGslb47qyHsxmbggTWK0yEA4qnYVNF+nxNlN88o14hIcPmSIEA==", + "version": "13.11.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.11.0.tgz", + "integrity": "sha512-Ii+sehpSfZy+At5nPdnyMhx78fEoPDkR2XW/zimHEL3MyGJQOCQ7WeP20jPYRz7ZCpcKLB21NxuXHF3bxjStBQ==", "engines": { "node": ">= 0.10" } @@ -5612,16 +5629,15 @@ } }, "node_modules/which-typed-array": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz", - "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==", + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.11.tgz", + "integrity": "sha512-qe9UWWpkeG5yzZ0tNYxDmd7vo58HDBc39mZ0xWWpolAGADdFOzkfamWLDxkOWcvHQKVmdTyQdLD4NOfjLWTKew==", "dependencies": { "available-typed-arrays": "^1.0.5", "call-bind": "^1.0.2", "for-each": "^0.3.3", "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0", - "is-typed-array": "^1.1.10" + "has-tostringtag": "^1.0.0" }, "engines": { "node": ">= 0.4" @@ -5674,9 +5690,9 @@ } }, "node_modules/word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", "engines": { "node": ">=0.10.0" } @@ -5808,9 +5824,9 @@ } }, "node_modules/zod": { - "version": "3.21.4", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.21.4.tgz", - "integrity": "sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw==", + "version": "3.22.2", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.2.tgz", + "integrity": "sha512-wvWkphh5WQsJbVk1tbx1l1Ly4yg+XecD+Mq280uBGt9wa5BKSWf4Mhp6GmrkPixhMxmabYY7RbzlwVP32pbGCg==", "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/package.json b/package.json index a94aaa7..3d47932 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ }, "homepage": "https://github.com/PretendoNetwork/account#readme", "dependencies": { + "@pretendonetwork/grpc": "^1.0.1", "aws-sdk": "^2.978.0", "bcrypt": "^5.0.0", "buffer-crc32": "^0.2.13", diff --git a/src/models/pnid.ts b/src/models/pnid.ts index 0b7cb79..4bead0b 100644 --- a/src/models/pnid.ts +++ b/src/models/pnid.ts @@ -10,6 +10,7 @@ import { DeviceSchema } from '@/models/device'; import { uploadCDNAsset } from '@/util'; import { LOG_ERROR, LOG_WARN } from '@/logger'; import { HydratedPNIDDocument, IPNID, IPNIDMethods, PNIDModel } from '@/types/mongoose/pnid'; +import { PNIDPermissionFlag } from '@/types/common/permission-flags'; import { config } from '@/config-manager'; let stripe: Stripe; @@ -26,6 +27,10 @@ const PNIDSchema = new Schema({ type: Boolean, default: false }, + permissions: { + type: BigInt, + default: 0n + }, access_level: { type: Number, default: 0 // 0: standard, 1: tester, 2: mod?, 3: dev @@ -273,4 +278,16 @@ PNIDSchema.method('scrub', async function scrub() { this.connections.stripe.latest_webhook_timestamp = ''; }); +PNIDSchema.method('hasPermission', function hasPermission(flag: PNIDPermissionFlag): boolean { + return (this.permissions & flag) === flag; +}); + +PNIDSchema.method('addPermission', function addPermission(flag: PNIDPermissionFlag): void { + this.permissions |= flag; +}); + +PNIDSchema.method('clearPermission', function clearPermission(flag: PNIDPermissionFlag): void { + this.permissions &= ~flag; +}); + export const PNID: PNIDModel = model('PNID', PNIDSchema); \ No newline at end of file diff --git a/src/services/grpc/account/get-nex-data.ts b/src/services/grpc/account/get-nex-data.ts index fbf12ff..1731620 100644 --- a/src/services/grpc/account/get-nex-data.ts +++ b/src/services/grpc/account/get-nex-data.ts @@ -1,5 +1,5 @@ import { Status, ServerError } from 'nice-grpc'; -import {GetNEXDataRequest,GetNEXDataResponse, DeepPartial } from 'pretendo-grpc-ts/dist/account/get_nex_data_rpc'; +import {GetNEXDataRequest,GetNEXDataResponse, DeepPartial } from '@pretendonetwork/grpc/account/get_nex_data_rpc'; import { NEXAccount } from '@/models/nex-account'; import { HydratedNEXAccountDocument } from '@/types/mongoose/nex-account'; diff --git a/src/services/grpc/account/get-nex-password.ts b/src/services/grpc/account/get-nex-password.ts index a7ed930..7a06886 100644 --- a/src/services/grpc/account/get-nex-password.ts +++ b/src/services/grpc/account/get-nex-password.ts @@ -1,5 +1,5 @@ import { Status, ServerError } from 'nice-grpc'; -import {GetNEXPasswordRequest,GetNEXPasswordResponse, DeepPartial } from 'pretendo-grpc-ts/dist/account/get_nex_password_rpc'; +import {GetNEXPasswordRequest,GetNEXPasswordResponse, DeepPartial } from '@pretendonetwork/grpc/account/get_nex_password_rpc'; import { NEXAccount } from '@/models/nex-account'; import { HydratedNEXAccountDocument } from '@/types/mongoose/nex-account'; diff --git a/src/services/grpc/account/get-user-data.ts b/src/services/grpc/account/get-user-data.ts index bc4c7fb..c1a9d9b 100644 --- a/src/services/grpc/account/get-user-data.ts +++ b/src/services/grpc/account/get-user-data.ts @@ -1,10 +1,11 @@ import { Status, ServerError } from 'nice-grpc'; -import { GetUserDataRequest, GetUserDataResponse, DeepPartial } from 'pretendo-grpc-ts/dist/account/get_user_data_rpc'; +import { GetUserDataRequest, GetUserDataResponse } from '@pretendonetwork/grpc/account/get_user_data_rpc'; import { getPNIDByPID } from '@/database'; import { HydratedPNIDDocument } from '@/types/mongoose/pnid'; +import { PNID_PERMISSION_FLAGS } from '@/types/common/permission-flags'; import { config } from '@/config-manager'; -export async function getUserData(request: GetUserDataRequest): Promise> { +export async function getUserData(request: GetUserDataRequest): Promise { const pnid: HydratedPNIDDocument | null = await getPNIDByPID(request.pid); if (!pnid) { @@ -31,6 +32,23 @@ export async function getUserData(request: GetUserDataRequest): Promise { + const pnid: HydratedPNIDDocument | null = await getPNIDByPID(request.pid); + + if (!pnid) { + throw new ServerError( + Status.INVALID_ARGUMENT, + 'No PNID found', + ); + } + + if (!request.permissions) { + throw new ServerError( + Status.INVALID_ARGUMENT, + 'Permissions flags not found', + ); + } + + if (request.permissions.bannedAllPermanently === true) { + await pnid.addPermission(PNID_PERMISSION_FLAGS.BANNED_ALL_PERMANENTLY); + } else if (request.permissions.bannedAllPermanently === false) { + await pnid.clearPermission(PNID_PERMISSION_FLAGS.BANNED_ALL_PERMANENTLY); + } + + if (request.permissions.bannedAllTemporarily === true) { + await pnid.addPermission(PNID_PERMISSION_FLAGS.BANNED_ALL_TEMPORARILY); + } else if (request.permissions.bannedAllTemporarily === false) { + await pnid.clearPermission(PNID_PERMISSION_FLAGS.BANNED_ALL_TEMPORARILY); + } + + if (request.permissions.betaAccess === true) { + await pnid.addPermission(PNID_PERMISSION_FLAGS.BETA_ACCESS); + } else if (request.permissions.betaAccess === false) { + await pnid.clearPermission(PNID_PERMISSION_FLAGS.BETA_ACCESS); + } + + if (request.permissions.accessAdminPanel === true) { + await pnid.addPermission(PNID_PERMISSION_FLAGS.ACCESS_ADMIN_PANEL); + } else if (request.permissions.accessAdminPanel === false) { + await pnid.clearPermission(PNID_PERMISSION_FLAGS.ACCESS_ADMIN_PANEL); + } + + if (request.permissions.createServerConfigs === true) { + await pnid.addPermission(PNID_PERMISSION_FLAGS.CREATE_SERVER_CONFIGS); + } else if (request.permissions.createServerConfigs === false) { + await pnid.clearPermission(PNID_PERMISSION_FLAGS.CREATE_SERVER_CONFIGS); + } + + if (request.permissions.modifyServerConfigs === true) { + await pnid.addPermission(PNID_PERMISSION_FLAGS.MODIFY_SERVER_CONFIGS); + } else if (request.permissions.modifyServerConfigs === false) { + await pnid.clearPermission(PNID_PERMISSION_FLAGS.MODIFY_SERVER_CONFIGS); + } + + if (request.permissions.deployServer === true) { + await pnid.addPermission(PNID_PERMISSION_FLAGS.DEPLOY_SERVER); + } else if (request.permissions.deployServer === false) { + await pnid.clearPermission(PNID_PERMISSION_FLAGS.DEPLOY_SERVER); + } + + if (request.permissions.modifyPnids === true) { + await pnid.addPermission(PNID_PERMISSION_FLAGS.MODIFY_PNIDS); + } else if (request.permissions.modifyPnids === false) { + await pnid.clearPermission(PNID_PERMISSION_FLAGS.MODIFY_PNIDS); + } + + if (request.permissions.modifyNexAccounts === true) { + await pnid.addPermission(PNID_PERMISSION_FLAGS.MODIFY_NEX_ACCOUNTS); + } else if (request.permissions.modifyNexAccounts === false) { + await pnid.clearPermission(PNID_PERMISSION_FLAGS.MODIFY_NEX_ACCOUNTS); + } + + if (request.permissions.modifyConsoles === true) { + await pnid.addPermission(PNID_PERMISSION_FLAGS.MODIFY_CONSOLES); + } else if (request.permissions.modifyConsoles === false) { + await pnid.clearPermission(PNID_PERMISSION_FLAGS.MODIFY_CONSOLES); + } + + if (request.permissions.banPnids === true) { + await pnid.addPermission(PNID_PERMISSION_FLAGS.BAN_PNIDS); + } else if (request.permissions.banPnids === false) { + await pnid.clearPermission(PNID_PERMISSION_FLAGS.BAN_PNIDS); + } + + if (request.permissions.banNexAccounts === true) { + await pnid.addPermission(PNID_PERMISSION_FLAGS.BAN_NEX_ACCOUNTS); + } else if (request.permissions.banNexAccounts === false) { + await pnid.clearPermission(PNID_PERMISSION_FLAGS.BAN_NEX_ACCOUNTS); + } + + if (request.permissions.banConsoles === true) { + await pnid.addPermission(PNID_PERMISSION_FLAGS.BAN_CONSOLES); + } else if (request.permissions.banConsoles === false) { + await pnid.clearPermission(PNID_PERMISSION_FLAGS.BAN_CONSOLES); + } + + if (request.permissions.moderateMiiverse === true) { + await pnid.addPermission(PNID_PERMISSION_FLAGS.MODERATE_MIIVERSE); + } else if (request.permissions.moderateMiiverse === false) { + await pnid.clearPermission(PNID_PERMISSION_FLAGS.MODERATE_MIIVERSE); + } + + if (request.permissions.createApiKeys === true) { + await pnid.addPermission(PNID_PERMISSION_FLAGS.CREATE_API_KEYS); + } else if (request.permissions.createApiKeys === false) { + await pnid.clearPermission(PNID_PERMISSION_FLAGS.CREATE_API_KEYS); + } + + await pnid.save(); + + return {}; +} \ No newline at end of file diff --git a/src/services/grpc/api/forgot-password.ts b/src/services/grpc/api/forgot-password.ts index d9a887f..7cdbd4f 100644 --- a/src/services/grpc/api/forgot-password.ts +++ b/src/services/grpc/api/forgot-password.ts @@ -1,9 +1,9 @@ import { Status, ServerError } from 'nice-grpc'; import validator from 'validator'; -import { ForgotPasswordRequest } from 'pretendo-grpc-ts/dist/api/forgot_password_rpc'; +import { ForgotPasswordRequest } from '@pretendonetwork/grpc/api/forgot_password_rpc'; import { getPNIDByEmailAddress, getPNIDByUsername } from '@/database'; import { sendForgotPasswordEmail } from '@/util'; -import type { Empty } from 'pretendo-grpc-ts/dist/api/google/protobuf/empty'; +import type { Empty } from '@pretendonetwork/grpc/api/google/protobuf/empty'; import type { HydratedPNIDDocument } from '@/types/mongoose/pnid'; export async function forgotPassword(request: ForgotPasswordRequest): Promise { diff --git a/src/services/grpc/api/get-user-data.ts b/src/services/grpc/api/get-user-data.ts index beeb92e..f87c4be 100644 --- a/src/services/grpc/api/get-user-data.ts +++ b/src/services/grpc/api/get-user-data.ts @@ -1,7 +1,7 @@ import { CallContext } from 'nice-grpc'; -import { GetUserDataResponse, DeepPartial } from 'pretendo-grpc-ts/dist/api/get_user_data_rpc'; +import { GetUserDataResponse, DeepPartial } from '@pretendonetwork/grpc/api/get_user_data_rpc'; import { config } from '@/config-manager'; -import type { Empty } from 'pretendo-grpc-ts/dist/api/google/protobuf/empty'; +import type { Empty } from '@pretendonetwork/grpc/api/google/protobuf/empty'; import type { AuthenticationCallContextExt } from '@/services/grpc/api/authentication-middleware'; import type { HydratedPNIDDocument } from '@/types/mongoose/pnid'; diff --git a/src/services/grpc/api/implementation.ts b/src/services/grpc/api/implementation.ts index 337c513..f606b5f 100644 --- a/src/services/grpc/api/implementation.ts +++ b/src/services/grpc/api/implementation.ts @@ -1,4 +1,4 @@ -import { APIServiceImplementation } from 'pretendo-grpc-ts/dist/api/api_service'; +import { APIServiceImplementation } from '@pretendonetwork/grpc/api/api_service'; import { register } from '@/services/grpc/api/register'; import { login } from '@/services/grpc/api/login'; import { getUserData } from '@/services/grpc/api/get-user-data'; diff --git a/src/services/grpc/api/login.ts b/src/services/grpc/api/login.ts index 6ff7d3b..043c65a 100644 --- a/src/services/grpc/api/login.ts +++ b/src/services/grpc/api/login.ts @@ -1,5 +1,5 @@ import { Status, ServerError } from 'nice-grpc'; -import { LoginRequest, LoginResponse, DeepPartial } from 'pretendo-grpc-ts/dist/api/login_rpc'; +import { LoginRequest, LoginResponse, DeepPartial } from '@pretendonetwork/grpc/api/login_rpc'; import bcrypt from 'bcrypt'; import { getPNIDByUsername, getPNIDByTokenAuth } from '@/database'; import { nintendoPasswordHash, generateToken} from '@/util'; diff --git a/src/services/grpc/api/register.ts b/src/services/grpc/api/register.ts index 8800d9c..db90da0 100644 --- a/src/services/grpc/api/register.ts +++ b/src/services/grpc/api/register.ts @@ -1,7 +1,7 @@ import crypto from 'node:crypto'; import { Status, ServerError } from 'nice-grpc'; -import { RegisterRequest, DeepPartial } from 'pretendo-grpc-ts/dist/api/register_rpc'; -import { LoginResponse } from 'pretendo-grpc-ts/dist/api/login_rpc'; +import { RegisterRequest, DeepPartial } from '@pretendonetwork/grpc/api/register_rpc'; +import { LoginResponse } from '@pretendonetwork/grpc/api/login_rpc'; import emailvalidator from 'email-validator'; import bcrypt from 'bcrypt'; import moment from 'moment'; diff --git a/src/services/grpc/api/reset-password.ts b/src/services/grpc/api/reset-password.ts index d01111c..c046edb 100644 --- a/src/services/grpc/api/reset-password.ts +++ b/src/services/grpc/api/reset-password.ts @@ -1,9 +1,9 @@ import bcrypt from 'bcrypt'; import { Status, ServerError } from 'nice-grpc'; -import { ResetPasswordRequest } from 'pretendo-grpc-ts/dist/api/reset_password_rpc'; +import { ResetPasswordRequest } from '@pretendonetwork/grpc/api/reset_password_rpc'; import { decryptToken, unpackToken, nintendoPasswordHash } from '@/util'; import { getPNIDByPID } from '@/database'; -import type { Empty } from 'pretendo-grpc-ts/dist/api/google/protobuf/empty'; +import type { Empty } from '@pretendonetwork/grpc/api/google/protobuf/empty'; import type { Token } from '@/types/common/token'; import type { HydratedPNIDDocument } from '@/types/mongoose/pnid'; diff --git a/src/services/grpc/api/set-discord-connection-data.ts b/src/services/grpc/api/set-discord-connection-data.ts index 2f3dbed..0d93bca 100644 --- a/src/services/grpc/api/set-discord-connection-data.ts +++ b/src/services/grpc/api/set-discord-connection-data.ts @@ -1,6 +1,6 @@ import { Status, ServerError, CallContext } from 'nice-grpc'; -import { SetDiscordConnectionDataRequest } from 'pretendo-grpc-ts/dist/api/set_discord_connection_data_rpc'; -import type { Empty } from 'pretendo-grpc-ts/dist/api/google/protobuf/empty'; +import { SetDiscordConnectionDataRequest } from '@pretendonetwork/grpc/api/set_discord_connection_data_rpc'; +import type { Empty } from '@pretendonetwork/grpc/api/google/protobuf/empty'; import type { AuthenticationCallContextExt } from '@/services/grpc/api/authentication-middleware'; import type { HydratedPNIDDocument } from '@/types/mongoose/pnid'; diff --git a/src/services/grpc/api/set-stripe-connection-data.ts b/src/services/grpc/api/set-stripe-connection-data.ts index 238bc08..b13db56 100644 --- a/src/services/grpc/api/set-stripe-connection-data.ts +++ b/src/services/grpc/api/set-stripe-connection-data.ts @@ -1,7 +1,7 @@ import { Status, ServerError, CallContext } from 'nice-grpc'; -import { SetStripeConnectionDataRequest } from 'pretendo-grpc-ts/dist/api/set_stripe_connection_data_rpc'; +import { SetStripeConnectionDataRequest } from '@pretendonetwork/grpc/api/set_stripe_connection_data_rpc'; import { PNID } from '@/models/pnid'; -import type { Empty } from 'pretendo-grpc-ts/dist/api/google/protobuf/empty'; +import type { Empty } from '@pretendonetwork/grpc/api/google/protobuf/empty'; import type { AuthenticationCallContextExt } from '@/services/grpc/api/authentication-middleware'; import type { HydratedPNIDDocument } from '@/types/mongoose/pnid'; diff --git a/src/services/grpc/api/update-user-data.ts b/src/services/grpc/api/update-user-data.ts index 3747144..38777de 100644 --- a/src/services/grpc/api/update-user-data.ts +++ b/src/services/grpc/api/update-user-data.ts @@ -1,6 +1,6 @@ import { CallContext } from 'nice-grpc'; -import { UpdateUserDataRequest, DeepPartial } from 'pretendo-grpc-ts/dist/api/update_user_data_rpc'; -import { GetUserDataResponse } from 'pretendo-grpc-ts/dist/api/get_user_data_rpc'; +import { UpdateUserDataRequest, DeepPartial } from '@pretendonetwork/grpc/api/update_user_data_rpc'; +import { GetUserDataResponse } from '@pretendonetwork/grpc/api/get_user_data_rpc'; import { config } from '@/config-manager'; import type { HydratedPNIDDocument } from '@/types/mongoose/pnid'; import type { AuthenticationCallContextExt } from '@/services/grpc/api/authentication-middleware'; diff --git a/src/services/grpc/server.ts b/src/services/grpc/server.ts index 5e4bc8d..af03741 100644 --- a/src/services/grpc/server.ts +++ b/src/services/grpc/server.ts @@ -1,6 +1,6 @@ import { createServer, Server } from 'nice-grpc'; -import { AccountDefinition } from 'pretendo-grpc-ts/dist/account/account_service'; -import { APIDefinition } from 'pretendo-grpc-ts/dist/api/api_service'; +import { AccountDefinition } from '@pretendonetwork/grpc/account/account_service'; +import { APIDefinition } from '@pretendonetwork/grpc/api/api_service'; import { apiKeyMiddleware as accountApiKeyMiddleware } from '@/services/grpc/account/api-key-middleware'; import { apiKeyMiddleware as apiApiKeyMiddleware } from '@/services/grpc/api/api-key-middleware'; diff --git a/src/types/common/permission-flags.ts b/src/types/common/permission-flags.ts new file mode 100644 index 0000000..73de404 --- /dev/null +++ b/src/types/common/permission-flags.ts @@ -0,0 +1,19 @@ +export const PNID_PERMISSION_FLAGS = { + BANNED_ALL_PERMANENTLY: 1n << 0n, + BANNED_ALL_TEMPORARILY: 1n << 1n, + BETA_ACCESS: 1n << 2n, + ACCESS_ADMIN_PANEL: 1n << 3n, + CREATE_SERVER_CONFIGS: 1n << 4n, + MODIFY_SERVER_CONFIGS: 1n << 5n, + DEPLOY_SERVER: 1n << 6n, + MODIFY_PNIDS: 1n << 7n, + MODIFY_NEX_ACCOUNTS: 1n << 8n, + MODIFY_CONSOLES: 1n << 9n, + BAN_PNIDS: 1n << 10n, + BAN_NEX_ACCOUNTS: 1n << 11n, + BAN_CONSOLES: 1n << 12n, + MODERATE_MIIVERSE: 1n << 13n, + CREATE_API_KEYS: 1n << 14n, // * This applies to all services +} as const; + +export type PNIDPermissionFlag = (typeof PNID_PERMISSION_FLAGS)[keyof typeof PNID_PERMISSION_FLAGS]; \ No newline at end of file diff --git a/src/types/mongoose/pnid.ts b/src/types/mongoose/pnid.ts index 5b5be65..37ab267 100644 --- a/src/types/mongoose/pnid.ts +++ b/src/types/mongoose/pnid.ts @@ -1,8 +1,10 @@ import { Model, Types, HydratedDocument } from 'mongoose'; import { IDevice } from '@/types/mongoose/device'; +import { PNIDPermissionFlag } from '@/types/common/permission-flags'; export interface IPNID { deleted: boolean; + permissions: bigint; access_level: number; server_access_level: string; pid: number; @@ -80,6 +82,9 @@ export interface IPNIDMethods { updateMii(mii: { name: string, primary: string, data: string}): Promise; generateMiiImages(): Promise; scrub(): Promise; + hasPermission(flag: PNIDPermissionFlag): boolean; + addPermission(flag: PNIDPermissionFlag): void; + clearPermission(flag: PNIDPermissionFlag): void; } interface IPNIDQueryHelpers {} From 7624263d46b650d9234b3481f323019affedd143 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Sat, 19 Aug 2023 09:48:37 -0400 Subject: [PATCH 130/219] Removed old gRPC package from package.json --- package-lock.json | 11 ----------- package.json | 1 - 2 files changed, 12 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9ce47af..1c21acf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -35,7 +35,6 @@ "nice-grpc": "^2.1.4", "node-rsa": "^1.0.7", "nodemailer": "^6.4.2", - "pretendo-grpc-ts": "github:PretendoNetwork/grpc-ts", "redis": "^4.3.1", "stripe": "^12.3.0", "tga": "^1.0.4", @@ -4411,16 +4410,6 @@ "node": ">= 0.8.0" } }, - "node_modules/pretendo-grpc-ts": { - "name": "@pretendonetwork/grpc", - "version": "1.0.1", - "resolved": "git+ssh://git@github.com/PretendoNetwork/grpc-ts.git#f0c54a7c17617ca4da84a05f7c5c25e2708c2879", - "license": "ISC", - "dependencies": { - "long": "^5.2.1", - "protobufjs": "^7.2.3" - } - }, "node_modules/primitive-pool": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/primitive-pool/-/primitive-pool-1.1.0.tgz", diff --git a/package.json b/package.json index 3d47932..7129596 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,6 @@ "nice-grpc": "^2.1.4", "node-rsa": "^1.0.7", "nodemailer": "^6.4.2", - "pretendo-grpc-ts": "github:PretendoNetwork/grpc-ts", "redis": "^4.3.1", "stripe": "^12.3.0", "tga": "^1.0.4", From b998f754a85bfa20032e34af01972f7f4f009be9 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Fri, 25 Aug 2023 20:55:37 -0400 Subject: [PATCH 131/219] Added support for BOSS permissions --- package-lock.json | 8 +-- package.json | 2 +- .../account/exchange-token-for-user-data.ts | 62 +++++++++++++++++++ src/services/grpc/account/get-user-data.ts | 8 ++- src/services/grpc/account/implementation.ts | 4 +- src/types/common/permission-flags.ts | 6 ++ 6 files changed, 83 insertions(+), 7 deletions(-) create mode 100644 src/services/grpc/account/exchange-token-for-user-data.ts diff --git a/package-lock.json b/package-lock.json index 1c21acf..23b4daf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "2.0.0", "license": "ISC", "dependencies": { - "@pretendonetwork/grpc": "^1.0.1", + "@pretendonetwork/grpc": "^1.0.2", "aws-sdk": "^2.978.0", "bcrypt": "^5.0.0", "buffer-crc32": "^0.2.13", @@ -400,9 +400,9 @@ } }, "node_modules/@pretendonetwork/grpc": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@pretendonetwork/grpc/-/grpc-1.0.1.tgz", - "integrity": "sha512-qNsZ//+U22KYTYZyjb4J2CnGUyGfYF35bG1D334WNvq4M2ubvVutWZp98ur5/fVW3g5Af6Eq2IuBwFO9xRhQ1w==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@pretendonetwork/grpc/-/grpc-1.0.2.tgz", + "integrity": "sha512-g78VP+/wY4VIlQ5EXP5lPzXK1uVflrM3ZA0PW1NZi88EaJRqNPVkYmb6iW+FM0j/lxlu07UcwqLJSHsy9eU01Q==", "dependencies": { "long": "^5.2.1", "protobufjs": "^7.2.3" diff --git a/package.json b/package.json index 7129596..bd38d9c 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ }, "homepage": "https://github.com/PretendoNetwork/account#readme", "dependencies": { - "@pretendonetwork/grpc": "^1.0.1", + "@pretendonetwork/grpc": "^1.0.2", "aws-sdk": "^2.978.0", "bcrypt": "^5.0.0", "buffer-crc32": "^0.2.13", diff --git a/src/services/grpc/account/exchange-token-for-user-data.ts b/src/services/grpc/account/exchange-token-for-user-data.ts new file mode 100644 index 0000000..386ff64 --- /dev/null +++ b/src/services/grpc/account/exchange-token-for-user-data.ts @@ -0,0 +1,62 @@ +import { Status, ServerError } from 'nice-grpc'; +import { ExchangeTokenForUserDataRequest } from '@pretendonetwork/grpc/account/exchange_token_for_user_data'; +import { GetUserDataResponse } from '@pretendonetwork/grpc/account/get_user_data_rpc'; +import { getPNIDByTokenAuth } from '@/database'; +import { HydratedPNIDDocument } from '@/types/mongoose/pnid'; +import { PNID_PERMISSION_FLAGS } from '@/types/common/permission-flags'; +import { config } from '@/config-manager'; + +export async function exchangeTokenForUserData(request: ExchangeTokenForUserDataRequest): Promise { + if (!request.token.trim()) { + throw new ServerError(Status.INVALID_ARGUMENT, 'Invalid token'); + } + + const pnid: HydratedPNIDDocument | null = await getPNIDByTokenAuth(request.token); + + if (!pnid) { + throw new ServerError(Status.INVALID_ARGUMENT, 'Invalid token'); + } + + return { + deleted: pnid.deleted, + pid: pnid.pid, + username: pnid.username, + accessLevel: pnid.access_level, + serverAccessLevel: pnid.server_access_level, + mii: { + name: pnid.mii.name, + data: pnid.mii.data, + url: `${config.cdn.base_url}/mii/${pnid.pid}/standard.tga`, + }, + creationDate: pnid.creation_date, + birthdate: pnid.birthdate, + gender: pnid.gender, + country: pnid.country, + language: pnid.language, + emailAddress: pnid.email.address, + tierName: pnid.connections.stripe.tier_name, + permissions: { + bannedAllPermanently: pnid.hasPermission(PNID_PERMISSION_FLAGS.BANNED_ALL_PERMANENTLY), + bannedAllTemporarily: pnid.hasPermission(PNID_PERMISSION_FLAGS.BANNED_ALL_TEMPORARILY), + betaAccess: pnid.hasPermission(PNID_PERMISSION_FLAGS.BETA_ACCESS), + accessAdminPanel: pnid.hasPermission(PNID_PERMISSION_FLAGS.ACCESS_ADMIN_PANEL), + createServerConfigs: pnid.hasPermission(PNID_PERMISSION_FLAGS.CREATE_SERVER_CONFIGS), + modifyServerConfigs: pnid.hasPermission(PNID_PERMISSION_FLAGS.MODIFY_SERVER_CONFIGS), + deployServer: pnid.hasPermission(PNID_PERMISSION_FLAGS.DEPLOY_SERVER), + modifyPnids: pnid.hasPermission(PNID_PERMISSION_FLAGS.MODIFY_PNIDS), + modifyNexAccounts: pnid.hasPermission(PNID_PERMISSION_FLAGS.MODIFY_NEX_ACCOUNTS), + modifyConsoles: pnid.hasPermission(PNID_PERMISSION_FLAGS.MODIFY_CONSOLES), + banPnids: pnid.hasPermission(PNID_PERMISSION_FLAGS.BAN_PNIDS), + banNexAccounts: pnid.hasPermission(PNID_PERMISSION_FLAGS.BAN_NEX_ACCOUNTS), + banConsoles: pnid.hasPermission(PNID_PERMISSION_FLAGS.BAN_CONSOLES), + moderateMiiverse: pnid.hasPermission(PNID_PERMISSION_FLAGS.MODERATE_MIIVERSE), + createApiKeys: pnid.hasPermission(PNID_PERMISSION_FLAGS.CREATE_API_KEYS), + createBossTasks: pnid.hasPermission(PNID_PERMISSION_FLAGS.CREATE_BOSS_TASKS), + updateBossTasks: pnid.hasPermission(PNID_PERMISSION_FLAGS.UPDATE_BOSS_TASKS), + deleteBossTasks: pnid.hasPermission(PNID_PERMISSION_FLAGS.DELETE_BOSS_TASKS), + uploadBossFiles: pnid.hasPermission(PNID_PERMISSION_FLAGS.UPLOAD_BOSS_FILES), + updateBossFiles: pnid.hasPermission(PNID_PERMISSION_FLAGS.UPDATE_BOSS_FILES), + deleteBossFiles: pnid.hasPermission(PNID_PERMISSION_FLAGS.DELETE_BOSS_FILES) + } + }; +} \ No newline at end of file diff --git a/src/services/grpc/account/get-user-data.ts b/src/services/grpc/account/get-user-data.ts index c1a9d9b..3a7709b 100644 --- a/src/services/grpc/account/get-user-data.ts +++ b/src/services/grpc/account/get-user-data.ts @@ -48,7 +48,13 @@ export async function getUserData(request: GetUserDataRequest): Promise Date: Fri, 25 Aug 2023 21:06:23 -0400 Subject: [PATCH 132/219] Fix BigInt and Number conflicts in grpc API --- src/services/grpc/api/get-user-data.ts | 2 +- src/services/grpc/api/set-stripe-connection-data.ts | 2 +- src/services/grpc/api/update-user-data.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/services/grpc/api/get-user-data.ts b/src/services/grpc/api/get-user-data.ts index f87c4be..33539a7 100644 --- a/src/services/grpc/api/get-user-data.ts +++ b/src/services/grpc/api/get-user-data.ts @@ -38,7 +38,7 @@ export async function getUserData(_request: Empty, context: CallContext & Authen priceId: pnid.connections.stripe.price_id, tierLevel: pnid.connections.stripe.tier_level, tierName: pnid.connections.stripe.tier_name, - latestWebhookTimestamp: pnid.connections.stripe.latest_webhook_timestamp + latestWebhookTimestamp: BigInt(pnid.connections.stripe.latest_webhook_timestamp) } }, marketingFlag: pnid.flags.marketing diff --git a/src/services/grpc/api/set-stripe-connection-data.ts b/src/services/grpc/api/set-stripe-connection-data.ts index b13db56..0994848 100644 --- a/src/services/grpc/api/set-stripe-connection-data.ts +++ b/src/services/grpc/api/set-stripe-connection-data.ts @@ -21,7 +21,7 @@ export async function setStripeConnectionData(request: SetStripeConnectionDataRe const pnid: HydratedPNIDDocument = context.pnid!; const updateData: StripeMongoUpdateScheme = { - 'connections.stripe.latest_webhook_timestamp': request.timestamp + 'connections.stripe.latest_webhook_timestamp': Number(request.timestamp) }; if (request.customerId && !pnid.connections.stripe.customer_id) { diff --git a/src/services/grpc/api/update-user-data.ts b/src/services/grpc/api/update-user-data.ts index 38777de..ae90135 100644 --- a/src/services/grpc/api/update-user-data.ts +++ b/src/services/grpc/api/update-user-data.ts @@ -40,7 +40,7 @@ export async function updateUserData(_request: UpdateUserDataRequest, context: C priceId: pnid.connections.stripe.price_id, tierLevel: pnid.connections.stripe.tier_level, tierName: pnid.connections.stripe.tier_name, - latestWebhookTimestamp: pnid.connections.stripe.latest_webhook_timestamp + latestWebhookTimestamp: BigInt(pnid.connections.stripe.latest_webhook_timestamp) } }, marketingFlag: pnid.flags.marketing From 5e49ea86661c070d620adc2c3e92c5a9d061f889 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20L=C3=B3pez=20Guimaraes?= Date: Mon, 28 Aug 2023 17:58:32 +0100 Subject: [PATCH 133/219] Check for undefined on latest_webhook_timestamp Not all accounts are guaranteed to have this field, so we have to check if it exists. If it doesn't, just set the value to 0. --- src/services/grpc/api/get-user-data.ts | 4 ++-- src/services/grpc/api/update-user-data.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/services/grpc/api/get-user-data.ts b/src/services/grpc/api/get-user-data.ts index 33539a7..6682c79 100644 --- a/src/services/grpc/api/get-user-data.ts +++ b/src/services/grpc/api/get-user-data.ts @@ -38,9 +38,9 @@ export async function getUserData(_request: Empty, context: CallContext & Authen priceId: pnid.connections.stripe.price_id, tierLevel: pnid.connections.stripe.tier_level, tierName: pnid.connections.stripe.tier_name, - latestWebhookTimestamp: BigInt(pnid.connections.stripe.latest_webhook_timestamp) + latestWebhookTimestamp: BigInt(pnid.connections.stripe.latest_webhook_timestamp ?? 0) } }, marketingFlag: pnid.flags.marketing }; -} \ No newline at end of file +} diff --git a/src/services/grpc/api/update-user-data.ts b/src/services/grpc/api/update-user-data.ts index ae90135..bb82887 100644 --- a/src/services/grpc/api/update-user-data.ts +++ b/src/services/grpc/api/update-user-data.ts @@ -40,9 +40,9 @@ export async function updateUserData(_request: UpdateUserDataRequest, context: C priceId: pnid.connections.stripe.price_id, tierLevel: pnid.connections.stripe.tier_level, tierName: pnid.connections.stripe.tier_name, - latestWebhookTimestamp: BigInt(pnid.connections.stripe.latest_webhook_timestamp) + latestWebhookTimestamp: BigInt(pnid.connections.stripe.latest_webhook_timestamp ?? 0) } }, marketingFlag: pnid.flags.marketing }; -} \ No newline at end of file +} From 6a880e890825010c980e5b87343010cacc3807b6 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Fri, 17 Nov 2023 09:59:18 -0500 Subject: [PATCH 134/219] added UPDATE_PNID_PERMISSIONS PNID permission --- src/types/common/permission-flags.ts | 43 ++++++++++++++-------------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/src/types/common/permission-flags.ts b/src/types/common/permission-flags.ts index da73404..7c22211 100644 --- a/src/types/common/permission-flags.ts +++ b/src/types/common/permission-flags.ts @@ -1,25 +1,26 @@ export const PNID_PERMISSION_FLAGS = { - BANNED_ALL_PERMANENTLY: 1n << 0n, - BANNED_ALL_TEMPORARILY: 1n << 1n, - BETA_ACCESS: 1n << 2n, - ACCESS_ADMIN_PANEL: 1n << 3n, - CREATE_SERVER_CONFIGS: 1n << 4n, - MODIFY_SERVER_CONFIGS: 1n << 5n, - DEPLOY_SERVER: 1n << 6n, - MODIFY_PNIDS: 1n << 7n, - MODIFY_NEX_ACCOUNTS: 1n << 8n, - MODIFY_CONSOLES: 1n << 9n, - BAN_PNIDS: 1n << 10n, - BAN_NEX_ACCOUNTS: 1n << 11n, - BAN_CONSOLES: 1n << 12n, - MODERATE_MIIVERSE: 1n << 13n, - CREATE_API_KEYS: 1n << 14n, // * This applies to all services - CREATE_BOSS_TASKS: 1n << 15n, - UPDATE_BOSS_TASKS: 1n << 16n, - DELETE_BOSS_TASKS: 1n << 17n, - UPLOAD_BOSS_FILES: 1n << 18n, - UPDATE_BOSS_FILES: 1n << 18n, - DELETE_BOSS_FILES: 1n << 19n + BANNED_ALL_PERMANENTLY: 1n << 0n, + BANNED_ALL_TEMPORARILY: 1n << 1n, + BETA_ACCESS: 1n << 2n, + ACCESS_ADMIN_PANEL: 1n << 3n, + CREATE_SERVER_CONFIGS: 1n << 4n, + MODIFY_SERVER_CONFIGS: 1n << 5n, + DEPLOY_SERVER: 1n << 6n, + MODIFY_PNIDS: 1n << 7n, + MODIFY_NEX_ACCOUNTS: 1n << 8n, + MODIFY_CONSOLES: 1n << 9n, + BAN_PNIDS: 1n << 10n, + BAN_NEX_ACCOUNTS: 1n << 11n, + BAN_CONSOLES: 1n << 12n, + MODERATE_MIIVERSE: 1n << 13n, + CREATE_API_KEYS: 1n << 14n, // * This applies to all services + CREATE_BOSS_TASKS: 1n << 15n, + UPDATE_BOSS_TASKS: 1n << 16n, + DELETE_BOSS_TASKS: 1n << 17n, + UPLOAD_BOSS_FILES: 1n << 18n, + UPDATE_BOSS_FILES: 1n << 18n, + DELETE_BOSS_FILES: 1n << 19n, + UPDATE_PNID_PERMISSIONS: 1n << 20n } as const; export type PNIDPermissionFlag = (typeof PNID_PERMISSION_FLAGS)[keyof typeof PNID_PERMISSION_FLAGS]; \ No newline at end of file From a6c1bd09eeb0d29c13c1f4f84bdf87919b48ff6a Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Fri, 17 Nov 2023 10:08:21 -0500 Subject: [PATCH 135/219] grpc: updated updatePNIDPermissions to latest permissions --- package-lock.json | 8 ++-- package.json | 2 +- .../grpc/account/update-pnid-permissions.ts | 42 +++++++++++++++++++ 3 files changed, 47 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 23b4daf..b2a7a69 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "2.0.0", "license": "ISC", "dependencies": { - "@pretendonetwork/grpc": "^1.0.2", + "@pretendonetwork/grpc": "^1.0.5", "aws-sdk": "^2.978.0", "bcrypt": "^5.0.0", "buffer-crc32": "^0.2.13", @@ -400,9 +400,9 @@ } }, "node_modules/@pretendonetwork/grpc": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@pretendonetwork/grpc/-/grpc-1.0.2.tgz", - "integrity": "sha512-g78VP+/wY4VIlQ5EXP5lPzXK1uVflrM3ZA0PW1NZi88EaJRqNPVkYmb6iW+FM0j/lxlu07UcwqLJSHsy9eU01Q==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@pretendonetwork/grpc/-/grpc-1.0.5.tgz", + "integrity": "sha512-l5aB6R7Z2RcX8ds0LIdF7HxW5vMmKdN02G43o+RmUTPCs7ONkwvIL6hIqRAxxNXf4Mz6QYwuTXK96VKWbDgqaQ==", "dependencies": { "long": "^5.2.1", "protobufjs": "^7.2.3" diff --git a/package.json b/package.json index bd38d9c..2a76cb8 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ }, "homepage": "https://github.com/PretendoNetwork/account#readme", "dependencies": { - "@pretendonetwork/grpc": "^1.0.2", + "@pretendonetwork/grpc": "^1.0.5", "aws-sdk": "^2.978.0", "bcrypt": "^5.0.0", "buffer-crc32": "^0.2.13", diff --git a/src/services/grpc/account/update-pnid-permissions.ts b/src/services/grpc/account/update-pnid-permissions.ts index de9b2d2..e899021 100644 --- a/src/services/grpc/account/update-pnid-permissions.ts +++ b/src/services/grpc/account/update-pnid-permissions.ts @@ -112,6 +112,48 @@ export async function updatePNIDPermissions(request: UpdatePNIDPermissionsReques await pnid.clearPermission(PNID_PERMISSION_FLAGS.CREATE_API_KEYS); } + if (request.permissions.createBossTasks === true) { + await pnid.addPermission(PNID_PERMISSION_FLAGS.CREATE_BOSS_TASKS); + } else if (request.permissions.createBossTasks === false) { + await pnid.clearPermission(PNID_PERMISSION_FLAGS.CREATE_BOSS_TASKS); + } + + if (request.permissions.updateBossTasks === true) { + await pnid.addPermission(PNID_PERMISSION_FLAGS.UPDATE_BOSS_TASKS); + } else if (request.permissions.updateBossTasks === false) { + await pnid.clearPermission(PNID_PERMISSION_FLAGS.UPDATE_BOSS_TASKS); + } + + if (request.permissions.deleteBossTasks === true) { + await pnid.addPermission(PNID_PERMISSION_FLAGS.DELETE_BOSS_TASKS); + } else if (request.permissions.deleteBossTasks === false) { + await pnid.clearPermission(PNID_PERMISSION_FLAGS.DELETE_BOSS_TASKS); + } + + if (request.permissions.uploadBossFiles === true) { + await pnid.addPermission(PNID_PERMISSION_FLAGS.UPLOAD_BOSS_FILES); + } else if (request.permissions.uploadBossFiles === false) { + await pnid.clearPermission(PNID_PERMISSION_FLAGS.UPLOAD_BOSS_FILES); + } + + if (request.permissions.updateBossFiles === true) { + await pnid.addPermission(PNID_PERMISSION_FLAGS.UPDATE_BOSS_FILES); + } else if (request.permissions.updateBossFiles === false) { + await pnid.clearPermission(PNID_PERMISSION_FLAGS.UPDATE_BOSS_FILES); + } + + if (request.permissions.deleteBossFiles === true) { + await pnid.addPermission(PNID_PERMISSION_FLAGS.DELETE_BOSS_FILES); + } else if (request.permissions.deleteBossFiles === false) { + await pnid.clearPermission(PNID_PERMISSION_FLAGS.DELETE_BOSS_FILES); + } + + if (request.permissions.updatePnidPermissions === true) { + await pnid.addPermission(PNID_PERMISSION_FLAGS.UPDATE_PNID_PERMISSIONS); + } else if (request.permissions.updatePnidPermissions === false) { + await pnid.clearPermission(PNID_PERMISSION_FLAGS.UPDATE_PNID_PERMISSIONS); + } + await pnid.save(); return {}; From 7d3b7bbd9c33720df35fadc7f99c68843aed847e Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Fri, 17 Nov 2023 10:09:26 -0500 Subject: [PATCH 136/219] updated some packages --- package-lock.json | 65 ++++++++++++++++++++++------------------------- 1 file changed, 31 insertions(+), 34 deletions(-) diff --git a/package-lock.json b/package-lock.json index b2a7a69..c85f6d7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -248,6 +248,15 @@ "node-pre-gyp": "bin/node-pre-gyp" } }, + "node_modules/@mongodb-js/saslprep": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.1.tgz", + "integrity": "sha512-t7c5K033joZZMspnHg/gWPE4kandgc2OxE74aYOtGKfgB9VPuVJPix0H6fhmm2erj5PBJ21mqcx34lpIGtUCsQ==", + "optional": true, + "dependencies": { + "sparse-bitfield": "^3.0.3" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -792,9 +801,9 @@ "dev": true }, "node_modules/@types/webidl-conversions": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.0.tgz", - "integrity": "sha512-xTE1E+YF4aWPJJeUzaZI5DRntlkY3+BCVJi0axFptnjGmAoWxkyREIh/XMrfxVLejwQxMCfDXdICo0VLxThrog==" + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz", + "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==" }, "node_modules/@types/whatwg-url": { "version": "8.2.2", @@ -1423,9 +1432,9 @@ } }, "node_modules/bson": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/bson/-/bson-5.4.0.tgz", - "integrity": "sha512-WRZ5SQI5GfUuKnPTNmAYPiKIof3ORXAF4IRU5UcgmivNIon01rWQlw5RUH954dpu8yGL8T59YShVddIPaU/gFA==", + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/bson/-/bson-5.5.1.tgz", + "integrity": "sha512-ix0EwukN2EpC0SRWIj/7B5+A6uQMQy6KMREI9qQqvgpkV2frH63T0UDVd1SYedL6dNCmDBYB3QtXi4ISk9YT+g==", "engines": { "node": ">=14.20.1" } @@ -3872,11 +3881,11 @@ } }, "node_modules/mongodb": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-5.7.0.tgz", - "integrity": "sha512-zm82Bq33QbqtxDf58fLWBwTjARK3NSvKYjyz997KSy6hpat0prjeX/kxjbPVyZY60XYPDNETaHkHJI2UCzSLuw==", + "version": "5.9.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-5.9.0.tgz", + "integrity": "sha512-g+GCMHN1CoRUA+wb1Agv0TI4YTSiWr42B5ulkiAfLLHitGK1R+PkSAf3Lr5rPZwi/3F04LiaZEW0Kxro9Fi2TA==", "dependencies": { - "bson": "^5.4.0", + "bson": "^5.5.0", "mongodb-connection-string-url": "^2.6.0", "socks": "^2.7.1" }, @@ -3884,12 +3893,12 @@ "node": ">=14.20.1" }, "optionalDependencies": { - "saslprep": "^1.0.3" + "@mongodb-js/saslprep": "^1.1.0" }, "peerDependencies": { - "@aws-sdk/credential-providers": "^3.201.0", - "@mongodb-js/zstd": "^1.1.0", - "kerberos": "^2.0.1", + "@aws-sdk/credential-providers": "^3.188.0", + "@mongodb-js/zstd": "^1.0.0", + "kerberos": "^1.0.0 || ^2.0.0", "mongodb-client-encryption": ">=2.3.0 <3", "snappy": "^7.2.2" }, @@ -3921,13 +3930,13 @@ } }, "node_modules/mongoose": { - "version": "7.4.3", - "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-7.4.3.tgz", - "integrity": "sha512-eok0lW6mZJHK2vVSWyJb9tUfPMUuRF3h7YC4pU2K2/YSZBlNDUwvKsHgftMOANbokP2Ry+4ylvzAdW4KjkRFjw==", + "version": "7.6.5", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-7.6.5.tgz", + "integrity": "sha512-ElHgGWVKQUawKBn0DXuHmSd3W5w5Kb8JUbDNQH30odhYCDKq9GCh+E1/SuN8jZGxrHgFyLrvYxLSpC36BpqS+w==", "dependencies": { - "bson": "^5.4.0", + "bson": "^5.5.0", "kareem": "2.5.1", - "mongodb": "5.7.0", + "mongodb": "5.9.0", "mpath": "0.9.0", "mquery": "5.0.0", "ms": "2.1.3", @@ -4806,18 +4815,6 @@ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, - "node_modules/saslprep": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.3.tgz", - "integrity": "sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==", - "optional": true, - "dependencies": { - "sparse-bitfield": "^3.0.3" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/sax": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz", @@ -5813,9 +5810,9 @@ } }, "node_modules/zod": { - "version": "3.22.2", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.2.tgz", - "integrity": "sha512-wvWkphh5WQsJbVk1tbx1l1Ly4yg+XecD+Mq280uBGt9wa5BKSWf4Mhp6GmrkPixhMxmabYY7RbzlwVP32pbGCg==", + "version": "3.22.4", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.4.tgz", + "integrity": "sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==", "funding": { "url": "https://github.com/sponsors/colinhacks" } From 80f457c2f780208434e01826a762eccd2f932053 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Fri, 17 Nov 2023 10:19:55 -0500 Subject: [PATCH 137/219] grpc: updated account details responses --- src/services/grpc/account/exchange-token-for-user-data.ts | 3 ++- src/services/grpc/account/get-user-data.ts | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/services/grpc/account/exchange-token-for-user-data.ts b/src/services/grpc/account/exchange-token-for-user-data.ts index 386ff64..164a6a3 100644 --- a/src/services/grpc/account/exchange-token-for-user-data.ts +++ b/src/services/grpc/account/exchange-token-for-user-data.ts @@ -56,7 +56,8 @@ export async function exchangeTokenForUserData(request: ExchangeTokenForUserData deleteBossTasks: pnid.hasPermission(PNID_PERMISSION_FLAGS.DELETE_BOSS_TASKS), uploadBossFiles: pnid.hasPermission(PNID_PERMISSION_FLAGS.UPLOAD_BOSS_FILES), updateBossFiles: pnid.hasPermission(PNID_PERMISSION_FLAGS.UPDATE_BOSS_FILES), - deleteBossFiles: pnid.hasPermission(PNID_PERMISSION_FLAGS.DELETE_BOSS_FILES) + deleteBossFiles: pnid.hasPermission(PNID_PERMISSION_FLAGS.DELETE_BOSS_FILES), + updatePnidPermissions: pnid.hasPermission(PNID_PERMISSION_FLAGS.UPDATE_PNID_PERMISSIONS) } }; } \ No newline at end of file diff --git a/src/services/grpc/account/get-user-data.ts b/src/services/grpc/account/get-user-data.ts index 3a7709b..70baca4 100644 --- a/src/services/grpc/account/get-user-data.ts +++ b/src/services/grpc/account/get-user-data.ts @@ -54,7 +54,8 @@ export async function getUserData(request: GetUserDataRequest): Promise Date: Fri, 17 Nov 2023 10:27:16 -0500 Subject: [PATCH 138/219] updated PNID scrubbing for number values --- src/models/pnid.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/models/pnid.ts b/src/models/pnid.ts index 4bead0b..e3054cc 100644 --- a/src/models/pnid.ts +++ b/src/models/pnid.ts @@ -273,9 +273,9 @@ PNIDSchema.method('scrub', async function scrub() { this.connections.stripe.customer_id = ''; this.connections.stripe.subscription_id = ''; this.connections.stripe.price_id = ''; - this.connections.stripe.tier_level = ''; + this.connections.stripe.tier_level = 0; this.connections.stripe.tier_name = ''; - this.connections.stripe.latest_webhook_timestamp = ''; + this.connections.stripe.latest_webhook_timestamp = 0; }); PNIDSchema.method('hasPermission', function hasPermission(flag: PNIDPermissionFlag): boolean { From 6779e84782c76dfa3a551b1d284af4afd9f41bd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20L=C3=B3pez=20Guimaraes?= Date: Wed, 3 Jan 2024 23:54:51 +0000 Subject: [PATCH 139/219] services: Add CBVC endpoint This allows any version of the 3DS internet browser to connect using Pretendo. The `:unknown` section seems to represent an incrementing "version" number, but I wasn't able to relate this number with the version shown in the browser info. --- src/server.ts | 4 +++- src/services/cbvc/index.ts | 31 +++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 src/services/cbvc/index.ts diff --git a/src/server.ts b/src/server.ts index d8fdb63..0050b1a 100644 --- a/src/server.ts +++ b/src/server.ts @@ -15,6 +15,7 @@ import { fullUrl, getValueFromHeaders } from '@/util'; import { LOG_INFO, LOG_SUCCESS, LOG_WARN } from '@/logger'; import conntest from '@/services/conntest'; +import cbvc from '@/services/cbvc'; import nnid from '@/services/nnid'; import nasc from '@/services/nasc'; import datastore from '@/services/datastore'; @@ -39,6 +40,7 @@ app.use(xmlparser); // import the servers into one app.use(conntest); +app.use(cbvc); app.use(nnid); app.use(nasc); app.use(datastore); @@ -109,4 +111,4 @@ async function main(): Promise { }); } -main().catch(console.error); \ No newline at end of file +main().catch(console.error); diff --git a/src/services/cbvc/index.ts b/src/services/cbvc/index.ts new file mode 100644 index 0000000..c3e5472 --- /dev/null +++ b/src/services/cbvc/index.ts @@ -0,0 +1,31 @@ +// handles CBVC (CTR Browser Version Check?) endpoints + +import express from 'express'; +import subdomain from 'express-subdomain'; +import { LOG_INFO } from '@/logger'; + +// Router to handle the subdomain restriction +const cbvc: express.Router = express.Router(); + +// Setup route +LOG_INFO('[cbvc] Applying imported routes'); +cbvc.get('/:consoleType/:unknown/:region', (request: express.Request, response: express.Response): void => { + response.set('Content-Type', 'text/plain'); + + // * https://www.3dbrew.org/wiki/Internet_Browser#Forced_system-update + // * The returned value is a number which the Internet Browser then compares + // * with its own version number. If the version number isn't higher than the + // * returned value, it will show a console update message. + // * + // * Return 0 and allow any browser to connect. + response.send('0'); +}); + +// Main router for endpoints +const router: express.Router = express.Router(); + +// Create subdomains +LOG_INFO('[cbvc] Creating \'cbvc\' subdomain'); +router.use(subdomain('cbvc.cdn', cbvc)); + +export default router; From ec99749753860444ebcc6d635a03e26ce99ef179 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Tue, 20 Feb 2024 13:51:10 -0500 Subject: [PATCH 140/219] chore: move email sending to AWS SES --- package-lock.json | 1465 ++++++++++++++++++++++++++++++++++++ package.json | 1 + src/config-manager.ts | 32 +- src/mailer.ts | 17 +- src/types/common/config.ts | 10 +- 5 files changed, 1496 insertions(+), 29 deletions(-) diff --git a/package-lock.json b/package-lock.json index c85f6d7..2bf1fe8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "2.0.0", "license": "ISC", "dependencies": { + "@aws-sdk/client-ses": "^3.515.0", "@pretendonetwork/grpc": "^1.0.5", "aws-sdk": "^2.978.0", "bcrypt": "^5.0.0", @@ -77,6 +78,696 @@ "node": ">=0.10.0" } }, + "node_modules/@aws-crypto/crc32": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-3.0.0.tgz", + "integrity": "sha512-IzSgsrxUcsrejQbPVilIKy16kAT52EwB6zSaI+M3xxIhKh5+aldEyvI+z6erM7TCLB2BJsFrtHjp6/4/sr+3dA==", + "dependencies": { + "@aws-crypto/util": "^3.0.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^1.11.1" + } + }, + "node_modules/@aws-crypto/ie11-detection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/ie11-detection/-/ie11-detection-3.0.0.tgz", + "integrity": "sha512-341lBBkiY1DfDNKai/wXM3aujNBkXR7tq1URPQDL9wi3AUbI80NR74uF1TXHMm7po1AcnFk8iu2S2IeU/+/A+Q==", + "dependencies": { + "tslib": "^1.11.1" + } + }, + "node_modules/@aws-crypto/sha256-browser": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-3.0.0.tgz", + "integrity": "sha512-8VLmW2B+gjFbU5uMeqtQM6Nj0/F1bro80xQXCW6CQBWgosFWXTx77aeOF5CAIAmbOK64SdMBJdNr6J41yP5mvQ==", + "dependencies": { + "@aws-crypto/ie11-detection": "^3.0.0", + "@aws-crypto/sha256-js": "^3.0.0", + "@aws-crypto/supports-web-crypto": "^3.0.0", + "@aws-crypto/util": "^3.0.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@aws-sdk/util-utf8-browser": "^3.0.0", + "tslib": "^1.11.1" + } + }, + "node_modules/@aws-crypto/sha256-js": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-3.0.0.tgz", + "integrity": "sha512-PnNN7os0+yd1XvXAy23CFOmTbMaDxgxXtTKHybrJ39Y8kGzBATgBFibWJKH6BhytLI/Zyszs87xCOBNyBig6vQ==", + "dependencies": { + "@aws-crypto/util": "^3.0.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^1.11.1" + } + }, + "node_modules/@aws-crypto/supports-web-crypto": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-3.0.0.tgz", + "integrity": "sha512-06hBdMwUAb2WFTuGG73LSC0wfPu93xWwo5vL2et9eymgmu3Id5vFAHBbajVWiGhPO37qcsdCap/FqXvJGJWPIg==", + "dependencies": { + "tslib": "^1.11.1" + } + }, + "node_modules/@aws-crypto/util": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-3.0.0.tgz", + "integrity": "sha512-2OJlpeJpCR48CC8r+uKVChzs9Iungj9wkZrl8Z041DWEWvyIHILYKCPNzJghKsivj+S3mLo6BVc7mBNzdxA46w==", + "dependencies": { + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-utf8-browser": "^3.0.0", + "tslib": "^1.11.1" + } + }, + "node_modules/@aws-sdk/client-ses": { + "version": "3.515.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-ses/-/client-ses-3.515.0.tgz", + "integrity": "sha512-X51TvcpqJ83ZOSUa/efWl+cj3cXBF6fCb2iNghjRD2McDvzGKoiR+Zaoy05doPdh/GZ7azTBqX0qKRIyUj//wg==", + "dependencies": { + "@aws-crypto/sha256-browser": "3.0.0", + "@aws-crypto/sha256-js": "3.0.0", + "@aws-sdk/client-sts": "3.515.0", + "@aws-sdk/core": "3.513.0", + "@aws-sdk/credential-provider-node": "3.515.0", + "@aws-sdk/middleware-host-header": "3.515.0", + "@aws-sdk/middleware-logger": "3.515.0", + "@aws-sdk/middleware-recursion-detection": "3.515.0", + "@aws-sdk/middleware-user-agent": "3.515.0", + "@aws-sdk/region-config-resolver": "3.515.0", + "@aws-sdk/types": "3.515.0", + "@aws-sdk/util-endpoints": "3.515.0", + "@aws-sdk/util-user-agent-browser": "3.515.0", + "@aws-sdk/util-user-agent-node": "3.515.0", + "@smithy/config-resolver": "^2.1.1", + "@smithy/core": "^1.3.2", + "@smithy/fetch-http-handler": "^2.4.1", + "@smithy/hash-node": "^2.1.1", + "@smithy/invalid-dependency": "^2.1.1", + "@smithy/middleware-content-length": "^2.1.1", + "@smithy/middleware-endpoint": "^2.4.1", + "@smithy/middleware-retry": "^2.1.1", + "@smithy/middleware-serde": "^2.1.1", + "@smithy/middleware-stack": "^2.1.1", + "@smithy/node-config-provider": "^2.2.1", + "@smithy/node-http-handler": "^2.3.1", + "@smithy/protocol-http": "^3.1.1", + "@smithy/smithy-client": "^2.3.1", + "@smithy/types": "^2.9.1", + "@smithy/url-parser": "^2.1.1", + "@smithy/util-base64": "^2.1.1", + "@smithy/util-body-length-browser": "^2.1.1", + "@smithy/util-body-length-node": "^2.2.1", + "@smithy/util-defaults-mode-browser": "^2.1.1", + "@smithy/util-defaults-mode-node": "^2.2.0", + "@smithy/util-endpoints": "^1.1.1", + "@smithy/util-middleware": "^2.1.1", + "@smithy/util-retry": "^2.1.1", + "@smithy/util-utf8": "^2.1.1", + "@smithy/util-waiter": "^2.1.1", + "fast-xml-parser": "4.2.5", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-ses/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@aws-sdk/client-sso": { + "version": "3.515.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.515.0.tgz", + "integrity": "sha512-4oGBLW476zmkdN98lAns3bObRNO+DLOfg4MDUSR6l6GYBV/zGAtoy2O/FhwYKgA2L5h2ZtElGopLlk/1Q0ePLw==", + "dependencies": { + "@aws-crypto/sha256-browser": "3.0.0", + "@aws-crypto/sha256-js": "3.0.0", + "@aws-sdk/core": "3.513.0", + "@aws-sdk/middleware-host-header": "3.515.0", + "@aws-sdk/middleware-logger": "3.515.0", + "@aws-sdk/middleware-recursion-detection": "3.515.0", + "@aws-sdk/middleware-user-agent": "3.515.0", + "@aws-sdk/region-config-resolver": "3.515.0", + "@aws-sdk/types": "3.515.0", + "@aws-sdk/util-endpoints": "3.515.0", + "@aws-sdk/util-user-agent-browser": "3.515.0", + "@aws-sdk/util-user-agent-node": "3.515.0", + "@smithy/config-resolver": "^2.1.1", + "@smithy/core": "^1.3.2", + "@smithy/fetch-http-handler": "^2.4.1", + "@smithy/hash-node": "^2.1.1", + "@smithy/invalid-dependency": "^2.1.1", + "@smithy/middleware-content-length": "^2.1.1", + "@smithy/middleware-endpoint": "^2.4.1", + "@smithy/middleware-retry": "^2.1.1", + "@smithy/middleware-serde": "^2.1.1", + "@smithy/middleware-stack": "^2.1.1", + "@smithy/node-config-provider": "^2.2.1", + "@smithy/node-http-handler": "^2.3.1", + "@smithy/protocol-http": "^3.1.1", + "@smithy/smithy-client": "^2.3.1", + "@smithy/types": "^2.9.1", + "@smithy/url-parser": "^2.1.1", + "@smithy/util-base64": "^2.1.1", + "@smithy/util-body-length-browser": "^2.1.1", + "@smithy/util-body-length-node": "^2.2.1", + "@smithy/util-defaults-mode-browser": "^2.1.1", + "@smithy/util-defaults-mode-node": "^2.2.0", + "@smithy/util-endpoints": "^1.1.1", + "@smithy/util-middleware": "^2.1.1", + "@smithy/util-retry": "^2.1.1", + "@smithy/util-utf8": "^2.1.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-sso-oidc": { + "version": "3.515.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.515.0.tgz", + "integrity": "sha512-zACa8LNlPUdlNUBqQRf5a3MfouLNtcBfm84v2c8M976DwJrMGONPe1QjyLLsD38uESQiXiVQRruj/b000iMXNw==", + "dependencies": { + "@aws-crypto/sha256-browser": "3.0.0", + "@aws-crypto/sha256-js": "3.0.0", + "@aws-sdk/client-sts": "3.515.0", + "@aws-sdk/core": "3.513.0", + "@aws-sdk/middleware-host-header": "3.515.0", + "@aws-sdk/middleware-logger": "3.515.0", + "@aws-sdk/middleware-recursion-detection": "3.515.0", + "@aws-sdk/middleware-user-agent": "3.515.0", + "@aws-sdk/region-config-resolver": "3.515.0", + "@aws-sdk/types": "3.515.0", + "@aws-sdk/util-endpoints": "3.515.0", + "@aws-sdk/util-user-agent-browser": "3.515.0", + "@aws-sdk/util-user-agent-node": "3.515.0", + "@smithy/config-resolver": "^2.1.1", + "@smithy/core": "^1.3.2", + "@smithy/fetch-http-handler": "^2.4.1", + "@smithy/hash-node": "^2.1.1", + "@smithy/invalid-dependency": "^2.1.1", + "@smithy/middleware-content-length": "^2.1.1", + "@smithy/middleware-endpoint": "^2.4.1", + "@smithy/middleware-retry": "^2.1.1", + "@smithy/middleware-serde": "^2.1.1", + "@smithy/middleware-stack": "^2.1.1", + "@smithy/node-config-provider": "^2.2.1", + "@smithy/node-http-handler": "^2.3.1", + "@smithy/protocol-http": "^3.1.1", + "@smithy/smithy-client": "^2.3.1", + "@smithy/types": "^2.9.1", + "@smithy/url-parser": "^2.1.1", + "@smithy/util-base64": "^2.1.1", + "@smithy/util-body-length-browser": "^2.1.1", + "@smithy/util-body-length-node": "^2.2.1", + "@smithy/util-defaults-mode-browser": "^2.1.1", + "@smithy/util-defaults-mode-node": "^2.2.0", + "@smithy/util-endpoints": "^1.1.1", + "@smithy/util-middleware": "^2.1.1", + "@smithy/util-retry": "^2.1.1", + "@smithy/util-utf8": "^2.1.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "@aws-sdk/credential-provider-node": "^3.515.0" + } + }, + "node_modules/@aws-sdk/client-sso-oidc/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@aws-sdk/client-sso/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@aws-sdk/client-sts": { + "version": "3.515.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.515.0.tgz", + "integrity": "sha512-ScYuvaIDgip3atOJIA1FU2n0gJkEdveu1KrrCPathoUCV5zpK8qQmO/n+Fj/7hKFxeKdFbB+4W4CsJWYH94nlg==", + "dependencies": { + "@aws-crypto/sha256-browser": "3.0.0", + "@aws-crypto/sha256-js": "3.0.0", + "@aws-sdk/core": "3.513.0", + "@aws-sdk/middleware-host-header": "3.515.0", + "@aws-sdk/middleware-logger": "3.515.0", + "@aws-sdk/middleware-recursion-detection": "3.515.0", + "@aws-sdk/middleware-user-agent": "3.515.0", + "@aws-sdk/region-config-resolver": "3.515.0", + "@aws-sdk/types": "3.515.0", + "@aws-sdk/util-endpoints": "3.515.0", + "@aws-sdk/util-user-agent-browser": "3.515.0", + "@aws-sdk/util-user-agent-node": "3.515.0", + "@smithy/config-resolver": "^2.1.1", + "@smithy/core": "^1.3.2", + "@smithy/fetch-http-handler": "^2.4.1", + "@smithy/hash-node": "^2.1.1", + "@smithy/invalid-dependency": "^2.1.1", + "@smithy/middleware-content-length": "^2.1.1", + "@smithy/middleware-endpoint": "^2.4.1", + "@smithy/middleware-retry": "^2.1.1", + "@smithy/middleware-serde": "^2.1.1", + "@smithy/middleware-stack": "^2.1.1", + "@smithy/node-config-provider": "^2.2.1", + "@smithy/node-http-handler": "^2.3.1", + "@smithy/protocol-http": "^3.1.1", + "@smithy/smithy-client": "^2.3.1", + "@smithy/types": "^2.9.1", + "@smithy/url-parser": "^2.1.1", + "@smithy/util-base64": "^2.1.1", + "@smithy/util-body-length-browser": "^2.1.1", + "@smithy/util-body-length-node": "^2.2.1", + "@smithy/util-defaults-mode-browser": "^2.1.1", + "@smithy/util-defaults-mode-node": "^2.2.0", + "@smithy/util-endpoints": "^1.1.1", + "@smithy/util-middleware": "^2.1.1", + "@smithy/util-retry": "^2.1.1", + "@smithy/util-utf8": "^2.1.1", + "fast-xml-parser": "4.2.5", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "@aws-sdk/credential-provider-node": "^3.515.0" + } + }, + "node_modules/@aws-sdk/client-sts/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@aws-sdk/core": { + "version": "3.513.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.513.0.tgz", + "integrity": "sha512-L+9DL4apWuqNKVOMJ8siAuWoRM9rZf9w1iPv8S2o83WO2jVK7E/m+rNW1dFo9HsA5V1ccDl2H2qLXx24HiHmOw==", + "dependencies": { + "@smithy/core": "^1.3.2", + "@smithy/protocol-http": "^3.1.1", + "@smithy/signature-v4": "^2.1.1", + "@smithy/smithy-client": "^2.3.1", + "@smithy/types": "^2.9.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/core/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@aws-sdk/credential-provider-env": { + "version": "3.515.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.515.0.tgz", + "integrity": "sha512-45vxdyqhTAaUMERYVWOziG3K8L2TV9G4ryQS/KZ84o7NAybE9GMdoZRVmGHAO7mJJ1wQiYCM/E+i5b3NW9JfNA==", + "dependencies": { + "@aws-sdk/types": "3.515.0", + "@smithy/property-provider": "^2.1.1", + "@smithy/types": "^2.9.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-env/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@aws-sdk/credential-provider-http": { + "version": "3.515.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.515.0.tgz", + "integrity": "sha512-Ba6FXK77vU4WyheiamNjEuTFmir0eAXuJGPO27lBaA8g+V/seXGHScsbOG14aQGDOr2P02OPwKGZrWWA7BFpfQ==", + "dependencies": { + "@aws-sdk/types": "3.515.0", + "@smithy/fetch-http-handler": "^2.4.1", + "@smithy/node-http-handler": "^2.3.1", + "@smithy/property-provider": "^2.1.1", + "@smithy/protocol-http": "^3.1.1", + "@smithy/smithy-client": "^2.3.1", + "@smithy/types": "^2.9.1", + "@smithy/util-stream": "^2.1.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-http/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.515.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.515.0.tgz", + "integrity": "sha512-ouDlNZdv2TKeVEA/YZk2+XklTXyAAGdbWnl4IgN9ItaodWI+lZjdIoNC8BAooVH+atIV/cZgoGTGQL7j2TxJ9A==", + "dependencies": { + "@aws-sdk/client-sts": "3.515.0", + "@aws-sdk/credential-provider-env": "3.515.0", + "@aws-sdk/credential-provider-process": "3.515.0", + "@aws-sdk/credential-provider-sso": "3.515.0", + "@aws-sdk/credential-provider-web-identity": "3.515.0", + "@aws-sdk/types": "3.515.0", + "@smithy/credential-provider-imds": "^2.2.1", + "@smithy/property-provider": "^2.1.1", + "@smithy/shared-ini-file-loader": "^2.3.1", + "@smithy/types": "^2.9.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-ini/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@aws-sdk/credential-provider-node": { + "version": "3.515.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.515.0.tgz", + "integrity": "sha512-Y4kHSpbxksiCZZNcvsiKUd8Fb2XlyUuONEwqWFNL82ZH6TCCjBGS31wJQCSxBHqYcOL3tiORUEJkoO7uS30uQA==", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.515.0", + "@aws-sdk/credential-provider-http": "3.515.0", + "@aws-sdk/credential-provider-ini": "3.515.0", + "@aws-sdk/credential-provider-process": "3.515.0", + "@aws-sdk/credential-provider-sso": "3.515.0", + "@aws-sdk/credential-provider-web-identity": "3.515.0", + "@aws-sdk/types": "3.515.0", + "@smithy/credential-provider-imds": "^2.2.1", + "@smithy/property-provider": "^2.1.1", + "@smithy/shared-ini-file-loader": "^2.3.1", + "@smithy/types": "^2.9.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-node/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@aws-sdk/credential-provider-process": { + "version": "3.515.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.515.0.tgz", + "integrity": "sha512-pSjiOA2FM63LHRKNDvEpBRp80FVGT0Mw/gzgbqFXP+sewk0WVonYbEcMDTJptH3VsLPGzqH/DQ1YL/aEIBuXFQ==", + "dependencies": { + "@aws-sdk/types": "3.515.0", + "@smithy/property-provider": "^2.1.1", + "@smithy/shared-ini-file-loader": "^2.3.1", + "@smithy/types": "^2.9.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-process/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.515.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.515.0.tgz", + "integrity": "sha512-j7vUkiSmuhpBvZYoPTRTI4ePnQbiZMFl6TNhg9b9DprC1zHkucsZnhRhqjOVlrw/H6J4jmcPGcHHTZ5WQNI5xQ==", + "dependencies": { + "@aws-sdk/client-sso": "3.515.0", + "@aws-sdk/token-providers": "3.515.0", + "@aws-sdk/types": "3.515.0", + "@smithy/property-provider": "^2.1.1", + "@smithy/shared-ini-file-loader": "^2.3.1", + "@smithy/types": "^2.9.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-sso/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.515.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.515.0.tgz", + "integrity": "sha512-66+2g4z3fWwdoGReY8aUHvm6JrKZMTRxjuizljVmMyOBttKPeBYXvUTop/g3ZGUx1f8j+C5qsGK52viYBvtjuQ==", + "dependencies": { + "@aws-sdk/client-sts": "3.515.0", + "@aws-sdk/types": "3.515.0", + "@smithy/property-provider": "^2.1.1", + "@smithy/types": "^2.9.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-web-identity/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@aws-sdk/middleware-host-header": { + "version": "3.515.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.515.0.tgz", + "integrity": "sha512-I1MwWPzdRKM1luvdDdjdGsDjNVPhj9zaIytEchjTY40NcKOg+p2evLD2y69ozzg8pyXK63r8DdvDGOo9QPuh0A==", + "dependencies": { + "@aws-sdk/types": "3.515.0", + "@smithy/protocol-http": "^3.1.1", + "@smithy/types": "^2.9.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/middleware-host-header/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@aws-sdk/middleware-logger": { + "version": "3.515.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.515.0.tgz", + "integrity": "sha512-qXomJzg2m/5seQOxHi/yOXOKfSjwrrJSmEmfwJKJyQgdMbBcjz3Cz0H/1LyC6c5hHm6a/SZgSTzDAbAoUmyL+Q==", + "dependencies": { + "@aws-sdk/types": "3.515.0", + "@smithy/types": "^2.9.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/middleware-logger/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.515.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.515.0.tgz", + "integrity": "sha512-dokHLbTV3IHRIBrw9mGoxcNTnQsjlm7TpkJhPdGT9T4Mq399EyQo51u6IsVMm07RXLl2Zw7u+u9p+qWBFzmFRA==", + "dependencies": { + "@aws-sdk/types": "3.515.0", + "@smithy/protocol-http": "^3.1.1", + "@smithy/types": "^2.9.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/middleware-recursion-detection/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.515.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.515.0.tgz", + "integrity": "sha512-nOqZjGA/GkjuJ5fUshec9Fv6HFd7ovOTxMJbw3MfAhqXuVZ6dKF41lpVJ4imNsgyFt3shUg9WDY8zGFjlYMB3g==", + "dependencies": { + "@aws-sdk/types": "3.515.0", + "@aws-sdk/util-endpoints": "3.515.0", + "@smithy/protocol-http": "^3.1.1", + "@smithy/types": "^2.9.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/middleware-user-agent/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@aws-sdk/region-config-resolver": { + "version": "3.515.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.515.0.tgz", + "integrity": "sha512-RIRx9loxMgEAc/r1wPfnfShOuzn4RBi8pPPv6/jhhITEeMnJe6enAh2k5y9DdiVDDgCWZgVFSv0YkAIfzAFsnQ==", + "dependencies": { + "@aws-sdk/types": "3.515.0", + "@smithy/node-config-provider": "^2.2.1", + "@smithy/types": "^2.9.1", + "@smithy/util-config-provider": "^2.2.1", + "@smithy/util-middleware": "^2.1.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/region-config-resolver/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@aws-sdk/token-providers": { + "version": "3.515.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.515.0.tgz", + "integrity": "sha512-MQuf04rIcTXqwDzmyHSpFPF1fKEzRl64oXtCRUF3ddxTdK6wxXkePfK6wNCuL+GEbEcJAoCtIGIRpzGPJvQjHA==", + "dependencies": { + "@aws-sdk/client-sso-oidc": "3.515.0", + "@aws-sdk/types": "3.515.0", + "@smithy/property-provider": "^2.1.1", + "@smithy/shared-ini-file-loader": "^2.3.1", + "@smithy/types": "^2.9.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/token-providers/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@aws-sdk/types": { + "version": "3.515.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.515.0.tgz", + "integrity": "sha512-B3gUpiMlpT6ERaLvZZ61D0RyrQPsFYDkCncLPVkZOKkCOoFU46zi1o6T5JcYiz8vkx1q9RGloQ5exh79s5pU/w==", + "dependencies": { + "@smithy/types": "^2.9.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/types/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@aws-sdk/util-endpoints": { + "version": "3.515.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.515.0.tgz", + "integrity": "sha512-UJi+jdwcGFV/F7d3+e2aQn5yZOVpDiAgfgNhPnEtgV0WozJ5/ZUeZBgWvSc/K415N4A4D/9cbBc7+I+35qzcDQ==", + "dependencies": { + "@aws-sdk/types": "3.515.0", + "@smithy/types": "^2.9.1", + "@smithy/util-endpoints": "^1.1.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/util-endpoints/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@aws-sdk/util-locate-window": { + "version": "3.495.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.495.0.tgz", + "integrity": "sha512-MfaPXT0kLX2tQaR90saBT9fWQq2DHqSSJRzW+MZWsmF+y5LGCOhO22ac/2o6TKSQm7h0HRc2GaADqYYYor62yg==", + "dependencies": { + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/util-locate-window/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.515.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.515.0.tgz", + "integrity": "sha512-pTWQb0JCafTmLHLDv3Qqs/nAAJghcPdGQIBpsCStb0YEzg3At/dOi2AIQ683yYnXmeOxLXJDzmlsovfVObJScw==", + "dependencies": { + "@aws-sdk/types": "3.515.0", + "@smithy/types": "^2.9.1", + "bowser": "^2.11.0", + "tslib": "^2.5.0" + } + }, + "node_modules/@aws-sdk/util-user-agent-browser/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.515.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.515.0.tgz", + "integrity": "sha512-A/KJ+/HTohHyVXLH+t/bO0Z2mPrQgELbQO8tX+B2nElo8uklj70r5cT7F8ETsI9oOy+HDVpiL5/v45ZgpUOiPg==", + "dependencies": { + "@aws-sdk/types": "3.515.0", + "@smithy/node-config-provider": "^2.2.1", + "@smithy/types": "^2.9.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } + } + }, + "node_modules/@aws-sdk/util-user-agent-node/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@aws-sdk/util-utf8-browser": { + "version": "3.259.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-utf8-browser/-/util-utf8-browser-3.259.0.tgz", + "integrity": "sha512-UvFa/vR+e19XookZF8RzFZBrw2EUkQWxiBW0yYQAhvk3C+QVGl0H3ouca8LDBlBfQKXwmW3huo/59H8rwb1wJw==", + "dependencies": { + "tslib": "^2.3.1" + } + }, + "node_modules/@aws-sdk/util-utf8-browser/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, "node_modules/@colors/colors": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", @@ -553,6 +1244,749 @@ "url": "https://github.com/sindresorhus/is?sponsor=1" } }, + "node_modules/@smithy/abort-controller": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-2.1.1.tgz", + "integrity": "sha512-1+qdrUqLhaALYL0iOcN43EP6yAXXQ2wWZ6taf4S2pNGowmOc5gx+iMQv+E42JizNJjB0+gEadOXeV1Bf7JWL1Q==", + "dependencies": { + "@smithy/types": "^2.9.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/abort-controller/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@smithy/config-resolver": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-2.1.1.tgz", + "integrity": "sha512-lxfLDpZm+AWAHPFZps5JfDoO9Ux1764fOgvRUBpHIO8HWHcSN1dkgsago1qLRVgm1BZ8RCm8cgv99QvtaOWIhw==", + "dependencies": { + "@smithy/node-config-provider": "^2.2.1", + "@smithy/types": "^2.9.1", + "@smithy/util-config-provider": "^2.2.1", + "@smithy/util-middleware": "^2.1.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/config-resolver/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@smithy/core": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-1.3.2.tgz", + "integrity": "sha512-tYDmTp0f2TZVE18jAOH1PnmkngLQ+dOGUlMd1u67s87ieueNeyqhja6z/Z4MxhybEiXKOWFOmGjfTZWFxljwJw==", + "dependencies": { + "@smithy/middleware-endpoint": "^2.4.1", + "@smithy/middleware-retry": "^2.1.1", + "@smithy/middleware-serde": "^2.1.1", + "@smithy/protocol-http": "^3.1.1", + "@smithy/smithy-client": "^2.3.1", + "@smithy/types": "^2.9.1", + "@smithy/util-middleware": "^2.1.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/core/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@smithy/credential-provider-imds": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-2.2.1.tgz", + "integrity": "sha512-7XHjZUxmZYnONheVQL7j5zvZXga+EWNgwEAP6OPZTi7l8J4JTeNh9aIOfE5fKHZ/ee2IeNOh54ZrSna+Vc6TFA==", + "dependencies": { + "@smithy/node-config-provider": "^2.2.1", + "@smithy/property-provider": "^2.1.1", + "@smithy/types": "^2.9.1", + "@smithy/url-parser": "^2.1.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/credential-provider-imds/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@smithy/eventstream-codec": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-2.1.1.tgz", + "integrity": "sha512-E8KYBxBIuU4c+zrpR22VsVrOPoEDzk35bQR3E+xm4k6Pa6JqzkDOdMyf9Atac5GPNKHJBdVaQ4JtjdWX2rl/nw==", + "dependencies": { + "@aws-crypto/crc32": "3.0.0", + "@smithy/types": "^2.9.1", + "@smithy/util-hex-encoding": "^2.1.1", + "tslib": "^2.5.0" + } + }, + "node_modules/@smithy/eventstream-codec/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@smithy/fetch-http-handler": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-2.4.1.tgz", + "integrity": "sha512-VYGLinPsFqH68lxfRhjQaSkjXM7JysUOJDTNjHBuN/ykyRb2f1gyavN9+VhhPTWCy32L4yZ2fdhpCs/nStEicg==", + "dependencies": { + "@smithy/protocol-http": "^3.1.1", + "@smithy/querystring-builder": "^2.1.1", + "@smithy/types": "^2.9.1", + "@smithy/util-base64": "^2.1.1", + "tslib": "^2.5.0" + } + }, + "node_modules/@smithy/fetch-http-handler/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@smithy/hash-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-2.1.1.tgz", + "integrity": "sha512-Qhoq0N8f2OtCnvUpCf+g1vSyhYQrZjhSwvJ9qvR8BUGOtTXiyv2x1OD2e6jVGmlpC4E4ax1USHoyGfV9JFsACg==", + "dependencies": { + "@smithy/types": "^2.9.1", + "@smithy/util-buffer-from": "^2.1.1", + "@smithy/util-utf8": "^2.1.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/hash-node/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@smithy/invalid-dependency": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-2.1.1.tgz", + "integrity": "sha512-7WTgnKw+VPg8fxu2v9AlNOQ5yaz6RA54zOVB4f6vQuR0xFKd+RzlCpt0WidYTsye7F+FYDIaS/RnJW4pxjNInw==", + "dependencies": { + "@smithy/types": "^2.9.1", + "tslib": "^2.5.0" + } + }, + "node_modules/@smithy/invalid-dependency/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@smithy/is-array-buffer": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.1.1.tgz", + "integrity": "sha512-xozSQrcUinPpNPNPds4S7z/FakDTh1MZWtRP/2vQtYB/u3HYrX2UXuZs+VhaKBd6Vc7g2XPr2ZtwGBNDN6fNKQ==", + "dependencies": { + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/is-array-buffer/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@smithy/middleware-content-length": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-2.1.1.tgz", + "integrity": "sha512-rSr9ezUl9qMgiJR0UVtVOGEZElMdGFyl8FzWEF5iEKTlcWxGr2wTqGfDwtH3LAB7h+FPkxqv4ZU4cpuCN9Kf/g==", + "dependencies": { + "@smithy/protocol-http": "^3.1.1", + "@smithy/types": "^2.9.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/middleware-content-length/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@smithy/middleware-endpoint": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-2.4.1.tgz", + "integrity": "sha512-XPZTb1E2Oav60Ven3n2PFx+rX9EDsU/jSTA8VDamt7FXks67ekjPY/XrmmPDQaFJOTUHJNKjd8+kZxVO5Ael4Q==", + "dependencies": { + "@smithy/middleware-serde": "^2.1.1", + "@smithy/node-config-provider": "^2.2.1", + "@smithy/shared-ini-file-loader": "^2.3.1", + "@smithy/types": "^2.9.1", + "@smithy/url-parser": "^2.1.1", + "@smithy/util-middleware": "^2.1.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/middleware-endpoint/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@smithy/middleware-retry": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-2.1.1.tgz", + "integrity": "sha512-eMIHOBTXro6JZ+WWzZWd/8fS8ht5nS5KDQjzhNMHNRcG5FkNTqcKpYhw7TETMYzbLfhO5FYghHy1vqDWM4FLDA==", + "dependencies": { + "@smithy/node-config-provider": "^2.2.1", + "@smithy/protocol-http": "^3.1.1", + "@smithy/service-error-classification": "^2.1.1", + "@smithy/smithy-client": "^2.3.1", + "@smithy/types": "^2.9.1", + "@smithy/util-middleware": "^2.1.1", + "@smithy/util-retry": "^2.1.1", + "tslib": "^2.5.0", + "uuid": "^8.3.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/middleware-retry/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@smithy/middleware-retry/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/@smithy/middleware-serde": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-2.1.1.tgz", + "integrity": "sha512-D8Gq0aQBeE1pxf3cjWVkRr2W54t+cdM2zx78tNrVhqrDykRA7asq8yVJij1u5NDtKzKqzBSPYh7iW0svUKg76g==", + "dependencies": { + "@smithy/types": "^2.9.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/middleware-serde/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@smithy/middleware-stack": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-2.1.1.tgz", + "integrity": "sha512-KPJhRlhsl8CjgGXK/DoDcrFGfAqoqvuwlbxy+uOO4g2Azn1dhH+GVfC3RAp+6PoL5PWPb+vt6Z23FP+Mr6qeCw==", + "dependencies": { + "@smithy/types": "^2.9.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/middleware-stack/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@smithy/node-config-provider": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-2.2.1.tgz", + "integrity": "sha512-epzK3x1xNxA9oJgHQ5nz+2j6DsJKdHfieb+YgJ7ATWxzNcB7Hc+Uya2TUck5MicOPhDV8HZImND7ZOecVr+OWg==", + "dependencies": { + "@smithy/property-provider": "^2.1.1", + "@smithy/shared-ini-file-loader": "^2.3.1", + "@smithy/types": "^2.9.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/node-config-provider/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@smithy/node-http-handler": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-2.3.1.tgz", + "integrity": "sha512-gLA8qK2nL9J0Rk/WEZSvgin4AppvuCYRYg61dcUo/uKxvMZsMInL5I5ZdJTogOvdfVug3N2dgI5ffcUfS4S9PA==", + "dependencies": { + "@smithy/abort-controller": "^2.1.1", + "@smithy/protocol-http": "^3.1.1", + "@smithy/querystring-builder": "^2.1.1", + "@smithy/types": "^2.9.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/node-http-handler/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@smithy/property-provider": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-2.1.1.tgz", + "integrity": "sha512-FX7JhhD/o5HwSwg6GLK9zxrMUrGnb3PzNBrcthqHKBc3dH0UfgEAU24xnJ8F0uow5mj17UeBEOI6o3CF2k7Mhw==", + "dependencies": { + "@smithy/types": "^2.9.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/property-provider/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@smithy/protocol-http": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-3.1.1.tgz", + "integrity": "sha512-6ZRTSsaXuSL9++qEwH851hJjUA0OgXdQFCs+VDw4tGH256jQ3TjYY/i34N4vd24RV3nrjNsgd1yhb57uMoKbzQ==", + "dependencies": { + "@smithy/types": "^2.9.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/protocol-http/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@smithy/querystring-builder": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-2.1.1.tgz", + "integrity": "sha512-C/ko/CeEa8jdYE4gt6nHO5XDrlSJ3vdCG0ZAc6nD5ZIE7LBp0jCx4qoqp7eoutBu7VrGMXERSRoPqwi1WjCPbg==", + "dependencies": { + "@smithy/types": "^2.9.1", + "@smithy/util-uri-escape": "^2.1.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/querystring-builder/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@smithy/querystring-parser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-2.1.1.tgz", + "integrity": "sha512-H4+6jKGVhG1W4CIxfBaSsbm98lOO88tpDWmZLgkJpt8Zkk/+uG0FmmqMuCAc3HNM2ZDV+JbErxr0l5BcuIf/XQ==", + "dependencies": { + "@smithy/types": "^2.9.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/querystring-parser/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@smithy/service-error-classification": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-2.1.1.tgz", + "integrity": "sha512-txEdZxPUgM1PwGvDvHzqhXisrc5LlRWYCf2yyHfvITWioAKat7srQvpjMAvgzf0t6t7j8yHrryXU9xt7RZqFpw==", + "dependencies": { + "@smithy/types": "^2.9.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/shared-ini-file-loader": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-2.3.1.tgz", + "integrity": "sha512-2E2kh24igmIznHLB6H05Na4OgIEilRu0oQpYXo3LCNRrawHAcfDKq9004zJs+sAMt2X5AbY87CUCJ7IpqpSgdw==", + "dependencies": { + "@smithy/types": "^2.9.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/shared-ini-file-loader/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@smithy/signature-v4": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-2.1.1.tgz", + "integrity": "sha512-Hb7xub0NHuvvQD3YwDSdanBmYukoEkhqBjqoxo+bSdC0ryV9cTfgmNjuAQhTPYB6yeU7hTR+sPRiFMlxqv6kmg==", + "dependencies": { + "@smithy/eventstream-codec": "^2.1.1", + "@smithy/is-array-buffer": "^2.1.1", + "@smithy/types": "^2.9.1", + "@smithy/util-hex-encoding": "^2.1.1", + "@smithy/util-middleware": "^2.1.1", + "@smithy/util-uri-escape": "^2.1.1", + "@smithy/util-utf8": "^2.1.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/signature-v4/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@smithy/smithy-client": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-2.3.1.tgz", + "integrity": "sha512-YsTdU8xVD64r2pLEwmltrNvZV6XIAC50LN6ivDopdt+YiF/jGH6PY9zUOu0CXD/d8GMB8gbhnpPsdrjAXHS9QA==", + "dependencies": { + "@smithy/middleware-endpoint": "^2.4.1", + "@smithy/middleware-stack": "^2.1.1", + "@smithy/protocol-http": "^3.1.1", + "@smithy/types": "^2.9.1", + "@smithy/util-stream": "^2.1.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/smithy-client/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@smithy/types": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-2.9.1.tgz", + "integrity": "sha512-vjXlKNXyprDYDuJ7UW5iobdmyDm6g8dDG+BFUncAg/3XJaN45Gy5RWWWUVgrzIK7S4R1KWgIX5LeJcfvSI24bw==", + "dependencies": { + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/types/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@smithy/url-parser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-2.1.1.tgz", + "integrity": "sha512-qC9Bv8f/vvFIEkHsiNrUKYNl8uKQnn4BdhXl7VzQRP774AwIjiSMMwkbT+L7Fk8W8rzYVifzJNYxv1HwvfBo3Q==", + "dependencies": { + "@smithy/querystring-parser": "^2.1.1", + "@smithy/types": "^2.9.1", + "tslib": "^2.5.0" + } + }, + "node_modules/@smithy/url-parser/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@smithy/util-base64": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-2.1.1.tgz", + "integrity": "sha512-UfHVpY7qfF/MrgndI5PexSKVTxSZIdz9InghTFa49QOvuu9I52zLPLUHXvHpNuMb1iD2vmc6R+zbv/bdMipR/g==", + "dependencies": { + "@smithy/util-buffer-from": "^2.1.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/util-base64/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@smithy/util-body-length-browser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-2.1.1.tgz", + "integrity": "sha512-ekOGBLvs1VS2d1zM2ER4JEeBWAvIOUKeaFch29UjjJsxmZ/f0L3K3x0dEETgh3Q9bkZNHgT+rkdl/J/VUqSRag==", + "dependencies": { + "tslib": "^2.5.0" + } + }, + "node_modules/@smithy/util-body-length-browser/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@smithy/util-body-length-node": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-2.2.1.tgz", + "integrity": "sha512-/ggJG+ta3IDtpNVq4ktmEUtOkH1LW64RHB5B0hcr5ZaWBmo96UX2cIOVbjCqqDickTXqBWZ4ZO0APuaPrD7Abg==", + "dependencies": { + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/util-body-length-node/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@smithy/util-buffer-from": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.1.1.tgz", + "integrity": "sha512-clhNjbyfqIv9Md2Mg6FffGVrJxw7bgK7s3Iax36xnfVj6cg0fUG7I4RH0XgXJF8bxi+saY5HR21g2UPKSxVCXg==", + "dependencies": { + "@smithy/is-array-buffer": "^2.1.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/util-buffer-from/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@smithy/util-config-provider": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-2.2.1.tgz", + "integrity": "sha512-50VL/tx9oYYcjJn/qKqNy7sCtpD0+s8XEBamIFo4mFFTclKMNp+rsnymD796uybjiIquB7VCB/DeafduL0y2kw==", + "dependencies": { + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/util-config-provider/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@smithy/util-defaults-mode-browser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-2.1.1.tgz", + "integrity": "sha512-lqLz/9aWRO6mosnXkArtRuQqqZBhNpgI65YDpww4rVQBuUT7qzKbDLG5AmnQTCiU4rOquaZO/Kt0J7q9Uic7MA==", + "dependencies": { + "@smithy/property-provider": "^2.1.1", + "@smithy/smithy-client": "^2.3.1", + "@smithy/types": "^2.9.1", + "bowser": "^2.11.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-browser/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@smithy/util-defaults-mode-node": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-2.2.0.tgz", + "integrity": "sha512-iFJp/N4EtkanFpBUtSrrIbtOIBf69KNuve03ic1afhJ9/korDxdM0c6cCH4Ehj/smI9pDCfVv+bqT3xZjF2WaA==", + "dependencies": { + "@smithy/config-resolver": "^2.1.1", + "@smithy/credential-provider-imds": "^2.2.1", + "@smithy/node-config-provider": "^2.2.1", + "@smithy/property-provider": "^2.1.1", + "@smithy/smithy-client": "^2.3.1", + "@smithy/types": "^2.9.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-node/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@smithy/util-endpoints": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-1.1.1.tgz", + "integrity": "sha512-sI4d9rjoaekSGEtq3xSb2nMjHMx8QXcz2cexnVyRWsy4yQ9z3kbDpX+7fN0jnbdOp0b3KSTZJZ2Yb92JWSanLw==", + "dependencies": { + "@smithy/node-config-provider": "^2.2.1", + "@smithy/types": "^2.9.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@smithy/util-endpoints/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@smithy/util-hex-encoding": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-2.1.1.tgz", + "integrity": "sha512-3UNdP2pkYUUBGEXzQI9ODTDK+Tcu1BlCyDBaRHwyxhA+8xLP8agEKQq4MGmpjqb4VQAjq9TwlCQX0kP6XDKYLg==", + "dependencies": { + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/util-hex-encoding/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@smithy/util-middleware": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-2.1.1.tgz", + "integrity": "sha512-mKNrk8oz5zqkNcbcgAAepeJbmfUW6ogrT2Z2gDbIUzVzNAHKJQTYmH9jcy0jbWb+m7ubrvXKb6uMjkSgAqqsFA==", + "dependencies": { + "@smithy/types": "^2.9.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/util-middleware/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@smithy/util-retry": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-2.1.1.tgz", + "integrity": "sha512-Mg+xxWPTeSPrthpC5WAamJ6PW4Kbo01Fm7lWM1jmGRvmrRdsd3192Gz2fBXAMURyXpaNxyZf6Hr/nQ4q70oVEA==", + "dependencies": { + "@smithy/service-error-classification": "^2.1.1", + "@smithy/types": "^2.9.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@smithy/util-retry/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@smithy/util-stream": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-2.1.1.tgz", + "integrity": "sha512-J7SMIpUYvU4DQN55KmBtvaMc7NM3CZ2iWICdcgaovtLzseVhAqFRYqloT3mh0esrFw+3VEK6nQFteFsTqZSECQ==", + "dependencies": { + "@smithy/fetch-http-handler": "^2.4.1", + "@smithy/node-http-handler": "^2.3.1", + "@smithy/types": "^2.9.1", + "@smithy/util-base64": "^2.1.1", + "@smithy/util-buffer-from": "^2.1.1", + "@smithy/util-hex-encoding": "^2.1.1", + "@smithy/util-utf8": "^2.1.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/util-stream/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@smithy/util-uri-escape": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-2.1.1.tgz", + "integrity": "sha512-saVzI1h6iRBUVSqtnlOnc9ssU09ypo7n+shdQ8hBTZno/9rZ3AuRYvoHInV57VF7Qn7B+pFJG7qTzFiHxWlWBw==", + "dependencies": { + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/util-uri-escape/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@smithy/util-utf8": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.1.1.tgz", + "integrity": "sha512-BqTpzYEcUMDwAKr7/mVRUtHDhs6ZoXDi9NypMvMfOr/+u1NW7JgqodPDECiiLboEm6bobcPcECxzjtQh865e9A==", + "dependencies": { + "@smithy/util-buffer-from": "^2.1.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/util-utf8/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@smithy/util-waiter": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-2.1.1.tgz", + "integrity": "sha512-kYy6BLJJNif+uqNENtJqWdXcpqo1LS+nj1AfXcDhOpqpSHJSAkVySLyZV9fkmuVO21lzGoxjvd1imGGJHph/IA==", + "dependencies": { + "@smithy/abort-controller": "^2.1.1", + "@smithy/types": "^2.9.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/util-waiter/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, "node_modules/@szmarczak/http-timer": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", @@ -1396,6 +2830,11 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, + "node_modules/bowser": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.11.0.tgz", + "integrity": "sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==" + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -2623,6 +4062,27 @@ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==" }, + "node_modules/fast-xml-parser": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.2.5.tgz", + "integrity": "sha512-B9/wizE4WngqQftFPmdaMYlXoJlJOYxGQOanC77fq9k8+Z0v5dDSVh+3glErdIROP//s/jgb7ZuxKfB8nVyo0g==", + "funding": [ + { + "type": "paypal", + "url": "https://paypal.me/naturalintelligence" + }, + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "dependencies": { + "strnum": "^1.0.5" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, "node_modules/fastq": { "version": "1.15.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", @@ -5194,6 +6654,11 @@ "node": ">=12.*" } }, + "node_modules/strnum": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz", + "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==" + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", diff --git a/package.json b/package.json index 2a76cb8..b1359b7 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ }, "homepage": "https://github.com/PretendoNetwork/account#readme", "dependencies": { + "@aws-sdk/client-ses": "^3.515.0", "@pretendonetwork/grpc": "^1.0.5", "aws-sdk": "^2.978.0", "bcrypt": "^5.0.0", diff --git a/src/config-manager.ts b/src/config-manager.ts index 7bad4ac..09a2220 100644 --- a/src/config-manager.ts +++ b/src/config-manager.ts @@ -42,12 +42,10 @@ export const config: Config = { } }, email: { - host: process.env.PN_ACT_CONFIG_EMAIL_HOST || '', - port: Number(process.env.PN_ACT_CONFIG_EMAIL_PORT || ''), - secure: (process.env.PN_ACT_CONFIG_EMAIL_SECURE || '') === 'true', - auth: { - user: process.env.PN_ACT_CONFIG_EMAIL_USERNAME || '', - pass: process.env.PN_ACT_CONFIG_EMAIL_PASSWORD || '' + ses: { + region: process.env.PN_ACT_CONFIG_EMAIL_SES_REGION || '', + key: process.env.PN_ACT_CONFIG_EMAIL_SES_ACCESS_KEY || '', + secret: process.env.PN_ACT_CONFIG_EMAIL_SES_SECRET_KEY || '' }, from: process.env.PN_ACT_CONFIG_EMAIL_FROM || '' }, @@ -103,28 +101,18 @@ if (!config.redis.client.url) { disabledFeatures.redis = true; } -if (!config.email.host) { - LOG_WARN('Failed to find email SMTP host. Disabling feature. To enable feature set the PN_ACT_CONFIG_EMAIL_HOST environment variable'); +if (!config.email.ses.region) { + LOG_WARN('Failed to find AWS SES region. Disabling feature. To enable feature set the PN_ACT_CONFIG_EMAIL_SES_REGION environment variable'); disabledFeatures.email = true; } -if (!config.email.port) { - LOG_WARN('Failed to find email SMTP port. Disabling feature. To enable feature set the PN_ACT_CONFIG_EMAIL_PORT environment variable'); +if (!config.email.ses.key) { + LOG_WARN('Failed to find AWS SES access key. Disabling feature. To enable feature set the PN_ACT_CONFIG_EMAIL_SES_ACCESS_KEY environment variable'); disabledFeatures.email = true; } -if (config.email.secure === undefined) { - LOG_WARN('Failed to find email SMTP secure flag. Disabling feature. To enable feature set the PN_ACT_CONFIG_EMAIL_SECURE environment variable'); - disabledFeatures.email = true; -} - -if (!config.email.auth.user) { - LOG_WARN('Failed to find email account username. Disabling feature. To enable feature set the auth.user environment variable'); - disabledFeatures.email = true; -} - -if (!config.email.auth.pass) { - LOG_WARN('Failed to find email account password. Disabling feature. To enable feature set the PN_ACT_CONFIG_EMAIL_PASSWORD environment variable'); +if (!config.email.ses.secret) { + LOG_WARN('Failed to find AWS SES secret key. Disabling feature. To enable feature set the PN_ACT_CONFIG_EMAIL_SES_SECRET_KEY environment variable'); disabledFeatures.email = true; } diff --git a/src/mailer.ts b/src/mailer.ts index 230d412..ea9a1cd 100644 --- a/src/mailer.ts +++ b/src/mailer.ts @@ -1,6 +1,7 @@ import path from 'node:path'; import fs from 'node:fs'; import nodemailer from 'nodemailer'; +import * as aws from '@aws-sdk/client-ses'; import { config, disabledFeatures } from '@/config-manager'; import { MailerOptions } from '@/types/common/mailer-options'; @@ -10,7 +11,21 @@ const confirmationEmailTemplate: string = fs.readFileSync(path.join(__dirname, ' let transporter: nodemailer.Transporter; if (!disabledFeatures.email) { - transporter = nodemailer.createTransport(config.email); + const ses = new aws.SES({ + apiVersion: '2010-12-01', + region: config.email.ses.region, + credentials: { + accessKeyId: config.email.ses.key, + secretAccessKey: config.email.ses.secret + } + }); + + transporter = transporter = nodemailer.createTransport({ + SES: { + ses, + aws + } + }); } export async function sendMail(options: MailerOptions): Promise { diff --git a/src/types/common/config.ts b/src/types/common/config.ts index ce328d9..9c11436 100644 --- a/src/types/common/config.ts +++ b/src/types/common/config.ts @@ -14,12 +14,10 @@ export interface Config { } }; email: { - host: string; - port: number; - secure: boolean; - auth: { - user: string; - pass: string; + ses: { + region: string; + key: string; + secret: string; }; from: string; }; From 426a9574f64aaf306d6b6f082e23af3a7df81a8b Mon Sep 17 00:00:00 2001 From: Matthew Lopez <73856503+MatthewL246@users.noreply.github.com> Date: Sun, 25 Feb 2024 20:09:46 -0500 Subject: [PATCH 141/219] Fix 3DS profile updates causing an unhandledRejection For example, when attempting to change the email address. The 3DS sends an empty person.tz_name, which is interpreted as an empty object. This additional error handling is necessary in this case because empty objects are truthy. --- src/services/nnid/routes/people.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/services/nnid/routes/people.ts b/src/services/nnid/routes/people.ts index 7499bab..083247c 100644 --- a/src/services/nnid/routes/people.ts +++ b/src/services/nnid/routes/people.ts @@ -580,7 +580,13 @@ router.put('/@me', async (request: express.Request, response: express.Response): const region: number = person.region ? person.region : pnid.region; const countryCode: string = person.country ? person.country : pnid.country; const language: string = person.language ? person.language : pnid.language; - const timezoneName: string = person.tz_name ? person.tz_name : pnid.timezone.name; + let timezoneName: string = person.tz_name ? person.tz_name : pnid.timezone.name; + + // Fix for 3DS sending empty person.tz_name, which is interpreted as an empty object + if (typeof timezoneName === 'object' && Object.keys(timezoneName).length === 0) { + timezoneName = pnid.timezone.name; + } + const marketingFlag: boolean = person.marketing_flag ? person.marketing_flag === 'Y' : pnid.flags.marketing; const offDeviceFlag: boolean = person.off_device_flag ? person.off_device_flag === 'Y' : pnid.flags.off_device; @@ -702,4 +708,4 @@ router.put('/@me/emails/@primary', async (request: express.Request, response: ex response.send(''); }); -export default router; \ No newline at end of file +export default router; From 93139a121eff5d25df971a0d204ed57b8a6ec0bd Mon Sep 17 00:00:00 2001 From: Matthew Lopez <73856503+MatthewL246@users.noreply.github.com> Date: Mon, 26 Feb 2024 14:39:01 -0500 Subject: [PATCH 142/219] Update comment syntax and add todo Co-authored-by: Jonathan Barrow --- src/services/nnid/routes/people.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/services/nnid/routes/people.ts b/src/services/nnid/routes/people.ts index 083247c..8b95850 100644 --- a/src/services/nnid/routes/people.ts +++ b/src/services/nnid/routes/people.ts @@ -582,7 +582,9 @@ router.put('/@me', async (request: express.Request, response: express.Response): const language: string = person.language ? person.language : pnid.language; let timezoneName: string = person.tz_name ? person.tz_name : pnid.timezone.name; - // Fix for 3DS sending empty person.tz_name, which is interpreted as an empty object + // * Fix for 3DS sending empty person.tz_name, which is interpreted as an empty object + // TODO - See if there's a cleaner way to do this? + if (typeof timezoneName === 'object' && Object.keys(timezoneName).length === 0) { timezoneName = pnid.timezone.name; } From aa480673d2ef5d3d37155134450a0dc93b760102 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20L=C3=B3pez=20Guimaraes?= Date: Sun, 5 Nov 2023 01:05:24 +0000 Subject: [PATCH 143/219] Better handling of system transfered devices If a user performs a system transfer, we can't guarantee the validity of the linked PIDs to a device, not this device's existance on the database. Add them to the database if necessary. --- src/middleware/nasc.ts | 63 +++++++++++++++++++++++++++++++++++------- 1 file changed, 53 insertions(+), 10 deletions(-) diff --git a/src/middleware/nasc.ts b/src/middleware/nasc.ts index 7a79e85..8dd6bbe 100644 --- a/src/middleware/nasc.ts +++ b/src/middleware/nasc.ts @@ -95,6 +95,13 @@ async function NASCMiddleware(request: express.Request, response: express.Respon return; } + const nexAccount: HydratedNEXAccountDocument | null = await NEXAccount.findOne({ pid }); + + if (!nexAccount || nexAccount.access_level < 0) { + response.status(200).send(nascError('102').toString()); + return; + } + let device: HydratedDeviceDocument | null = await Device.findOne({ fcdcert_hash: fcdcertHash, }); @@ -108,13 +115,56 @@ async function NASCMiddleware(request: express.Request, response: express.Respon if (pid) { const linkedPIDs: number[] = device.linked_pids; + // * If a user performs a system transfer from + // * a console to another using a Nintendo account + // * during the transfer and both consoles have + // * a Pretendo account, the devices will be swapped + // * since we check it using the LFCS. + // * + // * So, the linked PIDs won't have the user's PID + // * anymore. if (!linkedPIDs.includes(pid)) { - response.status(200).send(nascError('102').toString()); - return; + const session: mongoose.ClientSession = await databaseConnection().startSession(); + await session.startTransaction(); + + device.linked_pids.push(pid); + + await device.save({ session }); + + await session.commitTransaction(); } } } + // * Workaround for edge case on system transfers + // * if a console that has a Pretendo account performs + // * a system transfer using the Nintendo account to + // * another that doesn't have a Pretendo account. + // * + // * This would make the Pretendo account to not have + // * a device on the database. + // * + // * TODO: With this change, now multiple devices can + // * have the same serial number and MAC address. + // * Do we want this? If not, are there other solutions? + if (!device && pid) { + const session: mongoose.ClientSession = await databaseConnection().startSession(); + await session.startTransaction(); + + device = new Device({ + model, + serial: serialNumber, + environment, + mac_hash: macAddressHash, + fcdcert_hash: fcdcertHash, + linked_pids: [pid] + }); + + await device.save({ session }); + + await session.commitTransaction(); + } + if (titleID === '0004013000003202') { if (password && !pid && !pidHmac) { // Register new user @@ -183,13 +233,6 @@ async function NASCMiddleware(request: express.Request, response: express.Respon } } - const nexAccount: HydratedNEXAccountDocument | null = await NEXAccount.findOne({ pid }); - - if (!nexAccount || nexAccount.access_level < 0) { - response.status(200).send(nascError('102').toString()); - return; - } - request.nexAccount = nexAccount; return next(); @@ -234,4 +277,4 @@ function validNintendoMACAddress(macAddress: string): boolean { return MAC_REGEX.test(macAddress); } -export default NASCMiddleware; \ No newline at end of file +export default NASCMiddleware; From dc7d44baf0b9eba6ce8494124ab33d6ee35fe8a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20L=C3=B3pez=20Guimaraes?= Date: Sun, 5 Nov 2023 12:08:47 +0000 Subject: [PATCH 144/219] Remove redundant transactions We can update the data directly without a transaction. --- src/middleware/nasc.ts | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/src/middleware/nasc.ts b/src/middleware/nasc.ts index 8dd6bbe..c55135a 100644 --- a/src/middleware/nasc.ts +++ b/src/middleware/nasc.ts @@ -124,14 +124,9 @@ async function NASCMiddleware(request: express.Request, response: express.Respon // * So, the linked PIDs won't have the user's PID // * anymore. if (!linkedPIDs.includes(pid)) { - const session: mongoose.ClientSession = await databaseConnection().startSession(); - await session.startTransaction(); - device.linked_pids.push(pid); - await device.save({ session }); - - await session.commitTransaction(); + await device.save(); } } } @@ -148,9 +143,6 @@ async function NASCMiddleware(request: express.Request, response: express.Respon // * have the same serial number and MAC address. // * Do we want this? If not, are there other solutions? if (!device && pid) { - const session: mongoose.ClientSession = await databaseConnection().startSession(); - await session.startTransaction(); - device = new Device({ model, serial: serialNumber, @@ -160,9 +152,7 @@ async function NASCMiddleware(request: express.Request, response: express.Respon linked_pids: [pid] }); - await device.save({ session }); - - await session.commitTransaction(); + await device.save(); } if (titleID === '0004013000003202') { From 20eed7fc96b572aacc2efb04b0463bc75e15d6b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20L=C3=B3pez=20Guimaraes?= Date: Mon, 6 Nov 2023 21:59:12 +0000 Subject: [PATCH 145/219] Fix NASC registration path --- src/middleware/nasc.ts | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/middleware/nasc.ts b/src/middleware/nasc.ts index c55135a..ca5a241 100644 --- a/src/middleware/nasc.ts +++ b/src/middleware/nasc.ts @@ -95,13 +95,17 @@ async function NASCMiddleware(request: express.Request, response: express.Respon return; } - const nexAccount: HydratedNEXAccountDocument | null = await NEXAccount.findOne({ pid }); + let nexAccount: HydratedNEXAccountDocument | null = null; + if (pid) { + nexAccount = await NEXAccount.findOne({ pid }); - if (!nexAccount || nexAccount.access_level < 0) { - response.status(200).send(nascError('102').toString()); - return; + if (!nexAccount || nexAccount.access_level < 0) { + response.status(200).send(nascError('102').toString()); + return; + } } + let device: HydratedDeviceDocument | null = await Device.findOne({ fcdcert_hash: fcdcertHash, }); @@ -164,7 +168,7 @@ async function NASCMiddleware(request: express.Request, response: express.Respon try { // Create new NEX account - const nexAccount: HydratedNEXAccountDocument = new NEXAccount({ + nexAccount = new NEXAccount({ device_type: '3ds', password }); From 603b209cb57f4e3d0e30a323f227ca201725755c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20L=C3=B3pez=20Guimaraes?= Date: Sat, 13 Apr 2024 22:37:29 +0100 Subject: [PATCH 146/219] nasc: Add more integrity checks --- src/middleware/nasc.ts | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/middleware/nasc.ts b/src/middleware/nasc.ts index ca5a241..4cc657f 100644 --- a/src/middleware/nasc.ts +++ b/src/middleware/nasc.ts @@ -122,8 +122,8 @@ async function NASCMiddleware(request: express.Request, response: express.Respon // * If a user performs a system transfer from // * a console to another using a Nintendo account // * during the transfer and both consoles have - // * a Pretendo account, the devices will be swapped - // * since we check it using the LFCS. + // * a Pretendo account, the new device won't have + // * the user's PID. // * // * So, the linked PIDs won't have the user's PID // * anymore. @@ -133,6 +133,16 @@ async function NASCMiddleware(request: express.Request, response: express.Respon await device.save(); } } + + if (device.serial !== serialNumber) { + response.status(200).send(nascError('102').toString()); + return; + } + + if (device.mac_hash !== macAddressHash) { + response.status(200).send(nascError('102').toString()); + return; + } } // * Workaround for edge case on system transfers @@ -142,10 +152,6 @@ async function NASCMiddleware(request: express.Request, response: express.Respon // * // * This would make the Pretendo account to not have // * a device on the database. - // * - // * TODO: With this change, now multiple devices can - // * have the same serial number and MAC address. - // * Do we want this? If not, are there other solutions? if (!device && pid) { device = new Device({ model, From 2bc1d9cf8e81485babc8ad013e2219db0ef66873 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Sun, 14 Apr 2024 18:02:01 -0400 Subject: [PATCH 147/219] nnas: renamed nnid service to nnas --- package.json | 2 +- src/database.ts | 2 +- src/server.ts | 4 +- src/services/nnas/index.ts | 48 +++++++++++++++++++ src/services/{nnid => nnas}/routes/admin.ts | 0 src/services/{nnid => nnas}/routes/content.ts | 6 +-- src/services/{nnid => nnas}/routes/devices.ts | 0 src/services/{nnid => nnas}/routes/miis.ts | 0 src/services/{nnid => nnas}/routes/oauth.ts | 0 src/services/{nnid => nnas}/routes/people.ts | 10 ++-- .../{nnid => nnas}/routes/provider.ts | 0 src/services/{nnid => nnas}/routes/support.ts | 0 src/services/{nnid => nnas}/timezones.json | 0 src/services/nnid/index.ts | 48 ------------------- src/types/services/{nnid => nnas}/person.ts | 0 .../services/{nnid => nnas}/pnid-profile.ts | 0 .../{nnid => nnas}/region-languages.ts | 2 +- .../{nnid => nnas}/region-timezones.ts | 0 18 files changed, 61 insertions(+), 61 deletions(-) create mode 100644 src/services/nnas/index.ts rename src/services/{nnid => nnas}/routes/admin.ts (100%) rename src/services/{nnid => nnas}/routes/content.ts (94%) rename src/services/{nnid => nnas}/routes/devices.ts (100%) rename src/services/{nnid => nnas}/routes/miis.ts (100%) rename src/services/{nnid => nnas}/routes/oauth.ts (100%) rename src/services/{nnid => nnas}/routes/people.ts (95%) rename src/services/{nnid => nnas}/routes/provider.ts (100%) rename src/services/{nnid => nnas}/routes/support.ts (100%) rename src/services/{nnid => nnas}/timezones.json (100%) delete mode 100644 src/services/nnid/index.ts rename src/types/services/{nnid => nnas}/person.ts (100%) rename src/types/services/{nnid => nnas}/pnid-profile.ts (100%) rename src/types/services/{nnid => nnas}/region-languages.ts (55%) rename src/types/services/{nnid => nnas}/region-timezones.ts (100%) diff --git a/package.json b/package.json index b1359b7..b945ec9 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "clean": "rimraf ./dist", "copy-static": "npm run copy-assets && npm run copy-timezones", "copy-assets": "cp -r ./src/assets ./dist/assets", - "copy-timezones": "cp ./src/services/nnid/timezones.json ./dist/services/nnid/timezones.json", + "copy-timezones": "cp ./src/services/nnas/timezones.json ./dist/services/nnas/timezones.json", "start": "node .", "start:dev": "NODE_ENV=development node ." }, diff --git a/src/database.ts b/src/database.ts index a301b92..656e547 100644 --- a/src/database.ts +++ b/src/database.ts @@ -11,7 +11,7 @@ import { IDevice } from '@/types/mongoose/device'; import { IDeviceAttribute } from '@/types/mongoose/device-attribute'; import { HydratedServerDocument } from '@/types/mongoose/server'; import { Token } from '@/types/common/token'; -import { PNIDProfile } from '@/types/services/nnid/pnid-profile'; +import { PNIDProfile } from '@/types/services/nnas/pnid-profile'; import { ConnectionData } from '@/types/services/api/connection-data'; import { ConnectionResponse } from '@/types/services/api/connection-response'; import { DiscordConnectionData } from '@/types/services/api/discord-connection-data'; diff --git a/src/server.ts b/src/server.ts index 0050b1a..ad5bd83 100644 --- a/src/server.ts +++ b/src/server.ts @@ -16,7 +16,7 @@ import { LOG_INFO, LOG_SUCCESS, LOG_WARN } from '@/logger'; import conntest from '@/services/conntest'; import cbvc from '@/services/cbvc'; -import nnid from '@/services/nnid'; +import nnas from '@/services/nnas'; import nasc from '@/services/nasc'; import datastore from '@/services/datastore'; import api from '@/services/api'; @@ -41,7 +41,7 @@ app.use(xmlparser); // import the servers into one app.use(conntest); app.use(cbvc); -app.use(nnid); +app.use(nnas); app.use(nasc); app.use(datastore); app.use(api); diff --git a/src/services/nnas/index.ts b/src/services/nnas/index.ts new file mode 100644 index 0000000..c90d5eb --- /dev/null +++ b/src/services/nnas/index.ts @@ -0,0 +1,48 @@ +// handles "account.nintendo.net" endpoints + +import express from 'express'; +import subdomain from 'express-subdomain'; +import clientHeaderCheck from '@/middleware/client-header'; +import cemuMiddleware from '@/middleware/cemu'; +import pnidMiddleware from '@/middleware/pnid'; +import { LOG_INFO } from '@/logger'; + +import admin from '@/services/nnas/routes/admin'; +import content from '@/services/nnas/routes/content'; +import devices from '@/services/nnas/routes/devices'; +import miis from '@/services/nnas/routes/miis'; +import oauth from '@/services/nnas/routes/oauth'; +import people from '@/services/nnas/routes/people'; +import provider from '@/services/nnas/routes/provider'; +import support from '@/services/nnas/routes/support'; + +// Router to handle the subdomain restriction +const nnas: express.Router = express.Router(); + +LOG_INFO('[NNAS] Importing middleware'); +nnas.use(clientHeaderCheck); +nnas.use(cemuMiddleware); +nnas.use(pnidMiddleware); + +// Setup routes +LOG_INFO('[NNAS] Applying imported routes'); +nnas.use('/v1/api/admin', admin); +nnas.use('/v1/api/content', content); +nnas.use('/v1/api/devices', devices); +nnas.use('/v1/api/miis', miis); +nnas.use('/v1/api/oauth20', oauth); +nnas.use('/v1/api/people', people); +nnas.use('/v1/api/provider', provider); +nnas.use('/v1/api/support', support); + +// Main router for endpoints +const router = express.Router(); + +// Create subdomains +LOG_INFO('[NNAS] Creating \'account\' subdomain'); +router.use(subdomain('account', nnas)); + +LOG_INFO('[NNAS] Creating \'c.account\' subdomain'); +router.use(subdomain('c.account', nnas)); + +export default router; \ No newline at end of file diff --git a/src/services/nnid/routes/admin.ts b/src/services/nnas/routes/admin.ts similarity index 100% rename from src/services/nnid/routes/admin.ts rename to src/services/nnas/routes/admin.ts diff --git a/src/services/nnid/routes/content.ts b/src/services/nnas/routes/content.ts similarity index 94% rename from src/services/nnid/routes/content.ts rename to src/services/nnas/routes/content.ts index b4d1ac3..db5100d 100644 --- a/src/services/nnid/routes/content.ts +++ b/src/services/nnas/routes/content.ts @@ -1,8 +1,8 @@ import express from 'express'; import xmlbuilder from 'xmlbuilder'; -import timezones from '@/services/nnid/timezones.json'; -import { RegionLanguages } from '@/types/services/nnid/region-languages'; -import { RegionTimezones } from '@/types/services/nnid/region-timezones'; +import timezones from '@/services/nnas/timezones.json'; +import { RegionLanguages } from '@/types/services/nnas/region-languages'; +import { RegionTimezones } from '@/types/services/nnas/region-timezones'; const router: express.Router = express.Router(); diff --git a/src/services/nnid/routes/devices.ts b/src/services/nnas/routes/devices.ts similarity index 100% rename from src/services/nnid/routes/devices.ts rename to src/services/nnas/routes/devices.ts diff --git a/src/services/nnid/routes/miis.ts b/src/services/nnas/routes/miis.ts similarity index 100% rename from src/services/nnid/routes/miis.ts rename to src/services/nnas/routes/miis.ts diff --git a/src/services/nnid/routes/oauth.ts b/src/services/nnas/routes/oauth.ts similarity index 100% rename from src/services/nnid/routes/oauth.ts rename to src/services/nnas/routes/oauth.ts diff --git a/src/services/nnid/routes/people.ts b/src/services/nnas/routes/people.ts similarity index 95% rename from src/services/nnid/routes/people.ts rename to src/services/nnas/routes/people.ts index 8b95850..c4b8bcd 100644 --- a/src/services/nnid/routes/people.ts +++ b/src/services/nnas/routes/people.ts @@ -12,14 +12,14 @@ import { PNID } from '@/models/pnid'; import { NEXAccount } from '@/models/nex-account'; import { LOG_ERROR } from '@/logger'; -import timezones from '@/services/nnid/timezones.json'; +import timezones from '@/services/nnas/timezones.json'; import { HydratedPNIDDocument } from '@/types/mongoose/pnid'; import { HydratedNEXAccountDocument } from '@/types/mongoose/nex-account'; -import { RegionLanguages } from '@/types/services/nnid/region-languages'; -import { RegionTimezone, RegionTimezones } from '@/types/services/nnid/region-timezones'; -import { Person } from '@/types/services/nnid/person'; -import { PNIDProfile } from '@/types/services/nnid/pnid-profile'; +import { RegionLanguages } from '@/types/services/nnas/region-languages'; +import { RegionTimezone, RegionTimezones } from '@/types/services/nnas/region-timezones'; +import { Person } from '@/types/services/nnas/person'; +import { PNIDProfile } from '@/types/services/nnas/pnid-profile'; const router: express.Router = express.Router(); diff --git a/src/services/nnid/routes/provider.ts b/src/services/nnas/routes/provider.ts similarity index 100% rename from src/services/nnid/routes/provider.ts rename to src/services/nnas/routes/provider.ts diff --git a/src/services/nnid/routes/support.ts b/src/services/nnas/routes/support.ts similarity index 100% rename from src/services/nnid/routes/support.ts rename to src/services/nnas/routes/support.ts diff --git a/src/services/nnid/timezones.json b/src/services/nnas/timezones.json similarity index 100% rename from src/services/nnid/timezones.json rename to src/services/nnas/timezones.json diff --git a/src/services/nnid/index.ts b/src/services/nnid/index.ts deleted file mode 100644 index 726b95c..0000000 --- a/src/services/nnid/index.ts +++ /dev/null @@ -1,48 +0,0 @@ -// handles "account.nintendo.net" endpoints - -import express from 'express'; -import subdomain from 'express-subdomain'; -import clientHeaderCheck from '@/middleware/client-header'; -import cemuMiddleware from '@/middleware/cemu'; -import pnidMiddleware from '@/middleware/pnid'; -import { LOG_INFO } from '@/logger'; - -import admin from '@/services/nnid/routes/admin'; -import content from '@/services/nnid/routes/content'; -import devices from '@/services/nnid/routes/devices'; -import miis from '@/services/nnid/routes/miis'; -import oauth from '@/services/nnid/routes/oauth'; -import people from '@/services/nnid/routes/people'; -import provider from '@/services/nnid/routes/provider'; -import support from '@/services/nnid/routes/support'; - -// Router to handle the subdomain restriction -const nnid: express.Router = express.Router(); - -LOG_INFO('[NNID] Importing middleware'); -nnid.use(clientHeaderCheck); -nnid.use(cemuMiddleware); -nnid.use(pnidMiddleware); - -// Setup routes -LOG_INFO('[NNID] Applying imported routes'); -nnid.use('/v1/api/admin', admin); -nnid.use('/v1/api/content', content); -nnid.use('/v1/api/devices', devices); -nnid.use('/v1/api/miis', miis); -nnid.use('/v1/api/oauth20', oauth); -nnid.use('/v1/api/people', people); -nnid.use('/v1/api/provider', provider); -nnid.use('/v1/api/support', support); - -// Main router for endpoints -const router = express.Router(); - -// Create subdomains -LOG_INFO('[NNID] Creating \'account\' subdomain'); -router.use(subdomain('account', nnid)); - -LOG_INFO('[NNID] Creating \'c.account\' subdomain'); -router.use(subdomain('c.account', nnid)); - -export default router; \ No newline at end of file diff --git a/src/types/services/nnid/person.ts b/src/types/services/nnas/person.ts similarity index 100% rename from src/types/services/nnid/person.ts rename to src/types/services/nnas/person.ts diff --git a/src/types/services/nnid/pnid-profile.ts b/src/types/services/nnas/pnid-profile.ts similarity index 100% rename from src/types/services/nnid/pnid-profile.ts rename to src/types/services/nnas/pnid-profile.ts diff --git a/src/types/services/nnid/region-languages.ts b/src/types/services/nnas/region-languages.ts similarity index 55% rename from src/types/services/nnid/region-languages.ts rename to src/types/services/nnas/region-languages.ts index e25307d..fd4bbf9 100644 --- a/src/types/services/nnid/region-languages.ts +++ b/src/types/services/nnas/region-languages.ts @@ -1,4 +1,4 @@ -import { RegionTimezones } from '@/types/services/nnid/region-timezones'; +import { RegionTimezones } from '@/types/services/nnas/region-timezones'; export interface RegionLanguages { [myKey: string]: RegionTimezones diff --git a/src/types/services/nnid/region-timezones.ts b/src/types/services/nnas/region-timezones.ts similarity index 100% rename from src/types/services/nnid/region-timezones.ts rename to src/types/services/nnas/region-timezones.ts From cb10a1666812b994f58ebc2fe9a83f03901e6dcc Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Sun, 14 Apr 2024 19:16:25 -0400 Subject: [PATCH 148/219] chore: removed unnecessary type declarations --- .eslintrc.json | 2 +- src/cache.ts | 12 +- src/config-manager.ts | 4 +- src/database.ts | 41 ++++--- src/logger.ts | 10 +- src/mailer.ts | 8 +- src/middleware/api.ts | 7 +- src/middleware/cemu.ts | 2 +- src/middleware/client-header.ts | 6 +- src/middleware/console-status-verification.ts | 13 +-- src/middleware/device-certificate.ts | 2 +- src/middleware/nasc.ts | 55 +++++----- src/middleware/pnid.ts | 8 +- src/middleware/ratelimit.ts | 2 +- src/middleware/xml-parser.ts | 6 +- src/models/device.ts | 2 +- src/models/nex-account.ts | 16 +-- src/models/pnid.ts | 42 +++---- src/models/server.ts | 2 +- src/nintendo-certificate.ts | 12 +- src/server.ts | 12 +- src/services/api/index.ts | 4 +- src/services/api/routes/v1/connections.ts | 21 ++-- src/services/api/routes/v1/email.ts | 9 +- src/services/api/routes/v1/forgotPassword.ts | 4 +- src/services/api/routes/v1/login.ts | 25 ++--- src/services/api/routes/v1/register.ts | 62 +++++------ src/services/api/routes/v1/resetPassword.ts | 25 ++--- src/services/api/routes/v1/user.ts | 15 ++- src/services/assets/index.ts | 4 +- src/services/cbvc/index.ts | 4 +- src/services/conntest/index.ts | 4 +- src/services/datastore/index.ts | 4 +- src/services/datastore/routes/upload.ts | 42 +++---- .../grpc/account/api-key-middleware.ts | 2 +- .../account/exchange-token-for-user-data.ts | 3 +- src/services/grpc/account/get-nex-data.ts | 3 +- src/services/grpc/account/get-nex-password.ts | 3 +- src/services/grpc/account/get-user-data.ts | 3 +- src/services/grpc/account/implementation.ts | 3 +- .../grpc/account/update-pnid-permissions.ts | 3 +- src/services/grpc/api/api-key-middleware.ts | 2 +- .../grpc/api/authentication-middleware.ts | 8 +- src/services/grpc/api/forgot-password.ts | 2 +- src/services/grpc/api/get-user-data.ts | 3 +- src/services/grpc/api/implementation.ts | 3 +- src/services/grpc/api/login.ts | 23 ++-- src/services/grpc/api/register.ts | 62 +++++------ src/services/grpc/api/reset-password.ts | 23 ++-- .../grpc/api/set-discord-connection-data.ts | 5 +- .../grpc/api/set-stripe-connection-data.ts | 5 +- src/services/grpc/api/update-user-data.ts | 3 +- src/services/grpc/server.ts | 4 +- src/services/local-cdn/index.ts | 4 +- src/services/local-cdn/routes/get.ts | 6 +- src/services/nasc/index.ts | 4 +- src/services/nasc/routes/ac.ts | 28 +++-- src/services/nnas/index.ts | 2 +- src/services/nnas/routes/admin.ts | 15 ++- src/services/nnas/routes/content.ts | 12 +- src/services/nnas/routes/devices.ts | 2 +- src/services/nnas/routes/miis.ts | 9 +- src/services/nnas/routes/oauth.ts | 26 ++--- src/services/nnas/routes/people.ts | 103 +++++++++--------- src/services/nnas/routes/provider.ts | 40 +++---- src/services/nnas/routes/support.ts | 23 ++-- src/types/common/config.ts | 7 -- src/types/services/nnas/region-languages.ts | 5 - src/types/services/nnas/region-timezones.ts | 9 -- src/util.ts | 57 +++++----- 70 files changed, 465 insertions(+), 537 deletions(-) delete mode 100644 src/types/services/nnas/region-languages.ts delete mode 100644 src/types/services/nnas/region-timezones.ts diff --git a/.eslintrc.json b/.eslintrc.json index d66114d..24079aa 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -29,7 +29,7 @@ "no-extra-semi": "off", "@typescript-eslint/no-extra-semi": "error", "@typescript-eslint/no-empty-interface": "warn", - "@typescript-eslint/no-inferrable-types": "off", + "@typescript-eslint/no-inferrable-types": "error", "@typescript-eslint/explicit-function-return-type": "error", "one-var": [ "error", diff --git a/src/cache.ts b/src/cache.ts index 593554b..e354732 100644 --- a/src/cache.ts +++ b/src/cache.ts @@ -4,9 +4,9 @@ import { config, disabledFeatures } from '@/config-manager'; let client: redis.RedisClientType; -const memoryCache: { [key: string]: Buffer } = {}; +const memoryCache: Record = {}; -const LOCAL_CDN_BASE: string = `${__dirname}/../cdn`; +const LOCAL_CDN_BASE = `${__dirname}/../cdn`; export async function connect(): Promise { if (!disabledFeatures.redis) { @@ -26,12 +26,12 @@ export async function setCachedFile(fileName: string, value: Buffer): Promise { - let cachedFile: Buffer = Buffer.alloc(0); + let cachedFile = Buffer.alloc(0); if (disabledFeatures.redis) { cachedFile = memoryCache[fileName] || null; } else { - const redisValue: string | null = await client.get(fileName); + const redisValue = await client.get(fileName); if (redisValue) { cachedFile = Buffer.from(redisValue, encoding); } @@ -43,11 +43,11 @@ export async function getCachedFile(fileName: string, encoding?: BufferEncoding) // * Local CDN cache functions export async function getLocalCDNFile(name: string, encoding?: BufferEncoding): Promise { - let file: Buffer = await getCachedFile(`local_cdn:${name}`, encoding); + let file = await getCachedFile(`local_cdn:${name}`, encoding); if (file === null) { if (await fs.pathExists(`${LOCAL_CDN_BASE}/${name}`)) { - const fileBuffer: string | Buffer = await fs.readFile(`${LOCAL_CDN_BASE}/${name}`, { encoding }); + const fileBuffer = await fs.readFile(`${LOCAL_CDN_BASE}/${name}`, { encoding }); file = Buffer.from(fileBuffer); await setLocalCDNFile(name, file); } diff --git a/src/config-manager.ts b/src/config-manager.ts index 09a2220..de58294 100644 --- a/src/config-manager.ts +++ b/src/config-manager.ts @@ -2,11 +2,11 @@ import fs from 'fs-extra'; import mongoose from 'mongoose'; import dotenv from 'dotenv'; import { LOG_INFO, LOG_WARN, LOG_ERROR } from '@/logger'; -import { Config, DisabledFeatures } from '@/types/common/config'; +import { Config } from '@/types/common/config'; dotenv.config(); -export const disabledFeatures: DisabledFeatures = { +export const disabledFeatures = { redis: false, email: false, captcha: false, diff --git a/src/database.ts b/src/database.ts index 656e547..4f0e7a2 100644 --- a/src/database.ts +++ b/src/database.ts @@ -7,20 +7,18 @@ import { Server } from '@/models/server'; import { LOG_ERROR } from '@/logger'; import { config } from '@/config-manager'; import { HydratedPNIDDocument } from '@/types/mongoose/pnid'; -import { IDevice } from '@/types/mongoose/device'; import { IDeviceAttribute } from '@/types/mongoose/device-attribute'; import { HydratedServerDocument } from '@/types/mongoose/server'; -import { Token } from '@/types/common/token'; import { PNIDProfile } from '@/types/services/nnas/pnid-profile'; import { ConnectionData } from '@/types/services/api/connection-data'; import { ConnectionResponse } from '@/types/services/api/connection-response'; import { DiscordConnectionData } from '@/types/services/api/discord-connection-data'; -const connection_string: string = config.mongoose.connection_string; -const options: mongoose.ConnectOptions = config.mongoose.options; +const connection_string = config.mongoose.connection_string; +const options = config.mongoose.options; // TODO: Extend this later with more settings -const discordConnectionSchema: joi.ObjectSchema = joi.object({ +const discordConnectionSchema = joi.object({ id: joi.string() }); @@ -79,19 +77,19 @@ export async function getPNIDByBasicAuth(token: string): Promise expireTime) { return null; @@ -128,13 +125,13 @@ export async function getPNIDByTokenAuth(token: string): Promise { verifyConnected(); - const pnid: HydratedPNIDDocument | null = await getPNIDByPID(pid); + const pnid = await getPNIDByPID(pid); if (!pnid) { return null; } - const device: IDevice = pnid.devices[0]; // * Just grab the first device + const device = pnid.devices[0]; // * Just grab the first device let device_attributes: { device_attribute: { name: string; @@ -145,9 +142,9 @@ export async function getPNIDProfileJSONByPID(pid: number): Promise { - const name: string = attribute.name; - const value: string = attribute.value; - const created_date: string | undefined = attribute.created_date; + const name = attribute.name; + const value = attribute.value; + const created_date = attribute.created_date; return { device_attribute: { @@ -237,7 +234,7 @@ export async function addPNIDConnection(pnid: HydratedPNIDDocument, data: Connec } export async function addPNIDConnectionDiscord(pnid: HydratedPNIDDocument, data: DiscordConnectionData): Promise { - const valid: joi.ValidationResult = discordConnectionSchema.validate(data); + const valid = discordConnectionSchema.validate(data); if (valid.error) { return { diff --git a/src/logger.ts b/src/logger.ts index 917b677..d3114ee 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -3,7 +3,7 @@ import colors from 'colors'; colors.enable(); -const root: string = process.env.PN_ACT_LOGGER_PATH ? process.env.PN_ACT_LOGGER_PATH : `${__dirname}/..`; +const root = process.env.PN_ACT_LOGGER_PATH ? process.env.PN_ACT_LOGGER_PATH : `${__dirname}/..`; fs.ensureDirSync(`${root}/logs`); const streams = { @@ -15,7 +15,7 @@ const streams = { } as const; export function LOG_SUCCESS(input: string): void { - const time: Date = new Date(); + const time = new Date(); input = `[${time.getHours()}:${time.getMinutes()}:${time.getSeconds()}] [SUCCESS]: ${input}`; streams.success.write(`${input}\n`); @@ -23,7 +23,7 @@ export function LOG_SUCCESS(input: string): void { } export function LOG_ERROR(input: string): void { - const time: Date = new Date(); + const time = new Date(); input = `[${time.getHours()}:${time.getMinutes()}:${time.getSeconds()}] [ERROR]: ${input}`; streams.error.write(`${input}\n`); @@ -31,7 +31,7 @@ export function LOG_ERROR(input: string): void { } export function LOG_WARN(input: string): void { - const time: Date = new Date(); + const time = new Date(); input = `[${time.getHours()}:${time.getMinutes()}:${time.getSeconds()}] [WARN]: ${input}`; streams.warn.write(`${input}\n`); @@ -39,7 +39,7 @@ export function LOG_WARN(input: string): void { } export function LOG_INFO(input: string): void { - const time: Date = new Date(); + const time = new Date(); input = `[${time.getHours()}:${time.getMinutes()}:${time.getSeconds()}] [INFO]: ${input}`; streams.info.write(`${input}\n`); diff --git a/src/mailer.ts b/src/mailer.ts index ea9a1cd..10efd10 100644 --- a/src/mailer.ts +++ b/src/mailer.ts @@ -5,8 +5,8 @@ import * as aws from '@aws-sdk/client-ses'; import { config, disabledFeatures } from '@/config-manager'; import { MailerOptions } from '@/types/common/mailer-options'; -const genericEmailTemplate: string = fs.readFileSync(path.join(__dirname, './assets/emails/genericTemplate.html'), 'utf8'); -const confirmationEmailTemplate: string = fs.readFileSync(path.join(__dirname, './assets/emails/confirmationTemplate.html'), 'utf8'); +const genericEmailTemplate = fs.readFileSync(path.join(__dirname, './assets/emails/genericTemplate.html'), 'utf8'); +const confirmationEmailTemplate = fs.readFileSync(path.join(__dirname, './assets/emails/confirmationTemplate.html'), 'utf8'); let transporter: nodemailer.Transporter; @@ -32,7 +32,7 @@ export async function sendMail(options: MailerOptions): Promise { if (!disabledFeatures.email) { const { to, subject, username, paragraph, preview, text, link, confirmation } = options; - let html: string = confirmation ? confirmationEmailTemplate : genericEmailTemplate; + let html = confirmation ? confirmationEmailTemplate : genericEmailTemplate; html = html.replace(/{{username}}/g, username); html = html.replace(/{{paragraph}}/g, paragraph || ''); @@ -43,7 +43,7 @@ export async function sendMail(options: MailerOptions): Promise { if (link) { const { href, text } = link; - const button: string = ` ${text}`; + const button = ` ${text}`; html = html.replace(//g, button); } diff --git a/src/middleware/api.ts b/src/middleware/api.ts index 6e03492..8a5f915 100644 --- a/src/middleware/api.ts +++ b/src/middleware/api.ts @@ -1,18 +1,17 @@ import express from 'express'; import { getValueFromHeaders } from '@/util'; import { getPNIDByTokenAuth } from '@/database'; -import { HydratedPNIDDocument } from '@/types/mongoose/pnid'; async function APIMiddleware(request: express.Request, _response: express.Response, next: express.NextFunction): Promise { - const authHeader: string | undefined = getValueFromHeaders(request.headers, 'authorization'); + const authHeader = getValueFromHeaders(request.headers, 'authorization'); if (!authHeader || !(authHeader.startsWith('Bearer'))) { return next(); } try { - const token: string = authHeader.split(' ')[1]; - const pnid: HydratedPNIDDocument | null = await getPNIDByTokenAuth(token); + const token = authHeader.split(' ')[1]; + const pnid = await getPNIDByTokenAuth(token); request.pnid = pnid; } catch (error) { diff --git a/src/middleware/cemu.ts b/src/middleware/cemu.ts index dc44985..6edca3b 100644 --- a/src/middleware/cemu.ts +++ b/src/middleware/cemu.ts @@ -1,7 +1,7 @@ import express from 'express'; function CemuMiddleware(request: express.Request, _response: express.Response, next: express.NextFunction): void { - const subdomain: string = request.subdomains.reverse().join('.'); + const subdomain = request.subdomains.reverse().join('.'); request.isCemu = subdomain === 'c.account'; diff --git a/src/middleware/client-header.ts b/src/middleware/client-header.ts index 2288c80..851317f 100644 --- a/src/middleware/client-header.ts +++ b/src/middleware/client-header.ts @@ -2,7 +2,7 @@ import express from 'express'; import xmlbuilder from 'xmlbuilder'; import { getValueFromHeaders } from '@/util'; -const VALID_CLIENT_ID_SECRET_PAIRS: { [key: string]: string } = { +const VALID_CLIENT_ID_SECRET_PAIRS: Record = { // * 'Key' is the client ID, 'Value' is the client secret 'a2efa818a34fa16b8afbc8a74eba3eda': 'c91cdb5658bd4954ade78533a339cf9a', // * Possibly WiiU exclusive? 'daf6227853bcbdce3d75baee8332b': '3eff548eac636e2bf45bb7b375e7b6b0', // * Possibly 3DS exclusive? @@ -14,8 +14,8 @@ function nintendoClientHeaderCheck(request: express.Request, response: express.R response.set('Server', 'Nintendo 3DS (http)'); response.set('X-Nintendo-Date', new Date().getTime().toString()); - const clientId: string | undefined = getValueFromHeaders(request.headers, 'x-nintendo-client-id'); - const clientSecret: string | undefined = getValueFromHeaders(request.headers, 'x-nintendo-client-secret'); + const clientId = getValueFromHeaders(request.headers, 'x-nintendo-client-id'); + const clientSecret = getValueFromHeaders(request.headers, 'x-nintendo-client-secret'); if ( !clientId || diff --git a/src/middleware/console-status-verification.ts b/src/middleware/console-status-verification.ts index cb1b7d3..90d5cb3 100644 --- a/src/middleware/console-status-verification.ts +++ b/src/middleware/console-status-verification.ts @@ -3,7 +3,6 @@ import express from 'express'; import xmlbuilder from 'xmlbuilder'; import { Device } from '@/models/device'; import { getValueFromHeaders } from '@/util'; -import { HydratedDeviceDocument } from '@/types/mongoose/device'; async function consoleStatusVerificationMiddleware(request: express.Request, response: express.Response, next: express.NextFunction): Promise { if (!request.certificate || !request.certificate.valid) { @@ -17,7 +16,7 @@ async function consoleStatusVerificationMiddleware(request: express.Request, res return; } - const deviceIDHeader: string | undefined = getValueFromHeaders(request.headers, 'x-nintendo-device-id'); + const deviceIDHeader = getValueFromHeaders(request.headers, 'x-nintendo-device-id'); if (!deviceIDHeader) { response.status(400).send(xmlbuilder.create({ @@ -30,7 +29,7 @@ async function consoleStatusVerificationMiddleware(request: express.Request, res return; } - const deviceID: number = Number(deviceIDHeader); + const deviceID = Number(deviceIDHeader); if (isNaN(deviceID)) { response.status(400).send(xmlbuilder.create({ @@ -43,7 +42,7 @@ async function consoleStatusVerificationMiddleware(request: express.Request, res return; } - const serialNumber: string | undefined = getValueFromHeaders(request.headers, 'x-nintendo-serial-number'); + const serialNumber = getValueFromHeaders(request.headers, 'x-nintendo-serial-number'); // TODO - Verify serial numbers somehow? // * This is difficult to do safely because serial numbers are @@ -72,7 +71,7 @@ async function consoleStatusVerificationMiddleware(request: express.Request, res // * This is kinda temp for now. Needs to be redone to handle linking this data to existing 3DS devices in the DB // TODO - 3DS consoles are created in the NASC middleware. They need special handling to link them up with the data in the NNID API! if (request.certificate.consoleType === 'wiiu') { - const certificateDeviceID: number = parseInt(request.certificate.certificateName.slice(2), 16); + const certificateDeviceID = parseInt(request.certificate.certificateName.slice(2), 16); if (deviceID !== certificateDeviceID) { // TODO - Change this to a different error @@ -88,9 +87,9 @@ async function consoleStatusVerificationMiddleware(request: express.Request, res } // * Only store a hash of the certificate in case of a breach - const certificateHash: string = crypto.createHash('sha256').update(request.certificate._certificate).digest('base64'); + const certificateHash = crypto.createHash('sha256').update(request.certificate._certificate).digest('base64'); - let device: HydratedDeviceDocument | null = await Device.findOne({ + let device = await Device.findOne({ certificate_hash: certificateHash, }); diff --git a/src/middleware/device-certificate.ts b/src/middleware/device-certificate.ts index 9e7d224..4080d89 100644 --- a/src/middleware/device-certificate.ts +++ b/src/middleware/device-certificate.ts @@ -3,7 +3,7 @@ import NintendoCertificate from '@/nintendo-certificate'; import { getValueFromHeaders } from '@/util'; function deviceCertificateMiddleware(request: express.Request, _response: express.Response, next: express.NextFunction): void { - const certificate: string | undefined = getValueFromHeaders(request.headers, 'x-nintendo-device-cert'); + const certificate = getValueFromHeaders(request.headers, 'x-nintendo-device-cert'); if (!certificate) { return next(); diff --git a/src/middleware/nasc.ts b/src/middleware/nasc.ts index 4cc657f..73bdb11 100644 --- a/src/middleware/nasc.ts +++ b/src/middleware/nasc.ts @@ -1,6 +1,5 @@ import crypto from 'node:crypto'; import express from 'express'; -import mongoose from 'mongoose'; import { Device } from '@/models/device'; import { NEXAccount } from '@/models/nex-account'; import { nascError, nintendoBase64Decode } from '@/util'; @@ -8,8 +7,6 @@ import { connection as databaseConnection } from '@/database'; import NintendoCertificate from '@/nintendo-certificate'; import { LOG_ERROR } from '@/logger'; import { NASCRequestParams } from '@/types/services/nasc/request-params'; -import { HydratedNEXAccountDocument } from '@/types/mongoose/nex-account'; -import { HydratedDeviceDocument } from '@/types/mongoose/device'; async function NASCMiddleware(request: express.Request, response: express.Response, next: express.NextFunction): Promise { const requestParams: NASCRequestParams = request.body; @@ -25,19 +22,19 @@ async function NASCMiddleware(request: express.Request, response: express.Respon return; } - const action: string = nintendoBase64Decode(requestParams.action).toString(); - const fcdcert: Buffer = nintendoBase64Decode(requestParams.fcdcert); - const serialNumber: string = nintendoBase64Decode(requestParams.csnum).toString(); - const macAddress: string = nintendoBase64Decode(requestParams.macadr).toString(); - const titleID: string = nintendoBase64Decode(requestParams.titleid).toString(); - const environment: string = nintendoBase64Decode(requestParams.servertype).toString(); + const action = nintendoBase64Decode(requestParams.action).toString(); + const fcdcert = nintendoBase64Decode(requestParams.fcdcert); + const serialNumber = nintendoBase64Decode(requestParams.csnum).toString(); + const macAddress = nintendoBase64Decode(requestParams.macadr).toString(); + const titleID = nintendoBase64Decode(requestParams.titleid).toString(); + const environment = nintendoBase64Decode(requestParams.servertype).toString(); - const macAddressHash: string = crypto.createHash('sha256').update(macAddress).digest('base64'); - const fcdcertHash: string = crypto.createHash('sha256').update(fcdcert).digest('base64'); + const macAddressHash = crypto.createHash('sha256').update(macAddress).digest('base64'); + const fcdcertHash = crypto.createHash('sha256').update(fcdcert).digest('base64'); - let pid: number = 0; // * Real PIDs are always positive and non-zero - let pidHmac: string = ''; - let password: string = ''; + let pid = 0; // * Real PIDs are always positive and non-zero + let pidHmac = ''; + let password = ''; if (requestParams.userid) { pid = Number(nintendoBase64Decode(requestParams.userid).toString()); @@ -56,7 +53,7 @@ async function NASCMiddleware(request: express.Request, response: express.Respon return; } - const cert: NintendoCertificate = new NintendoCertificate(fcdcert); + const cert = new NintendoCertificate(fcdcert); if (!cert.valid) { response.status(200).send(nascError('121').toString()); @@ -68,7 +65,7 @@ async function NASCMiddleware(request: express.Request, response: express.Respon return; } - let model: string = ''; + let model = ''; switch (serialNumber[0]) { case 'C': model = 'ctr'; @@ -95,7 +92,7 @@ async function NASCMiddleware(request: express.Request, response: express.Respon return; } - let nexAccount: HydratedNEXAccountDocument | null = null; + let nexAccount = null; if (pid) { nexAccount = await NEXAccount.findOne({ pid }); @@ -106,7 +103,7 @@ async function NASCMiddleware(request: express.Request, response: express.Respon } - let device: HydratedDeviceDocument | null = await Device.findOne({ + let device = await Device.findOne({ fcdcert_hash: fcdcertHash, }); @@ -117,7 +114,7 @@ async function NASCMiddleware(request: express.Request, response: express.Respon } if (pid) { - const linkedPIDs: number[] = device.linked_pids; + const linkedPIDs = device.linked_pids; // * If a user performs a system transfer from // * a console to another using a Nintendo account @@ -169,7 +166,7 @@ async function NASCMiddleware(request: express.Request, response: express.Respon if (password && !pid && !pidHmac) { // Register new user - const session: mongoose.ClientSession = await databaseConnection().startSession(); + const session = await databaseConnection().startSession(); await session.startTransaction(); try { @@ -185,15 +182,15 @@ async function NASCMiddleware(request: express.Request, response: express.Respon pid = nexAccount.pid; - const pidBuffer: Buffer = Buffer.alloc(4); + const pidBuffer = Buffer.alloc(4); pidBuffer.writeUInt32LE(pid); - const hash: crypto.Hash = crypto.createHash('sha1').update(pidBuffer); - const pidHash: Buffer = hash.digest(); - const checksum: number = pidHash[0] >> 1; - const hex: string = checksum.toString(16) + pid.toString(16); - const int: number = parseInt(hex, 16); - const friendCode: string = int.toString().padStart(12, '0').match(/.{1,4}/g)!.join('-'); + const hash = crypto.createHash('sha1').update(pidBuffer); + const pidHash = hash.digest(); + const checksum = pidHash[0] >> 1; + const hex = checksum.toString(16) + pid.toString(16); + const int = parseInt(hex, 16); + const friendCode = int.toString().padStart(12, '0').match(/.{1,4}/g)!.join('-'); nexAccount.friend_code = friendCode; @@ -240,7 +237,7 @@ async function NASCMiddleware(request: express.Request, response: express.Respon // https://www.adminsub.net/mac-address-finder/nintendo // Saves us from doing an OUI lookup each time -const NINTENDO_VENDER_OUIS: string[] = [ +const NINTENDO_VENDER_OUIS = [ 'ECC40D', 'E84ECE', 'E0F6B5', 'E0E751', 'E00C7F', 'DC68EB', 'D86BF7', 'D4F057', 'CCFB65', 'CC9E00', 'B8AE6E', 'B88AEC', 'B87826', 'A4C0E1', 'A45C27', 'A438CC', '9CE635', '98E8FA', @@ -266,7 +263,7 @@ const NINTENDO_VENDER_OUIS: string[] = [ ]; // TODO: Make something better -const MAC_REGEX: RegExp = /^[0-9a-fA-F]{12}$/; +const MAC_REGEX = /^[0-9a-fA-F]{12}$/; // Maybe should later parse more data out function validNintendoMACAddress(macAddress: string): boolean { diff --git a/src/middleware/pnid.ts b/src/middleware/pnid.ts index 780dc74..cb9776e 100644 --- a/src/middleware/pnid.ts +++ b/src/middleware/pnid.ts @@ -5,15 +5,15 @@ import { getPNIDByBasicAuth, getPNIDByTokenAuth } from '@/database'; import { HydratedPNIDDocument } from '@/types/mongoose/pnid'; async function PNIDMiddleware(request: express.Request, response: express.Response, next: express.NextFunction): Promise { - const authHeader: string | undefined = getValueFromHeaders(request.headers, 'authorization'); + const authHeader = getValueFromHeaders(request.headers, 'authorization'); if (!authHeader || !(authHeader.startsWith('Bearer') || authHeader.startsWith('Basic'))) { return next(); } - const parts: string[] = authHeader.split(' '); - const type: string = parts[0]; - let token: string = parts[1]; + const parts = authHeader.split(' '); + const type = parts[0]; + let token = parts[1]; let pnid: HydratedPNIDDocument | null; if (request.isCemu) { diff --git a/src/middleware/ratelimit.ts b/src/middleware/ratelimit.ts index 7538724..c12f7f1 100644 --- a/src/middleware/ratelimit.ts +++ b/src/middleware/ratelimit.ts @@ -7,7 +7,7 @@ export default ratelimit({ windowMs: 60 * 1000, max: 1, keyGenerator: (request: express.Request): string => { - let data: string | undefined = getValueFromHeaders(request.headers, 'x-nintendo-device-cert'); + let data = getValueFromHeaders(request.headers, 'x-nintendo-device-cert'); if (!data) { data = request.ip; diff --git a/src/middleware/xml-parser.ts b/src/middleware/xml-parser.ts index 435b1e3..dec8caa 100644 --- a/src/middleware/xml-parser.ts +++ b/src/middleware/xml-parser.ts @@ -5,9 +5,9 @@ import { getValueFromHeaders, mapToObject } from '@/util'; function XMLMiddleware(request: express.Request, response: express.Response, next: express.NextFunction): void { if (request.method == 'POST' || request.method == 'PUT') { - const contentType: string | undefined = getValueFromHeaders(request.headers, 'content-type'); - const contentLength: string | undefined = getValueFromHeaders(request.headers, 'content-length'); - let body: string = ''; + const contentType = getValueFromHeaders(request.headers, 'content-type'); + const contentLength = getValueFromHeaders(request.headers, 'content-length'); + let body = ''; if ( !contentType || diff --git a/src/models/device.ts b/src/models/device.ts index cf4ff10..a219516 100644 --- a/src/models/device.ts +++ b/src/models/device.ts @@ -44,4 +44,4 @@ export const DeviceSchema = new Schema({ certificate_hash: String }); -export const Device: DeviceModel = model('Device', DeviceSchema); \ No newline at end of file +export const Device = model('Device', DeviceSchema); \ No newline at end of file diff --git a/src/models/nex-account.ts b/src/models/nex-account.ts index 3f7c0dd..9372ac6 100644 --- a/src/models/nex-account.ts +++ b/src/models/nex-account.ts @@ -1,6 +1,6 @@ import { Schema, model } from 'mongoose'; import uniqueValidator from 'mongoose-unique-validator'; -import { HydratedNEXAccountDocument, INEXAccount, INEXAccountMethods, NEXAccountModel } from '@/types/mongoose/nex-account'; +import { INEXAccount, INEXAccountMethods, NEXAccountModel } from '@/types/mongoose/nex-account'; const NEXAccountSchema = new Schema({ device_type: { @@ -39,12 +39,12 @@ NEXAccountSchema.plugin(uniqueValidator, { message: '{PATH} already in use.' }); and the next few accounts counting down seem to be admin, service and internal test accounts */ NEXAccountSchema.method('generatePID', async function generatePID(): Promise { - const min: number = 1000000000; // The console (WiiU) seems to not accept PIDs smaller than this - const max: number = 1799999999; + const min = 1000000000; // The console (WiiU) seems to not accept PIDs smaller than this + const max = 1799999999; - const pid: number = Math.floor(Math.random() * (max - min + 1) + min); + const pid = Math.floor(Math.random() * (max - min + 1) + min); - const inuse: HydratedNEXAccountDocument | null = await NEXAccount.findOne({ pid }); + const inuse = await NEXAccount.findOne({ pid }); if (inuse) { await this.generatePID(); @@ -55,13 +55,13 @@ NEXAccountSchema.method('generatePID', async function generatePID(): Promise('NEXAccount', NEXAccountSchema); \ No newline at end of file +export const NEXAccount = model('NEXAccount', NEXAccountSchema); \ No newline at end of file diff --git a/src/models/pnid.ts b/src/models/pnid.ts index e3054cc..1e19629 100644 --- a/src/models/pnid.ts +++ b/src/models/pnid.ts @@ -9,7 +9,7 @@ import Stripe from 'stripe'; import { DeviceSchema } from '@/models/device'; import { uploadCDNAsset } from '@/util'; import { LOG_ERROR, LOG_WARN } from '@/logger'; -import { HydratedPNIDDocument, IPNID, IPNIDMethods, PNIDModel } from '@/types/mongoose/pnid'; +import { IPNID, IPNIDMethods, PNIDModel } from '@/types/mongoose/pnid'; import { PNIDPermissionFlag } from '@/types/common/permission-flags'; import { config } from '@/config-manager'; @@ -133,12 +133,12 @@ PNIDSchema.plugin(uniqueValidator, {message: '{PATH} already in use.'}); and the next few accounts counting down seem to be admin, service and internal test accounts */ PNIDSchema.method('generatePID', async function generatePID(): Promise { - const min: number = 1000000000; // The console (WiiU) seems to not accept PIDs smaller than this - const max: number = 1799999999; + const min = 1000000000; // The console (WiiU) seems to not accept PIDs smaller than this + const max = 1799999999; - const pid: number = Math.floor(Math.random() * (max - min + 1) + min); + const pid = Math.floor(Math.random() * (max - min + 1) + min); - const inuse: HydratedPNIDDocument | null = await PNID.findOne({ + const inuse = await PNID.findOne({ pid }); @@ -152,15 +152,15 @@ PNIDSchema.method('generatePID', async function generatePID(): Promise { PNIDSchema.method('generateEmailValidationCode', async function generateEmailValidationCode(): Promise { // WiiU passes the PID along with the email code // Does not actually need to be unique to all users - const code: string = Math.random().toFixed(6).split('.')[1]; // Dirty one-liner to generate numbers of 6 length and padded 0 + const code = Math.random().toFixed(6).split('.')[1]; // Dirty one-liner to generate numbers of 6 length and padded 0 this.identification.email_code = code; }); PNIDSchema.method('generateEmailValidationToken', async function generateEmailValidationToken(): Promise { - const token: string = crypto.randomBytes(32).toString('hex'); + const token = crypto.randomBytes(32).toString('hex'); - const inuse: HydratedPNIDDocument | null = await PNID.findOne({ + const inuse = await PNID.findOne({ 'identification.email_token': token }); @@ -185,40 +185,40 @@ PNIDSchema.method('updateMii', async function updateMii({ name, primary, data }: }); PNIDSchema.method('generateMiiImages', async function generateMiiImages(): Promise { - const miiData: string = this.mii.data; - const mii: Mii = new Mii(Buffer.from(miiData, 'base64')); - const miiStudioUrl: string = mii.studioUrl({ + const miiData = this.mii.data; + const mii = new Mii(Buffer.from(miiData, 'base64')); + const miiStudioUrl = mii.studioUrl({ type: 'face', width: 128, instanceCount: 1, }); - const miiStudioNormalFaceImageData: Buffer = await got(miiStudioUrl).buffer(); - const pngData: ImageData = await imagePixels(miiStudioNormalFaceImageData); - const tga: Buffer = TGA.createTgaBuffer(pngData.width, pngData.height, Uint8Array.from(pngData.data), false); + const miiStudioNormalFaceImageData = await got(miiStudioUrl).buffer(); + const pngData = await imagePixels(miiStudioNormalFaceImageData); + const tga = TGA.createTgaBuffer(pngData.width, pngData.height, Uint8Array.from(pngData.data), false); - const userMiiKey: string = `mii/${this.pid}`; + const userMiiKey = `mii/${this.pid}`; await uploadCDNAsset('pn-cdn', `${userMiiKey}/standard.tga`, tga, 'public-read'); await uploadCDNAsset('pn-cdn', `${userMiiKey}/normal_face.png`, miiStudioNormalFaceImageData, 'public-read'); - const expressions: string[] = ['frustrated', 'smile_open_mouth', 'wink_left', 'sorrow', 'surprise_open_mouth']; + const expressions = ['frustrated', 'smile_open_mouth', 'wink_left', 'sorrow', 'surprise_open_mouth']; for (const expression of expressions) { - const miiStudioExpressionUrl: string = mii.studioUrl({ + const miiStudioExpressionUrl = mii.studioUrl({ type: 'face', expression: expression, width: 128, instanceCount: 1, }); - const miiStudioExpressionImageData: Buffer = await got(miiStudioExpressionUrl).buffer(); + const miiStudioExpressionImageData = await got(miiStudioExpressionUrl).buffer(); await uploadCDNAsset('pn-cdn', `${userMiiKey}/${expression}.png`, miiStudioExpressionImageData, 'public-read'); } - const miiStudioBodyUrl: string = mii.studioUrl({ + const miiStudioBodyUrl = mii.studioUrl({ type: 'all_body', width: 270, instanceCount: 1, }); - const miiStudioBodyImageData: Buffer = await got(miiStudioBodyUrl).buffer(); + const miiStudioBodyImageData = await got(miiStudioBodyUrl).buffer(); await uploadCDNAsset('pn-cdn', `${userMiiKey}/body.png`, miiStudioBodyImageData, 'public-read'); }); @@ -290,4 +290,4 @@ PNIDSchema.method('clearPermission', function clearPermission(flag: PNIDPermissi this.permissions &= ~flag; }); -export const PNID: PNIDModel = model('PNID', PNIDSchema); \ No newline at end of file +export const PNID = model('PNID', PNIDSchema); \ No newline at end of file diff --git a/src/models/server.ts b/src/models/server.ts index afdd9af..3ab6fce 100644 --- a/src/models/server.ts +++ b/src/models/server.ts @@ -18,4 +18,4 @@ const ServerSchema = new Schema({ ServerSchema.plugin(uniqueValidator, { message: '{PATH} already in use.' }); -export const Server: ServerModel = model('Server', ServerSchema); \ No newline at end of file +export const Server = model('Server', ServerSchema); \ No newline at end of file diff --git a/src/nintendo-certificate.ts b/src/nintendo-certificate.ts index ba15762..da1741c 100644 --- a/src/nintendo-certificate.ts +++ b/src/nintendo-certificate.ts @@ -120,7 +120,7 @@ class NintendoCertificate { // * Assume regular certificate this.signatureType = this._certificate.readUInt32BE(0x00); - const signatureTypeSizes: SignatureSize = this._signatureTypeSizes(this.signatureType); + const signatureTypeSizes = this._signatureTypeSizes(this.signatureType); this._certificateBody = this._certificate.subarray(0x4 + signatureTypeSizes.SIZE + signatureTypeSizes.PADDING_SIZE); @@ -180,7 +180,7 @@ class NintendoCertificate { } _verifySignatureRSA4096(): void { - const publicKey: NodeRSA = new NodeRSA(); + const publicKey = new NodeRSA(); publicKey.importKey({ n: this.publicKeyData.subarray(0x0, 0x200), @@ -191,7 +191,7 @@ class NintendoCertificate { } _verifySignatureRSA2048(): void { - const publicKey: NodeRSA = new NodeRSA(); + const publicKey = new NodeRSA(); publicKey.importKey({ n: this.publicKeyData.subarray(0x0, 0x100), @@ -206,8 +206,8 @@ class NintendoCertificate { // from bytes to PEM! // https://github.com/Myriachan _verifySignatureECDSA(): void { - const pem: string = this.consoleType === 'wiiu' ? WIIU_DEVICE_PUB_PEM : CTR_DEVICE_PUB_PEM; - const key: crypto.VerifyPublicKeyInput = { + const pem = this.consoleType === 'wiiu' ? WIIU_DEVICE_PUB_PEM : CTR_DEVICE_PUB_PEM; + const key = { key: pem, dsaEncoding: 'ieee-p1363' as crypto.DSAEncoding }; @@ -216,7 +216,7 @@ class NintendoCertificate { } _verifySignatureLFCS(): void { - const publicKey: NodeRSA = new NodeRSA(); + const publicKey = new NodeRSA(); publicKey.importKey({ n: CTR_LFCS_B_PUB, diff --git a/src/server.ts b/src/server.ts index ad5bd83..e496b74 100644 --- a/src/server.ts +++ b/src/server.ts @@ -25,7 +25,7 @@ import assets from '@/services/assets'; import { config } from '@/config-manager'; -const app: express.Express = express(); +const app = express(); // START APPLICATION @@ -51,8 +51,8 @@ app.use(assets); // 404 handler LOG_INFO('Creating 404 status handler'); app.use((request: express.Request, response: express.Response): void => { - const url: string = fullUrl(request); - let deviceId: string | undefined = getValueFromHeaders(request.headers, 'X-Nintendo-Device-ID'); + const url = fullUrl(request); + let deviceId = getValueFromHeaders(request.headers, 'X-Nintendo-Device-ID'); if (!deviceId) { deviceId = 'Unknown'; @@ -78,9 +78,9 @@ app.use((request: express.Request, response: express.Response): void => { // non-404 error handler LOG_INFO('Creating non-404 status handler'); app.use((error: any, request: express.Request, response: express.Response, _next: express.NextFunction): void => { - const status: number = error.status || 500; - const url: string = fullUrl(request); - let deviceId: string | undefined = getValueFromHeaders(request.headers, 'X-Nintendo-Device-ID'); + const status = error.status || 500; + const url = fullUrl(request); + let deviceId = getValueFromHeaders(request.headers, 'X-Nintendo-Device-ID'); if (!deviceId) { deviceId = 'Unknown'; diff --git a/src/services/api/index.ts b/src/services/api/index.ts index b849f3e..45efc53 100644 --- a/src/services/api/index.ts +++ b/src/services/api/index.ts @@ -7,7 +7,7 @@ import { LOG_INFO } from '@/logger'; import { V1 } from '@/services/api/routes'; // Router to handle the subdomain restriction -const api: express.Router = express.Router(); +const api = express.Router(); LOG_INFO('[USER API] Importing middleware'); api.use(APIMiddleware); @@ -26,7 +26,7 @@ api.use('/v1/user', V1.USER); // Main router for endpoints -const router: express.Router = express.Router(); +const router = express.Router(); // Create subdomains LOG_INFO('[USER API] Creating \'api\' subdomain'); diff --git a/src/services/api/routes/v1/connections.ts b/src/services/api/routes/v1/connections.ts index 92c3fd8..2a0612a 100644 --- a/src/services/api/routes/v1/connections.ts +++ b/src/services/api/routes/v1/connections.ts @@ -1,12 +1,9 @@ import express from 'express'; import { addPNIDConnection, removePNIDConnection } from '@/database'; -import { ConnectionData } from '@/types/services/api/connection-data'; -import { ConnectionResponse } from '@/types/services/api/connection-response'; -import { HydratedPNIDDocument } from '@/types/mongoose/pnid'; -const router: express.Router = express.Router(); +const router = express.Router(); -const VALID_CONNECTION_TYPES: string[] = [ +const VALID_CONNECTION_TYPES = [ 'discord' ]; @@ -16,9 +13,9 @@ const VALID_CONNECTION_TYPES: string[] = [ * Description: Adds an account connection to the users PNID */ router.post('/add/:type', async (request: express.Request, response: express.Response): Promise => { - const data: ConnectionData = request.body?.data; - const pnid: HydratedPNIDDocument | null = request.pnid; - const type: string = request.params.type; + const data = request.body?.data; + const pnid = request.pnid; + const type = request.params.type; if (!pnid) { response.status(400).json({ @@ -50,7 +47,7 @@ router.post('/add/:type', async (request: express.Request, response: express.Res return; } - let result: ConnectionResponse | undefined = await addPNIDConnection(pnid, data, type); + let result = await addPNIDConnection(pnid, data, type); if (!result) { result = { @@ -69,8 +66,8 @@ router.post('/add/:type', async (request: express.Request, response: express.Res * Description: Removes an account connection from the users PNID */ router.delete('/remove/:type', async (request: express.Request, response: express.Response): Promise => { - const pnid: HydratedPNIDDocument | null = request.pnid; - const type: string = request.params.type; + const pnid = request.pnid; + const type = request.params.type; if (!pnid) { response.status(400).json({ @@ -92,7 +89,7 @@ router.delete('/remove/:type', async (request: express.Request, response: expres return; } - let result: ConnectionResponse | undefined = await removePNIDConnection(pnid, type); + let result = await removePNIDConnection(pnid, type); if (!result) { result = { diff --git a/src/services/api/routes/v1/email.ts b/src/services/api/routes/v1/email.ts index b0b41f3..fa306c5 100644 --- a/src/services/api/routes/v1/email.ts +++ b/src/services/api/routes/v1/email.ts @@ -2,12 +2,11 @@ import express from 'express'; import moment from 'moment'; import { PNID } from '@/models/pnid'; import { getValueFromQueryString, sendEmailConfirmedEmail } from '@/util'; -import { HydratedPNIDDocument } from '@/types/mongoose/pnid'; -const router: express.Router = express.Router(); +const router = express.Router(); router.get('/verify', async (request: express.Request, response: express.Response): Promise => { - const token: string | undefined = getValueFromQueryString(request.query, 'token'); + const token = getValueFromQueryString(request.query, 'token'); if (!token || token.trim() == '') { response.status(400).json({ @@ -19,7 +18,7 @@ router.get('/verify', async (request: express.Request, response: express.Respons return; } - const pnid: HydratedPNIDDocument | null = await PNID.findOne({ + const pnid = await PNID.findOne({ 'identification.email_token': token }); @@ -33,7 +32,7 @@ router.get('/verify', async (request: express.Request, response: express.Respons return; } - const validatedDate: string = moment().format('YYYY-MM-DDTHH:MM:SS'); + const validatedDate = moment().format('YYYY-MM-DDTHH:MM:SS'); pnid.email.reachable = true; pnid.email.validated = true; diff --git a/src/services/api/routes/v1/forgotPassword.ts b/src/services/api/routes/v1/forgotPassword.ts index 521bc56..58838bb 100644 --- a/src/services/api/routes/v1/forgotPassword.ts +++ b/src/services/api/routes/v1/forgotPassword.ts @@ -4,10 +4,10 @@ import { getPNIDByEmailAddress, getPNIDByUsername } from '@/database'; import { sendForgotPasswordEmail } from '@/util'; import { HydratedPNIDDocument } from '@/types/mongoose/pnid'; -const router: express.Router = express.Router(); +const router = express.Router(); router.post('/', async (request: express.Request, response: express.Response): Promise => { - const input: string = request.body?.input; + const input = request.body?.input; if (!input || input.trim() === '') { response.status(400).json({ diff --git a/src/services/api/routes/v1/login.ts b/src/services/api/routes/v1/login.ts index 03e47bb..21a92b6 100644 --- a/src/services/api/routes/v1/login.ts +++ b/src/services/api/routes/v1/login.ts @@ -3,10 +3,9 @@ import bcrypt from 'bcrypt'; import { getPNIDByUsername, getPNIDByTokenAuth } from '@/database'; import { nintendoPasswordHash, generateToken} from '@/util'; import { config } from '@/config-manager'; -import { TokenOptions } from '@/types/common/token-options'; import { HydratedPNIDDocument } from '@/types/mongoose/pnid'; -const router: express.Router = express.Router(); +const router = express.Router(); /** * [POST] @@ -15,10 +14,10 @@ const router: express.Router = express.Router(); * TODO: Replace this with a more robust OAuth2 implementation */ router.post('/', async (request: express.Request, response: express.Response): Promise => { - const grantType: string = request.body?.grant_type; - const username: string = request.body?.username; - const password: string = request.body?.password; - const refreshToken: string = request.body?.refresh_token; + const grantType = request.body?.grant_type; + const username = request.body?.username; + const password = request.body?.password; + const refreshToken = request.body?.refresh_token; if (!['password', 'refresh_token'].includes(grantType)) { response.status(400).json({ @@ -75,7 +74,7 @@ router.post('/', async (request: express.Request, response: express.Response): P return; } - const hashedPassword: string = nintendoPasswordHash(password, pnid.pid); + const hashedPassword = nintendoPasswordHash(password, pnid.pid); if (!pnid || !bcrypt.compareSync(hashedPassword, pnid.password)) { response.status(400).json({ @@ -100,7 +99,7 @@ router.post('/', async (request: express.Request, response: express.Response): P } } - const accessTokenOptions: TokenOptions = { + const accessTokenOptions = { system_type: 0x3, // * API token_type: 0x1, // * OAuth Access pid: pnid.pid, @@ -109,7 +108,7 @@ router.post('/', async (request: express.Request, response: express.Response): P expire_time: BigInt(Date.now() + (3600 * 1000)) }; - const refreshTokenOptions: TokenOptions = { + const refreshTokenOptions = { system_type: 0x3, // * API token_type: 0x2, // * OAuth Refresh pid: pnid.pid, @@ -118,11 +117,11 @@ router.post('/', async (request: express.Request, response: express.Response): P expire_time: BigInt(Date.now() + (3600 * 1000)) }; - const accessTokenBuffer: Buffer | null = await generateToken(config.aes_key, accessTokenOptions); - const refreshTokenBuffer: Buffer | null = await generateToken(config.aes_key, refreshTokenOptions); + const accessTokenBuffer = await generateToken(config.aes_key, accessTokenOptions); + const refreshTokenBuffer = await generateToken(config.aes_key, refreshTokenOptions); - const accessToken: string = accessTokenBuffer ? accessTokenBuffer.toString('hex') : ''; - const newRefreshToken: string = refreshTokenBuffer ? refreshTokenBuffer.toString('hex') : ''; + const accessToken = accessTokenBuffer ? accessTokenBuffer.toString('hex') : ''; + const newRefreshToken = refreshTokenBuffer ? refreshTokenBuffer.toString('hex') : ''; // TODO - Handle null tokens diff --git a/src/services/api/routes/v1/register.ts b/src/services/api/routes/v1/register.ts index 7dd89ba..12a1b5e 100644 --- a/src/services/api/routes/v1/register.ts +++ b/src/services/api/routes/v1/register.ts @@ -6,31 +6,29 @@ import bcrypt from 'bcrypt'; import moment from 'moment'; import hcaptcha from 'hcaptcha'; import Mii from 'mii-js'; -import mongoose from 'mongoose'; import { doesPNIDExist, connection as databaseConnection } from '@/database'; import { nintendoPasswordHash, sendConfirmationEmail, generateToken } from '@/util'; import { LOG_ERROR } from '@/logger'; import { PNID } from '@/models/pnid'; import { NEXAccount } from '@/models/nex-account'; import { config, disabledFeatures } from '@/config-manager'; -import { TokenOptions } from '@/types/common/token-options'; import { HydratedNEXAccountDocument } from '@/types/mongoose/nex-account'; import { HydratedPNIDDocument } from '@/types/mongoose/pnid'; -const router: express.Router = express.Router(); +const router = express.Router(); -const PNID_VALID_CHARACTERS_REGEX: RegExp = /^[\w\-.]*$/; -const PNID_PUNCTUATION_START_REGEX: RegExp = /^[_\-.]/; -const PNID_PUNCTUATION_END_REGEX: RegExp = /[_\-.]$/; -const PNID_PUNCTUATION_DUPLICATE_REGEX: RegExp = /[_\-.]{2,}/; +const PNID_VALID_CHARACTERS_REGEX = /^[\w\-.]*$/; +const PNID_PUNCTUATION_START_REGEX = /^[_\-.]/; +const PNID_PUNCTUATION_END_REGEX = /[_\-.]$/; +const PNID_PUNCTUATION_DUPLICATE_REGEX = /[_\-.]{2,}/; // This sucks -const PASSWORD_WORD_OR_NUMBER_REGEX: RegExp = /(?=.*[a-zA-Z])(?=.*\d).*/; -const PASSWORD_WORD_OR_PUNCTUATION_REGEX: RegExp = /(?=.*[a-zA-Z])(?=.*[_\-.]).*/; -const PASSWORD_NUMBER_OR_PUNCTUATION_REGEX: RegExp = /(?=.*\d)(?=.*[_\-.]).*/; -const PASSWORD_REPEATED_CHARACTER_REGEX: RegExp = /(.)\1\1/; +const PASSWORD_WORD_OR_NUMBER_REGEX = /(?=.*[a-zA-Z])(?=.*\d).*/; +const PASSWORD_WORD_OR_PUNCTUATION_REGEX = /(?=.*[a-zA-Z])(?=.*[_\-.]).*/; +const PASSWORD_NUMBER_OR_PUNCTUATION_REGEX = /(?=.*\d)(?=.*[_\-.]).*/; +const PASSWORD_REPEATED_CHARACTER_REGEX = /(.)\1\1/; -const DEFAULT_MII_DATA: Buffer = Buffer.from('AwAAQOlVognnx0GC2/uogAOzuI0n2QAAAEBEAGUAZgBhAHUAbAB0AAAAAAAAAEBAAAAhAQJoRBgmNEYUgRIXaA0AACkAUkhQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGm9', 'base64'); +const DEFAULT_MII_DATA = Buffer.from('AwAAQOlVognnx0GC2/uogAOzuI0n2QAAAEBEAGUAZgBhAHUAbAB0AAAAAAAAAEBAAAAhAQJoRBgmNEYUgRIXaA0AACkAUkhQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGm9', 'base64'); /** * [POST] @@ -38,12 +36,12 @@ const DEFAULT_MII_DATA: Buffer = Buffer.from('AwAAQOlVognnx0GC2/uogAOzuI0n2QAAAE * Description: Creates a new user PNID */ router.post('/', async (request: express.Request, response: express.Response): Promise => { - const email: string = request.body.email?.trim(); - const username: string = request.body.username?.trim(); - const miiName: string = request.body.mii_name?.trim(); - const password: string = request.body.password?.trim(); - const passwordConfirm: string = request.body.password_confirm?.trim(); - const hCaptchaResponse: string = request.body.hCaptchaResponse?.trim(); + const email = request.body.email?.trim(); + const username = request.body.username?.trim(); + const miiName = request.body.mii_name?.trim(); + const password = request.body.password?.trim(); + const passwordConfirm = request.body.password_confirm?.trim(); + const hCaptchaResponse = request.body.hCaptchaResponse?.trim(); if (!disabledFeatures.captcha) { if (!hCaptchaResponse || hCaptchaResponse === '') { @@ -56,7 +54,7 @@ router.post('/', async (request: express.Request, response: express.Response): P return; } - const captchaVerify: VerifyResponse = await hcaptcha.verify(config.hcaptcha.secret, hCaptchaResponse); + const captchaVerify = await hcaptcha.verify(config.hcaptcha.secret, hCaptchaResponse); if (!captchaVerify.success) { response.status(400).json({ @@ -160,7 +158,7 @@ router.post('/', async (request: express.Request, response: express.Response): P return; } - const userExists: boolean = await doesPNIDExist(username); + const userExists = await doesPNIDExist(username); if (userExists) { response.status(400).json({ @@ -252,7 +250,7 @@ router.post('/', async (request: express.Request, response: express.Response): P return; } - const miiNameBuffer: Buffer = Buffer.from(miiName, 'utf16le'); // UTF8 to UTF16 + const miiNameBuffer = Buffer.from(miiName, 'utf16le'); // UTF8 to UTF16 if (miiNameBuffer.length > 0x14) { response.status(400).json({ @@ -264,14 +262,14 @@ router.post('/', async (request: express.Request, response: express.Response): P return; } - const mii: Mii = new Mii(DEFAULT_MII_DATA); + const mii = new Mii(DEFAULT_MII_DATA); mii.miiName = miiName; - const creationDate: string = moment().format('YYYY-MM-DDTHH:MM:SS'); + const creationDate = moment().format('YYYY-MM-DDTHH:MM:SS'); let pnid: HydratedPNIDDocument; let nexAccount: HydratedNEXAccountDocument; - const session: mongoose.ClientSession = await databaseConnection().startSession(); + const session = await databaseConnection().startSession(); await session.startTransaction(); try { @@ -293,8 +291,8 @@ router.post('/', async (request: express.Request, response: express.Response): P await nexAccount.save({ session }); - const primaryPasswordHash: string = nintendoPasswordHash(password, nexAccount.pid); - const passwordHash: string = await bcrypt.hash(primaryPasswordHash, 10); + const primaryPasswordHash = nintendoPasswordHash(password, nexAccount.pid); + const passwordHash = await bcrypt.hash(primaryPasswordHash, 10); pnid = new PNID({ pid: nexAccount.pid, @@ -367,7 +365,7 @@ router.post('/', async (request: express.Request, response: express.Response): P await sendConfirmationEmail(pnid); - const accessTokenOptions: TokenOptions = { + const accessTokenOptions = { system_type: 0x3, // * API token_type: 0x1, // * OAuth Access pid: pnid.pid, @@ -376,7 +374,7 @@ router.post('/', async (request: express.Request, response: express.Response): P expire_time: BigInt(Date.now() + (3600 * 1000)) }; - const refreshTokenOptions: TokenOptions = { + const refreshTokenOptions = { system_type: 0x3, // * API token_type: 0x2, // * OAuth Refresh pid: pnid.pid, @@ -385,11 +383,11 @@ router.post('/', async (request: express.Request, response: express.Response): P expire_time: BigInt(Date.now() + (3600 * 1000)) }; - const accessTokenBuffer: Buffer | null = await generateToken(config.aes_key, accessTokenOptions); - const refreshTokenBuffer: Buffer | null = await generateToken(config.aes_key, refreshTokenOptions); + const accessTokenBuffer = await generateToken(config.aes_key, accessTokenOptions); + const refreshTokenBuffer = await generateToken(config.aes_key, refreshTokenOptions); - const accessToken: string = accessTokenBuffer ? accessTokenBuffer.toString('hex') : ''; - const refreshToken: string = refreshTokenBuffer ? refreshTokenBuffer.toString('hex') : ''; + const accessToken = accessTokenBuffer ? accessTokenBuffer.toString('hex') : ''; + const refreshToken = refreshTokenBuffer ? refreshTokenBuffer.toString('hex') : ''; // TODO - Handle null tokens diff --git a/src/services/api/routes/v1/resetPassword.ts b/src/services/api/routes/v1/resetPassword.ts index 7b1aa18..025ca64 100644 --- a/src/services/api/routes/v1/resetPassword.ts +++ b/src/services/api/routes/v1/resetPassword.ts @@ -3,20 +3,19 @@ import bcrypt from 'bcrypt'; import { PNID } from '@/models/pnid'; import { decryptToken, unpackToken, nintendoPasswordHash } from '@/util'; import { Token } from '@/types/common/token'; -import { HydratedPNIDDocument } from '@/types/mongoose/pnid'; -const router: express.Router = express.Router(); +const router = express.Router(); // This sucks -const PASSWORD_WORD_OR_NUMBER_REGEX: RegExp = /(?=.*[a-zA-Z])(?=.*\d).*/; -const PASSWORD_WORD_OR_PUNCTUATION_REGEX: RegExp = /(?=.*[a-zA-Z])(?=.*[_\-.]).*/; -const PASSWORD_NUMBER_OR_PUNCTUATION_REGEX: RegExp = /(?=.*\d)(?=.*[_\-.]).*/; -const PASSWORD_REPEATED_CHARACTER_REGEX: RegExp = /(.)\1\1/; +const PASSWORD_WORD_OR_NUMBER_REGEX = /(?=.*[a-zA-Z])(?=.*\d).*/; +const PASSWORD_WORD_OR_PUNCTUATION_REGEX = /(?=.*[a-zA-Z])(?=.*[_\-.]).*/; +const PASSWORD_NUMBER_OR_PUNCTUATION_REGEX = /(?=.*\d)(?=.*[_\-.]).*/; +const PASSWORD_REPEATED_CHARACTER_REGEX = /(.)\1\1/; router.post('/', async (request: express.Request, response: express.Response): Promise => { - const password: string = request.body.password?.trim(); - const passwordConfirm: string = request.body.password_confirm?.trim(); - const token: string = request.body.token?.trim(); + const password = request.body.password?.trim(); + const passwordConfirm = request.body.password_confirm?.trim(); + const token = request.body.token?.trim(); if (!token || token === '') { response.status(400).json({ @@ -30,7 +29,7 @@ router.post('/', async (request: express.Request, response: express.Response): P let unpackedToken: Token; try { - const decryptedToken: Buffer = await decryptToken(Buffer.from(token, 'hex')); + const decryptedToken = await decryptToken(Buffer.from(token, 'hex')); unpackedToken = unpackToken(decryptedToken); } catch (error) { response.status(400).json({ @@ -52,7 +51,7 @@ router.post('/', async (request: express.Request, response: express.Response): P return; } - const pnid: HydratedPNIDDocument | null = await PNID.findOne({ pid: unpackedToken.pid }); + const pnid = await PNID.findOne({ pid: unpackedToken.pid }); if (!pnid) { response.status(400).json({ @@ -135,8 +134,8 @@ router.post('/', async (request: express.Request, response: express.Response): P return; } - const primaryPasswordHash: string = nintendoPasswordHash(password, pnid.pid); - const passwordHash: string = await bcrypt.hash(primaryPasswordHash, 10); + const primaryPasswordHash = nintendoPasswordHash(password, pnid.pid); + const passwordHash = await bcrypt.hash(primaryPasswordHash, 10); pnid.password = passwordHash; diff --git a/src/services/api/routes/v1/user.ts b/src/services/api/routes/v1/user.ts index 0622763..0c27e88 100644 --- a/src/services/api/routes/v1/user.ts +++ b/src/services/api/routes/v1/user.ts @@ -3,10 +3,9 @@ import { z } from 'zod'; import Mii from 'mii-js'; import { config } from '@/config-manager'; import { PNID } from '@/models/pnid'; -import { HydratedPNIDDocument } from '@/types/mongoose/pnid'; import { UpdateUserRequest } from '@/types/services/api/update-user-request'; -const router: express.Router = express.Router(); +const router = express.Router(); // TODO - Extend this later with more settings const userSchema = z.object({ @@ -24,7 +23,7 @@ const userSchema = z.object({ * Description: Gets PNID details about the current user */ router.get('/', async (request: express.Request, response: express.Response): Promise => { - const pnid: HydratedPNIDDocument | null = request.pnid; + const pnid = request.pnid; if (!pnid) { response.status(400).json({ @@ -74,7 +73,7 @@ router.get('/', async (request: express.Request, response: express.Response): Pr * Description: Updates PNID certain details about the current user */ router.post('/', async (request: express.Request, response: express.Response): Promise => { - const pnid: HydratedPNIDDocument | null = request.pnid; + const pnid = request.pnid; const updateUserRequest: UpdateUserRequest = request.body; if (!pnid) { @@ -100,7 +99,7 @@ router.post('/', async (request: express.Request, response: express.Response): P } if (result.data.mii) { - const miiNameBuffer: Buffer = Buffer.from(result.data.mii.name, 'utf16le'); // * UTF8 to UTF16 + const miiNameBuffer = Buffer.from(result.data.mii.name, 'utf16le'); // * UTF8 to UTF16 if (miiNameBuffer.length < 1) { response.status(400).json({ @@ -123,7 +122,7 @@ router.post('/', async (request: express.Request, response: express.Response): P } try { - const miiDataBuffer: Buffer = Buffer.from(result.data.mii.data, 'base64'); + const miiDataBuffer = Buffer.from(result.data.mii.data, 'base64'); if (miiDataBuffer.length < 0x60) { response.status(400).json({ @@ -145,7 +144,7 @@ router.post('/', async (request: express.Request, response: express.Response): P return; } - const mii: Mii = new Mii(miiDataBuffer); + const mii = new Mii(miiDataBuffer); mii.validate(); } catch (_) { response.status(400).json({ @@ -167,7 +166,7 @@ router.post('/', async (request: express.Request, response: express.Response): P const updateData: Record = {}; if (result.data.environment) { - const environment: string = result.data.environment; + const environment = result.data.environment; if (environment === 'test' && pnid.access_level < 1) { response.status(400).json({ diff --git a/src/services/assets/index.ts b/src/services/assets/index.ts index 03faa93..22be241 100644 --- a/src/services/assets/index.ts +++ b/src/services/assets/index.ts @@ -6,14 +6,14 @@ import subdomain from 'express-subdomain'; import { LOG_INFO } from '@/logger'; // Router to handle the subdomain restriction -const assets: express.Router = express.Router(); +const assets = express.Router(); // Setup public folder LOG_INFO('[assets] Setting up public folder'); assets.use(express.static(path.join(__dirname, '../../assets'))); // Main router for endpoints -const router: express.Router = express.Router(); +const router = express.Router(); // Create subdomains LOG_INFO('[conntest] Creating \'assets\' subdomain'); diff --git a/src/services/cbvc/index.ts b/src/services/cbvc/index.ts index c3e5472..5116f93 100644 --- a/src/services/cbvc/index.ts +++ b/src/services/cbvc/index.ts @@ -5,7 +5,7 @@ import subdomain from 'express-subdomain'; import { LOG_INFO } from '@/logger'; // Router to handle the subdomain restriction -const cbvc: express.Router = express.Router(); +const cbvc = express.Router(); // Setup route LOG_INFO('[cbvc] Applying imported routes'); @@ -22,7 +22,7 @@ cbvc.get('/:consoleType/:unknown/:region', (request: express.Request, response: }); // Main router for endpoints -const router: express.Router = express.Router(); +const router = express.Router(); // Create subdomains LOG_INFO('[cbvc] Creating \'cbvc\' subdomain'); diff --git a/src/services/conntest/index.ts b/src/services/conntest/index.ts index 75784cc..8b5f68a 100644 --- a/src/services/conntest/index.ts +++ b/src/services/conntest/index.ts @@ -5,7 +5,7 @@ import subdomain from 'express-subdomain'; import { LOG_INFO } from '@/logger'; // Router to handle the subdomain restriction -const conntest: express.Router = express.Router(); +const conntest = express.Router(); // Setup route LOG_INFO('[conntest] Applying imported routes'); @@ -27,7 +27,7 @@ This is test.html page }); // Main router for endpoints -const router: express.Router = express.Router(); +const router = express.Router(); // Create subdomains LOG_INFO('[conntest] Creating \'conntest\' subdomain'); diff --git a/src/services/datastore/index.ts b/src/services/datastore/index.ts index 3d2e4f7..35b9ade 100644 --- a/src/services/datastore/index.ts +++ b/src/services/datastore/index.ts @@ -5,14 +5,14 @@ import { LOG_INFO } from '@/logger'; import upload from '@/services/datastore/routes/upload'; // Router to handle the subdomain -const datastore: express.Router = express.Router(); +const datastore = express.Router(); // Setup routes LOG_INFO('[DATASTORE] Applying imported routes'); datastore.use(upload); // Main router for endpoints -const router: express.Router = express.Router(); +const router = express.Router(); // Create subdomains LOG_INFO('[DATASTORE] Creating \'datastore\' subdomain'); diff --git a/src/services/datastore/routes/upload.ts b/src/services/datastore/routes/upload.ts index 3ceccda..09119d6 100644 --- a/src/services/datastore/routes/upload.ts +++ b/src/services/datastore/routes/upload.ts @@ -4,32 +4,32 @@ import express from 'express'; import Dicer from 'dicer'; import { uploadCDNAsset } from '@/util'; -const router: express.Router = express.Router(); +const router = express.Router(); -const signatureSecret: Buffer = fs.readFileSync(`${__dirname}/../../../../certs/nex/datastore/secret.key`); +const signatureSecret = fs.readFileSync(`${__dirname}/../../../../certs/nex/datastore/secret.key`); function multipartParser(request: express.Request, response: express.Response, next: express.NextFunction): void { - const RE_BOUNDARY: RegExp = /^multipart\/.+?(?:; boundary=(?:(?:"(.+)")|(?:([^\s]+))))$/i; - const RE_FILE_NAME: RegExp = /name="(.*)"/; + const RE_BOUNDARY = /^multipart\/.+?(?:; boundary=(?:(?:"(.+)")|(?:([^\s]+))))$/i; + const RE_FILE_NAME = /name="(.*)"/; - const contentType: string | undefined = request.header('content-type'); + const contentType = request.header('content-type'); if (!contentType) { return next(); } - const boundary: RegExpExecArray | null = RE_BOUNDARY.exec(contentType); + const boundary = RE_BOUNDARY.exec(contentType); if (!boundary) { return next(); } - const dicer: Dicer = new Dicer({ boundary: boundary[1] || boundary[2] }); - const files: { [key: string]: Buffer } = {}; + const dicer = new Dicer({ boundary: boundary[1] || boundary[2] }); + const files: Record = {}; dicer.on('part', (part: Dicer.PartStream) => { - let fileBuffer: Buffer = Buffer.alloc(0); - let fileName: string = ''; + let fileBuffer = Buffer.alloc(0); + let fileName = ''; part.on('header', header => { const contentDisposition = header['content-disposition' as keyof object]; @@ -67,26 +67,26 @@ router.post('/upload', multipartParser, async (request: express.Request, respons return; } - const bucket: string = request.files.bucket.toString(); - const key: string = request.files.key.toString(); - const file: Buffer = request.files.file; - const acl: string = request.files.acl.toString(); - const pid: string = request.files.pid.toString(); - const date: string = request.files.date.toString(); - const signature: string = request.files.signature.toString(); + const bucket = request.files.bucket.toString(); + const key = request.files.key.toString(); + const file = request.files.file; + const acl = request.files.acl.toString(); + const pid = request.files.pid.toString(); + const date = request.files.date.toString(); + const signature = request.files.signature.toString(); // Signatures only good for 1 minute - const minute: number = 1000 * 60; - const minuteAgo: number = Date.now() - minute; + const minute = 1000 * 60; + const minuteAgo = Date.now() - minute; if (Number(date) < Math.floor(minuteAgo / 1000)) { response.sendStatus(400); return; } - const data: string = `${pid}${bucket}${key}${date}`; + const data = `${pid}${bucket}${key}${date}`; - const hmac: string = crypto.createHmac('sha256', signatureSecret).update(data).digest('hex'); + const hmac = crypto.createHmac('sha256', signatureSecret).update(data).digest('hex'); console.log(hmac, signature); diff --git a/src/services/grpc/account/api-key-middleware.ts b/src/services/grpc/account/api-key-middleware.ts index ee42a13..2a6158d 100644 --- a/src/services/grpc/account/api-key-middleware.ts +++ b/src/services/grpc/account/api-key-middleware.ts @@ -5,7 +5,7 @@ export async function* apiKeyMiddleware( call: ServerMiddlewareCall, context: CallContext, ): AsyncGenerator { - const apiKey: string | undefined = context.metadata.get('X-API-Key'); + const apiKey = context.metadata.get('X-API-Key'); if (!apiKey || apiKey !== config.grpc.master_api_keys.account) { throw new ServerError(Status.UNAUTHENTICATED, 'Missing or invalid API key'); diff --git a/src/services/grpc/account/exchange-token-for-user-data.ts b/src/services/grpc/account/exchange-token-for-user-data.ts index 164a6a3..347c4da 100644 --- a/src/services/grpc/account/exchange-token-for-user-data.ts +++ b/src/services/grpc/account/exchange-token-for-user-data.ts @@ -2,7 +2,6 @@ import { Status, ServerError } from 'nice-grpc'; import { ExchangeTokenForUserDataRequest } from '@pretendonetwork/grpc/account/exchange_token_for_user_data'; import { GetUserDataResponse } from '@pretendonetwork/grpc/account/get_user_data_rpc'; import { getPNIDByTokenAuth } from '@/database'; -import { HydratedPNIDDocument } from '@/types/mongoose/pnid'; import { PNID_PERMISSION_FLAGS } from '@/types/common/permission-flags'; import { config } from '@/config-manager'; @@ -11,7 +10,7 @@ export async function exchangeTokenForUserData(request: ExchangeTokenForUserData throw new ServerError(Status.INVALID_ARGUMENT, 'Invalid token'); } - const pnid: HydratedPNIDDocument | null = await getPNIDByTokenAuth(request.token); + const pnid = await getPNIDByTokenAuth(request.token); if (!pnid) { throw new ServerError(Status.INVALID_ARGUMENT, 'Invalid token'); diff --git a/src/services/grpc/account/get-nex-data.ts b/src/services/grpc/account/get-nex-data.ts index 1731620..38a938d 100644 --- a/src/services/grpc/account/get-nex-data.ts +++ b/src/services/grpc/account/get-nex-data.ts @@ -1,10 +1,9 @@ import { Status, ServerError } from 'nice-grpc'; import {GetNEXDataRequest,GetNEXDataResponse, DeepPartial } from '@pretendonetwork/grpc/account/get_nex_data_rpc'; import { NEXAccount } from '@/models/nex-account'; -import { HydratedNEXAccountDocument } from '@/types/mongoose/nex-account'; export async function getNEXData(request: GetNEXDataRequest): Promise> { - const nexAccount: HydratedNEXAccountDocument | null = await NEXAccount.findOne({ pid: request.pid }); + const nexAccount = await NEXAccount.findOne({ pid: request.pid }); if (!nexAccount) { throw new ServerError( diff --git a/src/services/grpc/account/get-nex-password.ts b/src/services/grpc/account/get-nex-password.ts index 7a06886..a872d2b 100644 --- a/src/services/grpc/account/get-nex-password.ts +++ b/src/services/grpc/account/get-nex-password.ts @@ -1,10 +1,9 @@ import { Status, ServerError } from 'nice-grpc'; import {GetNEXPasswordRequest,GetNEXPasswordResponse, DeepPartial } from '@pretendonetwork/grpc/account/get_nex_password_rpc'; import { NEXAccount } from '@/models/nex-account'; -import { HydratedNEXAccountDocument } from '@/types/mongoose/nex-account'; export async function getNEXPassword(request: GetNEXPasswordRequest): Promise> { - const nexAccount: HydratedNEXAccountDocument | null = await NEXAccount.findOne({ pid: request.pid }); + const nexAccount = await NEXAccount.findOne({ pid: request.pid }); if (!nexAccount) { throw new ServerError( diff --git a/src/services/grpc/account/get-user-data.ts b/src/services/grpc/account/get-user-data.ts index 70baca4..80d2704 100644 --- a/src/services/grpc/account/get-user-data.ts +++ b/src/services/grpc/account/get-user-data.ts @@ -1,12 +1,11 @@ import { Status, ServerError } from 'nice-grpc'; import { GetUserDataRequest, GetUserDataResponse } from '@pretendonetwork/grpc/account/get_user_data_rpc'; import { getPNIDByPID } from '@/database'; -import { HydratedPNIDDocument } from '@/types/mongoose/pnid'; import { PNID_PERMISSION_FLAGS } from '@/types/common/permission-flags'; import { config } from '@/config-manager'; export async function getUserData(request: GetUserDataRequest): Promise { - const pnid: HydratedPNIDDocument | null = await getPNIDByPID(request.pid); + const pnid = await getPNIDByPID(request.pid); if (!pnid) { throw new ServerError( diff --git a/src/services/grpc/account/implementation.ts b/src/services/grpc/account/implementation.ts index 8e970d4..37a9dd4 100644 --- a/src/services/grpc/account/implementation.ts +++ b/src/services/grpc/account/implementation.ts @@ -1,11 +1,10 @@ -import { AccountServiceImplementation } from '@pretendonetwork/grpc/account/account_service'; import { getUserData } from '@/services/grpc/account/get-user-data'; import { getNEXPassword } from '@/services/grpc/account/get-nex-password'; import { getNEXData } from '@/services/grpc/account/get-nex-data'; import { updatePNIDPermissions } from '@/services/grpc/account/update-pnid-permissions'; import { exchangeTokenForUserData } from '@/services/grpc/account/exchange-token-for-user-data'; -export const accountServiceImplementation: AccountServiceImplementation = { +export const accountServiceImplementation = { getUserData, getNEXPassword, getNEXData, diff --git a/src/services/grpc/account/update-pnid-permissions.ts b/src/services/grpc/account/update-pnid-permissions.ts index e899021..d547311 100644 --- a/src/services/grpc/account/update-pnid-permissions.ts +++ b/src/services/grpc/account/update-pnid-permissions.ts @@ -3,10 +3,9 @@ import { UpdatePNIDPermissionsRequest } from '@pretendonetwork/grpc/account/upda import { getPNIDByPID } from '@/database'; import { PNID_PERMISSION_FLAGS } from '@/types/common/permission-flags'; import type { Empty } from '@pretendonetwork/grpc/api/google/protobuf/empty'; -import type { HydratedPNIDDocument } from '@/types/mongoose/pnid'; export async function updatePNIDPermissions(request: UpdatePNIDPermissionsRequest): Promise { - const pnid: HydratedPNIDDocument | null = await getPNIDByPID(request.pid); + const pnid = await getPNIDByPID(request.pid); if (!pnid) { throw new ServerError( diff --git a/src/services/grpc/api/api-key-middleware.ts b/src/services/grpc/api/api-key-middleware.ts index 19c4e3b..215df34 100644 --- a/src/services/grpc/api/api-key-middleware.ts +++ b/src/services/grpc/api/api-key-middleware.ts @@ -5,7 +5,7 @@ export async function* apiKeyMiddleware( call: ServerMiddlewareCall, context: CallContext, ): AsyncGenerator { - const apiKey: string | undefined = context.metadata.get('X-API-Key'); + const apiKey = context.metadata.get('X-API-Key'); if (!apiKey || apiKey !== config.grpc.master_api_keys.api) { throw new ServerError(Status.UNAUTHENTICATED, 'Missing or invalid API key'); diff --git a/src/services/grpc/api/authentication-middleware.ts b/src/services/grpc/api/authentication-middleware.ts index d0a4405..c466c9e 100644 --- a/src/services/grpc/api/authentication-middleware.ts +++ b/src/services/grpc/api/authentication-middleware.ts @@ -3,7 +3,7 @@ import { getPNIDByTokenAuth } from '@/database'; import type { HydratedPNIDDocument } from '@/types/mongoose/pnid'; // * These paths require that a token be present -const TOKEN_REQUIRED_PATHS: string[] = [ +const TOKEN_REQUIRED_PATHS = [ '/api.API/GetUserData', '/api.API/UpdateUserData', '/api.API/ResetPassword', // * This paths token is not an authentication token, it is a password reset token @@ -20,14 +20,14 @@ export async function* authenticationMiddleware( call: ServerMiddlewareCall, context: CallContext, ): AsyncGenerator { - const token: string | undefined = context.metadata.get('X-Token')?.trim(); + const token = context.metadata.get('X-Token')?.trim(); if (!token && TOKEN_REQUIRED_PATHS.includes(call.method.path)) { throw new ServerError(Status.UNAUTHENTICATED, 'Missing or invalid authentication token'); } try { - let pnid: HydratedPNIDDocument | null = null; + let pnid = null; if (token) { pnid = await getPNIDByTokenAuth(token); @@ -42,7 +42,7 @@ export async function* authenticationMiddleware( pnid }); } catch (error) { - let message: string = 'Unknown server error'; + let message = 'Unknown server error'; console.log(error); diff --git a/src/services/grpc/api/forgot-password.ts b/src/services/grpc/api/forgot-password.ts index 7cdbd4f..45b9b6e 100644 --- a/src/services/grpc/api/forgot-password.ts +++ b/src/services/grpc/api/forgot-password.ts @@ -7,7 +7,7 @@ import type { Empty } from '@pretendonetwork/grpc/api/google/protobuf/empty'; import type { HydratedPNIDDocument } from '@/types/mongoose/pnid'; export async function forgotPassword(request: ForgotPasswordRequest): Promise { - const input: string = request.emailAddressOrUsername.trim(); + const input = request.emailAddressOrUsername.trim(); if (!input) { throw new ServerError(Status.INVALID_ARGUMENT, 'Invalid or missing input'); diff --git a/src/services/grpc/api/get-user-data.ts b/src/services/grpc/api/get-user-data.ts index 6682c79..150f6cb 100644 --- a/src/services/grpc/api/get-user-data.ts +++ b/src/services/grpc/api/get-user-data.ts @@ -3,11 +3,10 @@ import { GetUserDataResponse, DeepPartial } from '@pretendonetwork/grpc/api/get_ import { config } from '@/config-manager'; import type { Empty } from '@pretendonetwork/grpc/api/google/protobuf/empty'; import type { AuthenticationCallContextExt } from '@/services/grpc/api/authentication-middleware'; -import type { HydratedPNIDDocument } from '@/types/mongoose/pnid'; export async function getUserData(_request: Empty, context: CallContext & AuthenticationCallContextExt): Promise> { // * This is asserted in authentication-middleware, we know this is never null - const pnid: HydratedPNIDDocument = context.pnid!; + const pnid = context.pnid!; return { deleted: pnid.deleted, diff --git a/src/services/grpc/api/implementation.ts b/src/services/grpc/api/implementation.ts index f606b5f..bc0c8fe 100644 --- a/src/services/grpc/api/implementation.ts +++ b/src/services/grpc/api/implementation.ts @@ -1,4 +1,3 @@ -import { APIServiceImplementation } from '@pretendonetwork/grpc/api/api_service'; import { register } from '@/services/grpc/api/register'; import { login } from '@/services/grpc/api/login'; import { getUserData } from '@/services/grpc/api/get-user-data'; @@ -8,7 +7,7 @@ import { resetPassword } from '@/services/grpc/api/reset-password'; import { setDiscordConnectionData } from '@/services/grpc/api/set-discord-connection-data'; import { setStripeConnectionData } from '@/services/grpc/api/set-stripe-connection-data'; -export const apiServiceImplementation: APIServiceImplementation = { +export const apiServiceImplementation = { register, login, getUserData, diff --git a/src/services/grpc/api/login.ts b/src/services/grpc/api/login.ts index 043c65a..8c4bfea 100644 --- a/src/services/grpc/api/login.ts +++ b/src/services/grpc/api/login.ts @@ -4,14 +4,13 @@ import bcrypt from 'bcrypt'; import { getPNIDByUsername, getPNIDByTokenAuth } from '@/database'; import { nintendoPasswordHash, generateToken} from '@/util'; import { config } from '@/config-manager'; -import type { TokenOptions } from '@/types/common/token-options'; import type { HydratedPNIDDocument } from '@/types/mongoose/pnid'; export async function login(request: LoginRequest): Promise> { - const grantType: string = request.grantType?.trim(); - const username: string | undefined = request.username?.trim(); - const password: string | undefined = request.password?.trim(); - const refreshToken: string | undefined = request.refreshToken?.trim(); + const grantType = request.grantType?.trim(); + const username = request.username?.trim(); + const password = request.password?.trim(); + const refreshToken = request.refreshToken?.trim(); if (!['password', 'refresh_token'].includes(grantType)) { throw new ServerError(Status.INVALID_ARGUMENT, 'Invalid grant type'); @@ -38,7 +37,7 @@ export async function login(request: LoginRequest): Promise> { - const email: string = request.email?.trim(); - const username: string = request.username?.trim(); - const miiName: string = request.miiName?.trim(); - const password: string = request.password?.trim(); - const passwordConfirm: string = request.passwordConfirm?.trim(); - const captchaResponse: string | undefined = request.captchaResponse?.trim(); + const email = request.email?.trim(); + const username = request.username?.trim(); + const miiName = request.miiName?.trim(); + const password = request.password?.trim(); + const passwordConfirm = request.passwordConfirm?.trim(); + const captchaResponse = request.captchaResponse?.trim(); // * Only validate the captcha if that's enabled if (!disabledFeatures.captcha) { @@ -45,7 +43,7 @@ export async function register(request: RegisterRequest): Promise 0x14) { throw new ServerError(Status.INVALID_ARGUMENT, 'Mii name too long'); @@ -128,14 +126,14 @@ export async function register(request: RegisterRequest): Promise { - const password: string = request.password.trim(); - const passwordConfirm: string = request.passwordConfirm.trim(); - const token: string = request.token.trim(); + const password = request.password.trim(); + const passwordConfirm = request.passwordConfirm.trim(); + const token = request.token.trim(); if (!token) { throw new ServerError(Status.INVALID_ARGUMENT, 'Missing token'); @@ -24,7 +23,7 @@ export async function resetPassword(request: ResetPasswordRequest): Promise{ // * This is asserted in authentication-middleware, we know this is never null - const pnid: HydratedPNIDDocument = context.pnid!; + const pnid = context.pnid!; try { pnid.connections.discord.id = request.id; await pnid.save(); } catch (error) { - let message: string = 'Unknown Mongo error'; + let message = 'Unknown Mongo error'; if (error instanceof Error) { message = error.message; diff --git a/src/services/grpc/api/set-stripe-connection-data.ts b/src/services/grpc/api/set-stripe-connection-data.ts index 0994848..2af96b8 100644 --- a/src/services/grpc/api/set-stripe-connection-data.ts +++ b/src/services/grpc/api/set-stripe-connection-data.ts @@ -3,7 +3,6 @@ import { SetStripeConnectionDataRequest } from '@pretendonetwork/grpc/api/set_st import { PNID } from '@/models/pnid'; import type { Empty } from '@pretendonetwork/grpc/api/google/protobuf/empty'; import type { AuthenticationCallContextExt } from '@/services/grpc/api/authentication-middleware'; -import type { HydratedPNIDDocument } from '@/types/mongoose/pnid'; type StripeMongoUpdateScheme = { access_level?: number; @@ -18,7 +17,7 @@ type StripeMongoUpdateScheme = { export async function setStripeConnectionData(request: SetStripeConnectionDataRequest, context: CallContext & AuthenticationCallContextExt): Promise{ // * This is asserted in authentication-middleware, we know this is never null - const pnid: HydratedPNIDDocument = context.pnid!; + const pnid = context.pnid!; const updateData: StripeMongoUpdateScheme = { 'connections.stripe.latest_webhook_timestamp': Number(request.timestamp) @@ -78,7 +77,7 @@ export async function setStripeConnectionData(request: SetStripeConnectionDataRe }, { upsert: true }).exec(); } } catch (error) { - let message: string = 'Unknown Mongo error'; + let message = 'Unknown Mongo error'; if (error instanceof Error) { message = error.message; diff --git a/src/services/grpc/api/update-user-data.ts b/src/services/grpc/api/update-user-data.ts index bb82887..a842f38 100644 --- a/src/services/grpc/api/update-user-data.ts +++ b/src/services/grpc/api/update-user-data.ts @@ -2,12 +2,11 @@ import { CallContext } from 'nice-grpc'; import { UpdateUserDataRequest, DeepPartial } from '@pretendonetwork/grpc/api/update_user_data_rpc'; import { GetUserDataResponse } from '@pretendonetwork/grpc/api/get_user_data_rpc'; import { config } from '@/config-manager'; -import type { HydratedPNIDDocument } from '@/types/mongoose/pnid'; import type { AuthenticationCallContextExt } from '@/services/grpc/api/authentication-middleware'; export async function updateUserData(_request: UpdateUserDataRequest, context: CallContext & AuthenticationCallContextExt): Promise> { // * This is asserted in authentication-middleware, we know this is never null - const pnid: HydratedPNIDDocument = context.pnid!; + const pnid = context.pnid!; // TODO - STUBBED, DO SOMETHING HERE diff --git a/src/services/grpc/server.ts b/src/services/grpc/server.ts index af03741..3032034 100644 --- a/src/services/grpc/server.ts +++ b/src/services/grpc/server.ts @@ -1,4 +1,4 @@ -import { createServer, Server } from 'nice-grpc'; +import { createServer } from 'nice-grpc'; import { AccountDefinition } from '@pretendonetwork/grpc/account/account_service'; import { APIDefinition } from '@pretendonetwork/grpc/api/api_service'; @@ -12,7 +12,7 @@ import { apiServiceImplementation } from '@/services/grpc/api/implementation'; import { config } from '@/config-manager'; export async function startGRPCServer(): Promise { - const server: Server = createServer(); + const server = createServer(); server.with(accountApiKeyMiddleware).add(AccountDefinition, accountServiceImplementation); server.with(apiApiKeyMiddleware).with(apiAuthenticationMiddleware).add(APIDefinition, apiServiceImplementation); diff --git a/src/services/local-cdn/index.ts b/src/services/local-cdn/index.ts index c1b2963..7cd59d3 100644 --- a/src/services/local-cdn/index.ts +++ b/src/services/local-cdn/index.ts @@ -5,13 +5,13 @@ import { LOG_INFO } from '@/logger'; import get from '@/services/local-cdn/routes/get'; -const router: express.Router = express.Router(); +const router = express.Router(); if (disabledFeatures.s3) { // * s3 disabled, setup local CDN // * Router to handle the subdomain - const localcdn: express.Router = express.Router(); + const localcdn = express.Router(); // * Setup routes LOG_INFO('[LOCAL-CDN] Applying imported routes'); diff --git a/src/services/local-cdn/routes/get.ts b/src/services/local-cdn/routes/get.ts index acd4570..c98b586 100644 --- a/src/services/local-cdn/routes/get.ts +++ b/src/services/local-cdn/routes/get.ts @@ -1,12 +1,12 @@ import express from 'express'; import { getLocalCDNFile } from '@/cache'; -const router: express.Router = express.Router(); +const router = express.Router(); router.get('/*', async (request: express.Request, response: express.Response): Promise => { - const filePath: string = request.params[0]; + const filePath = request.params[0]; - const file: Buffer = await getLocalCDNFile(filePath); + const file = await getLocalCDNFile(filePath); if (file) { response.send(file); diff --git a/src/services/nasc/index.ts b/src/services/nasc/index.ts index 29d1148..aac663b 100644 --- a/src/services/nasc/index.ts +++ b/src/services/nasc/index.ts @@ -8,7 +8,7 @@ import { LOG_INFO } from '@/logger'; import ac from '@/services/nasc/routes/ac'; // Router to handle the subdomain restriction -const nasc: express.Router = express.Router(); +const nasc = express.Router(); LOG_INFO('[NASC] Importing middleware'); nasc.use(NASCMiddleware); @@ -18,7 +18,7 @@ LOG_INFO('[NASC] Applying imported routes'); nasc.use('/ac', ac); // Main router for endpoints -const router: express.Router = express.Router(); +const router = express.Router(); // Create subdomains LOG_INFO('[NASC] Creating \'nasc\' subdomain'); diff --git a/src/services/nasc/routes/ac.ts b/src/services/nasc/routes/ac.ts index e556434..a4e35f2 100644 --- a/src/services/nasc/routes/ac.ts +++ b/src/services/nasc/routes/ac.ts @@ -1,12 +1,10 @@ import express from 'express'; import { nintendoBase64Encode, nintendoBase64Decode, nascError, generateToken } from '@/util'; import { getServerByTitleID } from '@/database'; -import { TokenOptions } from '@/types/common/token-options'; import { NASCRequestParams } from '@/types/services/nasc/request-params'; -import { HydratedNEXAccountDocument } from '@/types/mongoose/nex-account'; import { HydratedServerDocument } from '@/types/mongoose/server'; -const router: express.Router = express.Router(); +const router = express.Router(); /** * [POST] @@ -15,10 +13,10 @@ const router: express.Router = express.Router(); */ router.post('/', async (request: express.Request, response: express.Response): Promise => { const requestParams: NASCRequestParams = request.body; - const action: string = nintendoBase64Decode(requestParams.action).toString(); - const titleID: string = nintendoBase64Decode(requestParams.titleid).toString(); - const nexAccount: HydratedNEXAccountDocument | null = request.nexAccount; - let responseData: URLSearchParams = nascError('null'); + const action = nintendoBase64Decode(requestParams.action).toString(); + const titleID = nintendoBase64Decode(requestParams.titleid).toString(); + const nexAccount = request.nexAccount; + let responseData = nascError('null'); if (!nexAccount) { response.status(200).send(responseData.toString()); @@ -28,12 +26,12 @@ router.post('/', async (request: express.Request, response: express.Response): P // TODO: REMOVE AFTER PUBLIC LAUNCH // * LET EVERYONE IN THE `test` FRIENDS SERVER // * THAT WAY EVERYONE CAN GET AN ASSIGNED PID - let serverAccessLevel: string = 'test'; + let serverAccessLevel = 'test'; if (titleID !== '0004013000003202') { serverAccessLevel = nexAccount.server_access_level; } - const server: HydratedServerDocument | null = await getServerByTitleID(titleID, serverAccessLevel); + const server = await getServerByTitleID(titleID, serverAccessLevel); if (!server || !server.aes_key) { response.status(200).send(nascError('110').toString()); @@ -66,7 +64,7 @@ router.post('/', async (request: express.Request, response: express.Response): P }); async function processLoginRequest(server: HydratedServerDocument, pid: number, titleID: string): Promise { - const tokenOptions: TokenOptions = { + const tokenOptions = { system_type: 0x2, // * 3DS token_type: 0x3, // * NEX token pid: pid, @@ -77,8 +75,8 @@ async function processLoginRequest(server: HydratedServerDocument, pid: number, // TODO - Handle null tokens - const nexTokenBuffer: Buffer | null = await generateToken(server.aes_key, tokenOptions); - const nexToken: string = nintendoBase64Encode(nexTokenBuffer || ''); + const nexTokenBuffer = await generateToken(server.aes_key, tokenOptions); + const nexToken = nintendoBase64Encode(nexTokenBuffer || ''); return new URLSearchParams({ locator: nintendoBase64Encode(`${server.ip}:${server.port}`), @@ -90,7 +88,7 @@ async function processLoginRequest(server: HydratedServerDocument, pid: number, } async function processServiceTokenRequest(server: HydratedServerDocument, pid: number, titleID: string): Promise { - const tokenOptions: TokenOptions = { + const tokenOptions = { system_type: 0x2, // * 3DS token_type: 0x4, // * Service token pid: pid, @@ -101,8 +99,8 @@ async function processServiceTokenRequest(server: HydratedServerDocument, pid: n // TODO - Handle null tokens - const serviceTokenBuffer: Buffer | null = await generateToken(server.aes_key, tokenOptions); - const serviceToken: string = nintendoBase64Encode(serviceTokenBuffer || ''); + const serviceTokenBuffer = await generateToken(server.aes_key, tokenOptions); + const serviceToken = nintendoBase64Encode(serviceTokenBuffer || ''); return new URLSearchParams({ retry: nintendoBase64Encode('0'), diff --git a/src/services/nnas/index.ts b/src/services/nnas/index.ts index c90d5eb..5086c14 100644 --- a/src/services/nnas/index.ts +++ b/src/services/nnas/index.ts @@ -17,7 +17,7 @@ import provider from '@/services/nnas/routes/provider'; import support from '@/services/nnas/routes/support'; // Router to handle the subdomain restriction -const nnas: express.Router = express.Router(); +const nnas = express.Router(); LOG_INFO('[NNAS] Importing middleware'); nnas.use(clientHeaderCheck); diff --git a/src/services/nnas/routes/admin.ts b/src/services/nnas/routes/admin.ts index 3476e6b..c08c356 100644 --- a/src/services/nnas/routes/admin.ts +++ b/src/services/nnas/routes/admin.ts @@ -2,9 +2,8 @@ import express from 'express'; import xmlbuilder from 'xmlbuilder'; import { getValueFromQueryString } from '@/util'; import { PNID } from '@/models/pnid'; -import { HydratedPNIDDocument } from '@/types/mongoose/pnid'; -const router: express.Router = express.Router(); +const router = express.Router(); /** * [GET] @@ -12,9 +11,9 @@ const router: express.Router = express.Router(); * Description: Maps between NNID usernames and PIDs */ router.get('/mapped_ids', async (request: express.Request, response: express.Response): Promise => { - const inputType: string | undefined = getValueFromQueryString(request.query, 'input_type'); - const outputType: string | undefined = getValueFromQueryString(request.query, 'output_type'); - const input: string | undefined = getValueFromQueryString(request.query, 'input'); + const inputType = getValueFromQueryString(request.query, 'input_type'); + const outputType = getValueFromQueryString(request.query, 'output_type'); + const input = getValueFromQueryString(request.query, 'input'); if (!inputType || !outputType || !input) { response.status(400).send(xmlbuilder.create({ @@ -30,7 +29,7 @@ router.get('/mapped_ids', async (request: express.Request, response: express.Res return; } - let inputList: string[] = input.split(','); + let inputList = input.split(','); let queryInput: string; let queryOutput: string; @@ -57,7 +56,7 @@ router.get('/mapped_ids', async (request: express.Request, response: express.Res in_id: string; out_id: string; }[] = []; - const allowedTypes: string[] = ['pid', 'user_id']; + const allowedTypes = ['pid', 'user_id']; for (const input of inputList) { const result: { @@ -88,7 +87,7 @@ router.get('/mapped_ids', async (request: express.Request, response: express.Res } } - const searchResult: HydratedPNIDDocument | null = await PNID.findOne(query); + const searchResult = await PNID.findOne(query); if (searchResult) { result.out_id = searchResult.get(queryOutput); diff --git a/src/services/nnas/routes/content.ts b/src/services/nnas/routes/content.ts index db5100d..751728a 100644 --- a/src/services/nnas/routes/content.ts +++ b/src/services/nnas/routes/content.ts @@ -1,10 +1,8 @@ import express from 'express'; import xmlbuilder from 'xmlbuilder'; import timezones from '@/services/nnas/timezones.json'; -import { RegionLanguages } from '@/types/services/nnas/region-languages'; -import { RegionTimezones } from '@/types/services/nnas/region-timezones'; -const router: express.Router = express.Router(); +const router = express.Router(); /** * [GET] @@ -151,11 +149,11 @@ router.get('/time_zones/:countryCode/:language', (request: express.Request, resp }); */ - const countryCode: string = request.params.countryCode; - const language: string = request.params.language; + const countryCode = request.params.countryCode; + const language = request.params.language; - const regionLanguages: RegionLanguages = timezones[countryCode as keyof typeof timezones]; - const regionTimezones: RegionTimezones = regionLanguages[language] ? regionLanguages[language] : Object.values(regionLanguages)[0]; + const regionLanguages = timezones[countryCode as keyof typeof timezones]; + const regionTimezones = regionLanguages[language as keyof typeof regionLanguages] ? regionLanguages[language as keyof typeof regionLanguages] : Object.values(regionLanguages)[0]; response.send(xmlbuilder.create({ timezones: { diff --git a/src/services/nnas/routes/devices.ts b/src/services/nnas/routes/devices.ts index c770795..f46e6a4 100644 --- a/src/services/nnas/routes/devices.ts +++ b/src/services/nnas/routes/devices.ts @@ -1,7 +1,7 @@ import express from 'express'; import xmlbuilder from 'xmlbuilder'; -const router: express.Router = express.Router(); +const router = express.Router(); /** * [GET] diff --git a/src/services/nnas/routes/miis.ts b/src/services/nnas/routes/miis.ts index 3496a13..85487bc 100644 --- a/src/services/nnas/routes/miis.ts +++ b/src/services/nnas/routes/miis.ts @@ -3,10 +3,9 @@ import xmlbuilder from 'xmlbuilder'; import { getValueFromQueryString } from '@/util'; import { PNID } from '@/models/pnid'; import { config } from '@/config-manager'; -import { HydratedPNIDDocument } from '@/types/mongoose/pnid'; import { YesNoBoolString } from '@/types/common/yes-no-bool-string'; -const router: express.Router = express.Router(); +const router = express.Router(); /** * [GET] @@ -14,7 +13,7 @@ const router: express.Router = express.Router(); * Description: Returns a list of NNID miis */ router.get('/', async (request: express.Request, response: express.Response): Promise => { - const input: string | undefined = getValueFromQueryString(request.query, 'pids'); + const input = getValueFromQueryString(request.query, 'pids'); if (!input) { response.status(400).send(xmlbuilder.create({ @@ -30,7 +29,7 @@ router.get('/', async (request: express.Request, response: express.Response): Pr return; } - const pids: number[] = input.split(',').map(pid => Number(pid)).filter(pid => !isNaN(pid)); + const pids = input.split(',').map(pid => Number(pid)).filter(pid => !isNaN(pid)); const miis: { data: string; @@ -51,7 +50,7 @@ router.get('/', async (request: express.Request, response: express.Response): Pr for (const pid of pids) { // TODO - Replace this with a single query again somehow? Maybe aggregation? - const pnid: HydratedPNIDDocument | null = await PNID.findOne({ pid }); + const pnid = await PNID.findOne({ pid }); if (pnid) { miis.push({ diff --git a/src/services/nnas/routes/oauth.ts b/src/services/nnas/routes/oauth.ts index 20f92ad..7d25b75 100644 --- a/src/services/nnas/routes/oauth.ts +++ b/src/services/nnas/routes/oauth.ts @@ -6,11 +6,9 @@ import consoleStatusVerificationMiddleware from '@/middleware/console-status-ver import { getPNIDByTokenAuth, getPNIDByUsername } from '@/database'; import { generateToken } from '@/util'; import { config } from '@/config-manager'; -import { TokenOptions } from '@/types/common/token-options'; -import { HydratedPNIDDocument } from '@/types/mongoose/pnid'; import { Device } from '@/models/device'; -const router: express.Router = express.Router(); +const router = express.Router(); /** * [POST] @@ -18,10 +16,10 @@ const router: express.Router = express.Router(); * Description: Generates an access token for a user */ router.post('/access_token/generate', deviceCertificateMiddleware, consoleStatusVerificationMiddleware, async (request: express.Request, response: express.Response): Promise => { - const grantType: string = request.body.grant_type; - const username: string | undefined = request.body.user_id; - const password: string | undefined = request.body.password; - const refreshToken: string | undefined = request.body.refresh_token; + const grantType = request.body.grant_type; + const username = request.body.user_id; + const password = request.body.password; + const refreshToken = request.body.refresh_token; if (!['password', 'refresh_token'].includes(grantType)) { response.status(400).send(xmlbuilder.create({ @@ -35,7 +33,7 @@ router.post('/access_token/generate', deviceCertificateMiddleware, consoleStatus return; } - let pnid: HydratedPNIDDocument | null = null; + let pnid = null; if (grantType === 'password') { if (!username || username.trim() === '') { @@ -141,25 +139,25 @@ router.post('/access_token/generate', deviceCertificateMiddleware, consoleStatus return; } - const accessTokenOptions: TokenOptions = { + const accessTokenOptions = { system_type: 0x1, // * WiiU token_type: 0x1, // * OAuth Access pid: pnid.pid, expire_time: BigInt(Date.now() + (3600 * 1000)) }; - const refreshTokenOptions: TokenOptions = { + const refreshTokenOptions = { system_type: 0x1, // * WiiU token_type: 0x2, // * OAuth Refresh pid: pnid.pid, expire_time: BigInt(Date.now() + (3600 * 1000)) }; - const accessTokenBuffer: Buffer | null = await generateToken(config.aes_key, accessTokenOptions); - const refreshTokenBuffer: Buffer | null = await generateToken(config.aes_key, refreshTokenOptions); + const accessTokenBuffer = await generateToken(config.aes_key, accessTokenOptions); + const refreshTokenBuffer = await generateToken(config.aes_key, refreshTokenOptions); - const accessToken: string = accessTokenBuffer ? accessTokenBuffer.toString('hex') : ''; - const newRefreshToken: string = refreshTokenBuffer ? refreshTokenBuffer.toString('hex') : ''; + const accessToken = accessTokenBuffer ? accessTokenBuffer.toString('hex') : ''; + const newRefreshToken = refreshTokenBuffer ? refreshTokenBuffer.toString('hex') : ''; // TODO - Handle null tokens diff --git a/src/services/nnas/routes/people.ts b/src/services/nnas/routes/people.ts index c4b8bcd..ee3d9ba 100644 --- a/src/services/nnas/routes/people.ts +++ b/src/services/nnas/routes/people.ts @@ -3,7 +3,6 @@ import express from 'express'; import xmlbuilder from 'xmlbuilder'; import bcrypt from 'bcrypt'; import moment from 'moment'; -import mongoose from 'mongoose'; import deviceCertificateMiddleware from '@/middleware/device-certificate'; import ratelimit from '@/middleware/ratelimit'; import { connection as databaseConnection, doesPNIDExist, getPNIDProfileJSONByPID } from '@/database'; @@ -11,17 +10,13 @@ import { getValueFromHeaders, nintendoPasswordHash, sendConfirmationEmail, sendP import { PNID } from '@/models/pnid'; import { NEXAccount } from '@/models/nex-account'; import { LOG_ERROR } from '@/logger'; - import timezones from '@/services/nnas/timezones.json'; import { HydratedPNIDDocument } from '@/types/mongoose/pnid'; import { HydratedNEXAccountDocument } from '@/types/mongoose/nex-account'; -import { RegionLanguages } from '@/types/services/nnas/region-languages'; -import { RegionTimezone, RegionTimezones } from '@/types/services/nnas/region-timezones'; import { Person } from '@/types/services/nnas/person'; -import { PNIDProfile } from '@/types/services/nnas/pnid-profile'; -const router: express.Router = express.Router(); +const router = express.Router(); /** * [GET] @@ -29,9 +24,9 @@ const router: express.Router = express.Router(); * Description: Checks if a username is in use */ router.get('/:username', async (request: express.Request, response: express.Response): Promise => { - const username: string = request.params.username; + const username = request.params.username; - const userExists: boolean = await doesPNIDExist(username); + const userExists = await doesPNIDExist(username); if (userExists) { response.status(400).send(xmlbuilder.create({ @@ -70,7 +65,7 @@ router.post('/', ratelimit, deviceCertificateMiddleware, async (request: express const person: Person = request.body.person; - const userExists: boolean = await doesPNIDExist(person.user_id); + const userExists = await doesPNIDExist(person.user_id); if (userExists) { response.status(400).send(xmlbuilder.create({ @@ -85,11 +80,11 @@ router.post('/', ratelimit, deviceCertificateMiddleware, async (request: express return; } - const creationDate: string = moment().format('YYYY-MM-DDTHH:MM:SS'); + const creationDate = moment().format('YYYY-MM-DDTHH:MM:SS'); let pnid: HydratedPNIDDocument; let nexAccount: HydratedNEXAccountDocument; - const session: mongoose.ClientSession = await databaseConnection().startSession(); + const session = await databaseConnection().startSession(); await session.startTransaction(); try { @@ -109,16 +104,16 @@ router.post('/', ratelimit, deviceCertificateMiddleware, async (request: express await nexAccount.save({ session }); - const primaryPasswordHash: string = nintendoPasswordHash(person.password, nexAccount.pid); - const passwordHash: string = await bcrypt.hash(primaryPasswordHash, 10); + const primaryPasswordHash = nintendoPasswordHash(person.password, nexAccount.pid); + const passwordHash = await bcrypt.hash(primaryPasswordHash, 10); - const countryCode: string = person.country; - const language: string = person.language; - const timezoneName: string = person.tz_name; + const countryCode = person.country; + const language = person.language; + const timezoneName = person.tz_name; - const regionLanguages: RegionLanguages = timezones[countryCode as keyof typeof timezones]; - const regionTimezones: RegionTimezones = regionLanguages[language] ? regionLanguages[language] : Object.values(regionLanguages)[0]; - let timezone: RegionTimezone | undefined = regionTimezones.find(tz => tz.area === timezoneName); + const regionLanguages = timezones[countryCode as keyof typeof timezones]; + const regionTimezones = regionLanguages[language as keyof typeof regionLanguages] ? regionLanguages[language as keyof typeof regionLanguages] : Object.values(regionLanguages)[0]; + let timezone = regionTimezones.find(tz => tz.area === timezoneName); if (!timezone) { // TODO - Change this, handle the error @@ -221,7 +216,7 @@ router.get('/@me/profile', async (request: express.Request, response: express.Re response.set('Server', 'Nintendo 3DS (http)'); response.set('X-Nintendo-Date', new Date().getTime().toString()); - const pnid: HydratedPNIDDocument | null = request.pnid; + const pnid = request.pnid; if (!pnid) { // TODO - Research this error more @@ -238,7 +233,7 @@ router.get('/@me/profile', async (request: express.Request, response: express.Re return; } - const person: PNIDProfile | null = await getPNIDProfileJSONByPID(pnid.pid); + const person = await getPNIDProfileJSONByPID(pnid.pid); if (!person) { // TODO - Research this error more @@ -276,7 +271,7 @@ router.post('/@me/devices', async (request: express.Request, response: express.R // TODO - CHANGE THIS. WE NEED TO SAVE CONSOLE DETAILS !!! - const pnid: HydratedPNIDDocument | null = request.pnid; + const pnid = request.pnid; if (!pnid) { // TODO - Research this error more @@ -293,7 +288,7 @@ router.post('/@me/devices', async (request: express.Request, response: express.R return; } - const person: PNIDProfile | null = await getPNIDProfileJSONByPID(pnid.pid); + const person = await getPNIDProfileJSONByPID(pnid.pid); if (!person) { // TODO - Research this error more @@ -325,13 +320,13 @@ router.get('/@me/devices', async (request: express.Request, response: express.Re response.set('Server', 'Nintendo 3DS (http)'); response.set('X-Nintendo-Date', new Date().getTime().toString()); - const pnid: HydratedPNIDDocument | null = request.pnid; - const deviceId: string | undefined = getValueFromHeaders(request.headers, 'x-nintendo-device-id'); - const acceptLanguage: string | undefined = getValueFromHeaders(request.headers, 'accept-language'); - const platformId: string | undefined = getValueFromHeaders(request.headers, 'x-nintendo-platform-id'); - const region: string | undefined = getValueFromHeaders(request.headers, 'x-nintendo-region'); - const serialNumber: string | undefined = getValueFromHeaders(request.headers, 'x-nintendo-serial-number'); - const systemVersion: string | undefined = getValueFromHeaders(request.headers, 'x-nintendo-system-version'); + const pnid = request.pnid; + const deviceId = getValueFromHeaders(request.headers, 'x-nintendo-device-id'); + const acceptLanguage = getValueFromHeaders(request.headers, 'accept-language'); + const platformId = getValueFromHeaders(request.headers, 'x-nintendo-platform-id'); + const region = getValueFromHeaders(request.headers, 'x-nintendo-region'); + const serialNumber = getValueFromHeaders(request.headers, 'x-nintendo-serial-number'); + const systemVersion = getValueFromHeaders(request.headers, 'x-nintendo-system-version'); if (!deviceId || !acceptLanguage || !platformId || !region || !serialNumber || !systemVersion) { // TODO - Research these error more @@ -394,7 +389,7 @@ router.get('/@me/devices/owner', async (request: express.Request, response: expr response.set('Server', 'Nintendo 3DS (http)'); response.set('X-Nintendo-Date', moment().add(5, 'h').toString()); - const pnid: HydratedPNIDDocument | null = request.pnid; + const pnid = request.pnid; if (!pnid) { // TODO - Research this error more @@ -411,7 +406,7 @@ router.get('/@me/devices/owner', async (request: express.Request, response: expr return; } - const person: PNIDProfile | null = await getPNIDProfileJSONByPID(pnid.pid); + const person = await getPNIDProfileJSONByPID(pnid.pid); if (!person) { // TODO - Research this error more @@ -455,7 +450,7 @@ router.get('/@me/devices/status', async (_request: express.Request, response: ex * Description: Updates a users Mii */ router.put('/@me/miis/@primary', async (request: express.Request, response: express.Response): Promise => { - const pnid: HydratedPNIDDocument | null = request.pnid; + const pnid = request.pnid; if (!pnid) { // TODO - Research this error more @@ -480,9 +475,9 @@ router.put('/@me/miis/@primary', async (request: express.Request, response: expr // TODO - Better checks - const name: string = mii.name; - const primary: string = mii.primary; - const data: string = mii.data; + const name = mii.name; + const primary = mii.primary; + const data = mii.data; await pnid.updateMii({ name, primary, data }); @@ -498,7 +493,7 @@ router.put('/@me/devices/@current/inactivate', async (request: express.Request, response.set('Server', 'Nintendo 3DS (http)'); response.set('X-Nintendo-Date', new Date().getTime().toString()); - const pnid: HydratedPNIDDocument | null = request.pnid; + const pnid = request.pnid; if (!pnid) { response.status(400).send(xmlbuilder.create({ @@ -523,7 +518,7 @@ router.put('/@me/devices/@current/inactivate', async (request: express.Request, * Description: Deletes a NNID */ router.post('/@me/deletion', async (request: express.Request, response: express.Response): Promise => { - const pnid: HydratedPNIDDocument | null = request.pnid; + const pnid = request.pnid; if (!pnid) { response.status(400).send(xmlbuilder.create({ @@ -539,7 +534,7 @@ router.post('/@me/deletion', async (request: express.Request, response: express. return; } - const email: string = pnid.email.address; + const email = pnid.email.address; await pnid.scrub(); await pnid.save(); @@ -559,7 +554,7 @@ router.post('/@me/deletion', async (request: express.Request, response: express. * Description: Updates a PNIDs account details */ router.put('/@me', async (request: express.Request, response: express.Response): Promise => { - const pnid: HydratedPNIDDocument | null = request.pnid; + const pnid = request.pnid; const person: Person = request.body.person; if (!pnid) { @@ -576,11 +571,11 @@ router.put('/@me', async (request: express.Request, response: express.Response): return; } - const gender: string = person.gender ? person.gender : pnid.gender; - const region: number = person.region ? person.region : pnid.region; - const countryCode: string = person.country ? person.country : pnid.country; - const language: string = person.language ? person.language : pnid.language; - let timezoneName: string = person.tz_name ? person.tz_name : pnid.timezone.name; + const gender = person.gender ? person.gender : pnid.gender; + const region = person.region ? person.region : pnid.region; + const countryCode = person.country ? person.country : pnid.country; + const language = person.language ? person.language : pnid.language; + let timezoneName = person.tz_name ? person.tz_name : pnid.timezone.name; // * Fix for 3DS sending empty person.tz_name, which is interpreted as an empty object // TODO - See if there's a cleaner way to do this? @@ -589,12 +584,12 @@ router.put('/@me', async (request: express.Request, response: express.Response): timezoneName = pnid.timezone.name; } - const marketingFlag: boolean = person.marketing_flag ? person.marketing_flag === 'Y' : pnid.flags.marketing; - const offDeviceFlag: boolean = person.off_device_flag ? person.off_device_flag === 'Y' : pnid.flags.off_device; + const marketingFlag = person.marketing_flag ? person.marketing_flag === 'Y' : pnid.flags.marketing; + const offDeviceFlag = person.off_device_flag ? person.off_device_flag === 'Y' : pnid.flags.off_device; - const regionLanguages: RegionLanguages = timezones[countryCode as keyof typeof timezones]; - const regionTimezones: RegionTimezones = regionLanguages[language] ? regionLanguages[language] : Object.values(regionLanguages)[0]; - let timezone: RegionTimezone | undefined = regionTimezones.find(tz => tz.area === timezoneName); + const regionLanguages = timezones[countryCode as keyof typeof timezones]; + const regionTimezones = regionLanguages[language as keyof typeof regionLanguages] ? regionLanguages[language as keyof typeof regionLanguages] : Object.values(regionLanguages)[0]; + let timezone = regionTimezones.find(tz => tz.area === timezoneName); if (!timezone) { // TODO - Change this, handle the error @@ -608,8 +603,8 @@ router.put('/@me', async (request: express.Request, response: express.Response): } if (person.password) { - const primaryPasswordHash: string = nintendoPasswordHash(person.password, pnid.pid); - const passwordHash: string = await bcrypt.hash(primaryPasswordHash, 10); + const primaryPasswordHash = nintendoPasswordHash(person.password, pnid.pid); + const passwordHash = await bcrypt.hash(primaryPasswordHash, 10); pnid.password = passwordHash; } @@ -632,7 +627,7 @@ router.put('/@me', async (request: express.Request, response: express.Response): * Description: Gets a list (why?) of PNID emails */ router.get('/@me/emails', async (request: express.Request, response: express.Response): Promise => { - const pnid: HydratedPNIDDocument | null = request.pnid; + const pnid = request.pnid; if (!pnid) { response.status(400).send(xmlbuilder.create({ @@ -673,7 +668,7 @@ router.get('/@me/emails', async (request: express.Request, response: express.Res * Description: Updates a users email address */ router.put('/@me/emails/@primary', async (request: express.Request, response: express.Response): Promise => { - const pnid: HydratedPNIDDocument | null = request.pnid; + const pnid = request.pnid; const email: { address: string; diff --git a/src/services/nnas/routes/provider.ts b/src/services/nnas/routes/provider.ts index 6b7f69f..794c6cf 100644 --- a/src/services/nnas/routes/provider.ts +++ b/src/services/nnas/routes/provider.ts @@ -3,12 +3,8 @@ import xmlbuilder from 'xmlbuilder'; import { getServerByClientID, getServerByGameServerID } from '@/database'; import { generateToken, getValueFromHeaders, getValueFromQueryString } from '@/util'; import { NEXAccount } from '@/models/nex-account'; -import { TokenOptions } from '@/types/common/token-options'; -import { HydratedPNIDDocument } from '@/types/mongoose/pnid'; -import { HydratedServerDocument } from '@/types/mongoose/server'; -import { HydratedNEXAccountDocument } from '@/types/mongoose/nex-account'; -const router: express.Router = express.Router(); +const router = express.Router(); /** * [GET] @@ -16,7 +12,7 @@ const router: express.Router = express.Router(); * Description: Gets a service token */ router.get('/service_token/@me', async (request: express.Request, response: express.Response): Promise => { - const pnid: HydratedPNIDDocument | null = request.pnid; + const pnid = request.pnid; if (!pnid) { response.status(400).send(xmlbuilder.create({ @@ -32,7 +28,7 @@ router.get('/service_token/@me', async (request: express.Request, response: expr return; } - const clientID: string | undefined = getValueFromQueryString(request.query, 'client_id'); + const clientID = getValueFromQueryString(request.query, 'client_id'); if (!clientID) { // TODO - Research this error more @@ -48,7 +44,7 @@ router.get('/service_token/@me', async (request: express.Request, response: expr return; } - const titleID: string | undefined = getValueFromHeaders(request.headers, 'x-nintendo-title-id'); + const titleID = getValueFromHeaders(request.headers, 'x-nintendo-title-id'); if (!titleID) { // TODO - Research this error more @@ -64,8 +60,8 @@ router.get('/service_token/@me', async (request: express.Request, response: expr return; } - const serverAccessLevel: string = pnid.server_access_level; - const server: HydratedServerDocument | null = await getServerByClientID(clientID, serverAccessLevel); + const serverAccessLevel = pnid.server_access_level; + const server = await getServerByClientID(clientID, serverAccessLevel); if (!server || !server.aes_key) { response.send(xmlbuilder.create({ @@ -93,7 +89,7 @@ router.get('/service_token/@me', async (request: express.Request, response: expr return; } - const tokenOptions: TokenOptions = { + const tokenOptions = { system_type: server.device, token_type: 0x4, // * Service token pid: pnid.pid, @@ -102,8 +98,8 @@ router.get('/service_token/@me', async (request: express.Request, response: expr expire_time: BigInt(Date.now() + (3600 * 1000)) }; - const serviceTokenBuffer: Buffer | null = await generateToken(server.aes_key, tokenOptions); - let serviceToken: string = serviceTokenBuffer ? serviceTokenBuffer.toString('base64') : ''; + const serviceTokenBuffer = await generateToken(server.aes_key, tokenOptions); + let serviceToken = serviceTokenBuffer ? serviceTokenBuffer.toString('base64') : ''; if (request.isCemu) { serviceToken = Buffer.from(serviceToken, 'base64').toString('hex'); @@ -122,7 +118,7 @@ router.get('/service_token/@me', async (request: express.Request, response: expr * Description: Gets a NEX server address and token */ router.get('/nex_token/@me', async (request: express.Request, response: express.Response): Promise => { - const pnid: HydratedPNIDDocument | null = request.pnid; + const pnid = request.pnid; if (!pnid) { response.status(400).send(xmlbuilder.create({ @@ -138,7 +134,7 @@ router.get('/nex_token/@me', async (request: express.Request, response: express. return; } - const nexAccount: HydratedNEXAccountDocument | null = await NEXAccount.findOne({ + const nexAccount = await NEXAccount.findOne({ owning_pid: pnid.pid }); @@ -156,7 +152,7 @@ router.get('/nex_token/@me', async (request: express.Request, response: express. return; } - const gameServerID: string | undefined = getValueFromQueryString(request.query, 'game_server_id'); + const gameServerID = getValueFromQueryString(request.query, 'game_server_id'); if (!gameServerID) { response.send(xmlbuilder.create({ @@ -171,8 +167,8 @@ router.get('/nex_token/@me', async (request: express.Request, response: express. return; } - const serverAccessLevel: string = pnid.server_access_level; - const server: HydratedServerDocument | null = await getServerByGameServerID(gameServerID, serverAccessLevel); + const serverAccessLevel = pnid.server_access_level; + const server = await getServerByGameServerID(gameServerID, serverAccessLevel); if (!server || !server.aes_key) { response.send(xmlbuilder.create({ @@ -200,7 +196,7 @@ router.get('/nex_token/@me', async (request: express.Request, response: express. return; } - const titleID: string | undefined = getValueFromHeaders(request.headers, 'x-nintendo-title-id'); + const titleID = getValueFromHeaders(request.headers, 'x-nintendo-title-id'); if (!titleID) { // TODO - Research this error more @@ -216,7 +212,7 @@ router.get('/nex_token/@me', async (request: express.Request, response: express. return; } - const tokenOptions: TokenOptions = { + const tokenOptions = { system_type: server.device, token_type: 0x3, // nex token, pid: pnid.pid, @@ -225,8 +221,8 @@ router.get('/nex_token/@me', async (request: express.Request, response: express. expire_time: BigInt(Date.now() + (3600 * 1000)) }; - const nexTokenBuffer: Buffer | null = await generateToken(server.aes_key, tokenOptions); - let nexToken: string = nexTokenBuffer ? nexTokenBuffer.toString('base64') : ''; + const nexTokenBuffer = await generateToken(server.aes_key, tokenOptions); + let nexToken = nexTokenBuffer ? nexTokenBuffer.toString('base64') : ''; if (request.isCemu) { nexToken = Buffer.from(nexToken || '', 'base64').toString('hex'); diff --git a/src/services/nnas/routes/support.ts b/src/services/nnas/routes/support.ts index a7e163d..46522a8 100644 --- a/src/services/nnas/routes/support.ts +++ b/src/services/nnas/routes/support.ts @@ -4,9 +4,8 @@ import xmlbuilder from 'xmlbuilder'; import moment from 'moment'; import { getPNIDByPID } from '@/database'; import { sendEmailConfirmedEmail, sendConfirmationEmail, sendForgotPasswordEmail } from '@/util'; -import { HydratedPNIDDocument } from '@/types/mongoose/pnid'; -const router: express.Router = express.Router(); +const router = express.Router(); /** * [POST] @@ -14,7 +13,7 @@ const router: express.Router = express.Router(); * Description: Verifies a provided email address is valid */ router.post('/validate/email', async (request: express.Request, response: express.Response): Promise => { - const email: string = request.body.email; + const email = request.body.email; if (!email) { response.send(xmlbuilder.create({ @@ -30,7 +29,7 @@ router.post('/validate/email', async (request: express.Request, response: expres return; } - const domain: string = email.split('@')[1]; + const domain = email.split('@')[1]; dns.resolveMx(domain, (error: NodeJS.ErrnoException | null) => { if (error) { @@ -54,10 +53,10 @@ router.post('/validate/email', async (request: express.Request, response: expres * Description: Verifies a users email via 6 digit code */ router.put('/email_confirmation/:pid/:code', async (request: express.Request, response: express.Response): Promise => { - const code: string = request.params.code; - const pid: number = Number(request.params.pid); + const code = request.params.code; + const pid = Number(request.params.pid); - const pnid: HydratedPNIDDocument | null = await getPNIDByPID(pid); + const pnid = await getPNIDByPID(pid); if (!pnid) { response.status(400).send(xmlbuilder.create({ @@ -84,7 +83,7 @@ router.put('/email_confirmation/:pid/:code', async (request: express.Request, re return; } - const validatedDate: string = moment().format('YYYY-MM-DDTHH:MM:SS'); + const validatedDate = moment().format('YYYY-MM-DDTHH:MM:SS'); pnid.email.reachable = true; pnid.email.validated = true; @@ -103,9 +102,9 @@ router.put('/email_confirmation/:pid/:code', async (request: express.Request, re * Description: Resends a users confirmation email */ router.get('/resend_confirmation', async (request: express.Request, response: express.Response): Promise => { - const pid: number = Number(request.headers['x-nintendo-pid']); + const pid = Number(request.headers['x-nintendo-pid']); - const pnid: HydratedPNIDDocument | null = await getPNIDByPID(pid); + const pnid = await getPNIDByPID(pid); if (!pnid) { // TODO - Unsure if this is the right error @@ -133,9 +132,9 @@ router.get('/resend_confirmation', async (request: express.Request, response: ex * NOTE: On NN this was a temp password that expired after 24 hours. We do not do that */ router.get('/forgotten_password/:pid', async (request: express.Request, response: express.Response): Promise => { - const pid: number = Number(request.params.pid); + const pid = Number(request.params.pid); - const pnid: HydratedPNIDDocument | null = await getPNIDByPID(pid); + const pnid = await getPNIDByPID(pid); if (!pnid) { // TODO - Better errors diff --git a/src/types/common/config.ts b/src/types/common/config.ts index 9c11436..82606fc 100644 --- a/src/types/common/config.ts +++ b/src/types/common/config.ts @@ -46,11 +46,4 @@ export interface Config { stripe?: { secret_key: string; }; -} - -export interface DisabledFeatures { - redis: boolean; - email: boolean; - captcha: boolean; - s3: boolean } \ No newline at end of file diff --git a/src/types/services/nnas/region-languages.ts b/src/types/services/nnas/region-languages.ts deleted file mode 100644 index fd4bbf9..0000000 --- a/src/types/services/nnas/region-languages.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { RegionTimezones } from '@/types/services/nnas/region-timezones'; - -export interface RegionLanguages { - [myKey: string]: RegionTimezones -} \ No newline at end of file diff --git a/src/types/services/nnas/region-timezones.ts b/src/types/services/nnas/region-timezones.ts deleted file mode 100644 index c904aaa..0000000 --- a/src/types/services/nnas/region-timezones.ts +++ /dev/null @@ -1,9 +0,0 @@ -export interface RegionTimezone { - area: string; - language: string; - name: string; - utc_offset: string; - order: string; -} - -export type RegionTimezones = RegionTimezone[]; \ No newline at end of file diff --git a/src/util.ts b/src/util.ts index 25558cf..f5d8b18 100644 --- a/src/util.ts +++ b/src/util.ts @@ -13,7 +13,6 @@ import { config, disabledFeatures } from '@/config-manager'; import { TokenOptions } from '@/types/common/token-options'; import { Token } from '@/types/common/token'; import { IPNID, IPNIDMethods } from '@/types/mongoose/pnid'; -import { MailerOptions } from '@/types/common/mailer-options'; import { SafeQs } from '@/types/common/safe-qs'; let s3: aws.S3; @@ -27,10 +26,10 @@ if (!disabledFeatures.s3) { } export function nintendoPasswordHash(password: string, pid: number): string { - const pidBuffer: Buffer = Buffer.alloc(4); + const pidBuffer = Buffer.alloc(4); pidBuffer.writeUInt32LE(pid); - const unpacked: Buffer = Buffer.concat([ + const unpacked = Buffer.concat([ pidBuffer, Buffer.from('\x02\x65\x43\x46'), Buffer.from(password) @@ -45,12 +44,12 @@ export function nintendoBase64Decode(encoded: string): Buffer { } export function nintendoBase64Encode(decoded: string | Buffer): string { - const encoded: string = Buffer.from(decoded).toString('base64'); + const encoded = Buffer.from(decoded).toString('base64'); return encoded.replaceAll('+', '.').replaceAll('/', '-').replaceAll('=', '*'); } export function generateToken(key: string, options: TokenOptions): Buffer | null { - let dataBuffer: Buffer = Buffer.alloc(1 + 1 + 4 + 8); + let dataBuffer = Buffer.alloc(1 + 1 + 4 + 8); dataBuffer.writeUInt8(options.system_type, 0x0); dataBuffer.writeUInt8(options.token_type, 0x1); @@ -73,19 +72,19 @@ export function generateToken(key: string, options: TokenOptions): Buffer | null dataBuffer.writeInt8(options.access_level, 0x16); } - const iv: Buffer = Buffer.alloc(16); - const cipher: crypto.Cipher = crypto.createCipheriv('aes-256-cbc', Buffer.from(key, 'hex'), iv); + const iv = Buffer.alloc(16); + const cipher = crypto.createCipheriv('aes-256-cbc', Buffer.from(key, 'hex'), iv); const encrypted = Buffer.concat([ cipher.update(dataBuffer), cipher.final() ]); - let final: Buffer = encrypted; + let final = encrypted; if ((options.token_type !== 0x1 && options.token_type !== 0x2) || options.system_type === 0x3) { // * Access and refresh tokens don't get a checksum due to size constraints - const checksum: Buffer = crc32(dataBuffer); + const checksum = crc32(dataBuffer); final = Buffer.concat([ checksum, @@ -98,7 +97,7 @@ export function generateToken(key: string, options: TokenOptions): Buffer | null export function decryptToken(token: Buffer): Buffer { let encryptedBody: Buffer; - let expectedChecksum: number = 0; + let expectedChecksum = 0; if (token.length === 16) { // * Token is an access/refresh token, no checksum @@ -108,10 +107,10 @@ export function decryptToken(token: Buffer): Buffer { encryptedBody = token.subarray(4); } - const iv: Buffer = Buffer.alloc(16); - const decipher: crypto.Decipher = crypto.createDecipheriv('aes-256-cbc', Buffer.from(config.aes_key, 'hex'), iv); + const iv = Buffer.alloc(16); + const decipher = crypto.createDecipheriv('aes-256-cbc', Buffer.from(config.aes_key, 'hex'), iv); - const decrypted: Buffer = Buffer.concat([ + const decrypted = Buffer.concat([ decipher.update(encryptedBody), decipher.final() ]); @@ -140,9 +139,9 @@ export function unpackToken(token: Buffer): Token { } export function fullUrl(request: express.Request): string { - const protocol: string = request.protocol; - const host: string = request.host; - const opath: string = request.originalUrl; + const protocol = request.protocol; + const host = request.host; + const opath = request.originalUrl; return `${protocol}://${host}${opath}`; } @@ -161,8 +160,8 @@ export async function uploadCDNAsset(bucket: string, key: string, data: Buffer, } export async function writeLocalCDNFile(key: string, data: Buffer): Promise { - const filePath: string = config.cdn.disk_path; - const folder: string = path.dirname(filePath); + const filePath = config.cdn.disk_path; + const folder = path.dirname(filePath); await fs.ensureDir(folder); await fs.writeFile(filePath, data); @@ -177,7 +176,7 @@ export function nascError(errorCode: string): URLSearchParams { } export async function sendConfirmationEmail(pnid: mongoose.HydratedDocument): Promise { - const options: MailerOptions = { + const options = { to: pnid.email.address, subject: '[Pretendo Network] Please confirm your email address', username: pnid.username, @@ -192,7 +191,7 @@ export async function sendConfirmationEmail(pnid: mongoose.HydratedDocument): Promise { - const options: MailerOptions = { + const options = { to: pnid.email.address, subject: '[Pretendo Network] Email address confirmed', username: pnid.username, @@ -204,7 +203,7 @@ export async function sendEmailConfirmedEmail(pnid: mongoose.HydratedDocument): Promise { - const tokenOptions: TokenOptions = { + const tokenOptions = { system_type: 0xF, // * API token_type: 0x5, // * Password reset pid: pnid.pid, @@ -213,12 +212,12 @@ export async function sendForgotPasswordEmail(pnid: mongoose.HydratedDocument { - const options: MailerOptions = { + const options = { to: email, subject: '[Pretendo Network] PNID Deleted', username: username, @@ -265,8 +264,8 @@ export function makeSafeQs(query: ParsedQs): SafeQs { } export function getValueFromQueryString(qs: ParsedQs, key: string): string | undefined { - let property: string | ParsedQs | string[] | ParsedQs[] | SafeQs | undefined = qs[key]; - let value: string | undefined; + let property = qs[key]; + let value; if (property) { if (Array.isArray(property)) { @@ -285,8 +284,8 @@ export function getValueFromQueryString(qs: ParsedQs, key: string): string | und } export function getValueFromHeaders(headers: IncomingHttpHeaders, key: string): string | undefined { - let header: string | string[] | undefined = headers[key]; - let value: string | undefined; + let header = headers[key]; + let value; if (header) { if (!Array.isArray(header)) { From 6d3f516612f78cd7ff226eb0304c1726e7ce65d5 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Sun, 14 Apr 2024 19:19:37 -0400 Subject: [PATCH 149/219] chore: update Id -> ID --- src/middleware/client-header.ts | 8 ++++---- src/nintendo-certificate.ts | 6 +++--- src/server.ts | 16 ++++++++-------- src/services/nnas/routes/people.ts | 10 +++++----- 4 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/middleware/client-header.ts b/src/middleware/client-header.ts index 851317f..483f8dc 100644 --- a/src/middleware/client-header.ts +++ b/src/middleware/client-header.ts @@ -14,14 +14,14 @@ function nintendoClientHeaderCheck(request: express.Request, response: express.R response.set('Server', 'Nintendo 3DS (http)'); response.set('X-Nintendo-Date', new Date().getTime().toString()); - const clientId = getValueFromHeaders(request.headers, 'x-nintendo-client-id'); + const clientID = getValueFromHeaders(request.headers, 'x-nintendo-client-id'); const clientSecret = getValueFromHeaders(request.headers, 'x-nintendo-client-secret'); if ( - !clientId || + !clientID || !clientSecret || - !VALID_CLIENT_ID_SECRET_PAIRS[clientId] || - clientSecret !== VALID_CLIENT_ID_SECRET_PAIRS[clientId] + !VALID_CLIENT_ID_SECRET_PAIRS[clientID] || + clientSecret !== VALID_CLIENT_ID_SECRET_PAIRS[clientID] ) { response.send(xmlbuilder.create({ errors: { diff --git a/src/nintendo-certificate.ts b/src/nintendo-certificate.ts index da1741c..8d5dce8 100644 --- a/src/nintendo-certificate.ts +++ b/src/nintendo-certificate.ts @@ -77,7 +77,7 @@ class NintendoCertificate { issuer: string; keyType: number; certificateName: string; - ngKeyId: number; + ngKeyID: number; publicKey: Buffer; valid: boolean; publicKeyData: Buffer; @@ -91,7 +91,7 @@ class NintendoCertificate { this.issuer = ''; this.keyType = 0; this.certificateName = ''; - this.ngKeyId = 0; + this.ngKeyID = 0; this.publicKey = Buffer.alloc(0); this.valid = false; this.publicKeyData = Buffer.alloc(0); @@ -128,7 +128,7 @@ class NintendoCertificate { this.issuer = this._certificate.subarray(0x80, 0xC0).toString().split('\0')[0]; this.keyType = this._certificate.readUInt32BE(0xC0); this.certificateName = this._certificate.subarray(0xC4, 0x104).toString().split('\0')[0]; - this.ngKeyId = this._certificate.readUInt32BE(0x104); + this.ngKeyID = this._certificate.readUInt32BE(0x104); this.publicKeyData = this._certificate.subarray(0x108); if (this.issuer === 'Root-CA00000003-MS00000012') { diff --git a/src/server.ts b/src/server.ts index e496b74..4d90d63 100644 --- a/src/server.ts +++ b/src/server.ts @@ -52,13 +52,13 @@ app.use(assets); LOG_INFO('Creating 404 status handler'); app.use((request: express.Request, response: express.Response): void => { const url = fullUrl(request); - let deviceId = getValueFromHeaders(request.headers, 'X-Nintendo-Device-ID'); + let deviceID = getValueFromHeaders(request.headers, 'X-Nintendo-Device-ID'); - if (!deviceId) { - deviceId = 'Unknown'; + if (!deviceID) { + deviceID = 'Unknown'; } - LOG_WARN(`HTTP 404 at ${url} from ${deviceId}`); + LOG_WARN(`HTTP 404 at ${url} from ${deviceID}`); response.set('Content-Type', 'text/xml'); response.set('Server', 'Nintendo 3DS (http)'); @@ -80,13 +80,13 @@ LOG_INFO('Creating non-404 status handler'); app.use((error: any, request: express.Request, response: express.Response, _next: express.NextFunction): void => { const status = error.status || 500; const url = fullUrl(request); - let deviceId = getValueFromHeaders(request.headers, 'X-Nintendo-Device-ID'); + let deviceID = getValueFromHeaders(request.headers, 'X-Nintendo-Device-ID'); - if (!deviceId) { - deviceId = 'Unknown'; + if (!deviceID) { + deviceID = 'Unknown'; } - LOG_WARN(`HTTP ${status} at ${url} from ${deviceId}: ${error.message}`); + LOG_WARN(`HTTP ${status} at ${url} from ${deviceID}: ${error.message}`); response.status(status).json({ app: 'api', diff --git a/src/services/nnas/routes/people.ts b/src/services/nnas/routes/people.ts index ee3d9ba..5d0bf3d 100644 --- a/src/services/nnas/routes/people.ts +++ b/src/services/nnas/routes/people.ts @@ -321,14 +321,14 @@ router.get('/@me/devices', async (request: express.Request, response: express.Re response.set('X-Nintendo-Date', new Date().getTime().toString()); const pnid = request.pnid; - const deviceId = getValueFromHeaders(request.headers, 'x-nintendo-device-id'); + const deviceID = getValueFromHeaders(request.headers, 'x-nintendo-device-id'); const acceptLanguage = getValueFromHeaders(request.headers, 'accept-language'); - const platformId = getValueFromHeaders(request.headers, 'x-nintendo-platform-id'); + const platformID = getValueFromHeaders(request.headers, 'x-nintendo-platform-id'); const region = getValueFromHeaders(request.headers, 'x-nintendo-region'); const serialNumber = getValueFromHeaders(request.headers, 'x-nintendo-serial-number'); const systemVersion = getValueFromHeaders(request.headers, 'x-nintendo-system-version'); - if (!deviceId || !acceptLanguage || !platformId || !region || !serialNumber || !systemVersion) { + if (!deviceID || !acceptLanguage || !platformID || !region || !serialNumber || !systemVersion) { // TODO - Research these error more response.status(400).send(xmlbuilder.create({ errors: { @@ -362,11 +362,11 @@ router.get('/@me/devices', async (request: express.Request, response: express.Re devices: [ { device: { - device_id: deviceId, + device_id: deviceID, language: acceptLanguage, updated: moment().format('YYYY-MM-DDTHH:MM:SS'), pid: pnid.pid, - platform_id: platformId, + platform_id: platformID, region: region, serial_number: serialNumber, status: 'ACTIVE', From 82d353357bc8d8cfc1a63fb6d64f43a7b7292482 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Sun, 14 Apr 2024 19:41:47 -0400 Subject: [PATCH 150/219] chore: updated comments to use Better Comments syntax --- src/database.ts | 6 +-- src/middleware/nasc.ts | 20 ++++----- src/middleware/xml-parser.ts | 2 +- src/models/device.ts | 4 +- src/models/nex-account.ts | 8 ++-- src/models/pnid.ts | 14 +++--- src/nintendo-certificate.ts | 10 ++--- src/server.ts | 12 ++--- src/services/api/index.ts | 8 ++-- src/services/api/routes/v1/register.ts | 50 ++++++++++----------- src/services/api/routes/v1/resetPassword.ts | 2 +- src/services/assets/index.ts | 10 ++--- src/services/cbvc/index.ts | 10 ++--- src/services/conntest/index.ts | 12 ++--- src/services/datastore/index.ts | 8 ++-- src/services/datastore/routes/upload.ts | 2 +- src/services/grpc/api/register.ts | 48 ++++++++++---------- src/services/grpc/api/reset-password.ts | 2 +- src/services/nasc/index.ts | 10 ++--- src/services/nasc/routes/ac.ts | 2 +- src/services/nnas/index.ts | 10 ++--- src/services/nnas/routes/content.ts | 4 +- src/services/nnas/routes/people.ts | 28 ++++++------ src/services/nnas/routes/provider.ts | 2 +- src/types/mongoose/pnid.ts | 2 +- src/types/services/nnas/pnid-profile.ts | 2 +- src/util.ts | 2 +- 27 files changed, 145 insertions(+), 145 deletions(-) diff --git a/src/database.ts b/src/database.ts index 4f0e7a2..b6d443e 100644 --- a/src/database.ts +++ b/src/database.ts @@ -17,7 +17,7 @@ import { DiscordConnectionData } from '@/types/services/api/discord-connection-d const connection_string = config.mongoose.connection_string; const options = config.mongoose.options; -// TODO: Extend this later with more settings +// TODO - Extend this later with more settings const discordConnectionSchema = joi.object({ id: joi.string() }); @@ -116,7 +116,7 @@ export async function getPNIDByTokenAuth(token: string): Promise({ linked_pids: [Number], access_level: { type: Number, - default: 0 // 0: standard, 1: tester, 2: mod?, 3: dev + default: 0 // * 0: standard, 1: tester, 2: mod?, 3: dev }, server_access_level: { type: String, - default: 'prod' // everyone is in production by default + default: 'prod' // * everyone is in production by default }, certificate_hash: String }); diff --git a/src/models/nex-account.ts b/src/models/nex-account.ts index 9372ac6..c731f07 100644 --- a/src/models/nex-account.ts +++ b/src/models/nex-account.ts @@ -6,7 +6,7 @@ const NEXAccountSchema = new Schema { - const min = 1000000000; // The console (WiiU) seems to not accept PIDs smaller than this + const min = 1000000000; // * The console (WiiU) seems to not accept PIDs smaller than this const max = 1799999999; const pid = Math.floor(Math.random() * (max - min + 1) + min); diff --git a/src/models/pnid.ts b/src/models/pnid.ts index 1e19629..5e2cc34 100644 --- a/src/models/pnid.ts +++ b/src/models/pnid.ts @@ -33,11 +33,11 @@ const PNIDSchema = new Schema({ }, access_level: { type: Number, - default: 0 // 0: standard, 1: tester, 2: mod?, 3: dev + default: 0 // * 0: standard, 1: tester, 2: mod?, 3: dev }, server_access_level: { type: String, - default: 'prod' // everyone is in production by default + default: 'prod' // * everyone is in production by default }, pid: { type: Number, @@ -89,7 +89,7 @@ const PNIDSchema = new Schema({ off_device: Boolean }, devices: [DeviceSchema], - identification: { // user identification tokens + identification: { // * user identification tokens email_code: { type: String, unique: true @@ -133,7 +133,7 @@ PNIDSchema.plugin(uniqueValidator, {message: '{PATH} already in use.'}); and the next few accounts counting down seem to be admin, service and internal test accounts */ PNIDSchema.method('generatePID', async function generatePID(): Promise { - const min = 1000000000; // The console (WiiU) seems to not accept PIDs smaller than this + const min = 1000000000; // * The console (WiiU) seems to not accept PIDs smaller than this const max = 1799999999; const pid = Math.floor(Math.random() * (max - min + 1) + min); @@ -150,9 +150,9 @@ PNIDSchema.method('generatePID', async function generatePID(): Promise { }); PNIDSchema.method('generateEmailValidationCode', async function generateEmailValidationCode(): Promise { - // WiiU passes the PID along with the email code - // Does not actually need to be unique to all users - const code = Math.random().toFixed(6).split('.')[1]; // Dirty one-liner to generate numbers of 6 length and padded 0 + // * WiiU passes the PID along with the email code + // * Does not actually need to be unique to all users + const code = Math.random().toFixed(6).split('.')[1]; // * Dirty one-liner to generate numbers of 6 length and padded 0 this.identification.email_code = code; }); diff --git a/src/nintendo-certificate.ts b/src/nintendo-certificate.ts index 8d5dce8..38e7a1a 100644 --- a/src/nintendo-certificate.ts +++ b/src/nintendo-certificate.ts @@ -41,7 +41,7 @@ const CTR_LFCS_B_PUB = Buffer.from([ 0xAF, 0x07, 0xEB, 0x9C, 0xBF, 0xA9, 0xC9 ]); -// Signature options +// * Signature options const SIGNATURE_SIZES = { RSA_4096_SHA1: { SIZE: 0x200, @@ -201,10 +201,10 @@ class NintendoCertificate { this.valid = publicKey.verify(this._certificateBody, this.signature); } - // Huge thanks to Myria for helping get ECDSA working - // with Nodes native crypto module and getting the keys - // from bytes to PEM! - // https://github.com/Myriachan + // * Huge thanks to Myria for helping get ECDSA working + // * with Nodes native crypto module and getting the keys + // * from bytes to PEM! + // * https://github.com/Myriachan _verifySignatureECDSA(): void { const pem = this.consoleType === 'wiiu' ? WIIU_DEVICE_PUB_PEM : CTR_DEVICE_PUB_PEM; const key = { diff --git a/src/server.ts b/src/server.ts index 4d90d63..6919a55 100644 --- a/src/server.ts +++ b/src/server.ts @@ -27,9 +27,9 @@ import { config } from '@/config-manager'; const app = express(); -// START APPLICATION +// * START APPLICATION -// Create router +// * Create router LOG_INFO('Setting up Middleware'); app.use(morgan('dev')); app.use(express.json()); @@ -38,7 +38,7 @@ app.use(express.urlencoded({ })); app.use(xmlparser); -// import the servers into one +// * import the servers into one app.use(conntest); app.use(cbvc); app.use(nnas); @@ -48,7 +48,7 @@ app.use(api); app.use(localcdn); app.use(assets); -// 404 handler +// * 404 handler LOG_INFO('Creating 404 status handler'); app.use((request: express.Request, response: express.Response): void => { const url = fullUrl(request); @@ -75,7 +75,7 @@ app.use((request: express.Request, response: express.Response): void => { }).end()); }); -// non-404 error handler +// * non-404 error handler LOG_INFO('Creating non-404 status handler'); app.use((error: any, request: express.Request, response: express.Response, _next: express.NextFunction): void => { const status = error.status || 500; @@ -96,7 +96,7 @@ app.use((error: any, request: express.Request, response: express.Response, _next }); async function main(): Promise { - // Starts the server + // * Starts the server LOG_INFO('Starting server'); await connectDatabase(); diff --git a/src/services/api/index.ts b/src/services/api/index.ts index 45efc53..f699f79 100644 --- a/src/services/api/index.ts +++ b/src/services/api/index.ts @@ -6,7 +6,7 @@ import { LOG_INFO } from '@/logger'; import { V1 } from '@/services/api/routes'; -// Router to handle the subdomain restriction +// * Router to handle the subdomain restriction const api = express.Router(); LOG_INFO('[USER API] Importing middleware'); @@ -14,7 +14,7 @@ api.use(APIMiddleware); api.use(cors()); api.options('*', cors()); -// Setup routes +// * Setup routes LOG_INFO('[USER API] Applying imported routes'); api.use('/v1/connections', V1.CONNECTIONS); api.use('/v1/email', V1.EMAIL); @@ -25,10 +25,10 @@ api.use('/v1/reset-password', V1.RESET_PASSWORD); api.use('/v1/user', V1.USER); -// Main router for endpoints +// * Main router for endpoints const router = express.Router(); -// Create subdomains +// * Create subdomains LOG_INFO('[USER API] Creating \'api\' subdomain'); router.use(subdomain('api', api)); diff --git a/src/services/api/routes/v1/register.ts b/src/services/api/routes/v1/register.ts index 12a1b5e..2b1928b 100644 --- a/src/services/api/routes/v1/register.ts +++ b/src/services/api/routes/v1/register.ts @@ -22,7 +22,7 @@ const PNID_PUNCTUATION_START_REGEX = /^[_\-.]/; const PNID_PUNCTUATION_END_REGEX = /[_\-.]$/; const PNID_PUNCTUATION_DUPLICATE_REGEX = /[_\-.]{2,}/; -// This sucks +// * This sucks const PASSWORD_WORD_OR_NUMBER_REGEX = /(?=.*[a-zA-Z])(?=.*\d).*/; const PASSWORD_WORD_OR_PUNCTUATION_REGEX = /(?=.*[a-zA-Z])(?=.*[_\-.]).*/; const PASSWORD_NUMBER_OR_PUNCTUATION_REGEX = /(?=.*\d)(?=.*[_\-.]).*/; @@ -250,7 +250,7 @@ router.post('/', async (request: express.Request, response: express.Response): P return; } - const miiNameBuffer = Buffer.from(miiName, 'utf16le'); // UTF8 to UTF16 + const miiNameBuffer = Buffer.from(miiName, 'utf16le'); // * UTF8 to UTF16 if (miiNameBuffer.length > 0x14) { response.status(400).json({ @@ -282,11 +282,11 @@ router.post('/', async (request: express.Request, response: express.Response): P await nexAccount.generatePID(); await nexAccount.generatePassword(); - // Quick hack to get the PIDs to match - // TODO: Change this maybe? - // NN with a NNID will always use the NNID PID - // even if the provided NEX PID is different - // To fix this we make them the same PID + // * Quick hack to get the PIDs to match + // TODO - Change this maybe? + // * NN with a NNID will always use the NNID PID + // * even if the provided NEX PID is different + // * To fix this we make them the same PID nexAccount.owning_pid = nexAccount.pid; await nexAccount.save({ session }); @@ -301,40 +301,40 @@ router.post('/', async (request: express.Request, response: express.Response): P username: username, usernameLower: username.toLowerCase(), password: passwordHash, - birthdate: '1990-01-01', // TODO: Change this - gender: 'M', // TODO: Change this - country: 'US', // TODO: Change this - language: 'en', // TODO: Change this + birthdate: '1990-01-01', // TODO - Change this + gender: 'M', // TODO - Change this + country: 'US', // TODO - Change this + language: 'en', // TODO - Change this email: { address: email.toLowerCase(), - primary: true, // TODO: Change this - parent: true, // TODO: Change this - reachable: false, // TODO: Change this - validated: false, // TODO: Change this + primary: true, // TODO - Change this + parent: true, // TODO - Change this + reachable: false, // TODO - Change this + validated: false, // TODO - Change this id: crypto.randomBytes(4).readUInt32LE() }, - region: 0x310B0000, // TODO: Change this + region: 0x310B0000, // TODO - Change this timezone: { - name: 'America/New_York', // TODO: Change this - offset: -14400 // TODO: Change this + name: 'America/New_York', // TODO - Change this + offset: -14400 // TODO - Change this }, mii: { name: miiName, - primary: true, // TODO: Change this + primary: true, // TODO - Change this data: mii.encode().toString('base64'), id: crypto.randomBytes(4).readUInt32LE(), hash: crypto.randomBytes(7).toString('hex'), - image_url: '', // deprecated, will be removed in the future + image_url: '', // * deprecated, will be removed in the future image_id: crypto.randomBytes(4).readUInt32LE() }, flags: { - active: true, // TODO: Change this - marketing: true, // TODO: Change this - off_device: true // TODO: Change this + active: true, // TODO - Change this + marketing: true, // TODO - Change this + off_device: true // TODO - Change this }, identification: { - email_code: 1, // will be overwritten before saving - email_token: '' // will be overwritten before saving + email_code: 1, // * will be overwritten before saving + email_token: '' // * will be overwritten before saving } }); diff --git a/src/services/api/routes/v1/resetPassword.ts b/src/services/api/routes/v1/resetPassword.ts index 025ca64..ad401fb 100644 --- a/src/services/api/routes/v1/resetPassword.ts +++ b/src/services/api/routes/v1/resetPassword.ts @@ -6,7 +6,7 @@ import { Token } from '@/types/common/token'; const router = express.Router(); -// This sucks +// * This sucks const PASSWORD_WORD_OR_NUMBER_REGEX = /(?=.*[a-zA-Z])(?=.*\d).*/; const PASSWORD_WORD_OR_PUNCTUATION_REGEX = /(?=.*[a-zA-Z])(?=.*[_\-.]).*/; const PASSWORD_NUMBER_OR_PUNCTUATION_REGEX = /(?=.*\d)(?=.*[_\-.]).*/; diff --git a/src/services/assets/index.ts b/src/services/assets/index.ts index 22be241..a20b710 100644 --- a/src/services/assets/index.ts +++ b/src/services/assets/index.ts @@ -1,21 +1,21 @@ -// handles serving assets +// * handles serving assets import path from 'node:path'; import express from 'express'; import subdomain from 'express-subdomain'; import { LOG_INFO } from '@/logger'; -// Router to handle the subdomain restriction +// * Router to handle the subdomain restriction const assets = express.Router(); -// Setup public folder +// * Setup public folder LOG_INFO('[assets] Setting up public folder'); assets.use(express.static(path.join(__dirname, '../../assets'))); -// Main router for endpoints +// * Main router for endpoints const router = express.Router(); -// Create subdomains +// * Create subdomains LOG_INFO('[conntest] Creating \'assets\' subdomain'); router.use(subdomain('assets', assets)); diff --git a/src/services/cbvc/index.ts b/src/services/cbvc/index.ts index 5116f93..8dd395b 100644 --- a/src/services/cbvc/index.ts +++ b/src/services/cbvc/index.ts @@ -1,13 +1,13 @@ -// handles CBVC (CTR Browser Version Check?) endpoints +// * handles CBVC (CTR Browser Version Check?) endpoints import express from 'express'; import subdomain from 'express-subdomain'; import { LOG_INFO } from '@/logger'; -// Router to handle the subdomain restriction +// * Router to handle the subdomain restriction const cbvc = express.Router(); -// Setup route +// * Setup route LOG_INFO('[cbvc] Applying imported routes'); cbvc.get('/:consoleType/:unknown/:region', (request: express.Request, response: express.Response): void => { response.set('Content-Type', 'text/plain'); @@ -21,10 +21,10 @@ cbvc.get('/:consoleType/:unknown/:region', (request: express.Request, response: response.send('0'); }); -// Main router for endpoints +// * Main router for endpoints const router = express.Router(); -// Create subdomains +// * Create subdomains LOG_INFO('[cbvc] Creating \'cbvc\' subdomain'); router.use(subdomain('cbvc.cdn', cbvc)); diff --git a/src/services/conntest/index.ts b/src/services/conntest/index.ts index 8b5f68a..47c353e 100644 --- a/src/services/conntest/index.ts +++ b/src/services/conntest/index.ts @@ -1,20 +1,20 @@ -// handles conntest endpoints +// * handles conntest endpoints import express from 'express'; import subdomain from 'express-subdomain'; import { LOG_INFO } from '@/logger'; -// Router to handle the subdomain restriction +// * Router to handle the subdomain restriction const conntest = express.Router(); -// Setup route +// * Setup route LOG_INFO('[conntest] Applying imported routes'); conntest.get('/', (request: express.Request, response: express.Response): void => { response.set('Content-Type', 'text/html'); response.set('X-Organization', 'Nintendo'); response.send(` - + HTML Page @@ -26,10 +26,10 @@ This is test.html page `); }); -// Main router for endpoints +// * Main router for endpoints const router = express.Router(); -// Create subdomains +// * Create subdomains LOG_INFO('[conntest] Creating \'conntest\' subdomain'); router.use(subdomain('conntest', conntest)); diff --git a/src/services/datastore/index.ts b/src/services/datastore/index.ts index 35b9ade..c8c3d01 100644 --- a/src/services/datastore/index.ts +++ b/src/services/datastore/index.ts @@ -4,17 +4,17 @@ import { LOG_INFO } from '@/logger'; import upload from '@/services/datastore/routes/upload'; -// Router to handle the subdomain +// * Router to handle the subdomain const datastore = express.Router(); -// Setup routes +// * Setup routes LOG_INFO('[DATASTORE] Applying imported routes'); datastore.use(upload); -// Main router for endpoints +// * Main router for endpoints const router = express.Router(); -// Create subdomains +// * Create subdomains LOG_INFO('[DATASTORE] Creating \'datastore\' subdomain'); router.use(subdomain('datastore', datastore)); diff --git a/src/services/datastore/routes/upload.ts b/src/services/datastore/routes/upload.ts index 09119d6..6e3585e 100644 --- a/src/services/datastore/routes/upload.ts +++ b/src/services/datastore/routes/upload.ts @@ -75,7 +75,7 @@ router.post('/upload', multipartParser, async (request: express.Request, respons const date = request.files.date.toString(); const signature = request.files.signature.toString(); - // Signatures only good for 1 minute + // * Signatures only good for 1 minute const minute = 1000 * 60; const minuteAgo = Date.now() - minute; diff --git a/src/services/grpc/api/register.ts b/src/services/grpc/api/register.ts index f381499..566c5a1 100644 --- a/src/services/grpc/api/register.ts +++ b/src/services/grpc/api/register.ts @@ -21,7 +21,7 @@ const PNID_PUNCTUATION_START_REGEX = /^[_\-.]/; const PNID_PUNCTUATION_END_REGEX = /[_\-.]$/; const PNID_PUNCTUATION_DUPLICATE_REGEX = /[_\-.]{2,}/; -// This sucks +// * This sucks const PASSWORD_WORD_OR_NUMBER_REGEX = /(?=.*[a-zA-Z])(?=.*\d).*/; const PASSWORD_WORD_OR_PUNCTUATION_REGEX = /(?=.*[a-zA-Z])(?=.*[_\-.]).*/; const PASSWORD_NUMBER_OR_PUNCTUATION_REGEX = /(?=.*\d)(?=.*[_\-.]).*/; @@ -146,11 +146,11 @@ export async function register(request: RegisterRequest): Promise { diff --git a/src/services/nnas/routes/people.ts b/src/services/nnas/routes/people.ts index 5d0bf3d..4e7549b 100644 --- a/src/services/nnas/routes/people.ts +++ b/src/services/nnas/routes/people.ts @@ -51,7 +51,7 @@ router.get('/:username', async (request: express.Request, response: express.Resp */ router.post('/', ratelimit, deviceCertificateMiddleware, async (request: express.Request, response: express.Response): Promise => { if (!request.certificate || !request.certificate.valid) { - // TODO: Change this to a different error + // TODO - Change this to a different error response.status(400).send(xmlbuilder.create({ error: { cause: 'Bad Request', @@ -95,11 +95,11 @@ router.post('/', ratelimit, deviceCertificateMiddleware, async (request: express await nexAccount.generatePID(); await nexAccount.generatePassword(); - // Quick hack to get the PIDs to match - // TODO: Change this maybe? - // NN with a NNID will always use the NNID PID - // even if the provided NEX PID is different - // To fix this we make them the same PID + // * Quick hack to get the PIDs to match + // TODO - Change this maybe? + // * NN with a NNID will always use the NNID PID + // * even if the provided NEX PID is different + // * To fix this we make them the same PID nexAccount.owning_pid = nexAccount.pid; await nexAccount.save({ session }); @@ -156,7 +156,7 @@ router.post('/', ratelimit, deviceCertificateMiddleware, async (request: express data: person.mii.data, id: crypto.randomBytes(4).readUInt32LE(), hash: crypto.randomBytes(7).toString('hex'), - image_url: '', // deprecated, will be removed in the future + image_url: '', // * deprecated, will be removed in the future image_id: crypto.randomBytes(4).readUInt32LE() }, flags: { @@ -165,8 +165,8 @@ router.post('/', ratelimit, deviceCertificateMiddleware, async (request: express off_device: person.off_device_flag === 'Y' }, identification: { - email_code: 1, // will be overwritten before saving - email_token: '' // will be overwritten before saving + email_code: 1, // * will be overwritten before saving + email_token: '' // * will be overwritten before saving } }); @@ -265,9 +265,9 @@ router.post('/@me/devices', async (request: express.Request, response: express.R response.set('Server', 'Nintendo 3DS (http)'); response.set('X-Nintendo-Date', new Date().getTime().toString()); - // We don't care about the device attributes - // The console ignores them and PNIDs are not tied to consoles anyway - // So the server also ignores them and does not save the ones posted here + // * We don't care about the device attributes + // * The console ignores them and PNIDs are not tied to consoles anyway + // * So the server also ignores them and does not save the ones posted here // TODO - CHANGE THIS. WE NEED TO SAVE CONSOLE DETAILS !!! @@ -652,8 +652,8 @@ router.get('/@me/emails', async (request: express.Request, response: express.Res parent: pnid.email.parent ? 'Y' : 'N', primary: pnid.email.primary ? 'Y' : 'N', reachable: pnid.email.reachable ? 'Y' : 'N', - type: 'DEFAULT', // what is this? - updated_by: 'USER', // need to actually update this + type: 'DEFAULT', // * what is this? + updated_by: 'USER', // * need to actually update this validated: pnid.email.validated ? 'Y' : 'N', validated_date: pnid.email.validated_date, } diff --git a/src/services/nnas/routes/provider.ts b/src/services/nnas/routes/provider.ts index 794c6cf..1f87e8d 100644 --- a/src/services/nnas/routes/provider.ts +++ b/src/services/nnas/routes/provider.ts @@ -214,7 +214,7 @@ router.get('/nex_token/@me', async (request: express.Request, response: express. const tokenOptions = { system_type: server.device, - token_type: 0x3, // nex token, + token_type: 0x3, // * nex token, pid: pnid.pid, access_level: pnid.access_level, title_id: BigInt(parseInt(titleID, 16)), diff --git a/src/types/mongoose/pnid.ts b/src/types/mongoose/pnid.ts index 37ab267..3252c44 100644 --- a/src/types/mongoose/pnid.ts +++ b/src/types/mongoose/pnid.ts @@ -48,7 +48,7 @@ export interface IPNID { off_device: boolean; }; devices: Types.DocumentArray; - identification: { // user identification tokens + identification: { // * user identification tokens email_code: string; email_token: string; access_token: { diff --git a/src/types/services/nnas/pnid-profile.ts b/src/types/services/nnas/pnid-profile.ts index cb02669..70adea5 100644 --- a/src/types/services/nnas/pnid-profile.ts +++ b/src/types/services/nnas/pnid-profile.ts @@ -1,7 +1,7 @@ import { YesNoBoolString } from '@/types/common/yes-no-bool-string'; export interface PNIDProfile { - //accounts: {}; // * We need to figure this out; no idea what these values mean or what they do + // *accounts: {}; // * We need to figure this out; no idea what these values mean or what they do active_flag: YesNoBoolString; birth_date: string; country: string; diff --git a/src/util.ts b/src/util.ts index f5d8b18..42b0b33 100644 --- a/src/util.ts +++ b/src/util.ts @@ -209,7 +209,7 @@ export async function sendForgotPasswordEmail(pnid: mongoose.HydratedDocument Date: Sun, 14 Apr 2024 20:56:33 -0400 Subject: [PATCH 151/219] nnas: link 3ds device certificate to document from NASC --- src/middleware/console-status-verification.ts | 138 ++++++++++-------- 1 file changed, 78 insertions(+), 60 deletions(-) diff --git a/src/middleware/console-status-verification.ts b/src/middleware/console-status-verification.ts index 90d5cb3..a8c98a4 100644 --- a/src/middleware/console-status-verification.ts +++ b/src/middleware/console-status-verification.ts @@ -68,70 +68,88 @@ async function consoleStatusVerificationMiddleware(request: express.Request, res return; } - // * This is kinda temp for now. Needs to be redone to handle linking this data to existing 3DS devices in the DB - // TODO - 3DS consoles are created in the NASC middleware. They need special handling to link them up with the data in the NNID API! - if (request.certificate.consoleType === 'wiiu') { - const certificateDeviceID = parseInt(request.certificate.certificateName.slice(2), 16); + let device = await Device.findOne({ + serial: serialNumber, + }); - if (deviceID !== certificateDeviceID) { - // TODO - Change this to a different error - response.status(400).send(xmlbuilder.create({ - error: { - cause: 'Bad Request', - code: '1600', - message: 'Unable to process request' - } - }).end()); + if (!device && request.certificate.consoleType === '3ds') { + // * A 3DS console document will ALWAYS be created by NASC before + // * Hitting the NNAS server. NASC stores the serial number at + // * the time the device document was created. Therefore we can + // * know that serial tampering happened on the 3DS if this fails + // * to find a device document. + response.status(400).send(xmlbuilder.create({ + error: { + code: '0002', + message: 'serialNumber format is invalid' + } + }).end()); - return; - } - - // * Only store a hash of the certificate in case of a breach - const certificateHash = crypto.createHash('sha256').update(request.certificate._certificate).digest('base64'); - - let device = await Device.findOne({ - certificate_hash: certificateHash, - }); - - if (!device) { - device = await Device.create({ - model: 'wup', - device_id: deviceID, - serial: serialNumber, - linked_pids: [], - certificate_hash: certificateHash - }); - } - - if (device.serial !== serialNumber) { - // TODO - Change this to a different error - response.status(400).send(xmlbuilder.create({ - error: { - cause: 'Bad Request', - code: '1600', - message: 'Unable to process request' - } - }).end()); - - return; - } - - if (device.access_level < 0) { - response.status(400).send(xmlbuilder.create({ - errors: { - error: { - code: '0012', - message: 'Device has been banned by game server' // TODO - This is not the right error message - } - } - }).end()); - - return; - } - - request.device = device; + return; } + const certificateHash = crypto.createHash('sha256').update(request.certificate._certificate).digest('base64'); + + if (!device) { + // * Device must be a fresh Wii U + device = await Device.create({ + model: 'wup', + device_id: deviceID, + serial: serialNumber, + linked_pids: [], + certificate_hash: certificateHash + }); + } + + if (!device.certificate_hash && request.certificate.consoleType === '3ds') { + device.certificate_hash = certificateHash; + + await device.save(); + } + + if (device.serial !== serialNumber) { + // TODO - Change this to a different error + response.status(400).send(xmlbuilder.create({ + error: { + cause: 'Bad Request', + code: '1600', + message: 'Unable to process request' + } + }).end()); + + return; + } + + const certificateDeviceID = parseInt(request.certificate.certificateName.slice(2).split('-')[0], 16); + + if (deviceID !== certificateDeviceID) { + // TODO - Change this to a different error + response.status(400).send(xmlbuilder.create({ + error: { + cause: 'Bad Request', + code: '1600', + message: 'Unable to process request' + } + }).end()); + + return; + } + + if (device.access_level < 0) { + response.status(400).send(xmlbuilder.create({ + errors: { + error: { + code: '0012', + message: 'Device has been banned by game server' // TODO - This is not the right error message + } + } + }).end()); + + return; + } + + request.device = device; + return next(); } From eb0b4aba00c0b955726f3e1341cc9cad079c39b4 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Sun, 14 Apr 2024 21:17:12 -0400 Subject: [PATCH 152/219] nnas: update console status middleware --- src/middleware/console-status-verification.ts | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/middleware/console-status-verification.ts b/src/middleware/console-status-verification.ts index a8c98a4..a26d9b6 100644 --- a/src/middleware/console-status-verification.ts +++ b/src/middleware/console-status-verification.ts @@ -72,6 +72,8 @@ async function consoleStatusVerificationMiddleware(request: express.Request, res serial: serialNumber, }); + const certificateHash = crypto.createHash('sha256').update(request.certificate._certificate).digest('base64'); + if (!device && request.certificate.consoleType === '3ds') { // * A 3DS console document will ALWAYS be created by NASC before // * Hitting the NNAS server. NASC stores the serial number at @@ -86,9 +88,15 @@ async function consoleStatusVerificationMiddleware(request: express.Request, res }).end()); return; + } else if (device && !device.certificate_hash && request.certificate.consoleType === '3ds') { + device.certificate_hash = certificateHash; + + await device.save(); } - const certificateHash = crypto.createHash('sha256').update(request.certificate._certificate).digest('base64'); + device = await Device.findOne({ + certificate_hash: certificateHash, + }); if (!device) { // * Device must be a fresh Wii U @@ -101,12 +109,6 @@ async function consoleStatusVerificationMiddleware(request: express.Request, res }); } - if (!device.certificate_hash && request.certificate.consoleType === '3ds') { - device.certificate_hash = certificateHash; - - await device.save(); - } - if (device.serial !== serialNumber) { // TODO - Change this to a different error response.status(400).send(xmlbuilder.create({ From ca5ea2e30219e209bfda88ca54eee54540576b51 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Tue, 16 Apr 2024 12:22:12 -0400 Subject: [PATCH 153/219] nasc: remove MAC check. MAC is allowed to be changed --- src/middleware/nasc.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/middleware/nasc.ts b/src/middleware/nasc.ts index 812446a..64fc2bd 100644 --- a/src/middleware/nasc.ts +++ b/src/middleware/nasc.ts @@ -135,11 +135,6 @@ async function NASCMiddleware(request: express.Request, response: express.Respon response.status(200).send(nascError('102').toString()); return; } - - if (device.mac_hash !== macAddressHash) { - response.status(200).send(nascError('102').toString()); - return; - } } // * Workaround for edge case on system transfers From 1a27c36f9c4c6b36d92feb55ab1bb64b588d5e1d Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Fri, 19 Apr 2024 11:36:40 -0400 Subject: [PATCH 154/219] nasc: fix datetime field being set incorrectly --- src/services/nasc/routes/ac.ts | 6 +++--- src/util.ts | 15 ++++++++++++++- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/services/nasc/routes/ac.ts b/src/services/nasc/routes/ac.ts index 3b9aa06..1a8c616 100644 --- a/src/services/nasc/routes/ac.ts +++ b/src/services/nasc/routes/ac.ts @@ -1,5 +1,5 @@ import express from 'express'; -import { nintendoBase64Encode, nintendoBase64Decode, nascError, generateToken } from '@/util'; +import { nintendoBase64Encode, nintendoBase64Decode, nascDateTime, nascError, generateToken } from '@/util'; import { getServerByTitleID } from '@/database'; import { NASCRequestParams } from '@/types/services/nasc/request-params'; import { HydratedServerDocument } from '@/types/mongoose/server'; @@ -83,7 +83,7 @@ async function processLoginRequest(server: HydratedServerDocument, pid: number, retry: nintendoBase64Encode('0'), returncd: nintendoBase64Encode('001'), token: nexToken, - datetime: nintendoBase64Encode(Date.now().toString()), + datetime: nintendoBase64Encode(nascDateTime()), }); } @@ -108,7 +108,7 @@ async function processServiceTokenRequest(server: HydratedServerDocument, pid: n servicetoken: serviceToken, statusdata: nintendoBase64Encode('Y'), svchost: nintendoBase64Encode('n/a'), - datetime: nintendoBase64Encode(Date.now().toString()), + datetime: nintendoBase64Encode(nascDateTime()), }); } diff --git a/src/util.ts b/src/util.ts index 42b0b33..cd0da6c 100644 --- a/src/util.ts +++ b/src/util.ts @@ -167,11 +167,24 @@ export async function writeLocalCDNFile(key: string, data: Buffer): Promise Date: Sun, 21 Apr 2024 18:28:18 -0500 Subject: [PATCH 155/219] Added support for user information app on Wii U (from #77) --- package-lock.json | 65 +- package.json | 4 +- src/assets/user-info-settings/index.css | 298 + src/config-manager.ts | 8 +- src/server.ts | 2 + src/services/nnas/index.ts | 27 +- src/services/nnas/regions.json | 42182 +++++++++++++++++ src/services/nnas/routes/account-settings.ts | 212 + src/types/common/config.ts | 1 + src/types/common/gender-types.ts | 1 + src/types/services/nnas/account-settings.ts | 11 + src/types/services/nnas/region-languages.ts | 5 + src/types/services/nnas/region-timezones.ts | 9 + src/types/services/nnas/regions.ts | 36 + src/util.ts | 8 +- src/views/index.ejs | 146 + 16 files changed, 43004 insertions(+), 11 deletions(-) create mode 100644 src/assets/user-info-settings/index.css create mode 100644 src/services/nnas/regions.json create mode 100644 src/services/nnas/routes/account-settings.ts create mode 100644 src/types/common/gender-types.ts create mode 100644 src/types/services/nnas/account-settings.ts create mode 100644 src/types/services/nnas/region-languages.ts create mode 100644 src/types/services/nnas/region-timezones.ts create mode 100644 src/types/services/nnas/regions.ts create mode 100644 src/views/index.ejs diff --git a/package-lock.json b/package-lock.json index 2bf1fe8..d0f634e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,7 @@ "crc": "^4.3.2", "dicer": "^0.2.5", "dotenv": "^16.0.3", + "ejs": "^3.1.10", "email-validator": "^2.0.4", "express": "^4.17.1", "express-rate-limit": "^6.7.0", @@ -2651,8 +2652,7 @@ "node_modules/async": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/async/-/async-3.2.3.tgz", - "integrity": "sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g==", - "dev": true + "integrity": "sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g==" }, "node_modules/asynckit": { "version": "0.4.0", @@ -3018,7 +3018,6 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -3493,6 +3492,20 @@ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, + "node_modules/ejs": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", + "dependencies": { + "jake": "^10.8.5" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/email-validator": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/email-validator/-/email-validator-2.0.4.tgz", @@ -4112,6 +4125,33 @@ "node": ">=6" } }, + "node_modules/filelist": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", + "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", + "dependencies": { + "minimatch": "^5.0.1" + } + }, + "node_modules/filelist/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/filelist/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -4516,7 +4556,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, "engines": { "node": ">=8" } @@ -4946,6 +4985,23 @@ "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==" }, + "node_modules/jake": { + "version": "10.8.7", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.7.tgz", + "integrity": "sha512-ZDi3aP+fG/LchyBzUM804VjddnwfSfsdeYkwt8NcbKRvo4rFkjhs456iLFn3k2ZUWvNe4i48WACDbza8fhq2+w==", + "dependencies": { + "async": "^3.2.3", + "chalk": "^4.0.2", + "filelist": "^1.0.4", + "minimatch": "^3.1.2" + }, + "bin": { + "jake": "bin/cli.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/jmespath": { "version": "0.16.0", "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.16.0.tgz", @@ -6663,7 +6719,6 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, "dependencies": { "has-flag": "^4.0.0" }, diff --git a/package.json b/package.json index b945ec9..9e496e7 100644 --- a/package.json +++ b/package.json @@ -7,8 +7,9 @@ "lint": "npx eslint .", "build": "npm run lint && npm run clean && npx tsc && npx tsc-alias && npm run copy-static", "clean": "rimraf ./dist", - "copy-static": "npm run copy-assets && npm run copy-timezones", + "copy-static": "npm run copy-assets && npm run copy-timezones && npm run copy-views", "copy-assets": "cp -r ./src/assets ./dist/assets", + "copy-views": "cp -r ./src/views ./dist/views", "copy-timezones": "cp ./src/services/nnas/timezones.json ./dist/services/nnas/timezones.json", "start": "node .", "start:dev": "NODE_ENV=development node ." @@ -35,6 +36,7 @@ "crc": "^4.3.2", "dicer": "^0.2.5", "dotenv": "^16.0.3", + "ejs": "^3.1.10", "email-validator": "^2.0.4", "express": "^4.17.1", "express-rate-limit": "^6.7.0", diff --git a/src/assets/user-info-settings/index.css b/src/assets/user-info-settings/index.css new file mode 100644 index 0000000..7eb8f3b --- /dev/null +++ b/src/assets/user-info-settings/index.css @@ -0,0 +1,298 @@ +html { + background: #FF0000; +} + +body { + font-family: Poppins, Arial, Helvetica, sans-serif; + font-size: 15px; + width: 1280px; + margin: 0; + overflow: auto; + box-sizing: border-box; + background: #EAEAEA; +} + +button:active, +.server-selection input:checked + label { + box-shadow: inset 0 0px 10px 0 rgba(66, 45, 120, 0.75) !important; + color: #9D6FF3; +} + +header { + position: relative; + background: #37A985; + color: #FFF; + height: 70px; + line-height: 70px; + border-bottom: 2px solid #4F2E8C; + background: -webkit-gradient(linear, left top, left bottom, from(#9D6FF3), to(#673DB6)); + z-index: 19; +} + +header h1 { + margin: 0; + font-size: 30px; + text-align: center; + font-weight: normal; +} + +button, +.button, +input[type="submit"] { + font-family: Poppins, Arial, Helvetica, sans-serif; + color: #45297A; + min-width: 200px; + box-sizing: content-box; + -webkit-box-sizing: content-box; + -webkit-box-align: center; + -webkit-box-pack: center; + min-height: 60px; + text-align: center; + font-size: 28px; + background: -webkit-gradient(linear, left top, left bottom, from(#FFF), color-stop(0.5, #FFF), color-stop(0.8, #F6F6F6), color-stop(0.95, #F5F5F5), to(#BBB))0 0; + border-radius: 12px; + cursor: pointer; + border: 0; + box-shadow: 0 1px 5px rgba(0, 0, 0, 0.4); + margin: 5px; +} + +.body-content { + margin: 30px 60px 0 200px; +} + +div.group h2 { + font-weight: normal; +} + +div.group { + width: 485px; + display: inline-block; + margin: 0 5px; +} + +div.body-content > h1 { + margin: 15px 0 0 0; +} + +div.group > * { + margin: 5px 0; + font-size: 30px; +} + +div.group > p.content, +div.group > input[type="text"], +div.group > select { + background: #FFF; + color: #6A6C75; + padding: 10px; + border-radius: 8px; + font-size: 25px; + box-shadow: none; + border: none; + width: 100%; + height: 58px; + box-sizing: border-box; + appearance: none; + -webkit-appearance: none; + -moz-appearance: none; + text-indent: 1px; + text-overflow: ''; + line-height: 38px; + user-select: none; +} + +div.group > input[type="text"] { + color: #000; +} + +div.group > select { + content: ' '; + background-color: #FFF; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='32' height='32' fill='%23673DB6' viewBox='0 0 256 256'%3E%3Cpath d='M213.66,101.66l-80,80a8,8,0,0,1-11.32,0l-80-80A8,8,0,0,1,53.66,90.34L128,164.69l74.34-74.35a8,8,0,0,1,11.32,11.32Z'%3E%3C/path%3E%3C/svg%3E"); + background-size: 40px; + background-position: 440px center; + background-repeat: no-repeat; + color: #9D6FF3; + cursor: pointer; +} + + +div.group > span { + color: #000; + } + div.group > svg { + fill: #9D6FF3; + border: none; + height: 40px; + width: 40px; + box-sizing: border-box; + user-select: none; + padding: 0px; + margin-bottom: -10px; + margin-left: 5px; + } + +.account-info { + float: left; + width: 200px; + box-sizing: content-box; + -webkit-box-sizing: content-box; + -webkit-box-align: center; + -webkit-box-pack: center; +} + +.account-info > img { + display: inline-block; + width: 128px; + height: 128px; + overflow: hidden; + border-radius: 100%; + background: #E4DBF2; + margin: 30px 30px 0 35px; + box-shadow: 0-1px 2px rgba(0, 0, 0, 0.4); +} + +.account-info > .content { + text-align: center; + color: #673DB6; + margin: 0; +} + +.account-info > h3.content { + font-weight: normal; + color: #9D6FF3; + font-size: 20px; +} + +.account-info .access-level-banned { + background: rgba(255, 63, 0, 0.3); + color: #FF3F00; + border-color: #FF3F00; +} + +.account-info .tier-level-1 { + background: #934D4D; + color: #FF8484; + border-color: #FF8484; +} + +.account-info .tier-level-2 { + background: #316C59; + color: #59C9A5; + border-color: #59C9A5; +} + +.account-info .tier-level-3 { + background: #6B5E84; + color: #CAB1FB; + border-color: #CAB1FB; +} + +.account-info .access-level-1 { + background: #3B918C; + color: #64F7EF; + border-color: #64F7EF; +} + +.account-info .access-level-2 { + background: #917235; + color: #FFC759; + border-color: #FFC759; +} + +.account-info .access-level-3 { + background: #3A973C; + color: #5AFF15; + border-color: #5AFF15; +} + +.account-info .tier-name { + margin: 12px auto; + line-height: 1.2em; + border-radius: 1.2em; + border-width: 2px; + border-style: solid; + padding: 4px 16px; + width: min-content; + display: inline-block; +} + +div.group > p.content > button { + min-width: 100px; + min-height: 20px; + display: block; + width: 100px; + float: right; +} + +div.radio { + display: inline-block; +} + +.server-selection input { + opacity: 0; + position: absolute; + width: 200px; + height: 70px; + cursor: pointer; +} + +div.radio label { + display: inline-block; + color: #6A6C75; + min-width: 200px; + box-sizing: content-box; + -webkit-box-sizing: content-box; + -webkit-box-align: center; + -webkit-box-pack: center; + text-align: center; + background: -webkit-gradient(linear, left top, left bottom, from(#FFF), color-stop(0.5, #FFF), color-stop(0.8, #F6F6F6), color-stop(0.95, #F5F5F5), to(#BBB))0 0; + border-radius: 12px; + cursor: pointer; + border: 0; + box-shadow: 0 1px 5px rgba(0, 0, 0, 0.4); + margin: 5px; + font-weight: normal; + font-size: 15px; +} + +div.radio label h2 { + display: inline-block; + font-weight: normal; +} + +div.radio label svg { + margin: -6px 0; +} + +header .fixed-bottom-button.left { + padding: 0 60px 0 40px !important; +} + +.fixed-bottom-button.left { + right: auto; + left: 0; + border-radius: 0 40px 0 0; +} + +header .fixed-bottom-button { + min-width: 120px; + padding: 0 40px 0 60px; +} + +.fixed-bottom-button, +input[type="submit"] { + position: fixed; + bottom: 0; + right: 0; + height: 85px; + line-height: 100px; + padding: 0 40px 0 60px; + background: -webkit-gradient(linear, left top, left bottom, from(#FFF), color-stop(0.5, #FFF), to(#E6E6E6))0 0; + font-size: 28px; + z-index: 20; + border-radius: 40px 0 0 0; + margin: 0; + border: none; +} \ No newline at end of file diff --git a/src/config-manager.ts b/src/config-manager.ts index de58294..9f5289b 100644 --- a/src/config-manager.ts +++ b/src/config-manager.ts @@ -70,7 +70,8 @@ export const config: Config = { api: process.env.PN_ACT_CONFIG_GRPC_MASTER_API_KEY_API || '', }, port: Number(process.env.PN_ACT_CONFIG_GRPC_PORT || ''), - } + }, + server_environment: process.env.PN_ACT_CONFIG_SERVER_ENVIRONMENT || '' }; if (process.env.PN_ACT_CONFIG_STRIPE_SECRET_KEY) { @@ -148,6 +149,11 @@ if (!config.s3.secret) { disabledFeatures.s3 = true; } +if (!config.server_environment) { + LOG_WARN('Failed to find server environment. To change the environment, set the PN_ACT_CONFIG_SERVER_ENVIRONMENT environment variable'); + config.server_environment = 'prod'; +} + if (disabledFeatures.s3) { if (!config.cdn.subdomain) { LOG_ERROR('s3 file storage is disabled and no CDN subdomain was set. Set the PN_ACT_CONFIG_CDN_SUBDOMAIN environment variable'); diff --git a/src/server.ts b/src/server.ts index 6919a55..797c60e 100644 --- a/src/server.ts +++ b/src/server.ts @@ -28,6 +28,8 @@ import { config } from '@/config-manager'; const app = express(); // * START APPLICATION +app.set('view engine', 'ejs'); +app.set('views', __dirname + '/views'); // * Create router LOG_INFO('Setting up Middleware'); diff --git a/src/services/nnas/index.ts b/src/services/nnas/index.ts index 1d0f60c..7f88efa 100644 --- a/src/services/nnas/index.ts +++ b/src/services/nnas/index.ts @@ -1,5 +1,6 @@ // * handles "account.nintendo.net" endpoints +import path from 'node:path'; import express from 'express'; import subdomain from 'express-subdomain'; import clientHeaderCheck from '@/middleware/client-header'; @@ -15,17 +16,39 @@ import oauth from '@/services/nnas/routes/oauth'; import people from '@/services/nnas/routes/people'; import provider from '@/services/nnas/routes/provider'; import support from '@/services/nnas/routes/support'; +import settings from '@/services/nnas/routes/account-settings'; // * Router to handle the subdomain restriction const nnas = express.Router(); +// Static routes for the user information app +async function setCSSHeader(request: express.Request, response: express.Response, next: express.NextFunction): Promise { + response.set('Content-Type', 'text/css'); + return next(); +} + +async function setJSHeader(request: express.Request, response: express.Response, next: express.NextFunction): Promise { + response.set('Content-Type', 'text/javascript'); + return next(); +} + +async function setIMGHeader(request: express.Request, response: express.Response, next: express.NextFunction): Promise { + response.set('Content-Type', 'image/png'); + return next(); +} + +// * Setup routes +LOG_INFO('[NNAS] Applying imported routes'); +nnas.use('/v1/account-settings/', settings); +nnas.use('/v1/account-settings/css/', setCSSHeader, express.static(path.join(__dirname, '../../assets/user-info-settings'))); +nnas.use('/v1/account-settings/js/', setJSHeader, express.static(path.join(__dirname, '../../assets/user-info-settings'))); +nnas.use('/v1/account-settings/img/', setIMGHeader, express.static(path.join(__dirname, '../../assets/user-info-settings'))); + LOG_INFO('[NNAS] Importing middleware'); nnas.use(clientHeaderCheck); nnas.use(cemuMiddleware); nnas.use(pnidMiddleware); -// * Setup routes -LOG_INFO('[NNAS] Applying imported routes'); nnas.use('/v1/api/admin', admin); nnas.use('/v1/api/content', content); nnas.use('/v1/api/devices', devices); diff --git a/src/services/nnas/regions.json b/src/services/nnas/regions.json new file mode 100644 index 0000000..69ad94c --- /dev/null +++ b/src/services/nnas/regions.json @@ -0,0 +1,42182 @@ +[ + { + "id": 1, + "iso_code": "JP", + "name": "Japan", + "translations": { + "japanese": "日本", + "english": "Japan", + "french": "Japon", + "german": "Japan", + "italian": "Giappone", + "spanish": "Japón", + "chinese_simple": "日本", + "korean": "일본", + "dutch": "Japan", + "portuguese": "Japão", + "russian": "Япония", + "chinese_traditional": "日本", + "unknown1": "Japan", + "unknown2": "Japan", + "unknown3": "Japan", + "unknown4": "Japan" + }, + "regions": [ + { + "id": 16777216, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": 35.683593344, + "longitude": 139.762953297 + } + }, + { + "id": 16908288, + "name": "Tokyo", + "translations": { + "japanese": "東京都", + "english": "Tokyo", + "french": "Tokyo", + "german": "Tokio", + "italian": "Tokyo", + "spanish": "Tokio", + "chinese_simple": "东京都", + "korean": "도쿄 도", + "dutch": "Tokio", + "portuguese": "Tóquio", + "russian": "Токио", + "chinese_traditional": "東京都", + "unknown1": "Tokyo", + "unknown2": "Tokyo", + "unknown3": "Tokyo", + "unknown4": "Tokyo" + }, + "coordinates": { + "latitude": 35.683593344, + "longitude": 139.762953297 + } + }, + { + "id": 16973824, + "name": "Hokkaido", + "translations": { + "japanese": "北海道", + "english": "Hokkaido", + "french": "Hokkaido", + "german": "Hokkaido", + "italian": "Hokkaido", + "spanish": "Hokaido", + "chinese_simple": "北海道", + "korean": "홋카이도", + "dutch": "Hokkaido", + "portuguese": "Hokkaido", + "russian": "Хоккайдо", + "chinese_traditional": "北海道", + "unknown1": "Hokkaido", + "unknown2": "Hokkaido", + "unknown3": "Hokkaido", + "unknown4": "Hokkaido" + }, + "coordinates": { + "latitude": 43.055419432, + "longitude": 141.328509312 + } + }, + { + "id": 17039360, + "name": "Aomori", + "translations": { + "japanese": "青森県", + "english": "Aomori", + "french": "Aomori", + "german": "Aomori", + "italian": "Aomori", + "spanish": "Aomori", + "chinese_simple": "青森县", + "korean": "아오모리 현", + "dutch": "Aomori", + "portuguese": "Aomori", + "russian": "Аомори", + "chinese_traditional": "青森縣", + "unknown1": "Aomori", + "unknown2": "Aomori", + "unknown3": "Aomori", + "unknown4": "Aomori" + }, + "coordinates": { + "latitude": 40.81420852, + "longitude": 140.768205054 + } + }, + { + "id": 17104896, + "name": "Iwate", + "translations": { + "japanese": "岩手県", + "english": "Iwate", + "french": "Iwate", + "german": "Iwate", + "italian": "Iwate", + "spanish": "Iwate", + "chinese_simple": "岩手县", + "korean": "이와테 현", + "dutch": "Iwate", + "portuguese": "Iwate", + "russian": "Иватэ", + "chinese_traditional": "岩手縣", + "unknown1": "Iwate", + "unknown2": "Iwate", + "unknown3": "Iwate", + "unknown4": "Iwate" + }, + "coordinates": { + "latitude": 39.693603064, + "longitude": 141.163713942 + } + }, + { + "id": 17170432, + "name": "Miyagi", + "translations": { + "japanese": "宮城県", + "english": "Miyagi", + "french": "Miyagi", + "german": "Miyagi", + "italian": "Miyagi", + "spanish": "Miyagi", + "chinese_simple": "宫城县", + "korean": "미야기 현", + "dutch": "Miyagi", + "portuguese": "Miyagi", + "russian": "Мияги", + "chinese_traditional": "宮城縣", + "unknown1": "Miyagi", + "unknown2": "Miyagi", + "unknown3": "Miyagi", + "unknown4": "Miyagi" + }, + "coordinates": { + "latitude": 38.254394096, + "longitude": 140.90004135 + } + }, + { + "id": 17235968, + "name": "Akita", + "translations": { + "japanese": "秋田県", + "english": "Akita", + "french": "Akita", + "german": "Akita", + "italian": "Akita", + "spanish": "Akita", + "chinese_simple": "秋田县", + "korean": "아키타 현", + "dutch": "Akita", + "portuguese": "Akita", + "russian": "Акита", + "chinese_traditional": "秋田縣", + "unknown1": "Akita", + "unknown2": "Akita", + "unknown3": "Akita", + "unknown4": "Akita" + }, + "coordinates": { + "latitude": 39.710082556, + "longitude": 140.098037216 + } + }, + { + "id": 17301504, + "name": "Yamagata", + "translations": { + "japanese": "山形県", + "english": "Yamagata", + "french": "Yamagata", + "german": "Yamagata", + "italian": "Yamagata", + "spanish": "Yamagata", + "chinese_simple": "山形县", + "korean": "야마가타 현", + "dutch": "Yamagata", + "portuguese": "Yamagata", + "russian": "Ямагата", + "chinese_traditional": "山形縣", + "unknown1": "Yamagata", + "unknown2": "Yamagata", + "unknown3": "Yamagata", + "unknown4": "Yamagata" + }, + "coordinates": { + "latitude": 38.248900932, + "longitude": 140.345230271 + } + }, + { + "id": 17367040, + "name": "Fukushima", + "translations": { + "japanese": "福島県", + "english": "Fukushima", + "french": "Fukushima", + "german": "Fukushima", + "italian": "Fukushima", + "spanish": "Fukushima", + "chinese_simple": "福岛县", + "korean": "후쿠시마 현", + "dutch": "Fukushima", + "portuguese": "Fukushima", + "russian": "Фукусима", + "chinese_traditional": "福島縣", + "unknown1": "Fukushima", + "unknown2": "Fukushima", + "unknown3": "Fukushima", + "unknown4": "Fukushima" + }, + "coordinates": { + "latitude": 37.754516172, + "longitude": 140.471573388 + } + }, + { + "id": 17432576, + "name": "Ibaraki", + "translations": { + "japanese": "茨城県", + "english": "Ibaraki", + "french": "Ibaraki", + "german": "Ibaraki", + "italian": "Ibaraki", + "spanish": "Ibaraki", + "chinese_simple": "茨城县", + "korean": "이바라키 현", + "dutch": "Ibaraki", + "portuguese": "Ibaraki", + "russian": "Ибараки", + "chinese_traditional": "茨城縣", + "unknown1": "Ibaraki", + "unknown2": "Ibaraki", + "unknown3": "Ibaraki", + "unknown4": "Ibaraki" + }, + "coordinates": { + "latitude": 36.375732008, + "longitude": 140.471573388 + } + }, + { + "id": 17498112, + "name": "Tochigi", + "translations": { + "japanese": "栃木県", + "english": "Tochigi", + "french": "Tochigi", + "german": "Tochigi", + "italian": "Tochigi", + "spanish": "Tochigi", + "chinese_simple": "枥木县", + "korean": "도치기 현", + "dutch": "Tochigi", + "portuguese": "Tochigi", + "russian": "Тотиги", + "chinese_traditional": "枋木縣", + "unknown1": "Tochigi", + "unknown2": "Tochigi", + "unknown3": "Tochigi", + "unknown4": "Tochigi" + }, + "coordinates": { + "latitude": 36.546020092, + "longitude": 139.867323698 + } + }, + { + "id": 17563648, + "name": "Gunma", + "translations": { + "japanese": "群馬県", + "english": "Gunma", + "french": "Gunma", + "german": "Gunma", + "italian": "Gunma", + "spanish": "Gunma", + "chinese_simple": "群马县", + "korean": "군마 현", + "dutch": "Gunma", + "portuguese": "Gunma", + "russian": "Гумма", + "chinese_traditional": "群馬縣", + "unknown1": "Gunma", + "unknown2": "Gunma", + "unknown3": "Gunma", + "unknown4": "Gunma" + }, + "coordinates": { + "latitude": 36.397704664, + "longitude": 139.065319564 + } + }, + { + "id": 17629184, + "name": "Saitama", + "translations": { + "japanese": "埼玉県", + "english": "Saitama", + "french": "Saitama", + "german": "Saitama", + "italian": "Saitama", + "spanish": "Saitama", + "chinese_simple": "琦玉县", + "korean": "사이타마 현", + "dutch": "Saitama", + "portuguese": "Saitama", + "russian": "Сайтама", + "chinese_traditional": "埼玉縣", + "unknown1": "Saitama", + "unknown2": "Saitama", + "unknown3": "Saitama", + "unknown4": "Saitama" + }, + "coordinates": { + "latitude": 35.87036092, + "longitude": 139.587171569 + } + }, + { + "id": 17694720, + "name": "Chiba", + "translations": { + "japanese": "千葉県", + "english": "Chiba", + "french": "Chiba", + "german": "Chiba", + "italian": "Chiba", + "spanish": "Chiba", + "chinese_simple": "千叶县", + "korean": "지바 현", + "dutch": "Chiba", + "portuguese": "Chiba", + "russian": "Тиба", + "chinese_traditional": "千葉縣", + "unknown1": "Chiba", + "unknown2": "Chiba", + "unknown3": "Chiba", + "unknown4": "Chiba" + }, + "coordinates": { + "latitude": 35.59570272, + "longitude": 140.103530395 + } + }, + { + "id": 17760256, + "name": "Kanagawa", + "translations": { + "japanese": "神奈川県", + "english": "Kanagawa", + "french": "Kanagawa", + "german": "Kanagawa", + "italian": "Kanagawa", + "spanish": "Kanagawa", + "chinese_simple": "神奈川县", + "korean": "가나가와 현", + "dutch": "Kanagawa", + "portuguese": "Kanagawa", + "russian": "Канагава", + "chinese_traditional": "神奈川縣", + "unknown1": "Kanagawa", + "unknown2": "Kanagawa", + "unknown3": "Kanagawa", + "unknown4": "Kanagawa" + }, + "coordinates": { + "latitude": 35.436400964, + "longitude": 139.653089717 + } + }, + { + "id": 17825792, + "name": "Toyama", + "translations": { + "japanese": "富山県", + "english": "Toyama", + "french": "Toyama", + "german": "Toyama", + "italian": "Toyama", + "spanish": "Toyama", + "chinese_simple": "富山县", + "korean": "도야마 현", + "dutch": "Toyama", + "portuguese": "Toyama", + "russian": "Тояма", + "chinese_traditional": "富山縣", + "unknown1": "Toyama", + "unknown2": "Toyama", + "unknown3": "Toyama", + "unknown4": "Toyama" + }, + "coordinates": { + "latitude": 36.705321848, + "longitude": 137.203131883 + } + }, + { + "id": 17891328, + "name": "Ishikawa", + "translations": { + "japanese": "石川県", + "english": "Ishikawa", + "french": "Ishikawa", + "german": "Ishikawa", + "italian": "Ishikawa", + "spanish": "Ishikawa", + "chinese_simple": "石川县", + "korean": "이시카와 현", + "dutch": "Ishikawa", + "portuguese": "Ishikawa", + "russian": "Исикава", + "chinese_traditional": "石川縣", + "unknown1": "Ishikawa", + "unknown2": "Ishikawa", + "unknown3": "Ishikawa", + "unknown4": "Ishikawa" + }, + "coordinates": { + "latitude": 36.584472240000004, + "longitude": 136.637334446 + } + }, + { + "id": 17956864, + "name": "Fukui", + "translations": { + "japanese": "福井県", + "english": "Fukui", + "french": "Fukui", + "german": "Fukui", + "italian": "Fukui", + "spanish": "Fukui", + "chinese_simple": "福井县", + "korean": "후쿠이 현", + "dutch": "Fukui", + "portuguese": "Fukui", + "russian": "Фукуи", + "chinese_traditional": "福井縣", + "unknown1": "Fukui", + "unknown2": "Fukui", + "unknown3": "Fukui", + "unknown4": "Fukui" + }, + "coordinates": { + "latitude": 36.051635332000004, + "longitude": 136.225346021 + } + }, + { + "id": 18022400, + "name": "Yamanashi", + "translations": { + "japanese": "山梨県", + "english": "Yamanashi", + "french": "Yamanashi", + "german": "Yamanashi", + "italian": "Yamanashi", + "spanish": "Yamanashi", + "chinese_simple": "山梨县", + "korean": "야마나시 현", + "dutch": "Yamanashi", + "portuguese": "Yamanashi", + "russian": "Яманаси", + "chinese_traditional": "山梨縣", + "unknown1": "Yamanashi", + "unknown2": "Yamanashi", + "unknown3": "Yamanashi", + "unknown4": "Yamanashi" + }, + "coordinates": { + "latitude": 35.661620688, + "longitude": 138.554453917 + } + }, + { + "id": 18087936, + "name": "Nagano", + "translations": { + "japanese": "長野県", + "english": "Nagano", + "french": "Nagano", + "german": "Nagano", + "italian": "Nagano", + "spanish": "Nagano", + "chinese_simple": "长野县", + "korean": "나가노 현", + "dutch": "Nagano", + "portuguese": "Nagano", + "russian": "Нагано", + "chinese_traditional": "長野縣", + "unknown1": "Nagano", + "unknown2": "Nagano", + "unknown3": "Nagano", + "unknown4": "Nagano" + }, + "coordinates": { + "latitude": 36.655883372, + "longitude": 138.19190410299998 + } + }, + { + "id": 18153472, + "name": "Niigata", + "translations": { + "japanese": "新潟県", + "english": "Niigata", + "french": "Niigata", + "german": "Niigata", + "italian": "Niigata", + "spanish": "Niigata", + "chinese_simple": "新泻县", + "korean": "니가타 현", + "dutch": "Niigata", + "portuguese": "Niigata", + "russian": "Ниигата", + "chinese_traditional": "新潟縣", + "unknown1": "Niigata", + "unknown2": "Niigata", + "unknown3": "Niigata", + "unknown4": "Niigata" + }, + "coordinates": { + "latitude": 37.908324764, + "longitude": 139.048840027 + } + }, + { + "id": 18219008, + "name": "Gifu", + "translations": { + "japanese": "岐阜県", + "english": "Gifu", + "french": "Gifu", + "german": "Gifu", + "italian": "Gifu", + "spanish": "Gifu", + "chinese_simple": "歧阜县", + "korean": "기후 현", + "dutch": "Gifu", + "portuguese": "Gifu", + "russian": "Гифу", + "chinese_traditional": "岐阜縣", + "unknown1": "Gifu", + "unknown2": "Gifu", + "unknown3": "Gifu", + "unknown4": "Gifu" + }, + "coordinates": { + "latitude": 35.392455652, + "longitude": 136.763677563 + } + }, + { + "id": 18284544, + "name": "Shizuoka", + "translations": { + "japanese": "静岡県", + "english": "Shizuoka", + "french": "Shizuoka", + "german": "Shizuoka", + "italian": "Shizuoka", + "spanish": "Shizuoka", + "chinese_simple": "静冈县", + "korean": "시즈오카 현", + "dutch": "Shizuoka", + "portuguese": "Shizuoka", + "russian": "Сидзуока", + "chinese_traditional": "靜岡縣", + "unknown1": "Shizuoka", + "unknown2": "Shizuoka", + "unknown3": "Shizuoka", + "unknown4": "Shizuoka" + }, + "coordinates": { + "latitude": 34.969482024, + "longitude": 138.406138084 + } + }, + { + "id": 18350080, + "name": "Aichi", + "translations": { + "japanese": "愛知県", + "english": "Aichi", + "french": "Aichi", + "german": "Aichi", + "italian": "Aichi", + "spanish": "Aichi", + "chinese_simple": "爱知县", + "korean": "아이치 현", + "dutch": "Aichi", + "portuguese": "Aichi", + "russian": "Айти", + "chinese_traditional": "愛知縣", + "unknown1": "Aichi", + "unknown2": "Aichi", + "unknown3": "Aichi", + "unknown4": "Aichi" + }, + "coordinates": { + "latitude": 35.161742764, + "longitude": 136.966925186 + } + }, + { + "id": 18415616, + "name": "Mie", + "translations": { + "japanese": "三重県", + "english": "Mie", + "french": "Mie", + "german": "Mie", + "italian": "Mie", + "spanish": "Mie", + "chinese_simple": "三重县", + "korean": "미에 현", + "dutch": "Mie", + "portuguese": "Mie", + "russian": "Миэ", + "chinese_traditional": "三重縣", + "unknown1": "Mie", + "unknown2": "Mie", + "unknown3": "Mie", + "unknown4": "Mie" + }, + "coordinates": { + "latitude": 34.727782808, + "longitude": 136.521977687 + } + }, + { + "id": 18481152, + "name": "Shiga", + "translations": { + "japanese": "滋賀県", + "english": "Shiga", + "french": "Shiga", + "german": "Shiga", + "italian": "Shiga", + "spanish": "Shiga", + "chinese_simple": "滋贺县", + "korean": "시가 현", + "dutch": "Shiga", + "portuguese": "Shiga", + "russian": "Сига", + "chinese_traditional": "滋賀縣", + "unknown1": "Shiga", + "unknown2": "Shiga", + "unknown3": "Shiga", + "unknown4": "Shiga" + }, + "coordinates": { + "latitude": 34.985961516, + "longitude": 135.912234818 + } + }, + { + "id": 18546688, + "name": "Kyoto", + "translations": { + "japanese": "京都府", + "english": "Kyoto", + "french": "Kyoto", + "german": "Kyoto", + "italian": "Kyoto", + "spanish": "Kioto", + "chinese_simple": "京都府", + "korean": "교토 부", + "dutch": "Kyoto", + "portuguese": "Quioto", + "russian": "Киото", + "chinese_traditional": "京都府", + "unknown1": "Kyoto", + "unknown2": "Kyoto", + "unknown3": "Kyoto", + "unknown4": "Kyoto" + }, + "coordinates": { + "latitude": 35.007934172, + "longitude": 135.73095991099999 + } + }, + { + "id": 18612224, + "name": "Osaka", + "translations": { + "japanese": "大阪府", + "english": "Osaka", + "french": "Osaka", + "german": "Osaka", + "italian": "Osaka", + "spanish": "Osaka", + "chinese_simple": "大阪府", + "korean": "오사카 부", + "dutch": "Osaka", + "portuguese": "Osaca", + "russian": "Осака", + "chinese_traditional": "大阪府", + "unknown1": "Osaka", + "unknown2": "Osaka", + "unknown3": "Osaka", + "unknown4": "Osaka" + }, + "coordinates": { + "latitude": 34.672851168, + "longitude": 135.522219109 + } + }, + { + "id": 18677760, + "name": "Hyogo", + "translations": { + "japanese": "兵庫県", + "english": "Hyogo", + "french": "Hyogo", + "german": "Hyogo", + "italian": "Hyogo", + "spanish": "Hiogo", + "chinese_simple": "兵库县", + "korean": "효고 현", + "dutch": "Hyogo", + "portuguese": "Hyogo", + "russian": "Хёго", + "chinese_traditional": "兵庫縣", + "unknown1": "Hyogo", + "unknown2": "Hyogo", + "unknown3": "Hyogo", + "unknown4": "Hyogo" + }, + "coordinates": { + "latitude": 34.68933066, + "longitude": 135.214601085 + } + }, + { + "id": 18743296, + "name": "Nara", + "translations": { + "japanese": "奈良県", + "english": "Nara", + "french": "Nara", + "german": "Nara", + "italian": "Nara", + "spanish": "Nara", + "chinese_simple": "奈良县", + "korean": "나라 현", + "dutch": "Nara", + "portuguese": "Nara", + "russian": "Нара", + "chinese_traditional": "奈良縣", + "unknown1": "Nara", + "unknown2": "Nara", + "unknown3": "Nara", + "unknown4": "Nara" + }, + "coordinates": { + "latitude": 34.68933066, + "longitude": 135.829837133 + } + }, + { + "id": 18808832, + "name": "Wakayama", + "translations": { + "japanese": "和歌山県", + "english": "Wakayama", + "french": "Wakayama", + "german": "Wakayama", + "italian": "Wakayama", + "spanish": "Wakayama", + "chinese_simple": "和歌山县", + "korean": "와카야마 현", + "dutch": "Wakayama", + "portuguese": "Wakayama", + "russian": "Вакаяма", + "chinese_traditional": "和歌山縣", + "unknown1": "Wakayama", + "unknown2": "Wakayama", + "unknown3": "Wakayama", + "unknown4": "Wakayama" + }, + "coordinates": { + "latitude": 34.222411720000004, + "longitude": 135.165162474 + } + }, + { + "id": 18874368, + "name": "Tottori", + "translations": { + "japanese": "鳥取県", + "english": "Tottori", + "french": "Tottori", + "german": "Tottori", + "italian": "Tottori", + "spanish": "Totori", + "chinese_simple": "鸟取县", + "korean": "돗토리 현", + "dutch": "Tottori", + "portuguese": "Tottori", + "russian": "Тоттори", + "chinese_traditional": "鳥取縣", + "unknown1": "Tottori", + "unknown2": "Tottori", + "unknown3": "Tottori", + "unknown4": "Tottori" + }, + "coordinates": { + "latitude": 35.480346276, + "longitude": 134.236815223 + } + }, + { + "id": 18939904, + "name": "Shimane", + "translations": { + "japanese": "島根県", + "english": "Shimane", + "french": "Shimane", + "german": "Shimane", + "italian": "Shimane", + "spanish": "Shimane", + "chinese_simple": "岛根县", + "korean": "시마네 현", + "dutch": "Shimane", + "portuguese": "Shimane", + "russian": "Симанэ", + "chinese_traditional": "島根縣", + "unknown1": "Shimane", + "unknown2": "Shimane", + "unknown3": "Shimane", + "unknown4": "Shimane" + }, + "coordinates": { + "latitude": 35.452880456, + "longitude": 133.066768096 + } + }, + { + "id": 19005440, + "name": "Okayama", + "translations": { + "japanese": "岡山県", + "english": "Okayama", + "french": "Okayama", + "german": "Okayama", + "italian": "Okayama", + "spanish": "Okayama", + "chinese_simple": "冈山县", + "korean": "오카야마 현", + "dutch": "Okayama", + "portuguese": "Okayama", + "russian": "Окаяма", + "chinese_traditional": "岡山縣", + "unknown1": "Okayama", + "unknown2": "Okayama", + "unknown3": "Okayama", + "unknown4": "Okayama" + }, + "coordinates": { + "latitude": 34.656371676, + "longitude": 133.918210841 + } + }, + { + "id": 19070976, + "name": "Hiroshima", + "translations": { + "japanese": "広島県", + "english": "Hiroshima", + "french": "Hiroshima", + "german": "Hiroshima", + "italian": "Hiroshima", + "spanish": "Hiroshima", + "chinese_simple": "广岛县", + "korean": "히로시마 현", + "dutch": "Hiroshima", + "portuguese": "Hiroshima", + "russian": "Хиросима", + "chinese_traditional": "廣島縣", + "unknown1": "Hiroshima", + "unknown2": "Hiroshima", + "unknown3": "Hiroshima", + "unknown4": "Hiroshima" + }, + "coordinates": { + "latitude": 34.392699804, + "longitude": 132.462518406 + } + }, + { + "id": 19136512, + "name": "Yamaguchi", + "translations": { + "japanese": "山口県", + "english": "Yamaguchi", + "french": "Yamaguchi", + "german": "Yamaguchi", + "italian": "Yamaguchi", + "spanish": "Yamaguchi", + "chinese_simple": "山口县", + "korean": "야마구치 현", + "dutch": "Yamaguchi", + "portuguese": "Yamaguchi", + "russian": "Ямагути", + "chinese_traditional": "山口縣", + "unknown1": "Yamaguchi", + "unknown2": "Yamaguchi", + "unknown3": "Yamaguchi", + "unknown4": "Yamaguchi" + }, + "coordinates": { + "latitude": 34.156493752, + "longitude": 131.457266649 + } + }, + { + "id": 19202048, + "name": "Tokushima", + "translations": { + "japanese": "徳島県", + "english": "Tokushima", + "french": "Tokushima", + "german": "Tokushima", + "italian": "Tokushima", + "spanish": "Tokushima", + "chinese_simple": "德岛县", + "korean": "도쿠시마 현", + "dutch": "Tokushima", + "portuguese": "Tokushima", + "russian": "Токусима", + "chinese_traditional": "德島縣", + "unknown1": "Tokushima", + "unknown2": "Tokushima", + "unknown3": "Tokushima", + "unknown4": "Tokushima" + }, + "coordinates": { + "latitude": 34.063109964, + "longitude": 134.571899142 + } + }, + { + "id": 19267584, + "name": "Kagawa", + "translations": { + "japanese": "香川県", + "english": "Kagawa", + "french": "Kagawa", + "german": "Kagawa", + "italian": "Kagawa", + "spanish": "Kagawa", + "chinese_simple": "香川县", + "korean": "가가와 현", + "dutch": "Kagawa", + "portuguese": "Kagawa", + "russian": "Кагава", + "chinese_traditional": "香川縣", + "unknown1": "Kagawa", + "unknown2": "Kagawa", + "unknown3": "Kagawa", + "unknown4": "Kagawa" + }, + "coordinates": { + "latitude": 34.310302344, + "longitude": 134.055540316 + } + }, + { + "id": 19333120, + "name": "Ehime", + "translations": { + "japanese": "愛媛県", + "english": "Ehime", + "french": "Ehime", + "german": "Ehime", + "italian": "Ehime", + "spanish": "Ehime", + "chinese_simple": "爱媛县", + "korean": "에히메 현", + "dutch": "Ehime", + "portuguese": "Ehime", + "russian": "Эхимэ", + "chinese_traditional": "愛媛縣", + "unknown1": "Ehime", + "unknown2": "Ehime", + "unknown3": "Ehime", + "unknown4": "Ehime" + }, + "coordinates": { + "latitude": 33.83789024, + "longitude": 132.775629609 + } + }, + { + "id": 19398656, + "name": "Kochi", + "translations": { + "japanese": "高知県", + "english": "Kochi", + "french": "Kochi", + "german": "Kochi", + "italian": "Kochi", + "spanish": "Kochi", + "chinese_simple": "高知县", + "korean": "고치 현", + "dutch": "Kochi", + "portuguese": "Kochi", + "russian": "Коти", + "chinese_traditional": "高知縣", + "unknown1": "Kochi", + "unknown2": "Kochi", + "unknown3": "Kochi", + "unknown4": "Kochi" + }, + "coordinates": { + "latitude": 33.56323204, + "longitude": 133.550167848 + } + }, + { + "id": 19464192, + "name": "Fukuoka", + "translations": { + "japanese": "福岡県", + "english": "Fukuoka", + "french": "Fukuoka", + "german": "Fukuoka", + "italian": "Fukuoka", + "spanish": "Fukuoka", + "chinese_simple": "福冈县", + "korean": "후쿠오카 현", + "dutch": "Fukuoka", + "portuguese": "Fukuoka", + "russian": "Фукуока", + "chinese_traditional": "福岡縣", + "unknown1": "Fukuoka", + "unknown2": "Fukuoka", + "unknown3": "Fukuoka", + "unknown4": "Fukuoka" + }, + "coordinates": { + "latitude": 33.579711532, + "longitude": 130.375110386 + } + }, + { + "id": 19529728, + "name": "Saga", + "translations": { + "japanese": "佐賀県", + "english": "Saga", + "french": "Saga", + "german": "Saga", + "italian": "Saga", + "spanish": "Saga", + "chinese_simple": "佐贺县", + "korean": "사가 현", + "dutch": "Saga", + "portuguese": "Saga", + "russian": "Сага", + "chinese_traditional": "佐賀縣", + "unknown1": "Saga", + "unknown2": "Saga", + "unknown3": "Saga", + "unknown4": "Saga" + }, + "coordinates": { + "latitude": 33.239135364, + "longitude": 130.303699059 + } + }, + { + "id": 19595264, + "name": "Nagasaki", + "translations": { + "japanese": "長崎県", + "english": "Nagasaki", + "french": "Nagasaki", + "german": "Nagasaki", + "italian": "Nagasaki", + "spanish": "Nagasaki", + "chinese_simple": "长崎县", + "korean": "나가사키 현", + "dutch": "Nagasaki", + "portuguese": "Nagasáqui", + "russian": "Нагасаки", + "chinese_traditional": "長崎縣", + "unknown1": "Nagasaki", + "unknown2": "Nagasaki", + "unknown3": "Nagasaki", + "unknown4": "Nagasaki" + }, + "coordinates": { + "latitude": 32.728271112, + "longitude": 129.869737918 + } + }, + { + "id": 19660800, + "name": "Kumamoto", + "translations": { + "japanese": "熊本県", + "english": "Kumamoto", + "french": "Kumamoto", + "german": "Kumamoto", + "italian": "Kumamoto", + "spanish": "Kumamoto", + "chinese_simple": "熊本县", + "korean": "구마모토 현", + "dutch": "Kumamoto", + "portuguese": "Kumamoto", + "russian": "Кумамото", + "chinese_traditional": "熊本縣", + "unknown1": "Kumamoto", + "unknown2": "Kumamoto", + "unknown3": "Kumamoto", + "unknown4": "Kumamoto" + }, + "coordinates": { + "latitude": 32.805175408, + "longitude": 130.710194305 + } + }, + { + "id": 19726336, + "name": "Oita", + "translations": { + "japanese": "大分県", + "english": "Oita", + "french": "Oita", + "german": "Oita", + "italian": "Oita", + "spanish": "Oita", + "chinese_simple": "大分县", + "korean": "오이타 현", + "dutch": "Oita", + "portuguese": "Oita", + "russian": "Оита", + "chinese_traditional": "大分縣", + "unknown1": "Oita", + "unknown2": "Oita", + "unknown3": "Oita", + "unknown4": "Oita" + }, + "coordinates": { + "latitude": 33.228149036, + "longitude": 131.61656883999999 + } + }, + { + "id": 19791872, + "name": "Miyazaki", + "translations": { + "japanese": "宮崎県", + "english": "Miyazaki", + "french": "Miyazaki", + "german": "Miyazaki", + "italian": "Miyazaki", + "spanish": "Miyazaki", + "chinese_simple": "宫崎县", + "korean": "미야자키 현", + "dutch": "Miyazaki", + "portuguese": "Miyazaki", + "russian": "Миядзаки", + "chinese_traditional": "宮崎縣", + "unknown1": "Miyazaki", + "unknown2": "Miyazaki", + "unknown3": "Miyazaki", + "unknown4": "Miyazaki" + }, + "coordinates": { + "latitude": 31.931762332, + "longitude": 131.413321217 + } + }, + { + "id": 19857408, + "name": "Kagoshima", + "translations": { + "japanese": "鹿児島県", + "english": "Kagoshima", + "french": "Kagoshima", + "german": "Kagoshima", + "italian": "Kagoshima", + "spanish": "Kagoshima", + "chinese_simple": "鹿儿岛县", + "korean": "가고시마 현", + "dutch": "Kagoshima", + "portuguese": "Kagoshima", + "russian": "Кагосима", + "chinese_traditional": "鹿兒島縣", + "unknown1": "Kagoshima", + "unknown2": "Kagoshima", + "unknown3": "Kagoshima", + "unknown4": "Kagoshima" + }, + "coordinates": { + "latitude": 31.547240852, + "longitude": 130.550892114 + } + }, + { + "id": 19922944, + "name": "Okinawa", + "translations": { + "japanese": "沖縄県", + "english": "Okinawa", + "french": "Okinawa", + "german": "Okinawa", + "italian": "Okinawa", + "spanish": "Okinawa", + "chinese_simple": "冲绳县", + "korean": "오키나와 현", + "dutch": "Okinawa", + "portuguese": "Okinawa", + "russian": "Окинава", + "chinese_traditional": "沖繩縣", + "unknown1": "Okinawa", + "unknown2": "Okinawa", + "unknown3": "Okinawa", + "unknown4": "Okinawa" + }, + "coordinates": { + "latitude": 26.20239228, + "longitude": 127.683452676 + } + } + ] + }, + { + "id": 8, + "iso_code": "AI", + "name": "Anguilla", + "translations": { + "japanese": "アンギラ", + "english": "Anguilla", + "french": "Anguilla", + "german": "Anguilla", + "italian": "Anguilla", + "spanish": "Anguila", + "chinese_simple": "安圭拉", + "korean": "앵귈라", + "dutch": "Anguilla", + "portuguese": "Anguilha", + "russian": "Ангилья", + "chinese_traditional": "Anguilla", + "unknown1": "Anguilla", + "unknown2": "Anguilla", + "unknown3": "Anguilla", + "unknown4": "Anguilla" + }, + "regions": [ + { + "id": 134217728, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": 18.215331824, + "longitude": -63.044725911 + } + }, + { + "id": 134283264, + "name": "Anguilla", + "translations": { + "japanese": "アンギラ", + "english": "Anguilla", + "french": "Anguilla", + "german": "Anguilla", + "italian": "Anguilla", + "spanish": "Anguila", + "chinese_simple": "安圭拉", + "korean": "앵귈라", + "dutch": "Anguilla", + "portuguese": "Anguilha", + "russian": "Ангилья", + "chinese_traditional": "Anguilla", + "unknown1": "Anguilla", + "unknown2": "Anguilla", + "unknown3": "Anguilla", + "unknown4": "Anguilla" + }, + "coordinates": { + "latitude": 18.215331824, + "longitude": -63.044725911 + } + } + ] + }, + { + "id": 9, + "iso_code": "AG", + "name": "Antigua and Barbuda", + "translations": { + "japanese": "アンティグア・バーブーダ", + "english": "Antigua and Barbuda", + "french": "Antigua-et-Barbuda", + "german": "Antigua und Barbuda", + "italian": "Antigua e Barbuda", + "spanish": "Antigua y Barbuda", + "chinese_simple": "安提瓜和巴布达", + "korean": "앤티가 바부다", + "dutch": "Antigua en Barbuda", + "portuguese": "Antígua e Barbuda", + "russian": "Антигуа и Барбуда", + "chinese_traditional": "Antigua and Barbuda", + "unknown1": "Antigua and Barbuda", + "unknown2": "Antigua and Barbuda", + "unknown3": "Antigua and Barbuda", + "unknown4": "Antigua and Barbuda" + }, + "regions": [ + { + "id": 150994944, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": 17.111205860000002, + "longitude": -61.847212889000005 + } + }, + { + "id": 151126016, + "name": "Saint John", + "translations": { + "japanese": "セント・ジョン", + "english": "Saint John", + "french": "Saint-Jean", + "german": "Saint John's", + "italian": "Saint John", + "spanish": "Saint John", + "chinese_simple": "圣约翰区", + "korean": "세인트존", + "dutch": "Saint John", + "portuguese": "Saint John", + "russian": "Сент-Джон", + "chinese_traditional": "Saint John", + "unknown1": "Saint John", + "unknown2": "Saint John", + "unknown3": "Saint John", + "unknown4": "Saint John" + }, + "coordinates": { + "latitude": 17.111205860000002, + "longitude": -61.847212889000005 + } + }, + { + "id": 151191552, + "name": "Barbuda", + "translations": { + "japanese": "バーブーダ島", + "english": "Barbuda", + "french": "Barbuda", + "german": "Barbuda", + "italian": "Barbuda", + "spanish": "Barbuda", + "chinese_simple": "巴布达岛", + "korean": "바부다", + "dutch": "Barbuda", + "portuguese": "Barbuda", + "russian": "Барбуда", + "chinese_traditional": "Barbuda", + "unknown1": "Barbuda", + "unknown2": "Barbuda", + "unknown3": "Barbuda", + "unknown4": "Barbuda" + }, + "coordinates": { + "latitude": 17.63305644, + "longitude": -61.830733351999996 + } + }, + { + "id": 151257088, + "name": "Saint George", + "translations": { + "japanese": "セント・ジョージ", + "english": "Saint George", + "french": "Saint-Georges", + "german": "Saint George", + "italian": "Saint George", + "spanish": "Saint George", + "chinese_simple": "圣乔治区", + "korean": "세인트조지", + "dutch": "Saint George", + "portuguese": "Saint George", + "russian": "Сент-Джордж", + "chinese_traditional": "Saint George", + "unknown1": "Saint George", + "unknown2": "Saint George", + "unknown3": "Saint George", + "unknown4": "Saint George" + }, + "coordinates": { + "latitude": 17.133178516, + "longitude": -61.797774278000006 + } + }, + { + "id": 151322624, + "name": "Saint Mary", + "translations": { + "japanese": "セント・メアリー", + "english": "Saint Mary", + "french": "Sainte-Marie", + "german": "Saint Mary", + "italian": "Saint Mary", + "spanish": "Saint Mary", + "chinese_simple": "圣玛丽区", + "korean": "세인트메리", + "dutch": "Saint Mary", + "portuguese": "Saint Mary", + "russian": "Сент-Мэри", + "chinese_traditional": "Saint Mary", + "unknown1": "Saint Mary", + "unknown2": "Saint Mary", + "unknown3": "Saint Mary", + "unknown4": "Saint Mary" + }, + "coordinates": { + "latitude": 17.0288084, + "longitude": -61.880171962999995 + } + }, + { + "id": 151388160, + "name": "Saint Paul", + "translations": { + "japanese": "セント・ポール", + "english": "Saint Paul", + "french": "Saint-Paul", + "german": "Saint Paul", + "italian": "Saint Paul", + "spanish": "Saint Paul", + "chinese_simple": "圣保罗区", + "korean": "세인트폴", + "dutch": "Saint Paul", + "portuguese": "Saint Paul", + "russian": "Сент-Пол", + "chinese_traditional": "Saint Paul", + "unknown1": "Saint Paul", + "unknown2": "Saint Paul", + "unknown3": "Saint Paul", + "unknown4": "Saint Paul" + }, + "coordinates": { + "latitude": 17.0288084, + "longitude": -61.781294740999996 + } + }, + { + "id": 151453696, + "name": "Saint Peter", + "translations": { + "japanese": "セント・ピーター", + "english": "Saint Peter", + "french": "Saint-Pierre", + "german": "Saint Peter", + "italian": "Saint Peter", + "spanish": "Saint Peter", + "chinese_simple": "圣彼得区", + "korean": "세인트피터", + "dutch": "Saint Peter", + "portuguese": "Saint Peter", + "russian": "Сент-Петер", + "chinese_traditional": "Saint Peter", + "unknown1": "Saint Peter", + "unknown2": "Saint Peter", + "unknown3": "Saint Peter", + "unknown4": "Saint Peter" + }, + "coordinates": { + "latitude": 17.078246876, + "longitude": -61.764815204 + } + }, + { + "id": 151519232, + "name": "Saint Philip", + "translations": { + "japanese": "セント・フィリップ", + "english": "Saint Philip", + "french": "Saint-Philippe", + "german": "Saint Philip", + "italian": "Saint Philip", + "spanish": "Saint Philip", + "chinese_simple": "圣菲利普区", + "korean": "세인트필립", + "dutch": "Saint Philip", + "portuguese": "Saint Philip", + "russian": "Сент-Филип", + "chinese_traditional": "Saint Philip", + "unknown1": "Saint Philip", + "unknown2": "Saint Philip", + "unknown3": "Saint Philip", + "unknown4": "Saint Philip" + }, + "coordinates": { + "latitude": 17.045287892, + "longitude": -61.69889705600001 + } + } + ] + }, + { + "id": 10, + "iso_code": "AR", + "name": "Argentina", + "translations": { + "japanese": "アルゼンチン", + "english": "Argentina", + "french": "Argentine", + "german": "Argentinien", + "italian": "Argentina", + "spanish": "Argentina", + "chinese_simple": "阿根廷", + "korean": "아르헨티나", + "dutch": "Argentinië", + "portuguese": "Argentina", + "russian": "Аргентина", + "chinese_traditional": "Argentina", + "unknown1": "Argentina", + "unknown2": "Argentina", + "unknown3": "Argentina", + "unknown4": "Argentina" + }, + "regions": [ + { + "id": 167772160, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": -34.584961568, + "longitude": -58.67215542700001 + } + }, + { + "id": 167903232, + "name": "Distrito Federal", + "translations": { + "japanese": "特別区", + "english": "Distrito Federal", + "french": "District Fédéral", + "german": "Autonome Stadt Buenos Aires", + "italian": "Capitale Federale", + "spanish": "Ciudad de Buenos Aires", + "chinese_simple": "联邦首都区", + "korean": "아르헨티나 연방구", + "dutch": "Federaal District", + "portuguese": "Distrito Federal", + "russian": "Федеральный округ", + "chinese_traditional": "Distrito Federal", + "unknown1": "Distrito Federal", + "unknown2": "Distrito Federal", + "unknown3": "Distrito Federal", + "unknown4": "Distrito Federal" + }, + "coordinates": { + "latitude": -34.584961568, + "longitude": -58.67215542700001 + } + }, + { + "id": 167968768, + "name": "Buenos Aires", + "translations": { + "japanese": "ブエノスアイレス州", + "english": "Buenos Aires", + "french": "Buenos Aires", + "german": "Buenos Aires", + "italian": "Buenos Aires", + "spanish": "Provincia de Buenos Aires", + "chinese_simple": "布宜诺斯艾利斯省", + "korean": "부에노스아이레스 주", + "dutch": "Buenos Aires", + "portuguese": "Buenos Aires", + "russian": "Буэнос-Айрес", + "chinese_traditional": "Buenos Aires", + "unknown1": "Buenos Aires", + "unknown2": "Buenos Aires", + "unknown3": "Buenos Aires", + "unknown4": "Buenos Aires" + }, + "coordinates": { + "latitude": -34.9310309, + "longitude": -57.947055799 + } + }, + { + "id": 168034304, + "name": "Catamarca", + "translations": { + "japanese": "カタマルカ州", + "english": "Catamarca", + "french": "Catamarca", + "german": "Catamarca", + "italian": "Catamarca", + "spanish": "Catamarca", + "chinese_simple": "卡塔马卡省", + "korean": "카타마르카 주", + "dutch": "Catamarca", + "portuguese": "Catamarca", + "russian": "Катамарка", + "chinese_traditional": "Catamarca", + "unknown1": "Catamarca", + "unknown2": "Catamarca", + "unknown3": "Catamarca", + "unknown4": "Catamarca" + }, + "coordinates": { + "latitude": -28.465576872, + "longitude": -65.780329053 + } + }, + { + "id": 168099840, + "name": "Chaco", + "translations": { + "japanese": "チャコ州", + "english": "Chaco", + "french": "Chaco", + "german": "Chaco", + "italian": "Chaco", + "spanish": "Chaco", + "chinese_simple": "查科省", + "korean": "차코 주", + "dutch": "Chaco", + "portuguese": "Chaco", + "russian": "Чако", + "chinese_traditional": "Chaco", + "unknown1": "Chaco", + "unknown2": "Chaco", + "unknown3": "Chaco", + "unknown4": "Chaco" + }, + "coordinates": { + "latitude": -27.449341532, + "longitude": -58.979773451 + } + }, + { + "id": 168165376, + "name": "Chubut", + "translations": { + "japanese": "チュブト州", + "english": "Chubut", + "french": "Chubut", + "german": "Chubut", + "italian": "Chubut", + "spanish": "Chubut", + "chinese_simple": "丘布特省", + "korean": "추부트 주", + "dutch": "Chubut", + "portuguese": "Chubut", + "russian": "Чубут", + "chinese_traditional": "Chubut", + "unknown1": "Chubut", + "unknown2": "Chubut", + "unknown3": "Chubut", + "unknown4": "Chubut" + }, + "coordinates": { + "latitude": -43.297119672, + "longitude": -65.099174857 + } + }, + { + "id": 168230912, + "name": "Córdoba", + "translations": { + "japanese": "コルドバ州", + "english": "Córdoba", + "french": "Córdoba", + "german": "Córdoba", + "italian": "Córdoba", + "spanish": "Córdoba", + "chinese_simple": "科尔多瓦省", + "korean": "코르도바 주", + "dutch": "Córdoba", + "portuguese": "Córdova", + "russian": "Кордова", + "chinese_traditional": "Córdoba", + "unknown1": "Córdoba", + "unknown2": "Córdoba", + "unknown3": "Córdoba", + "unknown4": "Córdoba" + }, + "coordinates": { + "latitude": -31.398926447999997, + "longitude": -64.181813964 + } + }, + { + "id": 168296448, + "name": "Corrientes", + "translations": { + "japanese": "コリエンテス州", + "english": "Corrientes", + "french": "Corrientes", + "german": "Corrientes", + "italian": "Corrientes", + "spanish": "Corrientes", + "chinese_simple": "科连特斯省", + "korean": "코리엔테스 주", + "dutch": "Corrientes", + "portuguese": "Corrientes", + "russian": "Корриентес", + "chinese_traditional": "Corrientes", + "unknown1": "Corrientes", + "unknown2": "Corrientes", + "unknown3": "Corrientes", + "unknown4": "Corrientes" + }, + "coordinates": { + "latitude": -27.465821024, + "longitude": -58.831457618 + } + }, + { + "id": 168361984, + "name": "Entre Ríos", + "translations": { + "japanese": "エントレ・リオス州", + "english": "Entre Ríos", + "french": "Entre Ríos", + "german": "Entre Ríos", + "italian": "Entre Ríos", + "spanish": "Entre Ríos", + "chinese_simple": "恩特雷里奥斯省", + "korean": "엔트레리오스 주", + "dutch": "Entre Ríos", + "portuguese": "Entre Ríos", + "russian": "Энтре-Риос", + "chinese_traditional": "Entre Ríos", + "unknown1": "Entre Ríos", + "unknown2": "Entre Ríos", + "unknown3": "Entre Ríos", + "unknown4": "Entre Ríos" + }, + "coordinates": { + "latitude": -31.728516288, + "longitude": -60.528849929 + } + }, + { + "id": 168427520, + "name": "Formosa", + "translations": { + "japanese": "フォルモサ州", + "english": "Formosa", + "french": "Formosa", + "german": "Formosa", + "italian": "Formosa", + "spanish": "Formosa", + "chinese_simple": "福尔摩沙省", + "korean": "포르모사 주", + "dutch": "Formosa", + "portuguese": "Formosa", + "russian": "Формоса", + "chinese_traditional": "Formosa", + "unknown1": "Formosa", + "unknown2": "Formosa", + "unknown3": "Formosa", + "unknown4": "Formosa" + }, + "coordinates": { + "latitude": -26.180420648000002, + "longitude": -58.177769317 + } + }, + { + "id": 168493056, + "name": "Jujuy", + "translations": { + "japanese": "フフイ州", + "english": "Jujuy", + "french": "Jujuy", + "german": "Jujuy", + "italian": "Jujuy", + "spanish": "Jujuy", + "chinese_simple": "胡胡伊省", + "korean": "후후이 주", + "dutch": "Jujuy", + "portuguese": "Jujuy", + "russian": "Жужуй", + "chinese_traditional": "Jujuy", + "unknown1": "Jujuy", + "unknown2": "Jujuy", + "unknown3": "Jujuy", + "unknown4": "Jujuy" + }, + "coordinates": { + "latitude": -24.180908951999996, + "longitude": -65.296929301 + } + }, + { + "id": 168558592, + "name": "La Pampa", + "translations": { + "japanese": "ラ・パンパ州", + "english": "La Pampa", + "french": "La Pampa", + "german": "La Pampa", + "italian": "La Pampa", + "spanish": "La Pampa", + "chinese_simple": "拉潘帕省", + "korean": "라팜파 주", + "dutch": "La Pampa", + "portuguese": "La Pampa", + "russian": "Ла-Пампа", + "chinese_traditional": "La Pampa", + "unknown1": "La Pampa", + "unknown2": "La Pampa", + "unknown3": "La Pampa", + "unknown4": "La Pampa" + }, + "coordinates": { + "latitude": -36.611939084, + "longitude": -64.280691186 + } + }, + { + "id": 168624128, + "name": "La Rioja", + "translations": { + "japanese": "ラ・リオハ州", + "english": "La Rioja", + "french": "La Rioja", + "german": "La Rioja", + "italian": "La Rioja", + "spanish": "La Rioja", + "chinese_simple": "拉里奥哈省", + "korean": "라리오하 주", + "dutch": "La Rioja", + "portuguese": "La Rioja", + "russian": "Ла-Риоха", + "chinese_traditional": "La Rioja", + "unknown1": "La Rioja", + "unknown2": "La Rioja", + "unknown3": "La Rioja", + "unknown4": "La Rioja" + }, + "coordinates": { + "latitude": -29.432373736000002, + "longitude": -66.846005779 + } + }, + { + "id": 168689664, + "name": "Mendoza", + "translations": { + "japanese": "メンドーサ州", + "english": "Mendoza", + "french": "Mendoza", + "german": "Mendoza", + "italian": "Mendoza", + "spanish": "Mendoza", + "chinese_simple": "门多萨省", + "korean": "멘도사 주", + "dutch": "Mendoza", + "portuguese": "Mendoza", + "russian": "Мендоса", + "chinese_traditional": "Mendoza", + "unknown1": "Mendoza", + "unknown2": "Mendoza", + "unknown3": "Mendoza", + "unknown4": "Mendoza" + }, + "coordinates": { + "latitude": -32.882080728, + "longitude": -68.812563861 + } + }, + { + "id": 168755200, + "name": "Misiones", + "translations": { + "japanese": "ミシオネス州", + "english": "Misiones", + "french": "Misiones", + "german": "Misiones", + "italian": "Misiones", + "spanish": "Misiones", + "chinese_simple": "米西奥内斯省", + "korean": "미시오네스 주", + "dutch": "Misiones", + "portuguese": "Misiones", + "russian": "Мисьонес", + "chinese_traditional": "Misiones", + "unknown1": "Misiones", + "unknown2": "Misiones", + "unknown3": "Misiones", + "unknown4": "Misiones" + }, + "coordinates": { + "latitude": -27.377930399999997, + "longitude": -55.88162049500001 + } + }, + { + "id": 168820736, + "name": "Neuquén", + "translations": { + "japanese": "ネウケン州", + "english": "Neuquén", + "french": "Neuquén", + "german": "Neuquén", + "italian": "Neuquén", + "spanish": "Neuquén", + "chinese_simple": "内乌肯省", + "korean": "네우켄 주", + "dutch": "Neuquén", + "portuguese": "Neuquén", + "russian": "Неукен", + "chinese_traditional": "Neuquén", + "unknown1": "Neuquén", + "unknown2": "Neuquén", + "unknown3": "Neuquén", + "unknown4": "Neuquén" + }, + "coordinates": { + "latitude": -38.946533784, + "longitude": -68.065491517 + } + }, + { + "id": 168886272, + "name": "Río Negro", + "translations": { + "japanese": "リオネグロ州", + "english": "Río Negro", + "french": "Río Negro", + "german": "Río Negro", + "italian": "Río Negro", + "spanish": "Río Negro", + "chinese_simple": "里奥内格罗省", + "korean": "리오네그로 주", + "dutch": "Río Negro", + "portuguese": "Río Negro", + "russian": "Рио-Негро", + "chinese_traditional": "Río Negro", + "unknown1": "Río Negro", + "unknown2": "Río Negro", + "unknown3": "Río Negro", + "unknown4": "Río Negro" + }, + "coordinates": { + "latitude": -40.797730052, + "longitude": -62.9952873 + } + }, + { + "id": 168951808, + "name": "Salta", + "translations": { + "japanese": "サルタ州", + "english": "Salta", + "french": "Salta", + "german": "Salta", + "italian": "Salta", + "spanish": "Salta", + "chinese_simple": "萨尔塔省", + "korean": "살타 주", + "dutch": "Salta", + "portuguese": "Salta", + "russian": "Сальта", + "chinese_traditional": "Salta", + "unknown1": "Salta", + "unknown2": "Salta", + "unknown3": "Salta", + "unknown4": "Salta" + }, + "coordinates": { + "latitude": -24.779663827999997, + "longitude": -65.41228606 + } + }, + { + "id": 169017344, + "name": "San Juan", + "translations": { + "japanese": "サン・フアン州", + "english": "San Juan", + "french": "San Juan", + "german": "San Juan", + "italian": "San Juan", + "spanish": "San Juan", + "chinese_simple": "圣胡安省", + "korean": "산후안 주", + "dutch": "San Juan", + "portuguese": "San Juan", + "russian": "Сан-Хуан", + "chinese_traditional": "San Juan", + "unknown1": "San Juan", + "unknown2": "San Juan", + "unknown3": "San Juan", + "unknown4": "San Juan" + }, + "coordinates": { + "latitude": -31.536255548, + "longitude": -68.532411732 + } + }, + { + "id": 169082880, + "name": "San Luis", + "translations": { + "japanese": "サン・ルイス州", + "english": "San Luis", + "french": "San Luis", + "german": "San Luis", + "italian": "San Luis", + "spanish": "San Luis", + "chinese_simple": "圣路易斯省", + "korean": "산루이스 주", + "dutch": "San Luis", + "portuguese": "San Luís", + "russian": "Сан-Луис", + "chinese_traditional": "San Luis", + "unknown1": "San Luis", + "unknown2": "San Luis", + "unknown3": "San Luis", + "unknown4": "San Luis" + }, + "coordinates": { + "latitude": -33.299561192, + "longitude": -66.34612649 + } + }, + { + "id": 169148416, + "name": "Santa Cruz", + "translations": { + "japanese": "サンタ・クルス州", + "english": "Santa Cruz", + "french": "Santa Cruz", + "german": "Santa Cruz", + "italian": "Santa Cruz", + "spanish": "Santa Cruz", + "chinese_simple": "圣克鲁斯省", + "korean": "산타크루스 주", + "dutch": "Santa Cruz", + "portuguese": "Santa Cruz", + "russian": "Санта-Крус", + "chinese_traditional": "Santa Cruz", + "unknown1": "Santa Cruz", + "unknown2": "Santa Cruz", + "unknown3": "Santa Cruz", + "unknown4": "Santa Cruz" + }, + "coordinates": { + "latitude": -51.63024946, + "longitude": -69.213565928 + } + }, + { + "id": 169213952, + "name": "Santa Fe", + "translations": { + "japanese": "サンタ・フェ州", + "english": "Santa Fe", + "french": "Santa Fe", + "german": "Santa Fe", + "italian": "Santa Fe", + "spanish": "Santa Fe", + "chinese_simple": "圣菲省", + "korean": "산타페 주", + "dutch": "Santa Fe", + "portuguese": "Santa Fé", + "russian": "Санта-Фе", + "chinese_traditional": "Santa Fe", + "unknown1": "Santa Fe", + "unknown2": "Santa Fe", + "unknown3": "Santa Fe", + "unknown4": "Santa Fe" + }, + "coordinates": { + "latitude": -31.629639335999997, + "longitude": -60.699138477999995 + } + }, + { + "id": 169279488, + "name": "Santiago del Estero", + "translations": { + "japanese": "サンティアゴ・デル・エステロ州", + "english": "Santiago del Estero", + "french": "Santiago del Estero", + "german": "Santiago del Estero", + "italian": "Santiago del Estero", + "spanish": "Santiago del Estero", + "chinese_simple": "圣地亚哥-德尔埃斯特罗省", + "korean": "산티아고델에스테로 주", + "dutch": "Santiago del Estero", + "portuguese": "Santiago del Estero", + "russian": "Сантьяго-дель-Эстеро", + "chinese_traditional": "Santiago del Estero", + "unknown1": "Santiago del Estero", + "unknown2": "Santiago del Estero", + "unknown3": "Santiago del Estero", + "unknown4": "Santiago del Estero" + }, + "coordinates": { + "latitude": -27.778931372000002, + "longitude": -64.264211649 + } + }, + { + "id": 169345024, + "name": "Tierra del Fuego, Antártida e Islas del Atlántico Sur", + "translations": { + "japanese": "ティエラ・デル・フエゴ州", + "english": "Tierra del Fuego, Antártida e Islas del Atlántico Sur", + "french": "Terre de Feu, Antarctique et Îles de l’Atlantique Sud", + "german": "Feuerland", + "italian": "Terra del Fuoco", + "spanish": "Tierra del Fuego", + "chinese_simple": "火地岛省", + "korean": "티에라델푸에고 주", + "dutch": "Vuurland", + "portuguese": "Terra do Fogo, Antártida e Ilhas do Atlântico Sul", + "russian": "Огненная Земля", + "chinese_traditional": "Tierra del Fuego, Antártida e Islas del Atlántico Sur", + "unknown1": "Tierra del Fuego, Antártida e Islas del Atlántico Sur", + "unknown2": "Tierra del Fuego, Antártida e Islas del Atlántico Sur", + "unknown3": "Tierra del Fuego, Antártida e Islas del Atlántico Sur", + "unknown4": "Tierra del Fuego, Antártida e Islas del Atlántico Sur" + }, + "coordinates": { + "latitude": -54.799805088, + "longitude": -68.296205035 + } + }, + { + "id": 169410560, + "name": "Tucumán", + "translations": { + "japanese": "トゥクマン州", + "english": "Tucumán", + "french": "Tucumán", + "german": "Tucumán", + "italian": "Tucumán", + "spanish": "Tucumán", + "chinese_simple": "图库曼省", + "korean": "투쿠만 주", + "dutch": "Tucumán", + "portuguese": "Tucumán", + "russian": "Тукуман", + "chinese_traditional": "Tucumán", + "unknown1": "Tucumán", + "unknown2": "Tucumán", + "unknown3": "Tucumán", + "unknown4": "Tucumán" + }, + "coordinates": { + "latitude": -26.812134508, + "longitude": -65.214531616 + } + } + ] + }, + { + "id": 11, + "iso_code": "AW", + "name": "Aruba", + "translations": { + "japanese": "アルバ", + "english": "Aruba", + "french": "Aruba", + "german": "Aruba", + "italian": "Aruba", + "spanish": "Aruba", + "chinese_simple": "阿鲁巴", + "korean": "아루바", + "dutch": "Aruba", + "portuguese": "Aruba", + "russian": "Аруба", + "chinese_traditional": "Aruba", + "unknown1": "Aruba", + "unknown2": "Aruba", + "unknown3": "Aruba", + "unknown4": "Aruba" + }, + "regions": [ + { + "id": 184549376, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": 12.513427592, + "longitude": -70.032049599 + } + }, + { + "id": 184614912, + "name": "Aruba", + "translations": { + "japanese": "アルバ", + "english": "Aruba", + "french": "Aruba", + "german": "Aruba", + "italian": "Aruba", + "spanish": "Aruba", + "chinese_simple": "阿鲁巴", + "korean": "아루바", + "dutch": "Aruba", + "portuguese": "Aruba", + "russian": "Аруба", + "chinese_traditional": "Aruba", + "unknown1": "Aruba", + "unknown2": "Aruba", + "unknown3": "Aruba", + "unknown4": "Aruba" + }, + "coordinates": { + "latitude": 12.513427592, + "longitude": -70.032049599 + } + } + ] + }, + { + "id": 12, + "iso_code": "BS", + "name": "Bahamas", + "translations": { + "japanese": "バハマ", + "english": "Bahamas", + "french": "Bahamas", + "german": "Bahamas", + "italian": "Bahamas", + "spanish": "Bahamas", + "chinese_simple": "巴哈马", + "korean": "바하마", + "dutch": "Bahama's", + "portuguese": "Bahamas", + "russian": "Багамские острова", + "chinese_traditional": "Bahamas", + "unknown1": "Bahamas", + "unknown2": "Bahamas", + "unknown3": "Bahamas", + "unknown4": "Bahamas" + }, + "regions": [ + { + "id": 201326592, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": 25.081786824, + "longitude": -77.34896402700001 + } + }, + { + "id": 201392128, + "name": "Bahamas", + "translations": { + "japanese": "バハマ", + "english": "Bahamas", + "french": "Bahamas", + "german": "Bahamas", + "italian": "Bahamas", + "spanish": "Bahamas", + "chinese_simple": "巴哈马", + "korean": "바하마", + "dutch": "Bahama's", + "portuguese": "Bahamas", + "russian": "Багамские острова", + "chinese_traditional": "Bahamas", + "unknown1": "Bahamas", + "unknown2": "Bahamas", + "unknown3": "Bahamas", + "unknown4": "Bahamas" + }, + "coordinates": { + "latitude": 25.081786824, + "longitude": -77.34896402700001 + } + } + ] + }, + { + "id": 13, + "iso_code": "BB", + "name": "Barbados", + "translations": { + "japanese": "バルバドス", + "english": "Barbados", + "french": "Barbade", + "german": "Barbados", + "italian": "Barbados", + "spanish": "Barbados", + "chinese_simple": "巴巴多斯", + "korean": "바베이도스", + "dutch": "Barbados", + "portuguese": "Barbados", + "russian": "Барбадос", + "chinese_traditional": "Barbados", + "unknown1": "Barbados", + "unknown2": "Barbados", + "unknown3": "Barbados", + "unknown4": "Barbados" + }, + "regions": [ + { + "id": 218103808, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": 13.095702976, + "longitude": -59.611489035999995 + } + }, + { + "id": 218169344, + "name": "Barbados", + "translations": { + "japanese": "バルバドス", + "english": "Barbados", + "french": "Barbade", + "german": "Barbados", + "italian": "Barbados", + "spanish": "Barbados", + "chinese_simple": "巴巴多斯", + "korean": "바베이도스", + "dutch": "Barbados", + "portuguese": "Barbados", + "russian": "Барбадос", + "chinese_traditional": "Barbados", + "unknown1": "Barbados", + "unknown2": "Barbados", + "unknown3": "Barbados", + "unknown4": "Barbados" + }, + "coordinates": { + "latitude": 13.095702976, + "longitude": -59.611489035999995 + } + } + ] + }, + { + "id": 14, + "iso_code": "BZ", + "name": "Belize", + "translations": { + "japanese": "ベリーズ", + "english": "Belize", + "french": "Belize", + "german": "Belize", + "italian": "Belize", + "spanish": "Belice", + "chinese_simple": "伯利兹", + "korean": "벨리즈", + "dutch": "Belize", + "portuguese": "Belize", + "russian": "Белиз", + "chinese_traditional": "Belize", + "unknown1": "Belize", + "unknown2": "Belize", + "unknown3": "Belize", + "unknown4": "Belize" + }, + "regions": [ + { + "id": 234881024, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": 17.24853496, + "longitude": -88.763789989 + } + }, + { + "id": 235012096, + "name": "Cayo", + "translations": { + "japanese": "カヨー州", + "english": "Cayo", + "french": "Cayo", + "german": "Cayo", + "italian": "Cayo", + "spanish": "Cayo", + "chinese_simple": "卡约区", + "korean": "카요 주", + "dutch": "Cayo", + "portuguese": "Cayo", + "russian": "Кайо", + "chinese_traditional": "Cayo", + "unknown1": "Cayo", + "unknown2": "Cayo", + "unknown3": "Cayo", + "unknown4": "Cayo" + }, + "coordinates": { + "latitude": 17.24853496, + "longitude": -88.763789989 + } + }, + { + "id": 235077632, + "name": "Belize", + "translations": { + "japanese": "ベリーズ州", + "english": "Belize", + "french": "Belize", + "german": "Belize", + "italian": "Belize", + "spanish": "Belice", + "chinese_simple": "伯利兹城", + "korean": "벨리즈 주", + "dutch": "Belize", + "portuguese": "Belize", + "russian": "Белиз", + "chinese_traditional": "Belize", + "unknown1": "Belize", + "unknown2": "Belize", + "unknown3": "Belize", + "unknown4": "Belize" + }, + "coordinates": { + "latitude": 17.479247848, + "longitude": -88.18151301500001 + } + }, + { + "id": 235143168, + "name": "Corozal", + "translations": { + "japanese": "コロサル州", + "english": "Corozal", + "french": "Corozal", + "german": "Corozal", + "italian": "Corozal", + "spanish": "Corozal", + "chinese_simple": "科罗萨尔区", + "korean": "코로살 주", + "dutch": "Corozal", + "portuguese": "Corozal", + "russian": "Корозал", + "chinese_traditional": "Corozal", + "unknown1": "Corozal", + "unknown2": "Corozal", + "unknown3": "Corozal", + "unknown4": "Corozal" + }, + "coordinates": { + "latitude": 18.380126744000002, + "longitude": -88.379267459 + } + }, + { + "id": 235208704, + "name": "Orange Walk", + "translations": { + "japanese": "オレンジウォーク州", + "english": "Orange Walk", + "french": "Orange Walk", + "german": "Orange Walk", + "italian": "Orange Walk", + "spanish": "Orange Walk", + "chinese_simple": "橘园区", + "korean": "오렌지워크 주", + "dutch": "Orange Walk", + "portuguese": "Orange Walk", + "russian": "Ориндж Уолк", + "chinese_traditional": "Orange Walk", + "unknown1": "Orange Walk", + "unknown2": "Orange Walk", + "unknown3": "Orange Walk", + "unknown4": "Orange Walk" + }, + "coordinates": { + "latitude": 18.061523232, + "longitude": -88.549556008 + } + }, + { + "id": 235274240, + "name": "Stann Creek", + "translations": { + "japanese": "スタンクリーク州", + "english": "Stann Creek", + "french": "Stann Creek", + "german": "Stann Creek", + "italian": "Stann Creek", + "spanish": "Stann Creek", + "chinese_simple": "斯坦港区", + "korean": "스탠크리크 주", + "dutch": "Stann Creek", + "portuguese": "Stann Creek", + "russian": "Стэн Крик", + "chinese_traditional": "Stann Creek", + "unknown1": "Stann Creek", + "unknown2": "Stann Creek", + "unknown3": "Stann Creek", + "unknown4": "Stann Creek" + }, + "coordinates": { + "latitude": 16.962890432000002, + "longitude": -88.214472089 + } + }, + { + "id": 235339776, + "name": "Toledo", + "translations": { + "japanese": "トレド州", + "english": "Toledo", + "french": "Toledo", + "german": "Toledo", + "italian": "Toledo", + "spanish": "Toledo", + "chinese_simple": "托莱多区", + "korean": "톨레도 주", + "dutch": "Toledo", + "portuguese": "Toledo", + "russian": "Толедо", + "chinese_traditional": "Toledo", + "unknown1": "Toledo", + "unknown2": "Toledo", + "unknown3": "Toledo", + "unknown4": "Toledo" + }, + "coordinates": { + "latitude": 16.09497052, + "longitude": -88.79674906300001 + } + } + ] + }, + { + "id": 15, + "iso_code": "BO", + "name": "Bolivia", + "translations": { + "japanese": "ボリビア", + "english": "Bolivia", + "french": "Bolivie", + "german": "Bolivien", + "italian": "Bolivia", + "spanish": "Bolivia", + "chinese_simple": "玻利维亚", + "korean": "볼리비아", + "dutch": "Bolivia", + "portuguese": "Bolívia", + "russian": "Боливия", + "chinese_traditional": "Bolivia", + "unknown1": "Bolivia", + "unknown2": "Bolivia", + "unknown3": "Bolivia", + "unknown4": "Bolivia" + }, + "regions": [ + { + "id": 251658240, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": -16.495972515999995, + "longitude": -68.147889202 + } + }, + { + "id": 251789312, + "name": "La Paz", + "translations": { + "japanese": "ラパス県", + "english": "La Paz", + "french": "La Paz", + "german": "La Paz", + "italian": "La Paz", + "spanish": "La Paz", + "chinese_simple": "拉巴斯省", + "korean": "라파스 주", + "dutch": "La Paz", + "portuguese": "La Paz", + "russian": "Ла-Пас", + "chinese_traditional": "La Paz", + "unknown1": "La Paz", + "unknown2": "La Paz", + "unknown3": "La Paz", + "unknown4": "La Paz" + }, + "coordinates": { + "latitude": -16.495972515999995, + "longitude": -68.147889202 + } + }, + { + "id": 251854848, + "name": "Chuquisaca", + "translations": { + "japanese": "チュキサカ県", + "english": "Chuquisaca", + "french": "Chuquisaca", + "german": "Chuquisaca", + "italian": "Chuquisaca", + "spanish": "Chuquisaca", + "chinese_simple": "丘基萨卡省", + "korean": "추키사카 주", + "dutch": "Chuquisaca", + "portuguese": "Chuquisaca", + "russian": "Чукисака", + "chinese_traditional": "Chuquisaca", + "unknown1": "Chuquisaca", + "unknown2": "Chuquisaca", + "unknown3": "Chuquisaca", + "unknown4": "Chuquisaca" + }, + "coordinates": { + "latitude": -19.039307448000002, + "longitude": -65.258477048 + } + }, + { + "id": 251920384, + "name": "Cochabamba", + "translations": { + "japanese": "コチャバンバ県", + "english": "Cochabamba", + "french": "Cochabamba", + "german": "Cochabamba", + "italian": "Cochabamba", + "spanish": "Cochabamba", + "chinese_simple": "科恰班巴省", + "korean": "코차밤바 주", + "dutch": "Cochabamba", + "portuguese": "Cochabamba", + "russian": "Кочабамба", + "chinese_traditional": "Cochabamba", + "unknown1": "Cochabamba", + "unknown2": "Cochabamba", + "unknown3": "Cochabamba", + "unknown4": "Cochabamba" + }, + "coordinates": { + "latitude": -17.38037192, + "longitude": -66.148372046 + } + }, + { + "id": 251985920, + "name": "El Beni", + "translations": { + "japanese": "ベニ県", + "english": "El Beni", + "french": "Beni", + "german": "Beni", + "italian": "Beni", + "spanish": "El Beni", + "chinese_simple": "贝尼省", + "korean": "엘베니 주", + "dutch": "Beni", + "portuguese": "El Beni", + "russian": "Бени", + "chinese_traditional": "El Beni", + "unknown1": "El Beni", + "unknown2": "El Beni", + "unknown3": "El Beni", + "unknown4": "El Beni" + }, + "coordinates": { + "latitude": -14.831543823999993, + "longitude": -64.895927234 + } + }, + { + "id": 252051456, + "name": "Oruro", + "translations": { + "japanese": "オルロ県", + "english": "Oruro", + "french": "Oruro", + "german": "Oruro", + "italian": "Oruro", + "spanish": "Oruro", + "chinese_simple": "奥鲁罗省", + "korean": "오루로 주", + "dutch": "Oruro", + "portuguese": "Oruro", + "russian": "Оруро", + "chinese_traditional": "Oruro", + "unknown1": "Oruro", + "unknown2": "Oruro", + "unknown3": "Oruro", + "unknown4": "Oruro" + }, + "coordinates": { + "latitude": -17.979126796000003, + "longitude": -67.148130624 + } + }, + { + "id": 252116992, + "name": "Pando", + "translations": { + "japanese": "パンド県", + "english": "Pando", + "french": "Pando", + "german": "Pando", + "italian": "Pando", + "spanish": "Pando", + "chinese_simple": "潘多省", + "korean": "판도 주", + "dutch": "Pando", + "portuguese": "Pando", + "russian": "Пандо", + "chinese_traditional": "Pando", + "unknown1": "Pando", + "unknown2": "Pando", + "unknown3": "Pando", + "unknown4": "Pando" + }, + "coordinates": { + "latitude": -11.030274336000005, + "longitude": -68.730166176 + } + }, + { + "id": 252182528, + "name": "Potosí", + "translations": { + "japanese": "ポトシ県", + "english": "Potosí", + "french": "Potosí", + "german": "Potosí", + "italian": "Potosí", + "spanish": "Potosí", + "chinese_simple": "波托西省", + "korean": "포토시 주", + "dutch": "Potosí", + "portuguese": "Potosí", + "russian": "Потоси", + "chinese_traditional": "Potosí", + "unknown1": "Potosí", + "unknown2": "Potosí", + "unknown3": "Potosí", + "unknown4": "Potosí" + }, + "coordinates": { + "latitude": -19.583130683999997, + "longitude": -65.747369979 + } + }, + { + "id": 252248064, + "name": "Santa Cruz", + "translations": { + "japanese": "サンタ・クルス県", + "english": "Santa Cruz", + "french": "Santa Cruz", + "german": "Santa Cruz", + "italian": "Santa Cruz", + "spanish": "Santa Cruz", + "chinese_simple": "圣克鲁斯省", + "korean": "산타크루스 주", + "dutch": "Santa Cruz", + "portuguese": "Santa Cruz", + "russian": "Санта-Крус", + "chinese_traditional": "Santa Cruz", + "unknown1": "Santa Cruz", + "unknown2": "Santa Cruz", + "unknown3": "Santa Cruz", + "unknown4": "Santa Cruz" + }, + "coordinates": { + "latitude": -17.797852383999995, + "longitude": -63.16557584900001 + } + }, + { + "id": 252313600, + "name": "Tarija", + "translations": { + "japanese": "タリハ県", + "english": "Tarija", + "french": "Tarija", + "german": "Tarija", + "italian": "Tarija", + "spanish": "Tarija", + "chinese_simple": "塔里哈省", + "korean": "타리하 주", + "dutch": "Tarija", + "portuguese": "Tarija", + "russian": "Тариха", + "chinese_traditional": "Tarija", + "unknown1": "Tarija", + "unknown2": "Tarija", + "unknown3": "Tarija", + "unknown4": "Tarija" + }, + "coordinates": { + "latitude": -21.511231248, + "longitude": -64.747611401 + } + } + ] + }, + { + "id": 16, + "iso_code": "BR", + "name": "Brazil", + "translations": { + "japanese": "ブラジル", + "english": "Brazil", + "french": "Brésil", + "german": "Brasilien", + "italian": "Brasile", + "spanish": "Brasil", + "chinese_simple": "巴西", + "korean": "브라질", + "dutch": "Brazilië", + "portuguese": "Brasil", + "russian": "Бразилия", + "chinese_traditional": "Brazil", + "unknown1": "Brazil", + "unknown2": "Brazil", + "unknown3": "Brazil", + "unknown4": "Brazil" + }, + "regions": [ + { + "id": 268435456, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": -15.781861195999994, + "longitude": -47.911017766000015 + } + }, + { + "id": 268566528, + "name": "Distrito Federal", + "translations": { + "japanese": "ディストリト・フェデラル州", + "english": "Distrito Federal", + "french": "District Fédéral", + "german": "Distrito Federal", + "italian": "Distretto Federale", + "spanish": "Distrito Federal", + "chinese_simple": "联邦区", + "korean": "브라질 연방구", + "dutch": "Federaal District", + "portuguese": "Distrito Federal", + "russian": "Федеральный округ", + "chinese_traditional": "Distrito Federal", + "unknown1": "Distrito Federal", + "unknown2": "Distrito Federal", + "unknown3": "Distrito Federal", + "unknown4": "Distrito Federal" + }, + "coordinates": { + "latitude": -15.781861195999994, + "longitude": -47.911017766000015 + } + }, + { + "id": 268632064, + "name": "Acre", + "translations": { + "japanese": "アクレ州", + "english": "Acre", + "french": "Acre", + "german": "Acre", + "italian": "Acre", + "spanish": "Acre", + "chinese_simple": "阿克里州", + "korean": "아크리 주", + "dutch": "Acre", + "portuguese": "Acre", + "russian": "Акри", + "chinese_traditional": "Acre", + "unknown1": "Acre", + "unknown2": "Acre", + "unknown3": "Acre", + "unknown4": "Acre" + }, + "coordinates": { + "latitude": -9.964600520000005, + "longitude": -67.79632574600001 + } + }, + { + "id": 268697600, + "name": "Alagoas", + "translations": { + "japanese": "アラゴアス州", + "english": "Alagoas", + "french": "Alagoas", + "german": "Alagoas", + "italian": "Alagoas", + "spanish": "Alagoas", + "chinese_simple": "阿拉戈斯州", + "korean": "알라고아스 주", + "dutch": "Alagoas", + "portuguese": "Alagoas", + "russian": "Алагоас", + "chinese_traditional": "Alagoas", + "unknown1": "Alagoas", + "unknown2": "Alagoas", + "unknown3": "Alagoas", + "unknown4": "Alagoas" + }, + "coordinates": { + "latitude": -9.662476499999997, + "longitude": -35.71616038600001 + } + }, + { + "id": 268763136, + "name": "Amapá", + "translations": { + "japanese": "アマパー州", + "english": "Amapá", + "french": "Amapá", + "german": "Amapá", + "italian": "Amapá", + "spanish": "Amapá", + "chinese_simple": "阿马帕州", + "korean": "아마파 주", + "dutch": "Amapá", + "portuguese": "Amapá", + "russian": "Амапа", + "chinese_traditional": "Amapá", + "unknown1": "Amapá", + "unknown2": "Amapá", + "unknown3": "Amapá", + "unknown4": "Amapá" + }, + "coordinates": { + "latitude": 0.032958984, + "longitude": -51.047622974999996 + } + }, + { + "id": 268828672, + "name": "Amazonas", + "translations": { + "japanese": "アマゾナス州", + "english": "Amazonas", + "french": "Amazonas", + "german": "Amazonas", + "italian": "Amazonas", + "spanish": "Amazonas", + "chinese_simple": "亚马孙州", + "korean": "아마조나스 주", + "dutch": "Amazonas", + "portuguese": "Amazonas", + "russian": "Амазонас", + "chinese_traditional": "Amazonas", + "unknown1": "Amazonas", + "unknown2": "Amazonas", + "unknown3": "Amazonas", + "unknown4": "Amazonas" + }, + "coordinates": { + "latitude": -3.109131848000004, + "longitude": -60.023477461 + } + }, + { + "id": 268894208, + "name": "Bahia", + "translations": { + "japanese": "バイア州", + "english": "Bahia", + "french": "Bahia", + "german": "Bahia", + "italian": "Bahia", + "spanish": "Bahía", + "chinese_simple": "巴伊亚州", + "korean": "바이아 주", + "dutch": "Bahia", + "portuguese": "Bahia", + "russian": "Баия", + "chinese_traditional": "Bahia", + "unknown1": "Bahia", + "unknown2": "Bahia", + "unknown3": "Bahia", + "unknown4": "Bahia" + }, + "coordinates": { + "latitude": -12.980347555999998, + "longitude": -38.51218849700001 + } + }, + { + "id": 268959744, + "name": "Ceará", + "translations": { + "japanese": "セアラ州", + "english": "Ceará", + "french": "Ceará", + "german": "Ceará", + "italian": "Ceará", + "spanish": "Ceará", + "chinese_simple": "塞阿拉州", + "korean": "세아라 주", + "dutch": "Ceará", + "portuguese": "Ceará", + "russian": "Сеара", + "chinese_traditional": "Ceará", + "unknown1": "Ceará", + "unknown2": "Ceará", + "unknown3": "Ceará", + "unknown4": "Ceará" + }, + "coordinates": { + "latitude": -3.768311527999998, + "longitude": -38.57261346600001 + } + }, + { + "id": 269025280, + "name": "Espírito Santo", + "translations": { + "japanese": "エスピリト・サント州", + "english": "Espírito Santo", + "french": "Espírito Santo", + "german": "Espírito Santo", + "italian": "Espírito Santo", + "spanish": "Espírito Santo", + "chinese_simple": "圣埃斯皮里图州", + "korean": "이스피리투산투 주", + "dutch": "Espírito Santo", + "portuguese": "Espírito Santo", + "russian": "Эспириту-Санту", + "chinese_traditional": "Espírito Santo", + "unknown1": "Espírito Santo", + "unknown2": "Espírito Santo", + "unknown3": "Espírito Santo", + "unknown4": "Espírito Santo" + }, + "coordinates": { + "latitude": -20.313721496, + "longitude": -40.346910283 + } + }, + { + "id": 269090816, + "name": "Mato Grosso do Sul", + "translations": { + "japanese": "マット・グロッソ・ド・スル州", + "english": "Mato Grosso do Sul", + "french": "Mato Grosso do Sul", + "german": "Mato Grosso do Sul", + "italian": "Mato Grosso do Sul", + "spanish": "Mato Grosso do Sul", + "chinese_simple": "南马托格罗索州", + "korean": "마투그로수두술 주", + "dutch": "Mato Grosso do Sul", + "portuguese": "Mato Grosso do Sul", + "russian": "Мату-Гросу-ду-Сул", + "chinese_traditional": "Mato Grosso do Sul", + "unknown1": "Mato Grosso do Sul", + "unknown2": "Mato Grosso do Sul", + "unknown3": "Mato Grosso do Sul", + "unknown4": "Mato Grosso do Sul" + }, + "coordinates": { + "latitude": -20.445557432, + "longitude": -54.612696146000005 + } + }, + { + "id": 269156352, + "name": "Maranhão", + "translations": { + "japanese": "マラニョン州", + "english": "Maranhão", + "french": "Maranhão", + "german": "Maranhão", + "italian": "Maranhão", + "spanish": "Maranhão", + "chinese_simple": "马拉尼昂州", + "korean": "마라냥 주", + "dutch": "Maranhão", + "portuguese": "Maranhão", + "russian": "Мараньян", + "chinese_traditional": "Maranhão", + "unknown1": "Maranhão", + "unknown2": "Maranhão", + "unknown3": "Maranhão", + "unknown4": "Maranhão" + }, + "coordinates": { + "latitude": -2.5158701360000038, + "longitude": -44.26354691 + } + }, + { + "id": 269221888, + "name": "Mato Grosso", + "translations": { + "japanese": "マット・グロッソ州", + "english": "Mato Grosso", + "french": "Mato Grosso", + "german": "Mato Grosso", + "italian": "Mato Grosso", + "spanish": "Mato Grosso", + "chinese_simple": "马托格罗索州", + "korean": "마투그로수 주", + "dutch": "Mato Grosso", + "portuguese": "Mato Grosso", + "russian": "Мату-Гросу", + "chinese_traditional": "Mato Grosso", + "unknown1": "Mato Grosso", + "unknown2": "Mato Grosso", + "unknown3": "Mato Grosso", + "unknown4": "Mato Grosso" + }, + "coordinates": { + "latitude": -15.578614127999998, + "longitude": -56.079374939000004 + } + }, + { + "id": 269287424, + "name": "Minas Gerais", + "translations": { + "japanese": "ミナス・ジェライス州", + "english": "Minas Gerais", + "french": "Minas Gerais", + "german": "Minas Gerais", + "italian": "Minas Gerais", + "spanish": "Minas Gerais", + "chinese_simple": "米纳斯吉拉斯州", + "korean": "미나스 제라이스 주", + "dutch": "Minas Gerais", + "portuguese": "Minas Gerais", + "russian": "Минас-Жерайс", + "chinese_traditional": "Minas Gerais", + "unknown1": "Minas Gerais", + "unknown2": "Minas Gerais", + "unknown3": "Minas Gerais", + "unknown4": "Minas Gerais" + }, + "coordinates": { + "latitude": -19.912720523999994, + "longitude": -43.928462991 + } + }, + { + "id": 269352960, + "name": "Pará", + "translations": { + "japanese": "パラー州", + "english": "Pará", + "french": "Pará", + "german": "Pará", + "italian": "Pará", + "spanish": "Pará", + "chinese_simple": "帕拉州", + "korean": "파라 주", + "dutch": "Pará", + "portuguese": "Pará", + "russian": "Пара", + "chinese_traditional": "Pará", + "unknown1": "Pará", + "unknown2": "Pará", + "unknown3": "Pará", + "unknown4": "Pará" + }, + "coordinates": { + "latitude": -1.4447031560000028, + "longitude": -48.482308382000014 + } + }, + { + "id": 269418496, + "name": "Paraíba", + "translations": { + "japanese": "パライーバ州", + "english": "Paraíba", + "french": "Paraíba", + "german": "Paraíba", + "italian": "Paraíba", + "spanish": "Paraíba", + "chinese_simple": "帕拉伊巴州", + "korean": "파라이바 주", + "dutch": "Paraíba", + "portuguese": "Paraíba", + "russian": "Параиба", + "chinese_traditional": "Paraíba", + "unknown1": "Paraíba", + "unknown2": "Paraíba", + "unknown3": "Paraíba", + "unknown4": "Paraíba" + }, + "coordinates": { + "latitude": -7.113648404000003, + "longitude": -34.864717641 + } + }, + { + "id": 269484032, + "name": "Paraná", + "translations": { + "japanese": "パラナ州", + "english": "Paraná", + "french": "Paraná", + "german": "Paraná", + "italian": "Paraná", + "spanish": "Paraná", + "chinese_simple": "巴拉那州", + "korean": "파라나 주", + "dutch": "Paraná", + "portuguese": "Paraná", + "russian": "Парана", + "chinese_traditional": "Paraná", + "unknown1": "Paraná", + "unknown2": "Paraná", + "unknown3": "Paraná", + "unknown4": "Paraná" + }, + "coordinates": { + "latitude": -25.411377688, + "longitude": -49.245860263 + } + }, + { + "id": 269549568, + "name": "Piauí", + "translations": { + "japanese": "ピアウイー州", + "english": "Piauí", + "french": "Piauí", + "german": "Piauí", + "italian": "Piauí", + "spanish": "Piauí", + "chinese_simple": "皮奥伊州", + "korean": "피아우이 주", + "dutch": "Piauí", + "portuguese": "Piauí", + "russian": "Пиауи", + "chinese_traditional": "Piauí", + "unknown1": "Piauí", + "unknown2": "Piauí", + "unknown3": "Piauí", + "unknown4": "Piauí" + }, + "coordinates": { + "latitude": -5.081177724, + "longitude": -42.81334765400001 + } + }, + { + "id": 269615104, + "name": "Rio de Janeiro", + "translations": { + "japanese": "リオ・デ・ジャネイロ州", + "english": "Rio de Janeiro", + "french": "État de Rio de Janeiro", + "german": "Rio de Janeiro", + "italian": "Rio de Janeiro", + "spanish": "Río de Janeiro", + "chinese_simple": "里约热内卢州", + "korean": "리우데자네이루 주", + "dutch": "Rio de Janeiro", + "portuguese": "Rio de Janeiro", + "russian": "Рио-де-Жанейро", + "chinese_traditional": "Rio de Janeiro", + "unknown1": "Rio de Janeiro", + "unknown2": "Rio de Janeiro", + "unknown3": "Rio de Janeiro", + "unknown4": "Rio de Janeiro" + }, + "coordinates": { + "latitude": -22.895508575999997, + "longitude": -43.230829258 + } + }, + { + "id": 269680640, + "name": "Rio Grande do Norte", + "translations": { + "japanese": "リオ・グランデ・ド・ノルテ州", + "english": "Rio Grande do Norte", + "french": "Rio Grande do Norte", + "german": "Rio Grande do Norte", + "italian": "Rio Grande do Norte", + "spanish": "Rio Grande do Norte", + "chinese_simple": "北里奥格兰德州", + "korean": "히우그란지두노르치 주", + "dutch": "Rio Grande do Norte", + "portuguese": "Rio Grande do Norte", + "russian": "Риу-Гранди-ду-Норти", + "chinese_traditional": "Rio Grande do Norte", + "unknown1": "Rio Grande do Norte", + "unknown2": "Rio Grande do Norte", + "unknown3": "Rio Grande do Norte", + "unknown4": "Rio Grande do Norte" + }, + "coordinates": { + "latitude": -5.7788095519999985, + "longitude": -35.210787917999994 + } + }, + { + "id": 269746176, + "name": "Rio Grande do Sul", + "translations": { + "japanese": "リオ・グランデ・ド・スル州", + "english": "Rio Grande do Sul", + "french": "Rio Grande do Sul", + "german": "Rio Grande do Sul", + "italian": "Rio Grande do Sul", + "spanish": "Rio Grande do Sul", + "chinese_simple": "南里奥格兰德州", + "korean": "히우그란지두술 주", + "dutch": "Rio Grande do Sul", + "portuguese": "Rio Grande do Sul", + "russian": "Риу-Гранди-ду-Сул", + "chinese_traditional": "Rio Grande do Sul", + "unknown1": "Rio Grande do Sul", + "unknown2": "Rio Grande do Sul", + "unknown3": "Rio Grande do Sul", + "unknown4": "Rio Grande do Sul" + }, + "coordinates": { + "latitude": -30.031128611999996, + "longitude": -51.195938807999994 + } + }, + { + "id": 269811712, + "name": "Rondônia", + "translations": { + "japanese": "ロンドニア州", + "english": "Rondônia", + "french": "Rondônia", + "german": "Rondônia", + "italian": "Rondônia", + "spanish": "Rondônia", + "chinese_simple": "朗多尼亚州", + "korean": "혼도니아 주", + "dutch": "Rondônia", + "portuguese": "Rondônia", + "russian": "Рондония", + "chinese_traditional": "Rondônia", + "unknown1": "Rondônia", + "unknown2": "Rondônia", + "unknown3": "Rondônia", + "unknown4": "Rondônia" + }, + "coordinates": { + "latitude": -8.761597604000002, + "longitude": -63.896168656 + } + }, + { + "id": 269877248, + "name": "Roraima", + "translations": { + "japanese": "ロライマ州", + "english": "Roraima", + "french": "Roraima", + "german": "Roraima", + "italian": "Roraima", + "spanish": "Roraima", + "chinese_simple": "罗赖马州", + "korean": "호라이마 주", + "dutch": "Roraima", + "portuguese": "Roraima", + "russian": "Рорайма", + "chinese_traditional": "Roraima", + "unknown1": "Roraima", + "unknown2": "Roraima", + "unknown3": "Roraima", + "unknown4": "Roraima" + }, + "coordinates": { + "latitude": 2.812499968, + "longitude": -60.666179404000005 + } + }, + { + "id": 269942784, + "name": "Santa Catarina", + "translations": { + "japanese": "サンタ・カタリーナ州", + "english": "Santa Catarina", + "french": "Santa Catarina", + "german": "Santa Catarina", + "italian": "Santa Catarina", + "spanish": "Santa Catarina", + "chinese_simple": "圣卡塔琳娜州", + "korean": "산타카타리나 주", + "dutch": "Santa Catarina", + "portuguese": "Santa Catarina", + "russian": "Санта-Катарина", + "chinese_traditional": "Santa Catarina", + "unknown1": "Santa Catarina", + "unknown2": "Santa Catarina", + "unknown3": "Santa Catarina", + "unknown4": "Santa Catarina" + }, + "coordinates": { + "latitude": -27.581177468, + "longitude": -48.564706067 + } + }, + { + "id": 270008320, + "name": "São Paulo", + "translations": { + "japanese": "サン・パウロ州", + "english": "São Paulo", + "french": "État de São Paulo", + "german": "São Paulo", + "italian": "San Paolo", + "spanish": "São Paulo", + "chinese_simple": "圣保罗州", + "korean": "상파울루 주", + "dutch": "São Paulo", + "portuguese": "São Paulo", + "russian": "Сан-Паулу", + "chinese_traditional": "São Paulo", + "unknown1": "São Paulo", + "unknown2": "São Paulo", + "unknown3": "São Paulo", + "unknown4": "São Paulo" + }, + "coordinates": { + "latitude": -23.532715600000003, + "longitude": -46.614627522000006 + } + }, + { + "id": 270073856, + "name": "Sergipe", + "translations": { + "japanese": "セルジッペ州", + "english": "Sergipe", + "french": "Sergipe", + "german": "Sergipe", + "italian": "Sergipe", + "spanish": "Sergipe", + "chinese_simple": "塞尔希培州", + "korean": "세르지피 주", + "dutch": "Sergipe", + "portuguese": "Sergipe", + "russian": "Сержипи", + "chinese_traditional": "Sergipe", + "unknown1": "Sergipe", + "unknown2": "Sergipe", + "unknown3": "Sergipe", + "unknown4": "Sergipe" + }, + "coordinates": { + "latitude": -10.914917892000005, + "longitude": -37.06198924099999 + } + }, + { + "id": 270139392, + "name": "Goiás", + "translations": { + "japanese": "ゴイアス州", + "english": "Goiás", + "french": "Goiás", + "german": "Goiás", + "italian": "Goiás", + "spanish": "Goiás", + "chinese_simple": "戈亚斯州", + "korean": "고이아스 주", + "dutch": "Goiás", + "portuguese": "Goiás", + "russian": "Гояс", + "chinese_traditional": "Goiás", + "unknown1": "Goiás", + "unknown2": "Goiás", + "unknown3": "Goiás", + "unknown4": "Goiás" + }, + "coordinates": { + "latitude": -16.6662606, + "longitude": -49.26233980000001 + } + }, + { + "id": 270204928, + "name": "Pernambuco", + "translations": { + "japanese": "ペルナンブコ州", + "english": "Pernambuco", + "french": "Pernambouc", + "german": "Pernambuco", + "italian": "Pernambuco", + "spanish": "Pernambuco", + "chinese_simple": "伯南布哥州", + "korean": "페르남부쿠 주", + "dutch": "Pernambuco", + "portuguese": "Pernambuco", + "russian": "Пернамбуку", + "chinese_traditional": "Pernambuco", + "unknown1": "Pernambuco", + "unknown2": "Pernambuco", + "unknown3": "Pernambuco", + "unknown4": "Pernambuco" + }, + "coordinates": { + "latitude": -8.047486284000001, + "longitude": -34.89767671499999 + } + }, + { + "id": 270270464, + "name": "Tocantins", + "translations": { + "japanese": "トカンティンス州", + "english": "Tocantins", + "french": "Tocantins", + "german": "Tocantins", + "italian": "Tocantins", + "spanish": "Tocantins", + "chinese_simple": "托坎廷斯州", + "korean": "토칸칭스 주", + "dutch": "Tocantins", + "portuguese": "Tocantins", + "russian": "Токантинс", + "chinese_traditional": "Tocantins", + "unknown1": "Tocantins", + "unknown2": "Tocantins", + "unknown3": "Tocantins", + "unknown4": "Tocantins" + }, + "coordinates": { + "latitude": -10.217286063999993, + "longitude": -48.27356757999999 + } + } + ] + }, + { + "id": 17, + "iso_code": "VG", + "name": "British Virgin Islands", + "translations": { + "japanese": "英領ヴァージン諸島", + "english": "British Virgin Islands", + "french": "Îles Vierges britanniques", + "german": "Britische Jungferninseln", + "italian": "Isole Vergini Britanniche", + "spanish": "Islas Vírgenes Británicas", + "chinese_simple": "英属维尔京群岛", + "korean": "영국령 버진아일랜드", + "dutch": "Britse Maagdeneilanden", + "portuguese": "Ilhas Virgens Britânicas", + "russian": "Британские Виргинские острова", + "chinese_traditional": "British Virgin Islands", + "unknown1": "British Virgin Islands", + "unknown2": "British Virgin Islands", + "unknown3": "British Virgin Islands", + "unknown4": "British Virgin Islands" + }, + "regions": [ + { + "id": 285212672, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": 18.413085728, + "longitude": -64.615775105 + } + }, + { + "id": 285278208, + "name": "British Virgin Islands", + "translations": { + "japanese": "英領ヴァージン諸島", + "english": "British Virgin Islands", + "french": "Îles Vierges britanniques", + "german": "Britische Jungferninseln", + "italian": "Isole Vergini Britanniche", + "spanish": "Islas Vírgenes Británicas", + "chinese_simple": "英属维尔京群岛", + "korean": "영국령 버진아일랜드", + "dutch": "Britse Maagdeneilanden", + "portuguese": "Ilhas Virgens Britânicas", + "russian": "Британские Виргинские острова", + "chinese_traditional": "British Virgin Islands", + "unknown1": "British Virgin Islands", + "unknown2": "British Virgin Islands", + "unknown3": "British Virgin Islands", + "unknown4": "British Virgin Islands" + }, + "coordinates": { + "latitude": 18.413085728, + "longitude": -64.615775105 + } + } + ] + }, + { + "id": 18, + "iso_code": "CA", + "name": "Canada", + "translations": { + "japanese": "カナダ", + "english": "Canada", + "french": "Canada", + "german": "Kanada", + "italian": "Canada", + "spanish": "Canadá", + "chinese_simple": "加拿大", + "korean": "캐나다", + "dutch": "Canada", + "portuguese": "Canadá", + "russian": "Канада", + "chinese_traditional": "Canada", + "unknown1": "Canada", + "unknown2": "Canada", + "unknown3": "Canada", + "unknown4": "Canada" + }, + "regions": [ + { + "id": 301989888, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": 45.263671360000004, + "longitude": -75.744955759 + } + }, + { + "id": 302120960, + "name": "Ontario", + "translations": { + "japanese": "オンタリオ州", + "english": "Ontario", + "french": "Ontario", + "german": "Ontario", + "italian": "Ontario", + "spanish": "Ontario", + "chinese_simple": "安大略省", + "korean": "온타리오 주", + "dutch": "Ontario", + "portuguese": "Ontário", + "russian": "Онтарио", + "chinese_traditional": "Ontario", + "unknown1": "Ontario", + "unknown2": "Ontario", + "unknown3": "Ontario", + "unknown4": "Ontario" + }, + "coordinates": { + "latitude": 45.263671360000004, + "longitude": -75.744955759 + } + }, + { + "id": 302186496, + "name": "Alberta", + "translations": { + "japanese": "アルバータ州", + "english": "Alberta", + "french": "Alberta", + "german": "Alberta", + "italian": "Alberta", + "spanish": "Alberta", + "chinese_simple": "艾伯塔省", + "korean": "앨버타 주", + "dutch": "Alberta", + "portuguese": "Alberta", + "russian": "Альберта", + "chinese_traditional": "Alberta", + "unknown1": "Alberta", + "unknown2": "Alberta", + "unknown3": "Alberta", + "unknown4": "Alberta" + }, + "coordinates": { + "latitude": 53.547362672, + "longitude": -113.466615952 + } + }, + { + "id": 302252032, + "name": "British Columbia", + "translations": { + "japanese": "ブリティッシュ・コロンビア州", + "english": "British Columbia", + "french": "Colombie-Britannique", + "german": "Britisch-Kolumbien", + "italian": "Columbia Britannica", + "spanish": "Columbia Británica", + "chinese_simple": "不列颠哥伦比亚省", + "korean": "브리티시컬럼비아 주", + "dutch": "Brits-Columbia", + "portuguese": "Colúmbia Britânica", + "russian": "Британская Колумбия", + "chinese_traditional": "British Columbia", + "unknown1": "British Columbia", + "unknown2": "British Columbia", + "unknown3": "British Columbia", + "unknown4": "British Columbia" + }, + "coordinates": { + "latitude": 48.427733824, + "longitude": -123.36532451 + } + }, + { + "id": 302317568, + "name": "Manitoba", + "translations": { + "japanese": "マニトバ州", + "english": "Manitoba", + "french": "Manitoba", + "german": "Manitoba", + "italian": "Manitoba", + "spanish": "Manitoba", + "chinese_simple": "马尼托巴省", + "korean": "매니토바 주", + "dutch": "Manitoba", + "portuguese": "Manitoba", + "russian": "Манитоба", + "chinese_traditional": "Manitoba", + "unknown1": "Manitoba", + "unknown2": "Manitoba", + "unknown3": "Manitoba", + "unknown4": "Manitoba" + }, + "coordinates": { + "latitude": 49.883422284, + "longitude": -97.146381143 + } + }, + { + "id": 302383104, + "name": "New Brunswick", + "translations": { + "japanese": "ニュー・ブランズウィック州", + "english": "New Brunswick", + "french": "Nouveau-Brunswick", + "german": "Neubraunschweig", + "italian": "Nuovo Brunswick", + "spanish": "Nuevo Brunswick", + "chinese_simple": "新不伦瑞克省", + "korean": "뉴브런즈윅 주", + "dutch": "Nieuw-Brunswijk", + "portuguese": "Nova Brunswick", + "russian": "Нью-Брансуик", + "chinese_traditional": "New Brunswick", + "unknown1": "New Brunswick", + "unknown2": "New Brunswick", + "unknown3": "New Brunswick", + "unknown4": "New Brunswick" + }, + "coordinates": { + "latitude": 45.944823696, + "longitude": -66.664730872 + } + }, + { + "id": 302448640, + "name": "Newfoundland and Labrador", + "translations": { + "japanese": "ニューファンドランド・ラブラドール州", + "english": "Newfoundland and Labrador", + "french": "Terre-Neuve et Labrador", + "german": "Neufundland und Labrador", + "italian": "Terranova e Labrador", + "spanish": "Terranova y Labrador", + "chinese_simple": "纽芬兰与拉布拉多省", + "korean": "뉴펀들랜드래브라도 주", + "dutch": "Newfoundland en Labrador", + "portuguese": "Terranova e Labrador", + "russian": "Ньюфаундленд и Лабрадор", + "chinese_traditional": "Newfoundland and Labrador", + "unknown1": "Newfoundland and Labrador", + "unknown2": "Newfoundland and Labrador", + "unknown3": "Newfoundland and Labrador", + "unknown4": "Newfoundland and Labrador" + }, + "coordinates": { + "latitude": 47.565307076, + "longitude": -52.728535749 + } + }, + { + "id": 302514176, + "name": "Nova Scotia", + "translations": { + "japanese": "ノバ・スコシア州", + "english": "Nova Scotia", + "french": "Nouvelle-Écosse", + "german": "Neuschottland", + "italian": "Nuova Scozia", + "spanish": "Nueva Escocia", + "chinese_simple": "新斯科舍省", + "korean": "노바스코샤 주", + "dutch": "Nova Scotia", + "portuguese": "Nova Escócia", + "russian": "Новая Шотландия", + "chinese_traditional": "Nova Scotia", + "unknown1": "Nova Scotia", + "unknown2": "Nova Scotia", + "unknown3": "Nova Scotia", + "unknown4": "Nova Scotia" + }, + "coordinates": { + "latitude": 44.895629372, + "longitude": -63.49516658900001 + } + }, + { + "id": 302579712, + "name": "Prince Edward Island", + "translations": { + "japanese": "プリンス・エドワード・アイランド州", + "english": "Prince Edward Island", + "french": "Île-du-Prince-Édouard", + "german": "Prinz-Edward-Insel", + "italian": "Isola del Principe Edoardo", + "spanish": "Isla del Príncipe Eduardo", + "chinese_simple": "爱德华王子岛省", + "korean": "프린스에드워드아일랜드 주", + "dutch": "Prins Edwardeiland", + "portuguese": "Ilha do Príncipe Eduardo", + "russian": "Остров Принца Эдуарда", + "chinese_traditional": "Prince Edward Island", + "unknown1": "Prince Edward Island", + "unknown2": "Prince Edward Island", + "unknown3": "Prince Edward Island", + "unknown4": "Prince Edward Island" + }, + "coordinates": { + "latitude": 46.235961388, + "longitude": -63.132616775 + } + }, + { + "id": 302645248, + "name": "Quebec", + "translations": { + "japanese": "ケベック州", + "english": "Quebec", + "french": "Québec", + "german": "Québec", + "italian": "Québec", + "spanish": "Quebec", + "chinese_simple": "魁北克省", + "korean": "퀘벡 주", + "dutch": "Québec", + "portuguese": "Quebeque", + "russian": "Квебек", + "chinese_traditional": "Quebec", + "unknown1": "Quebec", + "unknown2": "Quebec", + "unknown3": "Quebec", + "unknown4": "Quebec" + }, + "coordinates": { + "latitude": 46.80175728, + "longitude": -71.207589905 + } + }, + { + "id": 302710784, + "name": "Saskatchewan", + "translations": { + "japanese": "サスカチュワン州", + "english": "Saskatchewan", + "french": "Saskatchewan", + "german": "Saskatchewan", + "italian": "Saskatchewan", + "spanish": "Saskatchewan", + "chinese_simple": "萨斯喀彻温省", + "korean": "서스캐처원 주", + "dutch": "Saskatchewan", + "portuguese": "Saskatchewan", + "russian": "Саскачеван", + "chinese_traditional": "Saskatchewan", + "unknown1": "Saskatchewan", + "unknown2": "Saskatchewan", + "unknown3": "Saskatchewan", + "unknown4": "Saskatchewan" + }, + "coordinates": { + "latitude": 50.449218176, + "longitude": -104.617104583 + } + }, + { + "id": 302776320, + "name": "Yukon", + "translations": { + "japanese": "ユーコン準州", + "english": "Yukon", + "french": "Territoire du Yukon", + "german": "Yukon", + "italian": "Yukon", + "spanish": "Yukón", + "chinese_simple": "育空地区", + "korean": "유콘 준주", + "dutch": "Yukon", + "portuguese": "Yukon", + "russian": "Юкон", + "chinese_traditional": "Yukon", + "unknown1": "Yukon", + "unknown2": "Yukon", + "unknown3": "Yukon", + "unknown4": "Yukon" + }, + "coordinates": { + "latitude": 60.715941692, + "longitude": -135.049316243 + } + }, + { + "id": 302841856, + "name": "Northwest Territories", + "translations": { + "japanese": "ノースウェスト準州", + "english": "Northwest Territories", + "french": "Territoires du Nord-Ouest", + "german": "Nordwest-Territorien", + "italian": "Territori del Nord-Ovest", + "spanish": "Territorios del Noroeste", + "chinese_simple": "西北地区", + "korean": "노스웨스트 준주", + "dutch": "Northwest Territories", + "portuguese": "Territórios do Noroeste", + "russian": "Северо-Западные территории", + "chinese_traditional": "Northwest Territories", + "unknown1": "Northwest Territories", + "unknown2": "Northwest Territories", + "unknown3": "Northwest Territories", + "unknown4": "Northwest Territories" + }, + "coordinates": { + "latitude": 62.451781516, + "longitude": -114.351017771 + } + }, + { + "id": 302907392, + "name": "Nunavut", + "translations": { + "japanese": "ヌナブト準州", + "english": "Nunavut", + "french": "Territoire du Nunavut", + "german": "Nunavut", + "italian": "Nunavut", + "spanish": "Nunavut", + "chinese_simple": "努纳维特地区", + "korean": "누나부트 준주", + "dutch": "Nunavut", + "portuguese": "Nunavut", + "russian": "Нунавут", + "chinese_traditional": "Nunavut", + "unknown1": "Nunavut", + "unknown2": "Nunavut", + "unknown3": "Nunavut", + "unknown4": "Nunavut" + }, + "coordinates": { + "latitude": 63.748168220000004, + "longitude": -68.510439016 + } + } + ] + }, + { + "id": 19, + "iso_code": "KY", + "name": "Cayman Islands", + "translations": { + "japanese": "ケイマン諸島", + "english": "Cayman Islands", + "french": "Îles Caïmans", + "german": "Kaimaninseln", + "italian": "Isole Cayman", + "spanish": "Islas Caimán", + "chinese_simple": "开曼群岛", + "korean": "케이맨 제도", + "dutch": "Kaaimaneilanden", + "portuguese": "Ilhas Caimão", + "russian": "Каймановы острова", + "chinese_traditional": "Cayman Islands", + "unknown1": "Cayman Islands", + "unknown2": "Cayman Islands", + "unknown3": "Cayman Islands", + "unknown4": "Cayman Islands" + }, + "regions": [ + { + "id": 318767104, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": 19.297485132, + "longitude": -81.380957413 + } + }, + { + "id": 318832640, + "name": "Cayman Islands", + "translations": { + "japanese": "ケイマン諸島", + "english": "Cayman Islands", + "french": "Îles Caïmans", + "german": "Kaimaninseln", + "italian": "Isole Cayman", + "spanish": "Islas Caimán", + "chinese_simple": "开曼群岛", + "korean": "케이맨 제도", + "dutch": "Kaaimaneilanden", + "portuguese": "Ilhas Caimão", + "russian": "Каймановы острова", + "chinese_traditional": "Cayman Islands", + "unknown1": "Cayman Islands", + "unknown2": "Cayman Islands", + "unknown3": "Cayman Islands", + "unknown4": "Cayman Islands" + }, + "coordinates": { + "latitude": 19.297485132, + "longitude": -81.380957413 + } + } + ] + }, + { + "id": 20, + "iso_code": "CL", + "name": "Chile", + "translations": { + "japanese": "チリ", + "english": "Chile", + "french": "Chili", + "german": "Chile", + "italian": "Cile", + "spanish": "Chile", + "chinese_simple": "智利", + "korean": "칠레", + "dutch": "Chili", + "portuguese": "Chile", + "russian": "Чили", + "chinese_traditional": "Chile", + "unknown1": "Chile", + "unknown2": "Chile", + "unknown3": "Chile", + "unknown4": "Chile" + }, + "regions": [ + { + "id": 335544320, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": -33.44787662, + "longitude": -70.663765184 + } + }, + { + "id": 335675392, + "name": "Región Metropolitana", + "translations": { + "japanese": "レジョン・メトロポリタナ州", + "english": "Región Metropolitana", + "french": "Région Métropolitaine de Santiago", + "german": "Región Metropolitana", + "italian": "Regione Metropolitana di Santiago", + "spanish": "Región Metropolitana", + "chinese_simple": "圣地亚哥首都区", + "korean": "산티아고 수도주", + "dutch": "Región Metropolitana", + "portuguese": "Região Metropolitana", + "russian": "Столичный округ", + "chinese_traditional": "Región Metropolitana", + "unknown1": "Región Metropolitana", + "unknown2": "Región Metropolitana", + "unknown3": "Región Metropolitana", + "unknown4": "Región Metropolitana" + }, + "coordinates": { + "latitude": -33.44787662, + "longitude": -70.663765184 + } + }, + { + "id": 335740928, + "name": "Valparaíso", + "translations": { + "japanese": "バルパライソ州", + "english": "Valparaíso", + "french": "Valparaiso", + "german": "Valparaíso (Region V)", + "italian": "Valparaíso", + "spanish": "Valparaíso", + "chinese_simple": "瓦尔帕莱索大区", + "korean": "발파라이소 주", + "dutch": "Valparaíso", + "portuguese": "Valparaíso", + "russian": "Вальпараисо", + "chinese_traditional": "Valparaíso", + "unknown1": "Valparaíso", + "unknown2": "Valparaíso", + "unknown3": "Valparaíso", + "unknown4": "Valparaíso" + }, + "coordinates": { + "latitude": -33.046875648, + "longitude": -71.597605614 + } + }, + { + "id": 335806464, + "name": "Aisén del General Carlos Ibáñez del Campo", + "translations": { + "japanese": "アイセン・デル・G・カルロス・イバニェス・デル・カンポ州", + "english": "Aisén del General Carlos Ibáñez del Campo", + "french": "Aisén del General Carlos Ibáñez del Campo", + "german": "Aisén (Region XI)", + "italian": "Aisén del General Carlos Ibáñez del Campo", + "spanish": "Aisén del General Carlos Ibáñez del Campo", + "chinese_simple": "伊瓦涅斯将军的艾森大区", + "korean": "아이센 주", + "dutch": "Aysén del General Carlos Ibáñez del Campo", + "portuguese": "Aisén del General Carlos Ibáñez del Campo", + "russian": "Айсен", + "chinese_traditional": "Aisén del General Carlos Ibáñez del Campo", + "unknown1": "Aisén del General Carlos Ibáñez del Campo", + "unknown2": "Aisén del General Carlos Ibáñez del Campo", + "unknown3": "Aisén del General Carlos Ibáñez del Campo", + "unknown4": "Aisén del General Carlos Ibáñez del Campo" + }, + "coordinates": { + "latitude": -45.565796404, + "longitude": -72.064525829 + } + }, + { + "id": 335872000, + "name": "Antofagasta", + "translations": { + "japanese": "アントファガスタ州", + "english": "Antofagasta", + "french": "Antofagasta", + "german": "Antofagasta (Region II)", + "italian": "Antofagasta", + "spanish": "Antofagasta", + "chinese_simple": "安托法加斯塔大区", + "korean": "안토파가스타 주", + "dutch": "Antofagasta", + "portuguese": "Antofagasta", + "russian": "Антофагаста", + "chinese_traditional": "Antofagasta", + "unknown1": "Antofagasta", + "unknown2": "Antofagasta", + "unknown3": "Antofagasta", + "unknown4": "Antofagasta" + }, + "coordinates": { + "latitude": -23.648072044000003, + "longitude": -70.394599413 + } + }, + { + "id": 335937536, + "name": "Araucanía", + "translations": { + "japanese": "アラウカニア州", + "english": "Araucanía", + "french": "Araucanie", + "german": "Araukanien (Region IX)", + "italian": "Araucanía", + "spanish": "Araucanía", + "chinese_simple": "阿劳卡尼亚大区", + "korean": "아라우카니아 주", + "dutch": "Araucanía", + "portuguese": "Araucanía", + "russian": "Араукания", + "chinese_traditional": "Araucanía", + "unknown1": "Araucanía", + "unknown2": "Araucanía", + "unknown3": "Araucanía", + "unknown4": "Araucanía" + }, + "coordinates": { + "latitude": -38.732300388, + "longitude": -72.597364192 + } + }, + { + "id": 336003072, + "name": "Atacama", + "translations": { + "japanese": "アタカマ州", + "english": "Atacama", + "french": "Atacama", + "german": "Atacama (Region III)", + "italian": "Atacama", + "spanish": "Atacama", + "chinese_simple": "阿塔卡马大区", + "korean": "아타카마 주", + "dutch": "Atacama", + "portuguese": "Atacama", + "russian": "Атакама", + "chinese_traditional": "Atacama", + "unknown1": "Atacama", + "unknown2": "Atacama", + "unknown3": "Atacama", + "unknown4": "Atacama" + }, + "coordinates": { + "latitude": -27.361450908000002, + "longitude": -70.328681265 + } + }, + { + "id": 336068608, + "name": "Bío-Bío", + "translations": { + "japanese": "ビオビオ州", + "english": "Bío-Bío", + "french": "Biobío", + "german": "Bío-Bío (Region VIII)", + "italian": "Biobío", + "spanish": "Bío-Bío", + "chinese_simple": "比奥比奥大区", + "korean": "비오비오 주", + "dutch": "Bío-Bío", + "portuguese": "Bio-Bio", + "russian": "Био-Био", + "chinese_traditional": "Bío-Bío", + "unknown1": "Bío-Bío", + "unknown2": "Bío-Bío", + "unknown3": "Bío-Bío", + "unknown4": "Bío-Bío" + }, + "coordinates": { + "latitude": -36.831665644, + "longitude": -73.04780487000001 + } + }, + { + "id": 336134144, + "name": "Coquimbo", + "translations": { + "japanese": "コキンボ州", + "english": "Coquimbo", + "french": "Coquimbo", + "german": "Coquimbo (Region IV)", + "italian": "Coquimbo", + "spanish": "Coquimbo", + "chinese_simple": "科金博大区", + "korean": "코킴보 주", + "dutch": "Coquimbo", + "portuguese": "Coquimbo", + "russian": "Кокимбо", + "chinese_traditional": "Coquimbo", + "unknown1": "Coquimbo", + "unknown2": "Coquimbo", + "unknown3": "Coquimbo", + "unknown4": "Coquimbo" + }, + "coordinates": { + "latitude": -29.948731152, + "longitude": -71.339426201 + } + }, + { + "id": 336199680, + "name": "Libertador General Bernardo O'Higgins", + "translations": { + "japanese": "L・ベルナルド・オヒギンス州", + "english": "Libertador General Bernardo O'Higgins", + "french": "Libertador General Bernardo O'Higgins", + "german": "Libertador General Bernardo O'Higgins (Region VI)", + "italian": "Libertador General Bernardo O'Higgins", + "spanish": "Libertador General Bernardo O'Higgins", + "chinese_simple": "奥伊金斯将军解放者大区", + "korean": "오이긴스 주", + "dutch": "Libertador General Bernardo O'Higgins", + "portuguese": "Libertador General Bernardo O'Higgins", + "russian": "О’Хиггинс", + "chinese_traditional": "Libertador General Bernardo O'Higgins", + "unknown1": "Libertador General Bernardo O'Higgins", + "unknown2": "Libertador General Bernardo O'Higgins", + "unknown3": "Libertador General Bernardo O'Higgins", + "unknown4": "Libertador General Bernardo O'Higgins" + }, + "coordinates": { + "latitude": -34.16198794, + "longitude": -70.746162869 + } + }, + { + "id": 336265216, + "name": "Los Lagos", + "translations": { + "japanese": "ロス・ラゴス州", + "english": "Los Lagos", + "french": "Los Lagos", + "german": "Los Lagos (Region X)", + "italian": "Los Lagos", + "spanish": "Los Lagos", + "chinese_simple": "湖大区", + "korean": "로스라고스 주", + "dutch": "Los Lagos", + "portuguese": "Los Lagos", + "russian": "Лос-Лагос", + "chinese_traditional": "Los Lagos", + "unknown1": "Los Lagos", + "unknown2": "Los Lagos", + "unknown3": "Los Lagos", + "unknown4": "Los Lagos" + }, + "coordinates": { + "latitude": -41.46789606, + "longitude": -72.932448111 + } + }, + { + "id": 336330752, + "name": "Magallanes y Antártica Chilena", + "translations": { + "japanese": "マガリャネス州", + "english": "Magallanes y Antártica Chilena", + "french": "Magellan et Antarctique Chilienne", + "german": "Magallanes (Region XII) und Chilenische Antarktis", + "italian": "Magellane e Antartide Cilena", + "spanish": "Magallanes y Antártica Chilena", + "chinese_simple": "麦哲伦-智利南极大区", + "korean": "마가야네스 주", + "dutch": "Magallanes y de la Antártica Chilena", + "portuguese": "Magalhães e Antártica Chilena", + "russian": "Магальянес и Чилийская Антарктика", + "chinese_traditional": "Magallanes y Antártica Chilena", + "unknown1": "Magallanes y Antártica Chilena", + "unknown2": "Magallanes y Antártica Chilena", + "unknown3": "Magallanes y Antártica Chilena", + "unknown4": "Magallanes y Antártica Chilena" + }, + "coordinates": { + "latitude": -53.146362724, + "longitude": -70.910958239 + } + }, + { + "id": 336396288, + "name": "Maule", + "translations": { + "japanese": "マウレ州", + "english": "Maule", + "french": "Maule", + "german": "Maule (Region VII)", + "italian": "Maule", + "spanish": "Maule", + "chinese_simple": "马乌莱大区", + "korean": "마울레 주", + "dutch": "Maule", + "portuguese": "Maule", + "russian": "Мауле", + "chinese_traditional": "Maule", + "unknown1": "Maule", + "unknown2": "Maule", + "unknown3": "Maule", + "unknown4": "Maule" + }, + "coordinates": { + "latitude": -35.430908824, + "longitude": -71.663523762 + } + }, + { + "id": 336461824, + "name": "Tarapacá", + "translations": { + "japanese": "タラパカ州", + "english": "Tarapacá", + "french": "Tarapacá", + "german": "Tarapacá (Region I)", + "italian": "Tarapacá", + "spanish": "Tarapacá", + "chinese_simple": "塔拉帕卡大区", + "korean": "타라파카 주", + "dutch": "Tarapacá", + "portuguese": "Tarapacá", + "russian": "Тарапака", + "chinese_traditional": "Tarapacá", + "unknown1": "Tarapacá", + "unknown2": "Tarapacá", + "unknown3": "Tarapacá", + "unknown4": "Tarapacá" + }, + "coordinates": { + "latitude": -20.214844544, + "longitude": -70.163885895 + } + } + ] + }, + { + "id": 21, + "iso_code": "CO", + "name": "Colombia", + "translations": { + "japanese": "コロンビア", + "english": "Colombia", + "french": "Colombie", + "german": "Kolumbien", + "italian": "Colombia", + "spanish": "Colombia", + "chinese_simple": "哥伦比亚", + "korean": "콜롬비아", + "dutch": "Colombia", + "portuguese": "Colômbia", + "russian": "Колумбия", + "chinese_traditional": "Colombia", + "unknown1": "Colombia", + "unknown2": "Colombia", + "unknown3": "Colombia", + "unknown4": "Colombia" + }, + "regions": [ + { + "id": 352321536, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": 4.246215772, + "longitude": -74.179399744 + } + }, + { + "id": 352452608, + "name": "Distrito Capital", + "translations": { + "japanese": "ディストリト・キャピタル", + "english": "Distrito Capital", + "french": "District Capital de Santa Fe de Bogotá", + "german": "Bogotá D.C.", + "italian": "Distretto Capitale", + "spanish": "Distrito Capital", + "chinese_simple": "波哥大首都区", + "korean": "콜롬비아 수도주", + "dutch": "Hoofdstedelijk District", + "portuguese": "Distrito Capital", + "russian": "Столичный округ", + "chinese_traditional": "Distrito Capital", + "unknown1": "Distrito Capital", + "unknown2": "Distrito Capital", + "unknown3": "Distrito Capital", + "unknown4": "Distrito Capital" + }, + "coordinates": { + "latitude": 4.246215772, + "longitude": -74.179399744 + } + }, + { + "id": 352518144, + "name": "Cundinamarca", + "translations": { + "japanese": "クンディナマルカ県", + "english": "Cundinamarca", + "french": "Cundinamarca", + "german": "Cundinamarca", + "italian": "Cundinamarca", + "spanish": "Cundinamarca", + "chinese_simple": "昆迪纳马卡省", + "korean": "쿤디나마르카 주", + "dutch": "Cundinamarca", + "portuguese": "Cundinamarca", + "russian": "Кундинамарка", + "chinese_traditional": "Cundinamarca", + "unknown1": "Cundinamarca", + "unknown2": "Cundinamarca", + "unknown3": "Cundinamarca", + "unknown4": "Cundinamarca" + }, + "coordinates": { + "latitude": 4.597778268, + "longitude": -74.080522522 + } + }, + { + "id": 352583680, + "name": "Amazonas", + "translations": { + "japanese": "アマソナス県", + "english": "Amazonas", + "french": "Amazone", + "german": "Amazonas", + "italian": "Amazonas", + "spanish": "Amazonas", + "chinese_simple": "亚马孙省", + "korean": "아마소나스 주", + "dutch": "Amazonas", + "portuguese": "Amazonas", + "russian": "Амазонас", + "chinese_traditional": "Amazonas", + "unknown1": "Amazonas", + "unknown2": "Amazonas", + "unknown3": "Amazonas", + "unknown4": "Amazonas" + }, + "coordinates": { + "latitude": -4.213257811999995, + "longitude": -69.938665556 + } + }, + { + "id": 352649216, + "name": "Antioquia", + "translations": { + "japanese": "アンティオキア県", + "english": "Antioquia", + "french": "Antioquia", + "german": "Antioquia", + "italian": "Antioquia", + "spanish": "Antioquia", + "chinese_simple": "安提奥基亚省", + "korean": "안티오키아 주", + "dutch": "Antioquia", + "portuguese": "Antioquia", + "russian": "Антиокия", + "chinese_traditional": "Antioquia", + "unknown1": "Antioquia", + "unknown2": "Antioquia", + "unknown3": "Antioquia", + "unknown4": "Antioquia" + }, + "coordinates": { + "latitude": 6.28967278, + "longitude": -75.530721778 + } + }, + { + "id": 352714752, + "name": "Arauca", + "translations": { + "japanese": "アラウカ県", + "english": "Arauca", + "french": "Arauca", + "german": "Arauca", + "italian": "Arauca", + "spanish": "Arauca", + "chinese_simple": "阿劳卡省", + "korean": "아라우카 주", + "dutch": "Arauca", + "portuguese": "Arauca", + "russian": "Араука", + "chinese_traditional": "Arauca", + "unknown1": "Arauca", + "unknown2": "Arauca", + "unknown3": "Arauca", + "unknown4": "Arauca" + }, + "coordinates": { + "latitude": 7.08618156, + "longitude": -70.757149227 + } + }, + { + "id": 352780288, + "name": "Atlántico", + "translations": { + "japanese": "アトランティコ県", + "english": "Atlántico", + "french": "Atlantique", + "german": "Atlántico", + "italian": "Atlántico", + "spanish": "Atlántico", + "chinese_simple": "大西洋省", + "korean": "아틀란티코 주", + "dutch": "Atlántico", + "portuguese": "Atlántico", + "russian": "Атлантико", + "chinese_traditional": "Atlántico", + "unknown1": "Atlántico", + "unknown2": "Atlántico", + "unknown3": "Atlántico", + "unknown4": "Atlántico" + }, + "coordinates": { + "latitude": 10.95886218, + "longitude": -74.794635792 + } + }, + { + "id": 352845824, + "name": "Bolívar", + "translations": { + "japanese": "ボリーバル県", + "english": "Bolívar", + "french": "Bolívar", + "german": "Bolívar", + "italian": "Bolívar", + "spanish": "Bolívar", + "chinese_simple": "玻利瓦尔省", + "korean": "볼리바르 주", + "dutch": "Bolívar", + "portuguese": "Bolívar", + "russian": "Боливар", + "chinese_traditional": "Bolívar", + "unknown1": "Bolívar", + "unknown2": "Bolívar", + "unknown3": "Bolívar", + "unknown4": "Bolívar" + }, + "coordinates": { + "latitude": 10.398559452, + "longitude": -75.508749062 + } + }, + { + "id": 352911360, + "name": "Boyacá", + "translations": { + "japanese": "ボヤカ県", + "english": "Boyacá", + "french": "Boyacá", + "german": "Boyacá", + "italian": "Boyacá", + "spanish": "Boyacá", + "chinese_simple": "博亚卡省", + "korean": "보야카 주", + "dutch": "Boyacá", + "portuguese": "Boyacá", + "russian": "Бояка", + "chinese_traditional": "Boyacá", + "unknown1": "Boyacá", + "unknown2": "Boyacá", + "unknown3": "Boyacá", + "unknown4": "Boyacá" + }, + "coordinates": { + "latitude": 5.531616148, + "longitude": -73.366409252 + } + }, + { + "id": 352976896, + "name": "Caldas", + "translations": { + "japanese": "カルダス県", + "english": "Caldas", + "french": "Caldas", + "german": "Caldas", + "italian": "Caldas", + "spanish": "Caldas", + "chinese_simple": "卡尔达斯省", + "korean": "칼다스 주", + "dutch": "Caldas", + "portuguese": "Caldas", + "russian": "Калдас", + "chinese_traditional": "Caldas", + "unknown1": "Caldas", + "unknown2": "Caldas", + "unknown3": "Caldas", + "unknown4": "Caldas" + }, + "coordinates": { + "latitude": 5.064697208, + "longitude": -75.51973542 + } + }, + { + "id": 353042432, + "name": "Caquetá", + "translations": { + "japanese": "カケタ県", + "english": "Caquetá", + "french": "Caquetá", + "german": "Caquetá", + "italian": "Caquetá", + "spanish": "Caquetá", + "chinese_simple": "卡克塔省", + "korean": "카케타 주", + "dutch": "Caquetá", + "portuguese": "Caquetá", + "russian": "Какьета", + "chinese_traditional": "Caquetá", + "unknown1": "Caquetá", + "unknown2": "Caquetá", + "unknown3": "Caquetá", + "unknown4": "Caquetá" + }, + "coordinates": { + "latitude": 1.614990216, + "longitude": -75.613119463 + } + }, + { + "id": 353107968, + "name": "Cauca", + "translations": { + "japanese": "カウカ県", + "english": "Cauca", + "french": "Cauca", + "german": "Cauca", + "italian": "Cauca", + "spanish": "Cauca", + "chinese_simple": "考卡省", + "korean": "카우카 주", + "dutch": "Cauca", + "portuguese": "Cauca", + "russian": "Каука", + "chinese_traditional": "Cauca", + "unknown1": "Cauca", + "unknown2": "Cauca", + "unknown3": "Cauca", + "unknown4": "Cauca" + }, + "coordinates": { + "latitude": 2.482910128, + "longitude": -76.579918967 + } + }, + { + "id": 353173504, + "name": "Cesar", + "translations": { + "japanese": "セサル県", + "english": "Cesar", + "french": "Cesar", + "german": "Cesar", + "italian": "Cesar", + "spanish": "Cesar", + "chinese_simple": "塞萨尔省", + "korean": "세사르 주", + "dutch": "Cesar", + "portuguese": "Cesar", + "russian": "Сесар", + "chinese_traditional": "Cesar", + "unknown1": "Cesar", + "unknown2": "Cesar", + "unknown3": "Cesar", + "unknown4": "Cesar" + }, + "coordinates": { + "latitude": 10.475463748, + "longitude": -73.245559314 + } + }, + { + "id": 353239040, + "name": "Chocó", + "translations": { + "japanese": "チョコ県", + "english": "Chocó", + "french": "Chocó", + "german": "Chocó", + "italian": "Chocó", + "spanish": "Chocó", + "chinese_simple": "乔科省", + "korean": "초코 주", + "dutch": "Chocó", + "portuguese": "Chocó", + "russian": "Чоко", + "chinese_traditional": "Chocó", + "unknown1": "Chocó", + "unknown2": "Chocó", + "unknown3": "Chocó", + "unknown4": "Chocó" + }, + "coordinates": { + "latitude": 5.690917904, + "longitude": -76.656823473 + } + }, + { + "id": 353304576, + "name": "Córdoba", + "translations": { + "japanese": "コルドバ県", + "english": "Córdoba", + "french": "Córdoba", + "german": "Córdoba", + "italian": "Córdoba", + "spanish": "Córdoba", + "chinese_simple": "科尔多瓦省", + "korean": "코르도바 주", + "dutch": "Córdoba", + "portuguese": "Cordova", + "russian": "Кордоба", + "chinese_traditional": "Córdoba", + "unknown1": "Córdoba", + "unknown2": "Córdoba", + "unknown3": "Córdoba", + "unknown4": "Córdoba" + }, + "coordinates": { + "latitude": 8.756103416, + "longitude": -75.887778413 + } + }, + { + "id": 353370112, + "name": "Guaviare", + "translations": { + "japanese": "グアビアレ県", + "english": "Guaviare", + "french": "Guaviare", + "german": "Guaviare", + "italian": "Guaviare", + "spanish": "Guaviare", + "chinese_simple": "瓜维亚雷省", + "korean": "과비아레 주", + "dutch": "Guaviare", + "portuguese": "Guaviare", + "russian": "Гуавиаре", + "chinese_traditional": "Guaviare", + "unknown1": "Guaviare", + "unknown2": "Guaviare", + "unknown3": "Guaviare", + "unknown4": "Guaviare" + }, + "coordinates": { + "latitude": 2.565307588, + "longitude": -72.641309624 + } + }, + { + "id": 353435648, + "name": "Guainía", + "translations": { + "japanese": "グアイニア県", + "english": "Guainía", + "french": "Guainía", + "german": "Guainía", + "italian": "Guainía", + "spanish": "Guainía", + "chinese_simple": "瓜伊尼亚省", + "korean": "과이니아 주", + "dutch": "Guainía", + "portuguese": "Guainía", + "russian": "Гуаинья", + "chinese_traditional": "Guainía", + "unknown1": "Guainía", + "unknown2": "Guainía", + "unknown3": "Guainía", + "unknown4": "Guainía" + }, + "coordinates": { + "latitude": 3.861694292, + "longitude": -67.922668863 + } + }, + { + "id": 353501184, + "name": "Huila", + "translations": { + "japanese": "ウィラ県", + "english": "Huila", + "french": "Huila", + "german": "Huila", + "italian": "Huila", + "spanish": "Huila", + "chinese_simple": "乌伊拉省", + "korean": "우일라 주", + "dutch": "Huila", + "portuguese": "Huila", + "russian": "Уила", + "chinese_traditional": "Huila", + "unknown1": "Huila", + "unknown2": "Huila", + "unknown3": "Huila", + "unknown4": "Huila" + }, + "coordinates": { + "latitude": 2.927856412, + "longitude": -75.327474155 + } + }, + { + "id": 353566720, + "name": "La Guajira", + "translations": { + "japanese": "グアヒーラ県", + "english": "La Guajira", + "french": "La Guajira", + "german": "La Guajira", + "italian": "La Guajira", + "spanish": "La Guajira", + "chinese_simple": "瓜希拉省", + "korean": "라과히라 주", + "dutch": "Guajira", + "portuguese": "La Guajira", + "russian": "Гуахира", + "chinese_traditional": "La Guajira", + "unknown1": "La Guajira", + "unknown2": "La Guajira", + "unknown3": "La Guajira", + "unknown4": "La Guajira" + }, + "coordinates": { + "latitude": 11.541137564, + "longitude": -72.90498221600001 + } + }, + { + "id": 353632256, + "name": "Magdalena", + "translations": { + "japanese": "マグダレーナ県", + "english": "Magdalena", + "french": "Magdalena", + "german": "Magdalena", + "italian": "Magdalena", + "spanish": "Magdalena", + "chinese_simple": "马格达雷那省", + "korean": "마그달레나 주", + "dutch": "Magdalena", + "portuguese": "Magdalena", + "russian": "Магдалена", + "chinese_traditional": "Magdalena", + "unknown1": "Magdalena", + "unknown2": "Magdalena", + "unknown3": "Magdalena", + "unknown4": "Magdalena" + }, + "coordinates": { + "latitude": 11.244506708, + "longitude": -74.20137246 + } + }, + { + "id": 353697792, + "name": "Meta", + "translations": { + "japanese": "メタ県", + "english": "Meta", + "french": "Meta", + "german": "Meta", + "italian": "Meta", + "spanish": "Meta", + "chinese_simple": "梅塔省", + "korean": "메타 주", + "dutch": "Meta", + "portuguese": "Meta", + "russian": "Мета", + "chinese_traditional": "Meta", + "unknown1": "Meta", + "unknown2": "Meta", + "unknown3": "Meta", + "unknown4": "Meta" + }, + "coordinates": { + "latitude": 4.152831984, + "longitude": -73.630081844 + } + }, + { + "id": 353763328, + "name": "Nariño", + "translations": { + "japanese": "ナリーニョ県", + "english": "Nariño", + "french": "Nariño", + "german": "Nariño", + "italian": "Nariño", + "spanish": "Nariño", + "chinese_simple": "纳里尼奥省", + "korean": "나리뇨 주", + "dutch": "Nariño", + "portuguese": "Nariño", + "russian": "Нариньо", + "chinese_traditional": "Nariño", + "unknown1": "Nariño", + "unknown2": "Nariño", + "unknown3": "Nariño", + "unknown4": "Nariño" + }, + "coordinates": { + "latitude": 1.20849608, + "longitude": -77.2775527 + } + }, + { + "id": 353828864, + "name": "Norte de Santander", + "translations": { + "japanese": "ノルテ・デ・サンタンデル県", + "english": "Norte de Santander", + "french": "Norte de Santander", + "german": "Norte de Santander", + "italian": "Norte de Santander", + "spanish": "Norte de Santander", + "chinese_simple": "北桑坦德省", + "korean": "노르테데산탄데르 주", + "dutch": "Norte de Santander", + "portuguese": "Norte de Santander", + "russian": "Норте-де-Сантандер", + "chinese_traditional": "Norte de Santander", + "unknown1": "Norte de Santander", + "unknown2": "Norte de Santander", + "unknown3": "Norte de Santander", + "unknown4": "Norte de Santander" + }, + "coordinates": { + "latitude": 7.88269034, + "longitude": -72.503980149 + } + }, + { + "id": 353894400, + "name": "Putumayo", + "translations": { + "japanese": "プトゥマイオ県", + "english": "Putumayo", + "french": "Putumayo", + "german": "Putumayo", + "italian": "Putumayo", + "spanish": "Putumayo", + "chinese_simple": "普图马约省", + "korean": "푸투마요 주", + "dutch": "Putumayo", + "portuguese": "Putumayo", + "russian": "Путумайо", + "chinese_traditional": "Putumayo", + "unknown1": "Putumayo", + "unknown2": "Putumayo", + "unknown3": "Putumayo", + "unknown4": "Putumayo" + }, + "coordinates": { + "latitude": 1.148071276, + "longitude": -76.645837115 + } + }, + { + "id": 353959936, + "name": "Quindío", + "translations": { + "japanese": "キンディオ県", + "english": "Quindío", + "french": "Quindío", + "german": "Quindío", + "italian": "Quindío", + "spanish": "Quindío", + "chinese_simple": "金迪奥省", + "korean": "킨디오 주", + "dutch": "Quindío", + "portuguese": "Quindío", + "russian": "Киндио", + "chinese_traditional": "Quindío", + "unknown1": "Quindío", + "unknown2": "Quindío", + "unknown3": "Quindío", + "unknown4": "Quindío" + }, + "coordinates": { + "latitude": 4.5318603, + "longitude": -75.679037611 + } + }, + { + "id": 354025472, + "name": "Risaralda", + "translations": { + "japanese": "リサラルダ県", + "english": "Risaralda", + "french": "Risaralda", + "german": "Risaralda", + "italian": "Risaralda", + "spanish": "Risaralda", + "chinese_simple": "利萨拉尔达省", + "korean": "리사랄다 주", + "dutch": "Risaralda", + "portuguese": "Risaralda", + "russian": "Рисаральда", + "chinese_traditional": "Risaralda", + "unknown1": "Risaralda", + "unknown2": "Risaralda", + "unknown3": "Risaralda", + "unknown4": "Risaralda" + }, + "coordinates": { + "latitude": 4.812011664, + "longitude": -75.69551714800001 + } + }, + { + "id": 354091008, + "name": "Archipiélago de San Andrés, Providencia y Santa Catalina", + "translations": { + "japanese": "サン・アンドレス・イ・プロビデンシア県", + "english": "Archipiélago de San Andrés, Providencia y Santa Catalina", + "french": "Îles de San Andrés, Providencia et Santa Cataline", + "german": "San Andrés und Providencia", + "italian": "San Andrés e Providencia", + "spanish": "Archipiélago de San Andrés, Providencia y Santa Catalina", + "chinese_simple": "圣安德烈斯-普罗维登西亚省", + "korean": "산안드레스 이 프로비덴시아 주", + "dutch": "San Andrés en Providencia", + "portuguese": "Arquipélago de San Andrés, Providencia y Santa Catalina", + "russian": "Сан-Андрес-и-Провиденция", + "chinese_traditional": "Archipiélago de San Andrés, Providencia y Santa Catalina", + "unknown1": "Archipiélago de San Andrés, Providencia y Santa Catalina", + "unknown2": "Archipiélago de San Andrés, Providencia y Santa Catalina", + "unknown3": "Archipiélago de San Andrés, Providencia y Santa Catalina", + "unknown4": "Archipiélago de San Andrés, Providencia y Santa Catalina" + }, + "coordinates": { + "latitude": 12.57934556, + "longitude": -81.699561795 + } + }, + { + "id": 354156544, + "name": "Santander", + "translations": { + "japanese": "サンタンデル県", + "english": "Santander", + "french": "Santander", + "german": "Santander", + "italian": "Santander", + "spanish": "Santander", + "chinese_simple": "桑坦德省", + "korean": "산탄데르 주", + "dutch": "Santander", + "portuguese": "Santander", + "russian": "Сантандер", + "chinese_traditional": "Santander", + "unknown1": "Santander", + "unknown2": "Santander", + "unknown3": "Santander", + "unknown4": "Santander" + }, + "coordinates": { + "latitude": 7.124633708, + "longitude": -73.124709376 + } + }, + { + "id": 354222080, + "name": "Sucre", + "translations": { + "japanese": "スクレ県", + "english": "Sucre", + "french": "Sucre", + "german": "Sucre", + "italian": "Sucre", + "spanish": "Sucre", + "chinese_simple": "苏克雷省", + "korean": "수크레 주", + "dutch": "Sucre", + "portuguese": "Sucre", + "russian": "Сукре", + "chinese_traditional": "Sucre", + "unknown1": "Sucre", + "unknown2": "Sucre", + "unknown3": "Sucre", + "unknown4": "Sucre" + }, + "coordinates": { + "latitude": 9.299926652, + "longitude": -75.393392303 + } + }, + { + "id": 354287616, + "name": "Tolima", + "translations": { + "japanese": "トリマ県", + "english": "Tolima", + "french": "Tolima", + "german": "Tolima", + "italian": "Tolima", + "spanish": "Tolima", + "chinese_simple": "托利马省", + "korean": "톨리마 주", + "dutch": "Tolima", + "portuguese": "Tolima", + "russian": "Толима", + "chinese_traditional": "Tolima", + "unknown1": "Tolima", + "unknown2": "Tolima", + "unknown3": "Tolima", + "unknown4": "Tolima" + }, + "coordinates": { + "latitude": 4.438476512, + "longitude": -75.228596933 + } + }, + { + "id": 354353152, + "name": "Valle del Cauca", + "translations": { + "japanese": "バジェ・デル・カウカ県", + "english": "Valle del Cauca", + "french": "Valle del Cauca", + "german": "Valle del Cauca", + "italian": "Valle del Cauca", + "spanish": "Valle del Cauca", + "chinese_simple": "考卡山谷省", + "korean": "바예델카우카 주", + "dutch": "Valle del Cauca", + "portuguese": "Valle del Cauca", + "russian": "Валье-дель-Каука", + "chinese_traditional": "Valle del Cauca", + "unknown1": "Valle del Cauca", + "unknown2": "Valle del Cauca", + "unknown3": "Valle del Cauca", + "unknown4": "Valle del Cauca" + }, + "coordinates": { + "latitude": 3.4332275, + "longitude": -76.519493998 + } + }, + { + "id": 354418688, + "name": "Vaupés", + "translations": { + "japanese": "バウペス県", + "english": "Vaupés", + "french": "Vaupés", + "german": "Vaupés", + "italian": "Vaupés", + "spanish": "Vaupés", + "chinese_simple": "沃佩斯省", + "korean": "바우페스 주", + "dutch": "Vaupés", + "portuguese": "Vaupés", + "russian": "Ваупес", + "chinese_traditional": "Vaupés", + "unknown1": "Vaupés", + "unknown2": "Vaupés", + "unknown3": "Vaupés", + "unknown4": "Vaupés" + }, + "coordinates": { + "latitude": 1.197509752, + "longitude": -70.169379074 + } + }, + { + "id": 354484224, + "name": "Vichada", + "translations": { + "japanese": "ビチャダ県", + "english": "Vichada", + "french": "Vichada", + "german": "Vichada", + "italian": "Vichada", + "spanish": "Vichada", + "chinese_simple": "维查达省", + "korean": "비차다 주", + "dutch": "Vichada", + "portuguese": "Vichada", + "russian": "Вичада", + "chinese_traditional": "Vichada", + "unknown1": "Vichada", + "unknown2": "Vichada", + "unknown3": "Vichada", + "unknown4": "Vichada" + }, + "coordinates": { + "latitude": 6.185302664, + "longitude": -67.472228185 + } + }, + { + "id": 354549760, + "name": "Casanare", + "translations": { + "japanese": "カサナレ県", + "english": "Casanare", + "french": "Casanare", + "german": "Casanare", + "italian": "Casanare", + "spanish": "Casanare", + "chinese_simple": "卡萨纳雷省", + "korean": "카사나레 주", + "dutch": "Casanare", + "portuguese": "Casanare", + "russian": "Касанаре", + "chinese_traditional": "Casanare", + "unknown1": "Casanare", + "unknown2": "Casanare", + "unknown3": "Casanare", + "unknown4": "Casanare" + }, + "coordinates": { + "latitude": 5.339355408, + "longitude": -72.38862339 + } + } + ] + }, + { + "id": 22, + "iso_code": "CR", + "name": "Costa Rica", + "translations": { + "japanese": "コスタリカ", + "english": "Costa Rica", + "french": "Costa Rica", + "german": "Costa Rica", + "italian": "Costa Rica", + "spanish": "Costa Rica", + "chinese_simple": "哥斯达黎加", + "korean": "코스타리카", + "dutch": "Costa Rica", + "portuguese": "Costa Rica", + "russian": "Коста-Рика", + "chinese_traditional": "Costa Rica", + "unknown1": "Costa Rica", + "unknown2": "Costa Rica", + "unknown3": "Costa Rica", + "unknown4": "Costa Rica" + }, + "regions": [ + { + "id": 369098752, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": 9.931640512, + "longitude": -84.078108302 + } + }, + { + "id": 369229824, + "name": "San José", + "translations": { + "japanese": "サン・ホセ州", + "english": "San José", + "french": "San José", + "german": "San José", + "italian": "San José", + "spanish": "San José", + "chinese_simple": "圣何塞省", + "korean": "산호세 주", + "dutch": "San José", + "portuguese": "San José", + "russian": "Сан-Хосе", + "chinese_traditional": "San José", + "unknown1": "San José", + "unknown2": "San José", + "unknown3": "San José", + "unknown4": "San José" + }, + "coordinates": { + "latitude": 9.931640512, + "longitude": -84.078108302 + } + }, + { + "id": 369295360, + "name": "Alajuela", + "translations": { + "japanese": "アラフエラ州", + "english": "Alajuela", + "french": "Alajuela", + "german": "Alajuela", + "italian": "Alajuela", + "spanish": "Alajuela", + "chinese_simple": "阿拉胡埃拉省", + "korean": "알라후엘라 주", + "dutch": "Alajuela", + "portuguese": "Alajuela", + "russian": "Алахуэла", + "chinese_traditional": "Alajuela", + "unknown1": "Alajuela", + "unknown2": "Alajuela", + "unknown3": "Alajuela", + "unknown4": "Alajuela" + }, + "coordinates": { + "latitude": 10.014037972, + "longitude": -84.215437777 + } + }, + { + "id": 369360896, + "name": "Cartago", + "translations": { + "japanese": "カルタゴ州", + "english": "Cartago", + "french": "Cartago", + "german": "Cartago", + "italian": "Cartago", + "spanish": "Cartago", + "chinese_simple": "卡塔戈省", + "korean": "카르타고 주", + "dutch": "Cartago", + "portuguese": "Cartago", + "russian": "Картаго", + "chinese_traditional": "Cartago", + "unknown1": "Cartago", + "unknown2": "Cartago", + "unknown3": "Cartago", + "unknown4": "Cartago" + }, + "coordinates": { + "latitude": 9.865722544, + "longitude": -83.913312932 + } + }, + { + "id": 369426432, + "name": "Guanacaste", + "translations": { + "japanese": "グアナカステ州", + "english": "Guanacaste", + "french": "Guanacaste", + "german": "Guanacaste", + "italian": "Guanacaste", + "spanish": "Guanacaste", + "chinese_simple": "瓜纳卡斯特省", + "korean": "과나카스테 주", + "dutch": "Guanacaste", + "portuguese": "Guanacaste", + "russian": "Гуанакасте", + "chinese_traditional": "Guanacaste", + "unknown1": "Guanacaste", + "unknown2": "Guanacaste", + "unknown3": "Guanacaste", + "unknown4": "Guanacaste" + }, + "coordinates": { + "latitude": 10.62927234, + "longitude": -85.429430336 + } + }, + { + "id": 369491968, + "name": "Heredia", + "translations": { + "japanese": "エレディア州", + "english": "Heredia", + "french": "Heredia", + "german": "Heredia", + "italian": "Heredia", + "spanish": "Heredia", + "chinese_simple": "埃雷迪亚省", + "korean": "에레디아 주", + "dutch": "Heredia", + "portuguese": "Heredia", + "russian": "Эредия", + "chinese_traditional": "Heredia", + "unknown1": "Heredia", + "unknown2": "Heredia", + "unknown3": "Heredia", + "unknown4": "Heredia" + }, + "coordinates": { + "latitude": 9.99755848, + "longitude": -84.111067376 + } + }, + { + "id": 369557504, + "name": "Limón", + "translations": { + "japanese": "リモン州", + "english": "Limón", + "french": "Limón", + "german": "Limón", + "italian": "Limón", + "spanish": "Limón", + "chinese_simple": "利蒙省", + "korean": "리몬 주", + "dutch": "Limón", + "portuguese": "Limón", + "russian": "Лимон", + "chinese_traditional": "Limón", + "unknown1": "Limón", + "unknown2": "Limón", + "unknown3": "Limón", + "unknown4": "Limón" + }, + "coordinates": { + "latitude": 9.99755848, + "longitude": -83.028911113 + } + }, + { + "id": 369623040, + "name": "Puntarenas", + "translations": { + "japanese": "プンタレナス州", + "english": "Puntarenas", + "french": "Puntarenas", + "german": "Puntarenas", + "italian": "Puntarenas", + "spanish": "Puntarenas", + "chinese_simple": "蓬塔雷纳斯省", + "korean": "푼타레나스 주", + "dutch": "Puntarenas", + "portuguese": "Puntarenas", + "russian": "Пунтаренас", + "chinese_traditional": "Puntarenas", + "unknown1": "Puntarenas", + "unknown2": "Puntarenas", + "unknown3": "Puntarenas", + "unknown4": "Puntarenas" + }, + "coordinates": { + "latitude": 9.964599496, + "longitude": -84.830673825 + } + } + ] + }, + { + "id": 23, + "iso_code": "DM", + "name": "Dominica", + "translations": { + "japanese": "ドミニカ国", + "english": "Dominica", + "french": "Dominique", + "german": "Dominica", + "italian": "Dominica", + "spanish": "Dominica", + "chinese_simple": "多米尼克", + "korean": "도미니카 연방", + "dutch": "Dominica", + "portuguese": "Dominica", + "russian": "Доминика", + "chinese_traditional": "Dominica", + "unknown1": "Dominica", + "unknown2": "Dominica", + "unknown3": "Dominica", + "unknown4": "Dominica" + }, + "regions": [ + { + "id": 385875968, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": 15.29846174, + "longitude": -61.396772211 + } + }, + { + "id": 385941504, + "name": "Dominica", + "translations": { + "japanese": "ドミニカ国", + "english": "Dominica", + "french": "Dominique", + "german": "Dominica", + "italian": "Dominica", + "spanish": "Dominica", + "chinese_simple": "多米尼克", + "korean": "도미니카 연방", + "dutch": "Dominica", + "portuguese": "Dominica", + "russian": "Доминика", + "chinese_traditional": "Dominica", + "unknown1": "Dominica", + "unknown2": "Dominica", + "unknown3": "Dominica", + "unknown4": "Dominica" + }, + "coordinates": { + "latitude": 15.29846174, + "longitude": -61.396772211 + } + } + ] + }, + { + "id": 24, + "iso_code": "DO", + "name": "Dominican Republic", + "translations": { + "japanese": "ドミニカ共和国", + "english": "Dominican Republic", + "french": "République dominicaine", + "german": "Dominikanische Republik", + "italian": "Repubblica Dominicana", + "spanish": "República Dominicana", + "chinese_simple": "多米尼加共和国", + "korean": "도미니카 공화국", + "dutch": "Dominicaanse Republiek", + "portuguese": "República Dominicana", + "russian": "Доминиканская Республика", + "chinese_traditional": "Dominican Republic", + "unknown1": "Dominican Republic", + "unknown2": "Dominican Republic", + "unknown3": "Dominican Republic", + "unknown4": "Dominican Republic" + }, + "regions": [ + { + "id": 402653184, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": 18.462524204, + "longitude": -69.894720124 + } + }, + { + "id": 402784256, + "name": "Distrito Nacional", + "translations": { + "japanese": "ディストリト・ナショナル首都圏", + "english": "Distrito Nacional", + "french": "District National", + "german": "Distrito Nacional", + "italian": "Distretto Nazionale", + "spanish": "Distrito Nacional", + "chinese_simple": "国家区", + "korean": "도미니카 행정구", + "dutch": "Distrito Nacional", + "portuguese": "Distrito Nacional", + "russian": "Национальный округ", + "chinese_traditional": "Distrito Nacional", + "unknown1": "Distrito Nacional", + "unknown2": "Distrito Nacional", + "unknown3": "Distrito Nacional", + "unknown4": "Distrito Nacional" + }, + "coordinates": { + "latitude": 18.462524204, + "longitude": -69.894720124 + } + }, + { + "id": 402849792, + "name": "Azua", + "translations": { + "japanese": "アスア", + "english": "Azua", + "french": "Azua", + "german": "Azua", + "italian": "Azua", + "spanish": "Azua", + "chinese_simple": "阿苏阿省", + "korean": "아수아", + "dutch": "Azua", + "portuguese": "Azua", + "russian": "Азуа", + "chinese_traditional": "Azua", + "unknown1": "Azua", + "unknown2": "Azua", + "unknown3": "Azua", + "unknown4": "Azua" + }, + "coordinates": { + "latitude": 18.446044712, + "longitude": -70.72968333200001 + } + }, + { + "id": 402915328, + "name": "Baoruco", + "translations": { + "japanese": "バオルコ", + "english": "Baoruco", + "french": "Baoruco", + "german": "Baoruco", + "italian": "Baoruco", + "spanish": "Baoruco", + "chinese_simple": "巴奥鲁科省", + "korean": "바오루코", + "dutch": "Baoruco", + "portuguese": "Baoruco", + "russian": "Баоруко", + "chinese_traditional": "Baoruco", + "unknown1": "Baoruco", + "unknown2": "Baoruco", + "unknown3": "Baoruco", + "unknown4": "Baoruco" + }, + "coordinates": { + "latitude": 18.462524204, + "longitude": -71.416330707 + } + }, + { + "id": 402980864, + "name": "Barahona", + "translations": { + "japanese": "バラオナ", + "english": "Barahona", + "french": "Barahona", + "german": "Barahona", + "italian": "Barahona", + "spanish": "Barahona", + "chinese_simple": "巴拉奥纳省", + "korean": "바라오나", + "dutch": "Barahona", + "portuguese": "Barahona", + "russian": "Барахона", + "chinese_traditional": "Barahona", + "unknown1": "Barahona", + "unknown2": "Barahona", + "unknown3": "Barahona", + "unknown4": "Barahona" + }, + "coordinates": { + "latitude": 18.198852332, + "longitude": -71.097726325 + } + }, + { + "id": 403046400, + "name": "Dajabón", + "translations": { + "japanese": "ダハボン", + "english": "Dajabón", + "french": "Dajabón", + "german": "Dajabón", + "italian": "Dajabón", + "spanish": "Dajabón", + "chinese_simple": "达哈朋省", + "korean": "다하본", + "dutch": "Dajabón", + "portuguese": "Dajabón", + "russian": "Даджабон", + "chinese_traditional": "Dajabón", + "unknown1": "Dajabón", + "unknown2": "Dajabón", + "unknown3": "Dajabón", + "unknown4": "Dajabón" + }, + "coordinates": { + "latitude": 19.544677512, + "longitude": -71.696482836 + } + }, + { + "id": 403111936, + "name": "Duarte", + "translations": { + "japanese": "ドゥアルテ", + "english": "Duarte", + "french": "Duarte", + "german": "Duarte", + "italian": "Duarte", + "spanish": "Duarte", + "chinese_simple": "杜华德省", + "korean": "두아르테", + "dutch": "Duarte", + "portuguese": "Duarte", + "russian": "Дуарте", + "chinese_traditional": "Duarte", + "unknown1": "Duarte", + "unknown2": "Duarte", + "unknown3": "Duarte", + "unknown4": "Duarte" + }, + "coordinates": { + "latitude": 19.297485132, + "longitude": -70.24628358 + } + }, + { + "id": 403177472, + "name": "Espaillat", + "translations": { + "japanese": "エスパイジャト", + "english": "Espaillat", + "french": "Espaillat", + "german": "Espaillat", + "italian": "Espaillat", + "spanish": "Espaillat", + "chinese_simple": "艾斯派亚省", + "korean": "에스파이야트", + "dutch": "Espaillat", + "portuguese": "Espaillat", + "russian": "Эспаиллат", + "chinese_traditional": "Espaillat", + "unknown1": "Espaillat", + "unknown2": "Espaillat", + "unknown3": "Espaillat", + "unknown4": "Espaillat" + }, + "coordinates": { + "latitude": 19.396362084, + "longitude": -70.515449351 + } + }, + { + "id": 403243008, + "name": "Independencia", + "translations": { + "japanese": "インデペンデンシア", + "english": "Independencia", + "french": "Independencia", + "german": "Independencia", + "italian": "Independencia", + "spanish": "Independencia", + "chinese_simple": "独立省", + "korean": "인데펜덴시아", + "dutch": "Independencia", + "portuguese": "Independencia", + "russian": "Индепенденсия", + "chinese_traditional": "Independencia", + "unknown1": "Independencia", + "unknown2": "Independencia", + "unknown3": "Independencia", + "unknown4": "Independencia" + }, + "coordinates": { + "latitude": 18.489990024, + "longitude": -71.850291848 + } + }, + { + "id": 403308544, + "name": "La Altagracia", + "translations": { + "japanese": "ラ・アルタグラシア", + "english": "La Altagracia", + "french": "La Altagracia", + "german": "La Altagracia", + "italian": "La Altagracia", + "spanish": "La Altagracia", + "chinese_simple": "圣母省", + "korean": "라알타그라시아", + "dutch": "La Altagracia", + "portuguese": "La Altagracia", + "russian": "Ла-Альтаграсия", + "chinese_traditional": "La Altagracia", + "unknown1": "La Altagracia", + "unknown2": "La Altagracia", + "unknown3": "La Altagracia", + "unknown4": "La Altagracia" + }, + "coordinates": { + "latitude": 18.698730256, + "longitude": -68.664248028 + } + }, + { + "id": 403374080, + "name": "Elías Piña", + "translations": { + "japanese": "エリアス・ピーニャ", + "english": "Elías Piña", + "french": "Elías Piña", + "german": "Elías Piña", + "italian": "Elías Piña", + "spanish": "Elías Piña", + "chinese_simple": "埃利亚斯皮亚省", + "korean": "엘리아스피냐", + "dutch": "Elías Piña", + "portuguese": "Elías Piña", + "russian": "Эльяс-Пина", + "chinese_traditional": "Elías Piña", + "unknown1": "Elías Piña", + "unknown2": "Elías Piña", + "unknown3": "Elías Piña", + "unknown4": "Elías Piña" + }, + "coordinates": { + "latitude": 18.874511504, + "longitude": -71.701976015 + } + }, + { + "id": 403439616, + "name": "La Romana", + "translations": { + "japanese": "ラ・ロマーナ", + "english": "La Romana", + "french": "La Romana", + "german": "La Romana", + "italian": "La Romana", + "spanish": "La Romana", + "chinese_simple": "罗马纳省", + "korean": "라로마나", + "dutch": "La Romana", + "portuguese": "La Romana", + "russian": "Ла-Романа", + "chinese_traditional": "La Romana", + "unknown1": "La Romana", + "unknown2": "La Romana", + "unknown3": "La Romana", + "unknown4": "La Romana" + }, + "coordinates": { + "latitude": 18.413085728, + "longitude": -68.960879694 + } + }, + { + "id": 403505152, + "name": "María Trinidad Sánchez", + "translations": { + "japanese": "マリア・トリニダー・サンチェス", + "english": "María Trinidad Sánchez", + "french": "María Trinidad Sánchez", + "german": "María Trinidad Sánchez", + "italian": "María Trinidad Sánchez", + "spanish": "María Trinidad Sánchez", + "chinese_simple": "玛丽亚-桑其斯省", + "korean": "마리아 트리니다드산체스", + "dutch": "María Trinidad Sánchez", + "portuguese": "María Trinidad Sánchez", + "russian": "Мария-Тринидад-Санчес", + "chinese_traditional": "María Trinidad Sánchez", + "unknown1": "María Trinidad Sánchez", + "unknown2": "María Trinidad Sánchez", + "unknown3": "María Trinidad Sánchez", + "unknown4": "María Trinidad Sánchez" + }, + "coordinates": { + "latitude": 19.379882592, + "longitude": -69.828801976 + } + }, + { + "id": 403570688, + "name": "Monte Cristi", + "translations": { + "japanese": "モンテ・クリスティ", + "english": "Monte Cristi", + "french": "Monte Cristi", + "german": "Monte Cristi", + "italian": "Monte Cristi", + "spanish": "Monte Cristi", + "chinese_simple": "基度山省", + "korean": "몬테크리스티", + "dutch": "Monte Cristi", + "portuguese": "Monte Cristi", + "russian": "Монте-Кристи", + "chinese_traditional": "Monte Cristi", + "unknown1": "Monte Cristi", + "unknown2": "Monte Cristi", + "unknown3": "Monte Cristi", + "unknown4": "Monte Cristi" + }, + "coordinates": { + "latitude": 19.66552712, + "longitude": -71.663523762 + } + }, + { + "id": 403636224, + "name": "Pedernales", + "translations": { + "japanese": "ペデルナレス", + "english": "Pedernales", + "french": "Pedernales", + "german": "Pedernales", + "italian": "Pedernales", + "spanish": "Pedernales", + "chinese_simple": "佩德纳莱斯省", + "korean": "페데르날레스", + "dutch": "Pedernales", + "portuguese": "Pedernales", + "russian": "Педерналес", + "chinese_traditional": "Pedernales", + "unknown1": "Pedernales", + "unknown2": "Pedernales", + "unknown3": "Pedernales", + "unknown4": "Pedernales" + }, + "coordinates": { + "latitude": 18.028564248, + "longitude": -71.745921447 + } + }, + { + "id": 403701760, + "name": "Peravia", + "translations": { + "japanese": "ペラビア", + "english": "Peravia", + "french": "Peravia", + "german": "Peravia", + "italian": "Peravia", + "spanish": "Peravia", + "chinese_simple": "佩拉维亚省", + "korean": "페라비아", + "dutch": "Peravia", + "portuguese": "Peravia", + "russian": "Перавия", + "chinese_traditional": "Peravia", + "unknown1": "Peravia", + "unknown2": "Peravia", + "unknown3": "Peravia", + "unknown4": "Peravia" + }, + "coordinates": { + "latitude": 18.281249792, + "longitude": -70.328681265 + } + }, + { + "id": 403767296, + "name": "Puerto Plata", + "translations": { + "japanese": "プエルト・プラタ", + "english": "Puerto Plata", + "french": "Puerto Plata", + "german": "Puerto Plata", + "italian": "Puerto Plata", + "spanish": "Puerto Plata", + "chinese_simple": "银港省", + "korean": "푸에르토 플라타", + "dutch": "Puerto Plata", + "portuguese": "Puerto Plata", + "russian": "Пуэрто-Плата", + "chinese_traditional": "Puerto Plata", + "unknown1": "Puerto Plata", + "unknown2": "Puerto Plata", + "unknown3": "Puerto Plata", + "unknown4": "Puerto Plata" + }, + "coordinates": { + "latitude": 19.797363056000002, + "longitude": -70.68024472100001 + } + }, + { + "id": 403832832, + "name": "Salcedo", + "translations": { + "japanese": "サルセド", + "english": "Salcedo", + "french": "Salcedo", + "german": "Hermanas Mirabal", + "italian": "Salcedo", + "spanish": "Salcedo", + "chinese_simple": "萨尔塞多省", + "korean": "살세도", + "dutch": "Salcedo", + "portuguese": "Salcedo", + "russian": "Салкедо", + "chinese_traditional": "Salcedo", + "unknown1": "Salcedo", + "unknown2": "Salcedo", + "unknown3": "Salcedo", + "unknown4": "Salcedo" + }, + "coordinates": { + "latitude": 19.396362084, + "longitude": -70.345160802 + } + }, + { + "id": 403898368, + "name": "Samaná", + "translations": { + "japanese": "セマナ", + "english": "Samaná", + "french": "Samaná", + "german": "Samaná", + "italian": "Samaná", + "spanish": "Samaná", + "chinese_simple": "山美纳省", + "korean": "사마나", + "dutch": "Samaná", + "portuguese": "Samaná", + "russian": "Самана", + "chinese_traditional": "Samaná", + "unknown1": "Samaná", + "unknown2": "Samaná", + "unknown3": "Samaná", + "unknown4": "Samaná" + }, + "coordinates": { + "latitude": 19.215087672, + "longitude": -69.31244315000001 + } + }, + { + "id": 403963904, + "name": "Sánchez Ramírez", + "translations": { + "japanese": "サンチェス・ラミレス", + "english": "Sánchez Ramírez", + "french": "Sánchez Ramírez", + "german": "Sánchez Ramírez", + "italian": "Sánchez Ramírez", + "spanish": "Sánchez Ramírez", + "chinese_simple": "桑切斯-拉米斯省", + "korean": "산체스라미레스", + "dutch": "Sánchez Ramírez", + "portuguese": "Sánchez Ramírez", + "russian": "Санчес-Рамирес", + "chinese_traditional": "Sánchez Ramírez", + "unknown1": "Sánchez Ramírez", + "unknown2": "Sánchez Ramírez", + "unknown3": "Sánchez Ramírez", + "unknown4": "Sánchez Ramírez" + }, + "coordinates": { + "latitude": 19.044799588, + "longitude": -70.147406358 + } + }, + { + "id": 404029440, + "name": "San Juan", + "translations": { + "japanese": "サン・フアン", + "english": "San Juan", + "french": "San Juan", + "german": "San Juan", + "italian": "San Juan", + "spanish": "San Juan", + "chinese_simple": "圣胡安省", + "korean": "산후안", + "dutch": "San Juan", + "portuguese": "San Juan", + "russian": "Сан-Хуан", + "chinese_traditional": "San Juan", + "unknown1": "San Juan", + "unknown2": "San Juan", + "unknown3": "San Juan", + "unknown4": "San Juan" + }, + "coordinates": { + "latitude": 18.797607208, + "longitude": -71.229562621 + } + }, + { + "id": 404094976, + "name": "San Pedro de Macorís", + "translations": { + "japanese": "サン・ペドロ・デ・マコリス", + "english": "San Pedro de Macorís", + "french": "San Pedro de Macorís", + "german": "San Pedro de Macorís", + "italian": "San Pedro de Macorís", + "spanish": "San Pedro de Macorís", + "chinese_simple": "圣彼德省", + "korean": "산페드로데마코리스", + "dutch": "San Pedro de Macorís", + "portuguese": "San Pedro de Macorís", + "russian": "Сан-Педро-де-Маркорис", + "chinese_traditional": "San Pedro de Macorís", + "unknown1": "San Pedro de Macorís", + "unknown2": "San Pedro de Macorís", + "unknown3": "San Pedro de Macorís", + "unknown4": "San Pedro de Macorís" + }, + "coordinates": { + "latitude": 18.446044712, + "longitude": -69.295963613 + } + }, + { + "id": 404160512, + "name": "Santiago", + "translations": { + "japanese": "サンティアゴ", + "english": "Santiago", + "french": "Santiago", + "german": "Santiago", + "italian": "Santiago", + "spanish": "Santiago", + "chinese_simple": "圣地亚哥省", + "korean": "산티아고", + "dutch": "Santiago", + "portuguese": "Santiago", + "russian": "Сантьяго", + "chinese_traditional": "Santiago", + "unknown1": "Santiago", + "unknown2": "Santiago", + "unknown3": "Santiago", + "unknown4": "Santiago" + }, + "coordinates": { + "latitude": 19.44580056, + "longitude": -70.696724258 + } + }, + { + "id": 404226048, + "name": "Santiago Rodríguez", + "translations": { + "japanese": "サンティアゴ・ロドリゲス", + "english": "Santiago Rodríguez", + "french": "Santiago Rodríguez", + "german": "Santiago Rodríguez", + "italian": "Santiago Rodríguez", + "spanish": "Santiago Rodríguez", + "chinese_simple": "圣地亚哥-罗里盖兹省", + "korean": "산티아고로드리게스", + "dutch": "Santiago Rodríguez", + "portuguese": "Santiago Rodríguez", + "russian": "Сантьяго-Родригес", + "chinese_traditional": "Santiago Rodríguez", + "unknown1": "Santiago Rodríguez", + "unknown2": "Santiago Rodríguez", + "unknown3": "Santiago Rodríguez", + "unknown4": "Santiago Rodríguez" + }, + "coordinates": { + "latitude": 19.462280052, + "longitude": -71.328439843 + } + }, + { + "id": 404291584, + "name": "Valverde", + "translations": { + "japanese": "バルベルデ", + "english": "Valverde", + "french": "Valverde", + "german": "Valverde", + "italian": "Valverde", + "spanish": "Valverde", + "chinese_simple": "巴韦德省", + "korean": "발베르데", + "dutch": "Valverde", + "portuguese": "Valverde", + "russian": "Вальверде", + "chinese_traditional": "Valverde", + "unknown1": "Valverde", + "unknown2": "Valverde", + "unknown3": "Valverde", + "unknown4": "Valverde" + }, + "coordinates": { + "latitude": 19.566650168, + "longitude": -71.081246788 + } + }, + { + "id": 404357120, + "name": "El Seíbo", + "translations": { + "japanese": "エル・セイボ", + "english": "El Seíbo", + "french": "El Seibo", + "german": "El Seíbo", + "italian": "El Seíbo", + "spanish": "El Seíbo", + "chinese_simple": "赛堡省", + "korean": "엘세이보", + "dutch": "El Seibo", + "portuguese": "El Seíbo", + "russian": "Эль-Сейбо", + "chinese_traditional": "El Seíbo", + "unknown1": "El Seíbo", + "unknown2": "El Seíbo", + "unknown3": "El Seíbo", + "unknown4": "El Seíbo" + }, + "coordinates": { + "latitude": 18.764648224000002, + "longitude": -69.032291021 + } + }, + { + "id": 404422656, + "name": "Hato Mayor", + "translations": { + "japanese": "アト・マジョール", + "english": "Hato Mayor", + "french": "Hato Mayor", + "german": "Hato Mayor", + "italian": "Hato Mayor", + "spanish": "Hato Mayor", + "chinese_simple": "阿托马约省", + "korean": "아토마요르", + "dutch": "Hato Mayor", + "portuguese": "Hato Mayor", + "russian": "Хато-Майор", + "chinese_traditional": "Hato Mayor", + "unknown1": "Hato Mayor", + "unknown2": "Hato Mayor", + "unknown3": "Hato Mayor", + "unknown4": "Hato Mayor" + }, + "coordinates": { + "latitude": 18.764648224000002, + "longitude": -69.246525002 + } + }, + { + "id": 404488192, + "name": "La Vega", + "translations": { + "japanese": "ラ・ベガ", + "english": "La Vega", + "french": "La Vega", + "german": "La Vega", + "italian": "La Vega", + "spanish": "La Vega", + "chinese_simple": "拉维加省", + "korean": "라베가", + "dutch": "La Vega", + "portuguese": "La Vega", + "russian": "Ла-Вега", + "chinese_traditional": "La Vega", + "unknown1": "La Vega", + "unknown2": "La Vega", + "unknown3": "La Vega", + "unknown4": "La Vega" + }, + "coordinates": { + "latitude": 19.215087672, + "longitude": -70.515449351 + } + }, + { + "id": 404553728, + "name": "Monseñor Nouel", + "translations": { + "japanese": "モンセニョール・ノウエル", + "english": "Monseñor Nouel", + "french": "Monseñor Nouel", + "german": "Monseñor Nouel", + "italian": "Monseñor Nouel", + "spanish": "Monseñor Nouel", + "chinese_simple": "主教-瑙黎省", + "korean": "몬세뇨르노우엘", + "dutch": "Monseñor Nouel", + "portuguese": "Monseñor Nouel", + "russian": "Монсенор-Нуэль", + "chinese_traditional": "Monseñor Nouel", + "unknown1": "Monseñor Nouel", + "unknown2": "Monseñor Nouel", + "unknown3": "Monseñor Nouel", + "unknown4": "Monseñor Nouel" + }, + "coordinates": { + "latitude": 18.929443144, + "longitude": -70.41107895 + } + }, + { + "id": 404619264, + "name": "Monte Plata", + "translations": { + "japanese": "モンテ・プラタ", + "english": "Monte Plata", + "french": "Monte Plata", + "german": "Monte Plata", + "italian": "Monte Plata", + "spanish": "Monte Plata", + "chinese_simple": "银山省", + "korean": "몬테플라타", + "dutch": "Monte Plata", + "portuguese": "Monte Plata", + "russian": "Монте-Плата", + "chinese_traditional": "Monte Plata", + "unknown1": "Monte Plata", + "unknown2": "Monte Plata", + "unknown3": "Monte Plata", + "unknown4": "Monte Plata" + }, + "coordinates": { + "latitude": 18.797607208, + "longitude": -69.779363365 + } + }, + { + "id": 404684800, + "name": "San Cristóbal", + "translations": { + "japanese": "サン・クリストバル", + "english": "San Cristóbal", + "french": "San Cristóbal", + "german": "San Cristóbal", + "italian": "San Cristóbal", + "spanish": "San Cristóbal", + "chinese_simple": "圣克里斯多堡省", + "korean": "산크리스토발", + "dutch": "San Cristóbal", + "portuguese": "São Cristóvão", + "russian": "Сан-Кристобаль", + "chinese_traditional": "San Cristóbal", + "unknown1": "San Cristóbal", + "unknown2": "San Cristóbal", + "unknown3": "San Cristóbal", + "unknown4": "San Cristóbal" + }, + "coordinates": { + "latitude": 18.413085728, + "longitude": -70.097967747 + } + } + ] + }, + { + "id": 25, + "iso_code": "EC", + "name": "Ecuador", + "translations": { + "japanese": "エクアドル", + "english": "Ecuador", + "french": "Équateur", + "german": "Ecuador", + "italian": "Ecuador", + "spanish": "Ecuador", + "chinese_simple": "厄瓜多尔", + "korean": "에콰도르", + "dutch": "Ecuador", + "portuguese": "Equador", + "russian": "Эквадор", + "chinese_traditional": "Ecuador", + "unknown1": "Ecuador", + "unknown2": "Ecuador", + "unknown3": "Ecuador", + "unknown4": "Ecuador" + }, + "regions": [ + { + "id": 419430400, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": -0.21423441999999682, + "longitude": -78.497038438 + } + }, + { + "id": 419561472, + "name": "Pichincha", + "translations": { + "japanese": "ピチンチャ", + "english": "Pichincha", + "french": "Pichincha", + "german": "Pichincha", + "italian": "Pichincha", + "spanish": "Pichincha", + "chinese_simple": "皮钦查省", + "korean": "피친차", + "dutch": "Pichincha", + "portuguese": "Pichincha", + "russian": "Пичинча", + "chinese_traditional": "Pichincha", + "unknown1": "Pichincha", + "unknown2": "Pichincha", + "unknown3": "Pichincha", + "unknown4": "Pichincha" + }, + "coordinates": { + "latitude": -0.21423441999999682, + "longitude": -78.497038438 + } + }, + { + "id": 419627008, + "name": "Galápagos", + "translations": { + "japanese": "ガラパゴス", + "english": "Galápagos", + "french": "Galápagos", + "german": "Galapagosinseln", + "italian": "Galápagos", + "spanish": "Galápagos", + "chinese_simple": "加拉帕戈斯省", + "korean": "갈라파고스", + "dutch": "Galápagos", + "portuguese": "Galápagos", + "russian": "Галапагос", + "chinese_traditional": "Galápagos", + "unknown1": "Galápagos", + "unknown2": "Galápagos", + "unknown3": "Galápagos", + "unknown4": "Galápagos" + }, + "coordinates": { + "latitude": -0.8953867559999935, + "longitude": -89.59875319700001 + } + }, + { + "id": 419692544, + "name": "Azuay", + "translations": { + "japanese": "アスアイ", + "english": "Azuay", + "french": "Azuay", + "german": "Azuay", + "italian": "Azuay", + "spanish": "Azuay", + "chinese_simple": "阿苏艾省", + "korean": "아수아이", + "dutch": "Azuay", + "portuguese": "Azuay", + "russian": "Асуай", + "chinese_traditional": "Azuay", + "unknown1": "Azuay", + "unknown2": "Azuay", + "unknown3": "Azuay", + "unknown4": "Azuay" + }, + "coordinates": { + "latitude": -2.8784189600000047, + "longitude": -78.98043819 + } + }, + { + "id": 419758080, + "name": "Bolívar", + "translations": { + "japanese": "ボリーバル", + "english": "Bolívar", + "french": "Bolívar", + "german": "Bolívar", + "italian": "Bolívar", + "spanish": "Bolívar", + "chinese_simple": "玻利瓦尔省", + "korean": "볼리바르", + "dutch": "Bolívar", + "portuguese": "Bolívar", + "russian": "Боливар", + "chinese_traditional": "Bolívar", + "unknown1": "Bolívar", + "unknown2": "Bolívar", + "unknown3": "Bolívar", + "unknown4": "Bolívar" + }, + "coordinates": { + "latitude": -1.5985117479999928, + "longitude": -78.996917727 + } + }, + { + "id": 419823616, + "name": "Cañar", + "translations": { + "japanese": "カニャル", + "english": "Cañar", + "french": "Cañar", + "german": "Cañar", + "italian": "Cañar", + "spanish": "Cañar", + "chinese_simple": "卡尼亚尔省", + "korean": "카냐르", + "dutch": "Cañar", + "portuguese": "Cañar", + "russian": "Каньяр", + "chinese_traditional": "Cañar", + "unknown1": "Cañar", + "unknown2": "Cañar", + "unknown3": "Cañar", + "unknown4": "Cañar" + }, + "coordinates": { + "latitude": -2.730103532000001, + "longitude": -78.832122357 + } + }, + { + "id": 419889152, + "name": "Carchi", + "translations": { + "japanese": "カルチ", + "english": "Carchi", + "french": "Carchi", + "german": "Carchi", + "italian": "Carchi", + "spanish": "Carchi", + "chinese_simple": "卡尔奇省", + "korean": "카르치", + "dutch": "Carchi", + "portuguese": "Carchi", + "russian": "Карчи", + "chinese_traditional": "Carchi", + "unknown1": "Carchi", + "unknown2": "Carchi", + "unknown3": "Carchi", + "unknown4": "Carchi" + }, + "coordinates": { + "latitude": 0.79650878, + "longitude": -77.72799337800001 + } + }, + { + "id": 419954688, + "name": "Chimborazo", + "translations": { + "japanese": "チンボラソ", + "english": "Chimborazo", + "french": "Chimborazo", + "german": "Chimborazo", + "italian": "Chimborazo", + "spanish": "Chimborazo", + "chinese_simple": "钦博拉索省", + "korean": "침보라소", + "dutch": "Chimborazo", + "portuguese": "Chimborazo", + "russian": "Чимборасо", + "chinese_traditional": "Chimborazo", + "unknown1": "Chimborazo", + "unknown2": "Chimborazo", + "unknown3": "Chimborazo", + "unknown4": "Chimborazo" + }, + "coordinates": { + "latitude": -1.6644297160000008, + "longitude": -78.62887473400001 + } + }, + { + "id": 420020224, + "name": "Cotopaxi", + "translations": { + "japanese": "コトパクシ", + "english": "Cotopaxi", + "french": "Cotopaxi", + "german": "Cotopaxi", + "italian": "Cotopaxi", + "spanish": "Cotopaxi", + "chinese_simple": "科托帕希省", + "korean": "코토팍시", + "dutch": "Cotopaxi", + "portuguese": "Cotopaxi", + "russian": "Котопакси", + "chinese_traditional": "Cotopaxi", + "unknown1": "Cotopaxi", + "unknown2": "Cotopaxi", + "unknown3": "Cotopaxi", + "unknown4": "Cotopaxi" + }, + "coordinates": { + "latitude": -0.9283457399999975, + "longitude": -78.612395197 + } + }, + { + "id": 420085760, + "name": "El Oro", + "translations": { + "japanese": "エル・オロ", + "english": "El Oro", + "french": "El Oro", + "german": "El Oro", + "italian": "El Oro", + "spanish": "El Oro", + "chinese_simple": "埃尔奥罗省", + "korean": "엘오로", + "dutch": "El Oro", + "portuguese": "El Oro", + "russian": "Эль-Оро", + "chinese_traditional": "El Oro", + "unknown1": "El Oro", + "unknown2": "El Oro", + "unknown3": "El Oro", + "unknown4": "El Oro" + }, + "coordinates": { + "latitude": -3.262940439999994, + "longitude": -79.963717231 + } + }, + { + "id": 420151296, + "name": "Esmeraldas", + "translations": { + "japanese": "エスメラルダス", + "english": "Esmeraldas", + "french": "Esmeraldas", + "german": "Esmeraldas", + "italian": "Esmeraldas", + "spanish": "Esmeraldas", + "chinese_simple": "埃斯梅拉尔达斯省", + "korean": "에스메랄다스", + "dutch": "Esmeraldas", + "portuguese": "Esmeraldas", + "russian": "Эсмеральдас", + "chinese_traditional": "Esmeraldas", + "unknown1": "Esmeraldas", + "unknown2": "Esmeraldas", + "unknown3": "Esmeraldas", + "unknown4": "Esmeraldas" + }, + "coordinates": { + "latitude": 0.983276356, + "longitude": -79.69455146 + } + }, + { + "id": 420216832, + "name": "Guayas", + "translations": { + "japanese": "グアヤス", + "english": "Guayas", + "french": "Guayas", + "german": "Guayas", + "italian": "Guayas", + "spanish": "Guayas", + "chinese_simple": "瓜亚斯省", + "korean": "과야스", + "dutch": "Guayas", + "portuguese": "Guayas", + "russian": "Гуаяс", + "chinese_traditional": "Guayas", + "unknown1": "Guayas", + "unknown2": "Guayas", + "unknown3": "Guayas", + "unknown4": "Guayas" + }, + "coordinates": { + "latitude": -2.164307640000004, + "longitude": -79.897799083 + } + }, + { + "id": 420282368, + "name": "Imbabura", + "translations": { + "japanese": "インバブラ", + "english": "Imbabura", + "french": "Imbabura", + "german": "Imbabura", + "italian": "Imbabura", + "spanish": "Imbabura", + "chinese_simple": "因巴布拉省", + "korean": "임바부라", + "dutch": "Imbabura", + "portuguese": "Imbabura", + "russian": "Имбабура", + "chinese_traditional": "Imbabura", + "unknown1": "Imbabura", + "unknown2": "Imbabura", + "unknown3": "Imbabura", + "unknown4": "Imbabura" + }, + "coordinates": { + "latitude": 0.346069332, + "longitude": -78.112515908 + } + }, + { + "id": 420347904, + "name": "Loja", + "translations": { + "japanese": "ロハ", + "english": "Loja", + "french": "Loja", + "german": "Loja", + "italian": "Loja", + "spanish": "Loja", + "chinese_simple": "洛哈省", + "korean": "로하", + "dutch": "Loja", + "portuguese": "Loja", + "russian": "Лоха", + "chinese_traditional": "Loja", + "unknown1": "Loja", + "unknown2": "Loja", + "unknown3": "Loja", + "unknown4": "Loja" + }, + "coordinates": { + "latitude": -3.9990244159999975, + "longitude": -79.211151708 + } + }, + { + "id": 420413440, + "name": "Los Ríos", + "translations": { + "japanese": "ロス・リオス", + "english": "Los Ríos", + "french": "Los Ríos", + "german": "Los Ríos", + "italian": "Los Ríos", + "spanish": "Los Ríos", + "chinese_simple": "洛斯里奥斯省", + "korean": "로스 리오스", + "dutch": "Los Ríos", + "portuguese": "Los Ríos", + "russian": "Лос-Риос", + "chinese_traditional": "Los Ríos", + "unknown1": "Los Ríos", + "unknown2": "Los Ríos", + "unknown3": "Los Ríos", + "unknown4": "Los Ríos" + }, + "coordinates": { + "latitude": -1.8127451440000044, + "longitude": -79.513276553 + } + }, + { + "id": 420478976, + "name": "Manabí", + "translations": { + "japanese": "マナビ", + "english": "Manabí", + "french": "Manabí", + "german": "Manabí", + "italian": "Manabí", + "spanish": "Manabí", + "chinese_simple": "马纳比省", + "korean": "마나비", + "dutch": "Manabí", + "portuguese": "Manabí", + "russian": "Манаби", + "chinese_traditional": "Manabí", + "unknown1": "Manabí", + "unknown2": "Manabí", + "unknown3": "Manabí", + "unknown4": "Manabí" + }, + "coordinates": { + "latitude": -1.0491953479999978, + "longitude": -80.447116983 + } + }, + { + "id": 420544512, + "name": "Morona-Santiago", + "translations": { + "japanese": "モロナ・サンティアゴ", + "english": "Morona-Santiago", + "french": "Morona-Santiago", + "german": "Morona Santiago", + "italian": "Morona Santiago", + "spanish": "Morona-Santiago", + "chinese_simple": "莫罗纳-圣地亚哥省", + "korean": "모로나 산티아고", + "dutch": "Morona-Santiago", + "portuguese": "Morona-Santiago", + "russian": "Морона-Сантьяго", + "chinese_traditional": "Morona-Santiago", + "unknown1": "Morona-Santiago", + "unknown2": "Morona-Santiago", + "unknown3": "Morona-Santiago", + "unknown4": "Morona-Santiago" + }, + "coordinates": { + "latitude": -2.3126230679999935, + "longitude": -78.112515908 + } + }, + { + "id": 420610048, + "name": "Pastaza", + "translations": { + "japanese": "パスタサ", + "english": "Pastaza", + "french": "Pastaza", + "german": "Pastaza", + "italian": "Pastaza", + "spanish": "Pastaza", + "chinese_simple": "帕斯塔萨省", + "korean": "파스타사", + "dutch": "Pastaza", + "portuguese": "Pastaza", + "russian": "Пастаса", + "chinese_traditional": "Pastaza", + "unknown1": "Pastaza", + "unknown2": "Pastaza", + "unknown3": "Pastaza", + "unknown4": "Pastaza" + }, + "coordinates": { + "latitude": -1.4611826480000047, + "longitude": -77.980679612 + } + }, + { + "id": 420675584, + "name": "Tungurahua", + "translations": { + "japanese": "トゥングラワ", + "english": "Tungurahua", + "french": "Tungurahua", + "german": "Tungurahua", + "italian": "Tungurahua", + "spanish": "Tungurahua", + "chinese_simple": "通古拉瓦省", + "korean": "퉁그라우아", + "dutch": "Tungurahua", + "portuguese": "Tungurahua", + "russian": "Тункурагуа", + "chinese_traditional": "Tungurahua", + "unknown1": "Tungurahua", + "unknown2": "Tungurahua", + "unknown3": "Tungurahua", + "unknown4": "Tungurahua" + }, + "coordinates": { + "latitude": -1.2469492519999932, + "longitude": -78.612395197 + } + }, + { + "id": 420741120, + "name": "Zamora-Chinchipe", + "translations": { + "japanese": "サモラ・チンチペ", + "english": "Zamora-Chinchipe", + "french": "Zamora-Chinchipe", + "german": "Zamora Chinchipe", + "italian": "Zamora Chinchipe", + "spanish": "Zamora-Chinchipe", + "chinese_simple": "萨莫拉-钦奇佩省", + "korean": "사모라 친치페", + "dutch": "Zamora-Chinchipe", + "portuguese": "Zamora-Chinchipe", + "russian": "Самора-Чинчипе", + "chinese_traditional": "Zamora-Chinchipe", + "unknown1": "Zamora-Chinchipe", + "unknown2": "Zamora-Chinchipe", + "unknown3": "Zamora-Chinchipe", + "unknown4": "Zamora-Chinchipe" + }, + "coordinates": { + "latitude": -4.064942384000005, + "longitude": -78.952972295 + } + }, + { + "id": 420806656, + "name": "Sucumbios", + "translations": { + "japanese": "スクンビオス", + "english": "Sucumbios", + "french": "Sucumbíos", + "german": "Sucumbíos", + "italian": "Sucumbíos", + "spanish": "Sucumbíos", + "chinese_simple": "苏昆毕奥斯省", + "korean": "스쿰비오스", + "dutch": "Sucumbíos", + "portuguese": "Sucumbíos", + "russian": "Сукумбиос", + "chinese_traditional": "Sucumbios", + "unknown1": "Sucumbios", + "unknown2": "Sucumbios", + "unknown3": "Sucumbios", + "unknown4": "Sucumbios" + }, + "coordinates": { + "latitude": 0.08239746, + "longitude": -76.882043812 + } + }, + { + "id": 420872192, + "name": "Napo", + "translations": { + "japanese": "ナポ", + "english": "Napo", + "french": "Napo", + "german": "Napo", + "italian": "Napo", + "spanish": "Napo", + "chinese_simple": "纳波省", + "korean": "나포", + "dutch": "Napo", + "portuguese": "Napo", + "russian": "Напо", + "chinese_traditional": "Napo", + "unknown1": "Napo", + "unknown2": "Napo", + "unknown3": "Napo", + "unknown4": "Napo" + }, + "coordinates": { + "latitude": -0.9832773800000041, + "longitude": -77.815884242 + } + }, + { + "id": 420937728, + "name": "Orellana", + "translations": { + "japanese": "オレリャナ", + "english": "Orellana", + "french": "Orellana", + "german": "Orellana", + "italian": "Orellana", + "spanish": "Orellana", + "chinese_simple": "奥雷利亚纳省", + "korean": "오렐라나", + "dutch": "Orellana", + "portuguese": "Orellana", + "russian": "Орельяна", + "chinese_traditional": "Orellana", + "unknown1": "Orellana", + "unknown2": "Orellana", + "unknown3": "Orellana", + "unknown4": "Orellana" + }, + "coordinates": { + "latitude": -0.7965098039999958, + "longitude": -76.365684986 + } + }, + { + "id": 421003264, + "name": "Santa Elena", + "translations": { + "japanese": "サンタ・エレーナ", + "english": "Santa Elena", + "french": "Santa Elena", + "german": "Santa Elena", + "italian": "Santa Elena", + "spanish": "Santa Elena", + "chinese_simple": "圣埃伦娜省", + "korean": "산타엘레나", + "dutch": "Santa Elena", + "portuguese": "Santa Elena", + "russian": "Санта Элена", + "chinese_traditional": "Santa Elena", + "unknown1": "Santa Elena", + "unknown2": "Santa Elena", + "unknown3": "Santa Elena", + "unknown4": "Santa Elena" + }, + "coordinates": { + "latitude": -2.224732443999997, + "longitude": -80.853612229 + } + }, + { + "id": 421068800, + "name": "Santo Domingo de los Tsáchilas", + "translations": { + "japanese": "サント・ドミンゴ・デ・ロス・ツァチラス", + "english": "Santo Domingo de los Tsáchilas", + "french": "Santo Domingo de los Tsáchilas", + "german": "Santo Domingo de los Tsáchilas", + "italian": "Santo Domingo de los Tsáchilas", + "spanish": "Santo Domingo de los Tsáchilas", + "chinese_simple": "圣多明各-德洛斯查奇拉斯省", + "korean": "산토도밍고델로스트사칠라스", + "dutch": "Santo Domingo de los Tsáchilas", + "portuguese": "Santo Domingo de los Tsáchilas", + "russian": "Санто Доминго де лос Тсачилас", + "chinese_traditional": "Santo Domingo de los Tsáchilas", + "unknown1": "Santo Domingo de los Tsáchilas", + "unknown2": "Santo Domingo de los Tsáchilas", + "unknown3": "Santo Domingo de los Tsáchilas", + "unknown4": "Santo Domingo de los Tsáchilas" + }, + "coordinates": { + "latitude": -0.24719340400000078, + "longitude": -79.14523356 + } + } + ] + }, + { + "id": 26, + "iso_code": "SV", + "name": "El Salvador", + "translations": { + "japanese": "エルサルバドル", + "english": "El Salvador", + "french": "Salvador", + "german": "El Salvador", + "italian": "El Salvador", + "spanish": "El Salvador", + "chinese_simple": "萨尔瓦多", + "korean": "엘살바도르", + "dutch": "El Salvador", + "portuguese": "El Salvador", + "russian": "Сальвадор", + "chinese_traditional": "El Salvador", + "unknown1": "El Salvador", + "unknown2": "El Salvador", + "unknown3": "El Salvador", + "unknown4": "El Salvador" + }, + "regions": [ + { + "id": 436207616, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": 13.70544418, + "longitude": -89.19775113 + } + }, + { + "id": 436338688, + "name": "San Salvador", + "translations": { + "japanese": "サン・サルバドル県", + "english": "San Salvador", + "french": "San Salvador", + "german": "San Salvador", + "italian": "San Salvador", + "spanish": "San Salvador", + "chinese_simple": "圣萨尔瓦多省", + "korean": "산살바도르 주", + "dutch": "San Salvador", + "portuguese": "San Salvador", + "russian": "Сан-Сальвадор", + "chinese_traditional": "San Salvador", + "unknown1": "San Salvador", + "unknown2": "San Salvador", + "unknown3": "San Salvador", + "unknown4": "San Salvador" + }, + "coordinates": { + "latitude": 13.70544418, + "longitude": -89.19775113 + } + }, + { + "id": 436404224, + "name": "Ahuachapán", + "translations": { + "japanese": "アワチャパン県", + "english": "Ahuachapán", + "french": "Ahuachapán", + "german": "Ahuachapán", + "italian": "Ahuachapán", + "spanish": "Ahuachapán", + "chinese_simple": "阿瓦查潘省", + "korean": "아우아차판 주", + "dutch": "Ahuachapán", + "portuguese": "Ahuachapán", + "russian": "Ауачапан", + "chinese_traditional": "Ahuachapán", + "unknown1": "Ahuachapán", + "unknown2": "Ahuachapán", + "unknown3": "Ahuachapán", + "unknown4": "Ahuachapán" + }, + "coordinates": { + "latitude": 13.919677576, + "longitude": -89.840453073 + } + }, + { + "id": 436469760, + "name": "Cabañas", + "translations": { + "japanese": "カバニャス県", + "english": "Cabañas", + "french": "Cabañas", + "german": "Cabañas", + "italian": "Cabañas", + "spanish": "Cabañas", + "chinese_simple": "卡瓦尼亚斯省", + "korean": "카바냐스 주", + "dutch": "Cabañas", + "portuguese": "Cabañas", + "russian": "Кабанас", + "chinese_traditional": "Cabañas", + "unknown1": "Cabañas", + "unknown2": "Cabañas", + "unknown3": "Cabañas", + "unknown4": "Cabañas" + }, + "coordinates": { + "latitude": 13.864745936, + "longitude": -88.631953693 + } + }, + { + "id": 436535296, + "name": "Chalatenango", + "translations": { + "japanese": "チャラテナンゴ県", + "english": "Chalatenango", + "french": "Chalatenango", + "german": "Chalatenango", + "italian": "Chalatenango", + "spanish": "Chalatenango", + "chinese_simple": "查拉特南戈省", + "korean": "찰라테낭고 주", + "dutch": "Chalatenango", + "portuguese": "Chalatenango", + "russian": "Чалатенанго", + "chinese_traditional": "Chalatenango", + "unknown1": "Chalatenango", + "unknown2": "Chal얆徇᥄\udac7슃\udfd9榩", + "unknown3": "Chalatenango", + "unknown4": "Chalatenango" + }, + "coordinates": { + "latitude": 14.029540856, + "longitude": -88.928585359 + } + }, + { + "id": 436600832, + "name": "Cuscatlán", + "translations": { + "japanese": "クスカトラン県", + "english": "Cuscatlán", + "french": "Cuscatlán", + "german": "Cuscatlán", + "italian": "Cuscatlán", + "spanish": "Cuscatlán", + "chinese_simple": "库斯卡特兰省", + "korean": "쿠스카틀란 주", + "dutch": "Cuscatlán", + "portuguese": "Cuscatlán", + "russian": "Кускатлан", + "chinese_traditional": "Cuscatlán", + "unknown1": "Cuscatlán", + "unknown2": "Cuscatlán", + "unknown3": "Cuscatlán", + "unknown4": "Cuscatlán" + }, + "coordinates": { + "latitude": 13.716430508, + "longitude": -88.928585359 + } + }, + { + "id": 436666368, + "name": "La Libertad", + "translations": { + "japanese": "ラ・リベルター県", + "english": "La Libertad", + "french": "La Libertad", + "german": "La Libertad", + "italian": "La Libertad", + "spanish": "La Libertad", + "chinese_simple": "拉利伯塔德省", + "korean": "라리베르타드 주", + "dutch": "La Libertad", + "portuguese": "La Libertad", + "russian": "Ла-Либертад", + "chinese_traditional": "La Libertad", + "unknown1": "La Libertad", + "unknown2": "La Libertad", + "unknown3": "La Libertad", + "unknown4": "La Libertad" + }, + "coordinates": { + "latitude": 13.672485196, + "longitude": -89.274655636 + } + }, + { + "id": 436731904, + "name": "La Paz", + "translations": { + "japanese": "ラパス県", + "english": "La Paz", + "french": "La Paz", + "german": "La Paz", + "italian": "La Paz", + "spanish": "La Paz", + "chinese_simple": "拉巴斯省", + "korean": "라파스 주", + "dutch": "La Paz", + "portuguese": "La Paz", + "russian": "Ла-Пас", + "chinese_traditional": "La Paz", + "unknown1": "La Paz", + "unknown2": "La Paz", + "unknown3": "La Paz", + "unknown4": "La Paz" + }, + "coordinates": { + "latitude": 13.496703948, + "longitude": -88.862667211 + } + }, + { + "id": 436797440, + "name": "La Unión", + "translations": { + "japanese": "ラ・ウニオン県", + "english": "La Unión", + "french": "La Unión", + "german": "La Unión", + "italian": "La Unión", + "spanish": "La Unión", + "chinese_simple": "拉乌尼翁省", + "korean": "라우니온 주", + "dutch": "La Unión", + "portuguese": "La Unión", + "russian": "Ла-Унион", + "chinese_traditional": "La Unión", + "unknown1": "La Unión", + "unknown2": "La Unión", + "unknown3": "La Unión", + "unknown4": "La Unión" + }, + "coordinates": { + "latitude": 13.331909028, + "longitude": -87.840935917 + } + }, + { + "id": 436862976, + "name": "Morazán", + "translations": { + "japanese": "モラサン県", + "english": "Morazán", + "french": "Morazán", + "german": "Morazán", + "italian": "Morazán", + "spanish": "Morazán", + "chinese_simple": "莫拉桑省", + "korean": "모라산 주", + "dutch": "Morazán", + "portuguese": "Morazán", + "russian": "Морасан", + "chinese_traditional": "Morazán", + "unknown1": "Morazán", + "unknown2": "Morazán", + "unknown3": "Morazán", + "unknown4": "Morazán" + }, + "coordinates": { + "latitude": 13.699951016, + "longitude": -88.09911533 + } + }, + { + "id": 436928512, + "name": "San Miguel", + "translations": { + "japanese": "サン・ミゲル県", + "english": "San Miguel", + "french": "San Miguel", + "german": "San Miguel", + "italian": "San Miguel", + "spanish": "San Miguel", + "chinese_simple": "圣米格尔省", + "korean": "산미겔 주", + "dutch": "San Miguel", + "portuguese": "San Miguel", + "russian": "Сан-Мигель", + "chinese_traditional": "San Miguel", + "unknown1": "San Miguel", + "unknown2": "San Miguel", + "unknown3": "San Miguel", + "unknown4": "San Miguel" + }, + "coordinates": { + "latitude": 13.480224456, + "longitude": -88.18151301500001 + } + }, + { + "id": 436994048, + "name": "Santa Ana", + "translations": { + "japanese": "サンタ・アナ県", + "english": "Santa Ana", + "french": "Santa Ana", + "german": "Santa Ana", + "italian": "Santa Ana", + "spanish": "Santa Ana", + "chinese_simple": "圣安娜省", + "korean": "산타아나 주", + "dutch": "Santa Ana", + "portuguese": "Santa Ana", + "russian": "Санта-Ана", + "chinese_traditional": "Santa Ana", + "unknown1": "Santa Ana", + "unknown2": "Santa Ana", + "unknown3": "Santa Ana", + "unknown4": "Santa Ana" + }, + "coordinates": { + "latitude": 13.991088708, + "longitude": -89.554807765 + } + }, + { + "id": 437059584, + "name": "San Vicente", + "translations": { + "japanese": "サンビセンテ県", + "english": "San Vicente", + "french": "San Vicente", + "german": "San Vicente", + "italian": "San Vicente", + "spanish": "San Vicente", + "chinese_simple": "圣维森特省", + "korean": "산비센테 주", + "dutch": "San Vicente", + "portuguese": "San Vicente", + "russian": "Сан-Висенте", + "chinese_traditional": "San Vicente", + "unknown1": "San Vicente", + "unknown2": "San Vicente", + "unknown3": "San Vicente", + "unknown4": "San Vicente" + }, + "coordinates": { + "latitude": 13.628539884, + "longitude": -88.79674906300001 + } + }, + { + "id": 437125120, + "name": "Sonsonate", + "translations": { + "japanese": "ソンソナテ県", + "english": "Sonsonate", + "french": "Sonsonate", + "german": "Sonsonate", + "italian": "Sonsonate", + "spanish": "Sonsonate", + "chinese_simple": "松索纳特省", + "korean": "손소나테 주", + "dutch": "Sonsonate", + "portuguese": "Sonsonate", + "russian": "Сонсонате", + "chinese_traditional": "Sonsonate", + "unknown1": "Sonsonate", + "unknown2": "Sonsonate", + "unknown3": "Sonsonate", + "unknown4": "Sonsonate" + }, + "coordinates": { + "latitude": 13.716430508, + "longitude": -89.719603135 + } + }, + { + "id": 437190656, + "name": "Usulután", + "translations": { + "japanese": "ウスルタン県", + "english": "Usulután", + "french": "Usulután", + "german": "Usulután", + "italian": "Usulután", + "spanish": "Usulután", + "chinese_simple": "乌苏卢坦省", + "korean": "우술루탄 주", + "dutch": "Usulután", + "portuguese": "Usulután", + "russian": "Усулутан", + "chinese_traditional": "Usulután", + "unknown1": "Usulután", + "unknown2": "Usulután", + "unknown3": "Usulután", + "unknown4": "Usulután" + }, + "coordinates": { + "latitude": 13.34838852, + "longitude": -88.445185607 + } + } + ] + }, + { + "id": 27, + "iso_code": "GF", + "name": "French Guiana", + "translations": { + "japanese": "フランス領ギアナ", + "english": "French Guiana", + "french": "Guyane", + "german": "Französisch-Guyana", + "italian": "Guyana Francese", + "spanish": "Guayana Francesa", + "chinese_simple": "法属圭亚那", + "korean": "프랑스령 기아나", + "dutch": "Frans-Guyana", + "portuguese": "Guiana Francesa", + "russian": "Французская Гвиана", + "chinese_traditional": "French Guiana", + "unknown1": "French Guiana", + "unknown2": "French Guiana", + "unknown3": "French Guiana", + "unknown4": "French Guiana" + }, + "regions": [ + { + "id": 452984832, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": 4.932861272, + "longitude": -52.327533681999995 + } + }, + { + "id": 453050368, + "name": "French Guiana", + "translations": { + "japanese": "フランス領ギアナ", + "english": "French Guiana", + "french": "Guyane", + "german": "Französisch-Guyana", + "italian": "Guyana Francese", + "spanish": "Guayana Francesa", + "chinese_simple": "法属圭亚那", + "korean": "프랑스령 기아나", + "dutch": "Frans-Guyana", + "portuguese": "Guiana Francesa", + "russian": "Французская Гвиана", + "chinese_traditional": "French Guiana", + "unknown1": "French Guiana", + "unknown2": "French Guiana", + "unknown3": "French Guiana", + "unknown4": "French Guiana" + }, + "coordinates": { + "latitude": 4.932861272, + "longitude": -52.327533681999995 + } + } + ] + }, + { + "id": 28, + "iso_code": "GD", + "name": "Grenada", + "translations": { + "japanese": "グレナダ", + "english": "Grenada", + "french": "Grenade", + "german": "Grenada", + "italian": "Grenada", + "spanish": "Granada", + "chinese_simple": "格林纳达", + "korean": "그레나다", + "dutch": "Grenada", + "portuguese": "Granada", + "russian": "Гренада", + "chinese_traditional": "Grenada", + "unknown1": "Grenada", + "unknown2": "Grenada", + "unknown3": "Grenada", + "unknown4": "Grenada" + }, + "regions": [ + { + "id": 469762048, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": 12.046508652, + "longitude": -61.748335667000006 + } + }, + { + "id": 469827584, + "name": "Grenada", + "translations": { + "japanese": "グレナダ", + "english": "Grenada", + "french": "Grenade", + "german": "Grenada", + "italian": "Grenada", + "spanish": "Granada", + "chinese_simple": "格林纳达", + "korean": "그레나다", + "dutch": "Grenada", + "portuguese": "Granada", + "russian": "Гренада", + "chinese_traditional": "Grenada", + "unknown1": "Grenada", + "unknown2": "Grenada", + "unknown3": "Grenada", + "unknown4": "Grenada" + }, + "coordinates": { + "latitude": 12.046508652, + "longitude": -61.748335667000006 + } + } + ] + }, + { + "id": 29, + "iso_code": "GP", + "name": "Guadeloupe", + "translations": { + "japanese": "グアドループ", + "english": "Guadeloupe", + "french": "Guadeloupe", + "german": "Guadeloupe", + "italian": "Guadalupa", + "spanish": "Guadalupe", + "chinese_simple": "瓜德罗普", + "korean": "과들루프", + "dutch": "Guadeloupe", + "portuguese": "Guadalupe", + "russian": "Гваделупа", + "chinese_traditional": "Guadeloupe", + "unknown1": "Guadeloupe", + "unknown2": "Guadeloupe", + "unknown3": "Guadeloupe", + "unknown4": "Guadeloupe" + }, + "regions": [ + { + "id": 486539264, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": 15.996093568000001, + "longitude": -61.715376593 + } + }, + { + "id": 486604800, + "name": "Guadeloupe", + "translations": { + "japanese": "グアドループ", + "english": "Guadeloupe", + "french": "Guadeloupe", + "german": "Guadeloupe", + "italian": "Guadalupa", + "spanish": "Guadalupe", + "chinese_simple": "瓜德罗普", + "korean": "과들루프", + "dutch": "Guadeloupe", + "portuguese": "Guadalupe", + "russian": "Гваделупа", + "chinese_traditional": "Guadeloupe", + "unknown1": "Guadeloupe", + "unknown2": "Guadeloupe", + "unknown3": "Guadeloupe", + "unknown4": "Guadeloupe" + }, + "coordinates": { + "latitude": 15.996093568000001, + "longitude": -61.715376593 + } + } + ] + }, + { + "id": 30, + "iso_code": "GT", + "name": "Guatemala", + "translations": { + "japanese": "グアテマラ", + "english": "Guatemala", + "french": "Guatemala", + "german": "Guatemala", + "italian": "Guatemala", + "spanish": "Guatemala", + "chinese_simple": "危地马拉", + "korean": "과테말라", + "dutch": "Guatemala", + "portuguese": "Guatemala", + "russian": "Гватемала", + "chinese_traditional": "Guatemala", + "unknown1": "Guatemala", + "unknown2": "Guatemala", + "unknown3": "Guatemala", + "unknown4": "Guatemala" + }, + "regions": [ + { + "id": 503316480, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": 14.617309404, + "longitude": -90.521607269 + } + }, + { + "id": 503447552, + "name": "Guatemala", + "translations": { + "japanese": "グアテマラ県", + "english": "Guatemala", + "french": "Guatemala", + "german": "Guatemala", + "italian": "Guatemala", + "spanish": "Guatemala", + "chinese_simple": "危地马拉省", + "korean": "과테말라 주", + "dutch": "Guatemala", + "portuguese": "Guatemala", + "russian": "Гватемала", + "chinese_traditional": "Guatemala", + "unknown1": "Guatemala", + "unknown2": "Guatemala", + "unknown3": "Guatemala", + "unknown4": "Guatemala" + }, + "coordinates": { + "latitude": 14.617309404, + "longitude": -90.521607269 + } + }, + { + "id": 503513088, + "name": "Alta Verapaz", + "translations": { + "japanese": "アルタ・べラパス県", + "english": "Alta Verapaz", + "french": "Alta Verapaz", + "german": "Alta Verapaz", + "italian": "Alta Verapaz", + "spanish": "Alta Verapaz", + "chinese_simple": "上韦拉帕斯省", + "korean": "알타베라파스 주", + "dutch": "Alta Verapaz", + "portuguese": "Alta Verapaz", + "russian": "Альта-Верапас", + "chinese_traditional": "Alta Verapaz", + "unknown1": "Alta Verapaz", + "unknown2": "Alta Verapaz", + "unknown3": "Alta Verapaz", + "unknown4": "Alta Verapaz" + }, + "coordinates": { + "latitude": 15.479736152000001, + "longitude": -90.362305078 + } + }, + { + "id": 503578624, + "name": "Baja Verapaz", + "translations": { + "japanese": "バハ・べラパス県", + "english": "Baja Verapaz", + "french": "Baja Verapaz", + "german": "Baja Verapaz", + "italian": "Baja Verapaz", + "spanish": "Baja Verapaz", + "chinese_simple": "下韦拉帕斯省", + "korean": "바하베라파스 주", + "dutch": "Baja Verapaz", + "portuguese": "Baja Verapaz", + "russian": "Баха-Верапас", + "chinese_traditional": "Baja Verapaz", + "unknown1": "Baja Verapaz", + "unknown2": "Baja Verapaz", + "unknown3": "Baja Verapaz", + "unknown4": "Baja Verapaz" + }, + "coordinates": { + "latitude": 15.095214672000001, + "longitude": -90.263427856 + } + }, + { + "id": 503644160, + "name": "Chimaltenango", + "translations": { + "japanese": "チマルテナンゴ県", + "english": "Chimaltenango", + "french": "Chimaltenango", + "german": "Chimaltenango", + "italian": "Chimaltenango", + "spanish": "Chimaltenango", + "chinese_simple": "奇马尔特南戈省", + "korean": "치말테낭고 주", + "dutch": "Chimaltenango", + "portuguese": "Chimaltenango", + "russian": "Чимальтенанго", + "chinese_traditional": "Chimaltenango", + "unknown1": "Chimaltenango", + "unknown2": "Chimaltenango", + "unknown3": "Chimaltenango", + "unknown4": "Chimaltenango" + }, + "coordinates": { + "latitude": 14.66674788, + "longitude": -90.812745756 + } + }, + { + "id": 503709696, + "name": "Chiquimula", + "translations": { + "japanese": "チキムラ県", + "english": "Chiquimula", + "french": "Chiquimula", + "german": "Chiquimula", + "italian": "Chiquimula", + "spanish": "Chiquimula", + "chinese_simple": "奇基穆拉省", + "korean": "치키물라 주", + "dutch": "Chiquimula", + "portuguese": "Chiquimula", + "russian": "Чикимула", + "chinese_traditional": "Chiquimula", + "unknown1": "Chiquimula", + "unknown2": "Chiquimula", + "unknown3": "Chiquimula", + "unknown4": "Chiquimula" + }, + "coordinates": { + "latitude": 14.798583816, + "longitude": -89.549314586 + } + }, + { + "id": 503775232, + "name": "El Progreso", + "translations": { + "japanese": "エル・プログレソ県", + "english": "El Progreso", + "french": "El Progreso", + "german": "El Progreso", + "italian": "El Progreso", + "spanish": "El Progreso", + "chinese_simple": "埃尔普罗格雷索省", + "korean": "엘프로그레소 주", + "dutch": "El Progreso", + "portuguese": "El Progreso", + "russian": "Эль-Прогресо", + "chinese_traditional": "El Progreso", + "unknown1": "El Progreso", + "unknown2": "El Progreso", + "unknown3": "El Progreso", + "unknown4": "El Progreso" + }, + "coordinates": { + "latitude": 14.853515456, + "longitude": -90.060180233 + } + }, + { + "id": 503840768, + "name": "Escuintla", + "translations": { + "japanese": "エスクィントラ県", + "english": "Escuintla", + "french": "Escuintla", + "german": "Escuintla", + "italian": "Escuintla", + "spanish": "Escuintla", + "chinese_simple": "埃斯昆特拉省", + "korean": "에스쿠인틀라 주", + "dutch": "Escuintla", + "portuguese": "Escuintla", + "russian": "Эскуинтла", + "chinese_traditional": "Escuintla", + "unknown1": "Escuintla", + "unknown2": "Escuintla", + "unknown3": "Escuintla", + "unknown4": "Escuintla" + }, + "coordinates": { + "latitude": 14.304199056, + "longitude": -90.779786682 + } + }, + { + "id": 503906304, + "name": "Huehuetenango", + "translations": { + "japanese": "ウェウェテナンゴ県", + "english": "Huehuetenango", + "french": "Huehuetenango", + "german": "Huehuetenango", + "italian": "Huehuetenango", + "spanish": "Huehuetenango", + "chinese_simple": "韦韦特南戈省", + "korean": "우에우에테낭고 주", + "dutch": "Huehuetenango", + "portuguese": "Huehuetenango", + "russian": "Уэуэтенанго", + "chinese_traditional": "Huehuetenango", + "unknown1": "Huehuetenango", + "unknown2": "Huehuetenango", + "unknown3": "Huehuetenango", + "unknown4": "Huehuetenango" + }, + "coordinates": { + "latitude": 15.314941232, + "longitude": -91.466434057 + } + }, + { + "id": 503971840, + "name": "Izabal", + "translations": { + "japanese": "イザバル県", + "english": "Izabal", + "french": "Izabal", + "german": "Izabal", + "italian": "Izabal", + "spanish": "Izabal", + "chinese_simple": "伊萨瓦尔省", + "korean": "이사발 주", + "dutch": "Izabal", + "portuguese": "Izabal", + "russian": "Исабаль", + "chinese_traditional": "Izabal", + "unknown1": "Izabal", + "unknown2": "Izabal", + "unknown3": "Izabal", + "unknown4": "Izabal" + }, + "coordinates": { + "latitude": 15.715942204000001, + "longitude": -88.598994619 + } + }, + { + "id": 504037376, + "name": "Jalapa", + "translations": { + "japanese": "ハラパ県", + "english": "Jalapa", + "french": "Jalapa", + "german": "Jalapa", + "italian": "Jalapa", + "spanish": "Jalapa", + "chinese_simple": "哈拉帕省", + "korean": "할라파 주", + "dutch": "Jalapa", + "portuguese": "Jalapa", + "russian": "Халапа", + "chinese_traditional": "Jalapa", + "unknown1": "Jalapa", + "unknown2": "Jalapa", + "unknown3": "Jalapa", + "unknown4": "Jalapa" + }, + "coordinates": { + "latitude": 14.628295732, + "longitude": -89.97778254800001 + } + }, + { + "id": 504102912, + "name": "Jutiapa", + "translations": { + "japanese": "フティアパ県", + "english": "Jutiapa", + "french": "Jutiapa", + "german": "Jutiapa", + "italian": "Jutiapa", + "spanish": "Jutiapa", + "chinese_simple": "胡蒂亚帕省", + "korean": "후티아파 주", + "dutch": "Jutiapa", + "portuguese": "Jutiapa", + "russian": "Хутьяпа", + "chinese_traditional": "Jutiapa", + "unknown1": "Jutiapa", + "unknown2": "Jutiapa", + "unknown3": "Jutiapa", + "unknown4": "Jutiapa" + }, + "coordinates": { + "latitude": 14.2822264, + "longitude": -89.895384863 + } + }, + { + "id": 504168448, + "name": "Petén", + "translations": { + "japanese": "エル・ペテン県", + "english": "Petén", + "french": "El Petén", + "german": "El Petén", + "italian": "Petén", + "spanish": "Petén", + "chinese_simple": "佩滕省", + "korean": "페텐 주", + "dutch": "Petén", + "portuguese": "Petén", + "russian": "Петен", + "chinese_traditional": "Petén", + "unknown1": "Petén", + "unknown2": "Petén", + "unknown3": "Petén", + "unknown4": "Petén" + }, + "coordinates": { + "latitude": 16.929931448, + "longitude": -89.878905326 + } + }, + { + "id": 504233984, + "name": "Quetzaltenango", + "translations": { + "japanese": "ケツァルテナンゴ県", + "english": "Quetzaltenango", + "french": "Quetzaltenango", + "german": "Quetzaltenango", + "italian": "Quetzaltenango", + "spanish": "Quetzaltenango", + "chinese_simple": "克萨尔特南戈省", + "korean": "케트살테낭고 주", + "dutch": "Quetzaltenango", + "portuguese": "Quetzaltenango", + "russian": "Кецальтенанго", + "chinese_traditional": "Quetzaltenango", + "unknown1": "Quetzaltenango", + "unknown2": "Quetzaltenango", + "unknown3": "Quetzaltenango", + "unknown4": "Quetzaltenango" + }, + "coordinates": { + "latitude": 14.8315428, + "longitude": -91.515872668 + } + }, + { + "id": 504299520, + "name": "Quiché", + "translations": { + "japanese": "エル・キチェ県", + "english": "Quiché", + "french": "El Quiché", + "german": "El Quiché", + "italian": "Quiché", + "spanish": "Quiché", + "chinese_simple": "基切省", + "korean": "키체 주", + "dutch": "Quiché", + "portuguese": "Quiché", + "russian": "Киче", + "chinese_traditional": "Quiché", + "unknown1": "Quiché", + "unknown2": "Quiché", + "unknown3": "Quiché", + "unknown4": "Quiché" + }, + "coordinates": { + "latitude": 15.029296704, + "longitude": -91.147829675 + } + }, + { + "id": 504365056, + "name": "Retalhuleu", + "translations": { + "japanese": "レタルーレウ県", + "english": "Retalhuleu", + "french": "Retalhuleu", + "german": "Retalhuleu", + "italian": "Retalhuleu", + "spanish": "Retalhuleu", + "chinese_simple": "雷塔卢莱乌省", + "korean": "레탈룰레우 주", + "dutch": "Retalhuleu", + "portuguese": "Retalhuleu", + "russian": "Реталулеу", + "chinese_traditional": "Retalhuleu", + "unknown1": "Retalhuleu", + "unknown2": "Retalhuleu", + "unknown3": "Retalhuleu", + "unknown4": "Retalhuleu" + }, + "coordinates": { + "latitude": 14.52941878, + "longitude": -91.68066803800001 + } + }, + { + "id": 504430592, + "name": "Sacatepéquez", + "translations": { + "japanese": "サカテペケス県", + "english": "Sacatepéquez", + "french": "Sacatepéquez", + "german": "Sacatepéquez", + "italian": "Sacatepéquez", + "spanish": "Sacatepéquez", + "chinese_simple": "萨卡特佩克斯省", + "korean": "사카테페케스 주", + "dutch": "Sacatepéquez", + "portuguese": "Sacatepéquez", + "russian": "Сакатепекес", + "chinese_traditional": "Sacatepéquez", + "unknown1": "Sacatepéquez", + "unknown2": "Sacatepéquez", + "unknown3": "Sacatepéquez", + "unknown4": "Sacatepéquez" + }, + "coordinates": { + "latitude": 14.5568846, + "longitude": -90.730348071 + } + }, + { + "id": 504496128, + "name": "San Marcos", + "translations": { + "japanese": "サン・マルコス県", + "english": "San Marcos", + "french": "San Marcos", + "german": "San Marcos", + "italian": "San Marcos", + "spanish": "San Marcos", + "chinese_simple": "圣马科斯省", + "korean": "산마르코스 주", + "dutch": "San Marcos", + "portuguese": "San Marcos", + "russian": "Сан-Маркос", + "chinese_traditional": "San Marcos", + "unknown1": "San Marcos", + "unknown2": "San Marcos", + "unknown3": "San Marcos", + "unknown4": "San Marcos" + }, + "coordinates": { + "latitude": 14.963378736, + "longitude": -91.796024797 + } + }, + { + "id": 504561664, + "name": "Santa Rosa", + "translations": { + "japanese": "サンタ・ローサ県", + "english": "Santa Rosa", + "french": "Santa Rosa", + "german": "Santa Rosa", + "italian": "Santa Rosa", + "spanish": "Santa Rosa", + "chinese_simple": "圣罗莎省", + "korean": "산타로사 주", + "dutch": "Santa Rosa", + "portuguese": "Santa Rosa", + "russian": "Санта-Роса", + "chinese_traditional": "Santa Rosa", + "unknown1": "Santa Rosa", + "unknown2": "Santa Rosa", + "unknown3": "Santa Rosa", + "unknown4": "Santa Rosa" + }, + "coordinates": { + "latitude": 14.271240072, + "longitude": -90.29638693 + } + }, + { + "id": 504627200, + "name": "Sololá", + "translations": { + "japanese": "ソロラ県", + "english": "Sololá", + "french": "Sololá", + "german": "Sololá", + "italian": "Sololá", + "spanish": "Sololá", + "chinese_simple": "索洛拉省", + "korean": "솔롤라 주", + "dutch": "Sololá", + "portuguese": "Sololá", + "russian": "Солола", + "chinese_traditional": "Sololá", + "unknown1": "Sololá", + "unknown2": "Sololá", + "unknown3": "Sololá", + "unknown4": "Sololá" + }, + "coordinates": { + "latitude": 14.765624832, + "longitude": -91.180788749 + } + }, + { + "id": 504692736, + "name": "Suchitepéquez", + "translations": { + "japanese": "スチテペケス県", + "english": "Suchitepéquez", + "french": "Suchitepéquez", + "german": "Suchitepéquez", + "italian": "Suchitepéquez", + "spanish": "Suchitepéquez", + "chinese_simple": "苏奇特佩克斯省", + "korean": "수치테페케스 주", + "dutch": "Suchitepéquez", + "portuguese": "Suchitepéquez", + "russian": "Сучитепекес", + "chinese_traditional": "Suchitepéquez", + "unknown1": "Suchitepéquez", + "unknown2": "Suchitepéquez", + "unknown3": "Suchitepéquez", + "unknown4": "Suchitepéquez" + }, + "coordinates": { + "latitude": 14.52941878, + "longitude": -91.499393131 + } + }, + { + "id": 504758272, + "name": "Totonicapán", + "translations": { + "japanese": "トトニカパン県", + "english": "Totonicapán", + "french": "Totonicapán", + "german": "Totonicapán", + "italian": "Totonicapán", + "spanish": "Totonicapán", + "chinese_simple": "托托尼卡潘省", + "korean": "토토니카판 주", + "dutch": "Totonicapán", + "portuguese": "Totonicapán", + "russian": "Тотоникапан", + "chinese_traditional": "Totonicapán", + "unknown1": "Totonicapán", + "unknown2": "Totonicapán", + "unknown3": "Totonicapán", + "unknown4": "Totonicapán" + }, + "coordinates": { + "latitude": 14.91394026, + "longitude": -91.362063656 + } + }, + { + "id": 504823808, + "name": "Zacapa", + "translations": { + "japanese": "サカパ県", + "english": "Zacapa", + "french": "Zacapa", + "german": "Zacapa", + "italian": "Zacapa", + "spanish": "Zacapa", + "chinese_simple": "萨卡帕省", + "korean": "사카파 주", + "dutch": "Zacapa", + "portuguese": "Zacapa", + "russian": "Сакапа", + "chinese_traditional": "Zacapa", + "unknown1": "Zacapa", + "unknown2": "Zacapa", + "unknown3": "Zacapa", + "unknown4": "Zacapa" + }, + "coordinates": { + "latitude": 14.963378736, + "longitude": -89.532835049 + } + } + ] + }, + { + "id": 31, + "iso_code": "GY", + "name": "Guyana", + "translations": { + "japanese": "ガイアナ", + "english": "Guyana", + "french": "République coopérative de Guyana", + "german": "Guyana", + "italian": "Guyana", + "spanish": "Guyana", + "chinese_simple": "圭亚那", + "korean": "가이아나", + "dutch": "Guyana", + "portuguese": "Guiana", + "russian": "Гайана", + "chinese_traditional": "Guyana", + "unknown1": "Guyana", + "unknown2": "Guyana", + "unknown3": "Guyana", + "unknown4": "Guyana" + }, + "regions": [ + { + "id": 520093696, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": 6.795043868, + "longitude": -58.161289780000004 + } + }, + { + "id": 520224768, + "name": "Demerara-Mahaica", + "translations": { + "japanese": "デメララ・マハイカ州", + "english": "Demerara-Mahaica", + "french": "Demerara-Mahaica", + "german": "Demerara-Mahaica", + "italian": "Demerara-Mahaica", + "spanish": "Demerara-Mahaica", + "chinese_simple": "德梅拉拉-马海卡区", + "korean": "데메라라-마하이카 주", + "dutch": "Demerara-Mahaica", + "portuguese": "Demerara-Mahaica", + "russian": "Демерара-Махаиса", + "chinese_traditional": "Demerara-Mahaica", + "unknown1": "Demerara-Mahaica", + "unknown2": "Demerara-Mahaica", + "unknown3": "Demerara-Mahaica", + "unknown4": "Demerara-Mahaica" + }, + "coordinates": { + "latitude": 6.795043868, + "longitude": -58.161289780000004 + } + }, + { + "id": 520290304, + "name": "Barima-Waini", + "translations": { + "japanese": "バリマ・ワイニ州", + "english": "Barima-Waini", + "french": "Barima-Waini", + "german": "Barima-Waini", + "italian": "Barima-Waini", + "spanish": "Barima-Waini", + "chinese_simple": "巴里马-瓦伊尼区", + "korean": "바리마-와이니 주", + "dutch": "Barima-Waini", + "portuguese": "Barima-Waini", + "russian": "Барима-Ваини", + "chinese_traditional": "Barima-Waini", + "unknown1": "Barima-Waini", + "unknown2": "Barima-Waini", + "unknown3": "Barima-Waini", + "unknown4": "Barima-Waini" + }, + "coordinates": { + "latitude": 8.195800688, + "longitude": -59.781777585 + } + }, + { + "id": 520355840, + "name": "Cuyuni-Mazaruni", + "translations": { + "japanese": "クユニ・マザルニ州", + "english": "Cuyuni-Mazaruni", + "french": "Cuyuni-Mazaruni", + "german": "Cuyuni-Mazaruni", + "italian": "Cuyuni-Mazaruni", + "spanish": "Cuyuni-Mazaruni", + "chinese_simple": "库尤尼-马扎鲁尼区", + "korean": "쿠유니-마자루니 주", + "dutch": "Cuyuni-Mazaruni", + "portuguese": "Cuyuni-Mazaruni", + "russian": "Кайуни-Мазаруни", + "chinese_traditional": "Cuyuni-Mazaruni", + "unknown1": "Cuyuni-Mazaruni", + "unknown2": "Cuyuni-Mazaruni", + "unknown3": "Cuyuni-Mazaruni", + "unknown4": "Cuyuni-Mazaruni" + }, + "coordinates": { + "latitude": 6.39953606, + "longitude": -58.611730458 + } + }, + { + "id": 520421376, + "name": "East Berbice-Corentyne", + "translations": { + "japanese": "東ベルビセ・コレンティネ州", + "english": "East Berbice-Corentyne", + "french": "Berbice Oriental-Courantyne ", + "german": "East Berbice-Corentyne", + "italian": "Berbice Orientale-Corentyne", + "spanish": "Berbice Oriental-Corentyne", + "chinese_simple": "东伯比斯-科兰太因区", + "korean": "동부 버비스-코런타인 주", + "dutch": "East Berbice-Corentyne", + "portuguese": "Berbice Oriental-Corentyne", + "russian": "Восточный Бербайс-Корентин", + "chinese_traditional": "East Berbice-Corentyne", + "unknown1": "East Berbice-Corentyne", + "unknown2": "East Berbice-Corentyne", + "unknown3": "East Berbice-Corentyne", + "unknown4": "East Berbice-Corentyne" + }, + "coordinates": { + "latitude": 6.245727468, + "longitude": -57.513094658 + } + }, + { + "id": 520486912, + "name": "Essequibo Islands-West Demerara", + "translations": { + "japanese": "エセキボ諸島・西デメララ州", + "english": "Essequibo Islands-West Demerara", + "french": "Îles Essequibo-Demerara Occidental", + "german": "Essequibo Islands-West Demerara", + "italian": "Isole Essequibo-Demerara Occidentale", + "spanish": "Islas Essequibo-Demerara Occidental", + "chinese_simple": "埃塞奎博群岛-西德梅拉拉区", + "korean": "에세퀴보 섬-서부 데메라라 주", + "dutch": "Essequibo Islands-West Demerara", + "portuguese": "Ilhas Essequibo-Demerara Ocidental", + "russian": "Эссекуибо-Айлендс-Западный Демерара", + "chinese_traditional": "Essequibo Islands-West Demerara", + "unknown1": "Essequibo Islands-West Demerara", + "unknown2": "Essequibo Islands-West Demerara", + "unknown3": "Essequibo Islands-West Demerara", + "unknown4": "Essequibo Islands-West Demerara" + }, + "coordinates": { + "latitude": 6.866455, + "longitude": -58.413976014 + } + }, + { + "id": 520552448, + "name": "Mahaica-Berbice", + "translations": { + "japanese": "マハイカ・ベルビセ州", + "english": "Mahaica-Berbice", + "french": "Mahaica-Berbice", + "german": "Mahaica-Berbice", + "italian": "Mahaica-Berbice", + "spanish": "Mahaica-Berbice", + "chinese_simple": "马海卡-伯比斯区", + "korean": "마하이카-버비스 주", + "dutch": "Mahaica-Berbice", + "portuguese": "Mahaica-Berbice", + "russian": "Махаиса-Бербайс", + "chinese_traditional": "Mahaica-Berbice", + "unknown1": "Mahaica-Berbice", + "unknown2": "Mahaica-Berbice", + "unknown3": "Mahaica-Berbice", + "unknown4": "Mahaica-Berbice" + }, + "coordinates": { + "latitude": 6.39953606, + "longitude": -57.595492343000004 + } + }, + { + "id": 520617984, + "name": "Pomeroon-Supenaam", + "translations": { + "japanese": "ポメローン・スペナーム州", + "english": "Pomeroon-Supenaam", + "french": "Pomeroon-Supenaam", + "german": "Pomeroon-Supenaam", + "italian": "Pomeroon-Supenaam", + "spanish": "Pomeroon-Supenaam", + "chinese_simple": "波默伦-苏佩纳姆区", + "korean": "포메룬-수페남 주", + "dutch": "Pomeroon-Supenaam", + "portuguese": "Pomeroon-Supenaam", + "russian": "Померун-Супенаам", + "chinese_traditional": "Pomeroon-Supenaam", + "unknown1": "Pomeroon-Supenaam", + "unknown2": "Pomeroon-Supenaam", + "unknown3": "Pomeroon-Supenaam", + "unknown4": "Pomeroon-Supenaam" + }, + "coordinates": { + "latitude": 7.261962808, + "longitude": -58.496373699 + } + }, + { + "id": 520683520, + "name": "Potaro-Siparuni", + "translations": { + "japanese": "ポタロ・シパルニ州", + "english": "Potaro-Siparuni", + "french": "Potaro-Siparuni", + "german": "Potaro-Siparuni", + "italian": "Potaro-Siparuni", + "spanish": "Potaro-Siparuni", + "chinese_simple": "波塔罗-锡帕鲁尼区", + "korean": "포타로-시파루니 주", + "dutch": "Potaro-Siparuni", + "portuguese": "Potaro-Siparuni", + "russian": "Потаро-Сипаруни", + "chinese_traditional": "Potaro-Siparuni", + "unknown1": "Potaro-Siparuni", + "unknown2": "Potaro-Siparuni", + "unknown3": "Potaro-Siparuni", + "unknown4": "Potaro-Siparuni" + }, + "coordinates": { + "latitude": 5.262451112, + "longitude": -59.14456882100001 + } + }, + { + "id": 520749056, + "name": "Upper Demerara-Berbice", + "translations": { + "japanese": "アッパー・デメララ・ベルビセ州", + "english": "Upper Demerara-Berbice", + "french": "Haut-Demerara et Berbice", + "german": "Upper Demerara-Berbice", + "italian": "Alto Demerara-Berbice", + "spanish": "Alto Demerara-Berbice", + "chinese_simple": "上德梅拉拉-伯比斯区", + "korean": "북부 데메라라-버비스 주", + "dutch": "Upper Demerara-Berbice", + "portuguese": "Alto Demerara-Berbice", + "russian": "Верхний Демерара-Бербайс", + "chinese_traditional": "Upper Demerara-Berbice", + "unknown1": "Upper Demerara-Berbice", + "unknown2": "Upper Demerara-Berbice", + "unknown3": "Upper Demerara-Berbice", + "unknown4": "Upper Demerara-Berbice" + }, + "coordinates": { + "latitude": 5.998535088, + "longitude": -58.298619255000006 + } + }, + { + "id": 520814592, + "name": "Upper Takutu-Upper Essequibo", + "translations": { + "japanese": "アッパー・タクトゥ・アッパー・エセキボ州", + "english": "Upper Takutu-Upper Essequibo", + "french": "Haut-Takutu et Haut-Essequibo", + "german": "Upper Takutu-Upper Essequibo", + "italian": "Alto Takutu-Alto Essequibo", + "spanish": "Alto Takutu-Alto Essequibo", + "chinese_simple": "上塔库图-上埃塞奎博区", + "korean": "북부 타쿠투-북부 에세퀴보 주", + "dutch": "Upper Takutu-Upper Essequibo", + "portuguese": "Alto Takutu-Alto Essequibo", + "russian": "Верхний Такуту-Верхний Эссекуибо", + "chinese_traditional": "Upper Takutu-Upper Essequibo", + "unknown1": "Upper Takutu-Upper Essequibo", + "unknown2": "Upper Takutu-Upper Essequibo", + "unknown3": "Upper Takutu-Upper Essequibo", + "unknown4": "Upper Takutu-Upper Essequibo" + }, + "coordinates": { + "latitude": 3.37829586, + "longitude": -59.798257121999995 + } + } + ] + }, + { + "id": 32, + "iso_code": "HT", + "name": "Haiti", + "translations": { + "japanese": "ハイチ", + "english": "Haiti", + "french": "Haïti", + "german": "Haiti", + "italian": "Haiti", + "spanish": "Haití", + "chinese_simple": "海地", + "korean": "아이티", + "dutch": "Haïti", + "portuguese": "Haiti", + "russian": "Гаити", + "chinese_traditional": "Haiti", + "unknown1": "Haiti", + "unknown2": "Haiti", + "unknown3": "Haiti", + "unknown4": "Haiti" + }, + "regions": [ + { + "id": 536870912, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": 18.533935336, + "longitude": -72.3336916 + } + }, + { + "id": 537001984, + "name": "Ouest", + "translations": { + "japanese": "西県", + "english": "Ouest", + "french": "Ouest", + "german": "Ouest", + "italian": "Ovest", + "spanish": "Oeste", + "chinese_simple": "西部省", + "korean": "서부", + "dutch": "Ouest", + "portuguese": "Oeste", + "russian": "Западный департамент", + "chinese_traditional": "Ouest", + "unknown1": "Ouest", + "unknown2": "Ouest", + "unknown3": "Ouest", + "unknown4": "Ouest" + }, + "coordinates": { + "latitude": 18.533935336, + "longitude": -72.3336916 + } + }, + { + "id": 537067520, + "name": "Nord-Ouest", + "translations": { + "japanese": "北西県", + "english": "Nord-Ouest", + "french": "Nord-Ouest", + "german": "Nord-Ouest", + "italian": "Nord-Ovest", + "spanish": "Noroeste", + "chinese_simple": "西北省", + "korean": "북서부", + "dutch": "Nord-Ouest", + "portuguese": "Noroeste", + "russian": "Северо-Западный департамент", + "chinese_traditional": "Nord-Ouest", + "unknown1": "Nord-Ouest", + "unknown2": "Nord-Ouest", + "unknown3": "Nord-Ouest", + "unknown4": "Nord-Ouest" + }, + "coordinates": { + "latitude": 19.945678484000002, + "longitude": -72.82807771 + } + }, + { + "id": 537133056, + "name": "Artibonite", + "translations": { + "japanese": "アルティボニット県", + "english": "Artibonite", + "french": "Artibonite", + "german": "Artibonite", + "italian": "Artibonite", + "spanish": "Artibonito", + "chinese_simple": "阿蒂博尼特省", + "korean": "아르티보니트", + "dutch": "Artibonite", + "portuguese": "Artibonite", + "russian": "Артибонит", + "chinese_traditional": "Artibonite", + "unknown1": "Artibonite", + "unknown2": "Artibonite", + "unknown3": "Artibonite", + "unknown4": "Artibonite" + }, + "coordinates": { + "latitude": 19.44580056, + "longitude": -72.679761877 + } + }, + { + "id": 537198592, + "name": "Centre", + "translations": { + "japanese": "中央県", + "english": "Centre", + "french": "Centre", + "german": "Centre", + "italian": "Centro", + "spanish": "Centro", + "chinese_simple": "中部省", + "korean": "중앙부", + "dutch": "Centre", + "portuguese": "Centro", + "russian": "Центральный департамент", + "chinese_traditional": "Centre", + "unknown1": "Centre", + "unknown2": "Centre", + "unknown3": "Centre", + "unknown4": "Centre" + }, + "coordinates": { + "latitude": 19.149169704, + "longitude": -72.015087218 + } + }, + { + "id": 537264128, + "name": "Grand'Anse", + "translations": { + "japanese": "湾岸県", + "english": "Grand'Anse", + "french": "Grande-Anse", + "german": "Grand'Anse", + "italian": "Grande Anse", + "spanish": "Grand'Anse", + "chinese_simple": "大湾省", + "korean": "그랑당스", + "dutch": "Grand'Anse", + "portuguese": "Grand'Anse", + "russian": "Гранд-Анс", + "chinese_traditional": "Grand'Anse", + "unknown1": "Grand'Anse", + "unknown2": "Grand'Anse", + "unknown3": "Grand'Anse", + "unknown4": "Grand'Anse" + }, + "coordinates": { + "latitude": 18.64929178, + "longitude": -74.113481596 + } + }, + { + "id": 537329664, + "name": "Nord", + "translations": { + "japanese": "北県", + "english": "Nord", + "french": "Nord", + "german": "Nord", + "italian": "Nord", + "spanish": "Norte", + "chinese_simple": "北部省", + "korean": "북부", + "dutch": "Nord", + "portuguese": "Norte", + "russian": "Северный департамент", + "chinese_traditional": "Nord", + "unknown1": "Nord", + "unknown2": "Nord", + "unknown3": "Nord", + "unknown4": "Nord" + }, + "coordinates": { + "latitude": 19.753417744, + "longitude": -72.201855304 + } + }, + { + "id": 537395200, + "name": "Nord-Est", + "translations": { + "japanese": "北東県", + "english": "Nord-Est", + "french": "Nord-Est", + "german": "Nord-Est", + "italian": "Nord-Est", + "spanish": "Noreste", + "chinese_simple": "东北省", + "korean": "북동부", + "dutch": "Nord-Est", + "portuguese": "Nordeste", + "russian": "Северо-Восточный департамент", + "chinese_traditional": "Nord-Est", + "unknown1": "Nord-Est", + "unknown2": "Nord-Est", + "unknown3": "Nord-Est", + "unknown4": "Nord-Est" + }, + "coordinates": { + "latitude": 19.66552712, + "longitude": -71.83930549 + } + }, + { + "id": 537460736, + "name": "Sud", + "translations": { + "japanese": "南県", + "english": "Sud", + "french": "Sud", + "german": "Sud", + "italian": "Sud", + "spanish": "Sur", + "chinese_simple": "南部省", + "korean": "남부", + "dutch": "Sud", + "portuguese": "Sul", + "russian": "Южный департамент", + "chinese_traditional": "Sud", + "unknown1": "Sud", + "unknown2": "Sud", + "unknown3": "Sud", + "unknown4": "Sud" + }, + "coordinates": { + "latitude": 18.198852332, + "longitude": -73.745438603 + } + }, + { + "id": 537526272, + "name": "Sud-Est", + "translations": { + "japanese": "南東県", + "english": "Sud-Est", + "french": "Sud-Est", + "german": "Sud-Est", + "italian": "Sud-Est", + "spanish": "Sureste", + "chinese_simple": "东南省", + "korean": "남동부", + "dutch": "Sud-Est", + "portuguese": "Sudeste", + "russian": "Юго-Восточный департамент", + "chinese_traditional": "Sud-Est", + "unknown1": "Sud-Est", + "unknown2": "Sud-Est", + "unknown3": "Sud-Est", + "unknown4": "Sud-Est" + }, + "coordinates": { + "latitude": 18.231811316, + "longitude": -72.531446044 + } + }, + { + "id": 537591808, + "name": "Nippes", + "translations": { + "japanese": "ニップ県", + "english": "Nippes", + "french": "Nippes", + "german": "Nippes", + "italian": "Nippes", + "spanish": "Nippes", + "chinese_simple": "尼普斯省", + "korean": "니프", + "dutch": "Nippes", + "portuguese": "Nippes", + "russian": "Нип", + "chinese_traditional": "Nippes", + "unknown1": "Nippes", + "unknown2": "Nippes", + "unknown3": "Nippes", + "unknown4": "Nippes" + }, + "coordinates": { + "latitude": 18.440551548, + "longitude": -73.086257123 + } + } + ] + }, + { + "id": 33, + "iso_code": "HN", + "name": "Honduras", + "translations": { + "japanese": "ホンジュラス", + "english": "Honduras", + "french": "Honduras", + "german": "Honduras", + "italian": "Honduras", + "spanish": "Honduras", + "chinese_simple": "洪都拉斯", + "korean": "온두라스", + "dutch": "Honduras", + "portuguese": "Honduras", + "russian": "Гондурас", + "chinese_traditional": "Honduras", + "unknown1": "Honduras", + "unknown2": "Honduras", + "unknown3": "Honduras", + "unknown4": "Honduras" + }, + "regions": [ + { + "id": 553648128, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": 14.095458824, + "longitude": -87.214713511 + } + }, + { + "id": 553779200, + "name": "Fr얆徇᥄\udac7슃\udfd9榩\ud822ﰕ\udcca䮍輝n", + "translations": { + "japanese": "フランシスコ・モラサン", + "english": "Fr얆徇᥄\udac7슃\udfd9榩\ud822ﰕ\udcca䮍輝n", + "french": "Francisco Morazán", + "german": "Francisco Morazán", + "italian": "Francisco Morazán", + "spanish": "Francisco Morazán", + "chinese_simple": "弗朗西斯科-莫拉桑省", + "korean": "프란시스코모라산", + "dutch": "Francisco Morazán", + "portuguese": "Francisco Morazán", + "russian": "Франсиско Морасан", + "chinese_traditional": "Francisco Morazán", + "unknown1": "Francisco Morazán", + "unknown2": "Francisco Morazán", + "unknown3": "Francisco Morazán", + "unknown4": "Francisco Morazán" + }, + "coordinates": { + "latitude": 14.095458824, + "longitude": -87.214713511 + } + }, + { + "id": 553844736, + "name": "Atlántida", + "translations": { + "japanese": "アトランティダ", + "english": "Atlántida", + "french": "Atlántida", + "german": "Atlántida", + "italian": "Atlántida", + "spanish": "Atlántida", + "chinese_simple": "阿特兰蒂达省", + "korean": "아틀란티다", + "dutch": "Atlántida", + "portuguese": "Atlántida", + "russian": "Атлантида", + "chinese_traditional": "Atlántida", + "unknown1": "Atlántida", + "unknown2": "Atlántida", + "unknown3": "Atlántida", + "unknown4": "Atlántida" + }, + "coordinates": { + "latitude": 15.781860172, + "longitude": -86.797231907 + } + }, + { + "id": 553910272, + "name": "Choluteca", + "translations": { + "japanese": "チョルテカ", + "english": "Choluteca", + "french": "Choluteca", + "german": "Choluteca", + "italian": "Choluteca", + "spanish": "Choluteca", + "chinese_simple": "乔卢特卡省", + "korean": "촐루테카", + "dutch": "Choluteca", + "portuguese": "Choluteca", + "russian": "Чолутека", + "chinese_traditional": "Choluteca", + "unknown1": "Choluteca", + "unknown2": "Choluteca", + "unknown3": "Choluteca", + "unknown4": "Choluteca" + }, + "coordinates": { + "latitude": 13.315429536, + "longitude": -87.214713511 + } + }, + { + "id": 553975808, + "name": "Colón", + "translations": { + "japanese": "コロン", + "english": "Colón", + "french": "Colón", + "german": "Colón", + "italian": "Colón", + "spanish": "Colón", + "chinese_simple": "科隆省", + "korean": "콜론", + "dutch": "Colón", + "portuguese": "Colón", + "russian": "Колон", + "chinese_traditional": "Colón", + "unknown1": "Colón", + "unknown2": "Colón", + "unknown3": "Colón", + "unknown4": "Colón" + }, + "coordinates": { + "latitude": 15.913696108, + "longitude": -85.995227773 + } + }, + { + "id": 554041344, + "name": "Comayagua", + "translations": { + "japanese": "コマヤグア", + "english": "Comayagua", + "french": "Comayagua", + "german": "Comayagua", + "italian": "Comayagua", + "spanish": "Comayagua", + "chinese_simple": "科马亚瓜省", + "korean": "코마야과", + "dutch": "Comayagua", + "portuguese": "Comayagua", + "russian": "Камаягуа", + "chinese_traditional": "Comayagua", + "unknown1": "Comayagua", + "unknown2": "Comayagua", + "unknown3": "Comayagua", + "unknown4": "Comayagua" + }, + "coordinates": { + "latitude": 14.44702132, + "longitude": -87.632195115 + } + }, + { + "id": 554106880, + "name": "Copán", + "translations": { + "japanese": "コパン", + "english": "Copán", + "french": "Copán", + "german": "Copán", + "italian": "Copán", + "spanish": "Copán", + "chinese_simple": "科潘省", + "korean": "코판", + "dutch": "Copán", + "portuguese": "Copán", + "russian": "Копан", + "chinese_traditional": "Copán", + "unknown1": "Copán", + "unknown2": "Copán", + "unknown3": "Copán", + "unknown4": "Copán" + }, + "coordinates": { + "latitude": 14.765624832, + "longitude": -88.780269526 + } + }, + { + "id": 554172416, + "name": "Cortés", + "translations": { + "japanese": "コルテス", + "english": "Cortés", + "french": "Cortés", + "german": "Cortés", + "italian": "Cortés", + "spanish": "Cortés", + "chinese_simple": "科尔特斯省", + "korean": "코르테스", + "dutch": "Cortés", + "portuguese": "Cortês", + "russian": "Кортес", + "chinese_traditional": "Cortés", + "unknown1": "Cortés", + "unknown2": "Cortés", + "unknown3": "Cortés", + "unknown4": "Cortés" + }, + "coordinates": { + "latitude": 15.496215644, + "longitude": -88.027704003 + } + }, + { + "id": 554237952, + "name": "El Paraíso", + "translations": { + "japanese": "エル・パライソ", + "english": "El Paraíso", + "french": "El Paraíso", + "german": "El Paraíso", + "italian": "El Paraíso", + "spanish": "El Paraíso", + "chinese_simple": "埃尔帕拉伊索省", + "korean": "엘파라이소", + "dutch": "El Paraíso", + "portuguese": "El Paraíso", + "russian": "Эль-Параисо", + "chinese_traditional": "El Paraíso", + "unknown1": "El Paraíso", + "unknown2": "El Paraíso", + "unknown3": "El Paraíso", + "unknown4": "El Paraíso" + }, + "coordinates": { + "latitude": 13.930663904, + "longitude": -86.846670518 + } + }, + { + "id": 554303488, + "name": "Gracias a Dios", + "translations": { + "japanese": "グラシアス・ア・ディオス", + "english": "Gracias a Dios", + "french": "Gracias a Dios", + "german": "Gracias a Dios", + "italian": "Gracias a Dios", + "spanish": "Gracias a Dios", + "chinese_simple": "格拉西亚斯-阿迪奥斯省", + "korean": "그라시아스아디오스", + "dutch": "Gracias a Dios", + "portuguese": "Gracias a Dios", + "russian": "Грасьяс-а-Дьос", + "chinese_traditional": "Gracias a Dios", + "unknown1": "Gracias a Dios", + "unknown2": "Gracias a Dios", + "unknown3": "Gracias a Dios", + "unknown4": "Gracias a Dios" + }, + "coordinates": { + "latitude": 15.265502756, + "longitude": -83.764997099 + } + }, + { + "id": 554369024, + "name": "Intibucá", + "translations": { + "japanese": "インティブカ", + "english": "Intibucá", + "french": "Intibucá", + "german": "Intibucá", + "italian": "Intibucá", + "spanish": "Intibucá", + "chinese_simple": "因蒂布卡省", + "korean": "인티부카", + "dutch": "Intibucá", + "portuguese": "Intibucá", + "russian": "Интибука", + "chinese_traditional": "Intibucá", + "unknown1": "Intibucá", + "unknown2": "Intibucá", + "unknown3": "Intibucá", + "unknown4": "Intibucá" + }, + "coordinates": { + "latitude": 14.298705892000001, + "longitude": -88.18151301500001 + } + }, + { + "id": 554434560, + "name": "Islas de la Bahía", + "translations": { + "japanese": "イスラス・デ・ラ・バイア", + "english": "Islas de la Bahía", + "french": "Islas de la Bahía", + "german": "Islas de la Bahía", + "italian": "Islas de la Bahía", + "spanish": "Islas de la Bahía", + "chinese_simple": "海湾群岛省", + "korean": "이슬라스데라바이아", + "dutch": "Baai Eilanden", + "portuguese": "Islas de la Bahía", + "russian": "Ислас-де-ла-Баия", + "chinese_traditional": "Islas de la Bahía", + "unknown1": "Islas de la Bahía", + "unknown2": "Islas de la Bahía", + "unknown3": "Islas de la Bahía", + "unknown4": "Islas de la Bahía" + }, + "coordinates": { + "latitude": 16.298217588, + "longitude": -86.544545673 + } + }, + { + "id": 554500096, + "name": "La Paz", + "translations": { + "japanese": "ラ・パス", + "english": "La Paz", + "french": "La Paz", + "german": "La Paz", + "italian": "La Paz", + "spanish": "La Paz", + "chinese_simple": "拉巴斯省", + "korean": "라파스", + "dutch": "La Paz", + "portuguese": "La Paz", + "russian": "Ла-Пас", + "chinese_traditional": "La Paz", + "unknown1": "La Paz", + "unknown2": "La Paz", + "unknown3": "La Paz", + "unknown4": "La Paz" + }, + "coordinates": { + "latitude": 14.315185384, + "longitude": -87.681633726 + } + }, + { + "id": 554565632, + "name": "Lempira", + "translations": { + "japanese": "レンピラ", + "english": "Lempira", + "french": "Lempira", + "german": "Lempira", + "italian": "Lempira", + "spanish": "Lempira", + "chinese_simple": "伦皮拉省", + "korean": "렘피라", + "dutch": "Lempira", + "portuguese": "Lempira", + "russian": "Лемпира", + "chinese_traditional": "Lempira", + "unknown1": "Lempira", + "unknown2": "Lempira", + "unknown3": "Lempira", + "unknown4": "Lempira" + }, + "coordinates": { + "latitude": 14.578857256000001, + "longitude": -88.582515082 + } + }, + { + "id": 554631168, + "name": "Ocotepeque", + "translations": { + "japanese": "オコテペケ", + "english": "Ocotepeque", + "french": "Ocotepeque", + "german": "Ocotepeque", + "italian": "Ocotepeque", + "spanish": "Ocotepeque", + "chinese_simple": "奥科特佩克省", + "korean": "오코테페케", + "dutch": "Ocotepeque", + "portuguese": "Ocotepeque", + "russian": "Окатепеке", + "chinese_traditional": "Ocotepeque", + "unknown1": "Ocotepeque", + "unknown2": "Ocotepeque", + "unknown3": "Ocotepeque", + "unknown4": "Ocotepeque" + }, + "coordinates": { + "latitude": 14.430541828, + "longitude": -89.181271593 + } + }, + { + "id": 554696704, + "name": "Olancho", + "translations": { + "japanese": "オランチョ", + "english": "Olancho", + "french": "Olancho", + "german": "Olancho", + "italian": "Olancho", + "spanish": "Olancho", + "chinese_simple": "奥兰乔省", + "korean": "올란초", + "dutch": "Olancho", + "portuguese": "Olancho", + "russian": "Оланхо", + "chinese_traditional": "Olancho", + "unknown1": "Olancho", + "unknown2": "Olancho", + "unknown3": "Olancho", + "unknown4": "Olancho" + }, + "coordinates": { + "latitude": 14.644775224, + "longitude": -86.198475396 + } + }, + { + "id": 554762240, + "name": "Santa Bárbara", + "translations": { + "japanese": "サンタ・バルバラ", + "english": "Santa Bárbara", + "french": "Santa Bárbara", + "german": "Santa Bárbara", + "italian": "Santa Bárbara", + "spanish": "Santa Bárbara", + "chinese_simple": "圣巴巴拉省", + "korean": "산타바르바라", + "dutch": "Santa Bárbara", + "portuguese": "Santa Bárbara", + "russian": "Санта-Барбара", + "chinese_traditional": "Santa Bárbara", + "unknown1": "Santa Bárbara", + "unknown2": "Santa Bárbara", + "unknown3": "Santa Bárbara", + "unknown4": "Santa Bárbara" + }, + "coordinates": { + "latitude": 14.91394026, + "longitude": -88.230951626 + } + }, + { + "id": 554827776, + "name": "Valle", + "translations": { + "japanese": "バジェ", + "english": "Valle", + "french": "Valle", + "german": "Valle", + "italian": "Valle", + "spanish": "Valle", + "chinese_simple": "山谷省", + "korean": "바예", + "dutch": "Valle", + "portuguese": "Valle", + "russian": "Валле", + "chinese_traditional": "Valle", + "unknown1": "Valle", + "unknown2": "Valle", + "unknown3": "Valle", + "unknown4": "Valle" + }, + "coordinates": { + "latitude": 13.535156096, + "longitude": -87.483879282 + } + }, + { + "id": 554893312, + "name": "Yoro", + "translations": { + "japanese": "ヨロ", + "english": "Yoro", + "french": "Yoro", + "german": "Yoro", + "italian": "Yoro", + "spanish": "Yoro", + "chinese_simple": "约罗省", + "korean": "요로", + "dutch": "Yoro", + "portuguese": "Yoro", + "russian": "Йоро", + "chinese_traditional": "Yoro", + "unknown1": "Yoro", + "unknown2": "Yoro", + "unknown3": "Yoro", + "unknown4": "Yoro" + }, + "coordinates": { + "latitude": 15.128173656, + "longitude": -87.132315826 + } + } + ] + }, + { + "id": 34, + "iso_code": "JM", + "name": "Jamaica", + "translations": { + "japanese": "ジャマイカ", + "english": "Jamaica", + "french": "Jamaïque", + "german": "Jamaika", + "italian": "Giamaica", + "spanish": "Jamaica", + "chinese_simple": "牙买加", + "korean": "자메이카", + "dutch": "Jamaica", + "portuguese": "Jamaica", + "russian": "Ямайка", + "chinese_traditional": "Jamaica", + "unknown1": "Jamaica", + "unknown2": "Jamaica", + "unknown3": "Jamaica", + "unknown4": "Jamaica" + }, + "regions": [ + { + "id": 570425344, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": 17.995605264, + "longitude": -76.799646127 + } + }, + { + "id": 570556416, + "name": "Saint Thomas", + "translations": { + "japanese": "セント・トーマス", + "english": "Saint Thomas", + "french": "Saint-Thomas", + "german": "Saint Thomas", + "italian": "Saint Thomas", + "spanish": "Saint Thomas", + "chinese_simple": "圣托马斯区", + "korean": "세인트토머스", + "dutch": "Saint Thomas", + "portuguese": "Saint Thomas", + "russian": "Сент-Томас", + "chinese_traditional": "Saint Thomas", + "unknown1": "Saint Thomas", + "unknown2": "Saint Thomas", + "unknown3": "Saint Thomas", + "unknown4": "Saint Thomas" + }, + "coordinates": { + "latitude": 17.995605264, + "longitude": -76.799646127 + } + }, + { + "id": 570621952, + "name": "Clarendon", + "translations": { + "japanese": "クラレンドン", + "english": "Clarendon", + "french": "Clarendon", + "german": "Clarendon", + "italian": "Clarendon", + "spanish": "Clarendon", + "chinese_simple": "克拉伦登区", + "korean": "클래런던", + "dutch": "Clarendon", + "portuguese": "Clarendon", + "russian": "Клэрендон", + "chinese_traditional": "Clarendon", + "unknown1": "Clarendon", + "unknown2": "Clarendon", + "unknown3": "Clarendon", + "unknown4": "Clarendon" + }, + "coordinates": { + "latitude": 17.96264628, + "longitude": -77.228114089 + } + }, + { + "id": 570687488, + "name": "Hanover", + "translations": { + "japanese": "ハノーバー", + "english": "Hanover", + "french": "Hanover", + "german": "Hanover", + "italian": "Hanover", + "spanish": "Hanover", + "chinese_simple": "汉诺威区", + "korean": "해노버", + "dutch": "Hanover", + "portuguese": "Hanover", + "russian": "Хановер", + "chinese_traditional": "Hanover", + "unknown1": "Hanover", + "unknown2": "Hanover", + "unknown3": "Hanover", + "unknown4": "Hanover" + }, + "coordinates": { + "latitude": 18.446044712, + "longitude": -78.161954519 + } + }, + { + "id": 570753024, + "name": "Manchester", + "translations": { + "japanese": "マンチェスター", + "english": "Manchester", + "french": "Manchester", + "german": "Manchester", + "italian": "Manchester", + "spanish": "Manchester", + "chinese_simple": "曼彻斯特区", + "korean": "맨체스터", + "dutch": "Manchester", + "portuguese": "Manchester", + "russian": "Манчестер", + "chinese_traditional": "Manchester", + "unknown1": "Manchester", + "unknown2": "Manchester", + "unknown3": "Manchester", + "unknown4": "Manchester" + }, + "coordinates": { + "latitude": 18.028564248, + "longitude": -77.49727986 + } + }, + { + "id": 570818560, + "name": "Portland", + "translations": { + "japanese": "ポートランド", + "english": "Portland", + "french": "Portland", + "german": "Portland", + "italian": "Portland", + "spanish": "Portland", + "chinese_simple": "波特兰区", + "korean": "포틀랜드", + "dutch": "Portland", + "portuguese": "Portland", + "russian": "Портлэнд", + "chinese_traditional": "Portland", + "unknown1": "Portland", + "unknown2": "Portland", + "unknown3": "Portland", + "unknown4": "Portland" + }, + "coordinates": { + "latitude": 18.18237284, + "longitude": -76.464562208 + } + }, + { + "id": 570884096, + "name": "Saint Andrew", + "translations": { + "japanese": "セント・アンドリュー", + "english": "Saint Andrew", + "french": "Saint Andrew", + "german": "Saint Andrew", + "italian": "Saint Andrew", + "spanish": "Saint Andrew", + "chinese_simple": "圣安德鲁区", + "korean": "세인트앤드루", + "dutch": "Saint Andrew", + "portuguese": "Saint Andrew", + "russian": "Сент-Эндрю", + "chinese_traditional": "Saint Andrew", + "unknown1": "Saint Andrew", + "unknown2": "Saint Andrew", + "unknown3": "Saint Andrew", + "unknown4": "Saint Andrew" + }, + "coordinates": { + "latitude": 17.995605264, + "longitude": -76.799646127 + } + }, + { + "id": 570949632, + "name": "Saint Ann", + "translations": { + "japanese": "セント・アン", + "english": "Saint Ann", + "french": "Saint Ann", + "german": "Saint Ann", + "italian": "Saint Ann", + "spanish": "Saint Ann", + "chinese_simple": "圣安娜区", + "korean": "세인트앤", + "dutch": "Saint Ann", + "portuguese": "Saint Ann", + "russian": "Сент-Энн", + "chinese_traditional": "Saint Ann", + "unknown1": "Saint Ann", + "unknown2": "Saint Ann", + "unknown3": "Saint Ann", + "unknown4": "Saint Ann" + }, + "coordinates": { + "latitude": 18.42956522, + "longitude": -77.195155015 + } + }, + { + "id": 571015168, + "name": "Saint Catherine", + "translations": { + "japanese": "セント・キャサリン", + "english": "Saint Catherine", + "french": "Saint Catherine", + "german": "Saint Catherine", + "italian": "Saint Catherine", + "spanish": "Saint Catherine", + "chinese_simple": "圣凯瑟琳区", + "korean": "세인트캐서린", + "dutch": "Saint Catherine", + "portuguese": "Saint Catherine", + "russian": "Сент-Кэтрин", + "chinese_traditional": "Saint Catherine", + "unknown1": "Saint Catherine", + "unknown2": "Saint Catherine", + "unknown3": "Saint Catherine", + "unknown4": "Saint Catherine" + }, + "coordinates": { + "latitude": 17.979125772, + "longitude": -76.94796196 + } + }, + { + "id": 571080704, + "name": "Saint Elizabeth", + "translations": { + "japanese": "セント・エリザベス", + "english": "Saint Elizabeth", + "french": "Saint Elizabeth", + "german": "Saint Elizabeth", + "italian": "Saint Elizabeth", + "spanish": "Saint Elizabeth", + "chinese_simple": "圣伊丽莎白区", + "korean": "세인트엘리자베스", + "dutch": "Saint Elizabeth", + "portuguese": "Saint Elizabeth", + "russian": "Сент-Элизабет", + "chinese_traditional": "Saint Elizabeth", + "unknown1": "Saint Elizabeth", + "unknown2": "Saint Elizabeth", + "unknown3": "Saint Elizabeth", + "unknown4": "Saint Elizabeth" + }, + "coordinates": { + "latitude": 18.012084756, + "longitude": -77.848843316 + } + }, + { + "id": 571146240, + "name": "Saint James", + "translations": { + "japanese": "セント・ジェームズ", + "english": "Saint James", + "french": "Saint James", + "german": "Saint James", + "italian": "Saint James", + "spanish": "Saint James", + "chinese_simple": "圣詹姆斯区", + "korean": "세인트제임스", + "dutch": "Saint James", + "portuguese": "Saint James", + "russian": "Сент-Джэймс", + "chinese_traditional": "Saint James", + "unknown1": "Saint James", + "unknown2": "Saint James", + "unknown3": "Saint James", + "unknown4": "Saint James" + }, + "coordinates": { + "latitude": 18.462524204, + "longitude": -77.914761464 + } + }, + { + "id": 571211776, + "name": "Saint Mary", + "translations": { + "japanese": "セント・メアリー", + "english": "Saint Mary", + "french": "Saint Mary", + "german": "Saint Mary", + "italian": "Saint Mary", + "spanish": "Saint Mary", + "chinese_simple": "圣玛丽区", + "korean": "세인트메리", + "dutch": "Saint Mary", + "portuguese": "Saint Mary", + "russian": "Сент-Мэри", + "chinese_traditional": "Saint Mary", + "unknown1": "Saint Mary", + "unknown2": "Saint Mary", + "unknown3": "Saint Mary", + "unknown4": "Saint Mary" + }, + "coordinates": { + "latitude": 18.363647252, + "longitude": -76.898523349 + } + }, + { + "id": 571277312, + "name": "Trelawny", + "translations": { + "japanese": "トレローニー", + "english": "Trelawny", + "french": "Trelawny", + "german": "Trelawny", + "italian": "Trelawny", + "spanish": "Trelawny", + "chinese_simple": "特里洛尼区", + "korean": "트렐로니", + "dutch": "Trelawny", + "portuguese": "Trelawny", + "russian": "Трилони", + "chinese_traditional": "Trelawny", + "unknown1": "Trelawny", + "unknown2": "Trelawny", + "unknown3": "Trelawny", + "unknown4": "Trelawny" + }, + "coordinates": { + "latitude": 18.495483188, + "longitude": -77.645595693 + } + }, + { + "id": 571342848, + "name": "Westmoreland", + "translations": { + "japanese": "ウェストモアランド", + "english": "Westmoreland", + "french": "Westmoreland", + "german": "Westmoreland얆徇᥄\udac7", + "italian": "Westmoreland", + "spanish": "Westmoreland", + "chinese_simple": "西摩兰区", + "korean": "웨스트모얼랜드", + "dutch": "Westmoreland", + "portuguese": "Westmoreland", + "russian": "Уэстморлэнд", + "chinese_traditional": "Westmoreland", + "unknown1": "Westmoreland", + "unknown2": "Westmoreland", + "unknown3": "Westmoreland", + "unknown4": "Westmoreland" + }, + "coordinates": { + "latitude": 18.215331824, + "longitude": -78.128995445 + } + }, + { + "id": 571408384, + "name": "Kingston", + "translations": { + "japanese": "キングストン", + "english": "Kingston", + "french": "Kingston", + "german": "Kingston", + "italian": "Kingston", + "spanish": "Kingston", + "chinese_simple": "金斯敦区", + "korean": "킹스턴", + "dutch": "Kingston", + "portuguese": "Kingston", + "russian": "Кингстон", + "chinese_traditional": "Kingston", + "unknown1": "Kingston", + "unknown2": "Kingston", + "unknown3": "Kingston", + "unknown4": "Kingston" + }, + "coordinates": { + "latitude": 17.946166788, + "longitude": -76.777673411 + } + } + ] + }, + { + "id": 35, + "iso_code": "MQ", + "name": "Martinique", + "translations": { + "japanese": "マルティニーク", + "english": "Martinique", + "french": "Martinique", + "german": "Martinique", + "italian": "Martinica", + "spanish": "Martinica", + "chinese_simple": "马提尼克", + "korean": "마르티니크", + "dutch": "Martinique", + "portuguese": "Martinica", + "russian": "Мартиника", + "chinese_traditional": "Martinique", + "unknown1": "Martinique", + "unknown2": "Martinique", + "unknown3": "Martinique", + "unknown4": "Martinique" + }, + "regions": [ + { + "id": 587202560, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": 14.595336748, + "longitude": -61.078167828999995 + } + }, + { + "id": 587268096, + "name": "Martinique", + "translations": { + "japanese": "マルティニーク", + "english": "Martinique", + "french": "Martinique", + "german": "Martinique", + "italian": "Martinica", + "spanish": "Martinica", + "chinese_simple": "马提尼克", + "korean": "마르티니크", + "dutch": "Martinique", + "portuguese": "Martinica", + "russian": "Мартиника", + "chinese_traditional": "Martinique", + "unknown1": "Martinique", + "unknown2": "Martinique", + "unknown3": "Martinique", + "unknown4": "Martinique" + }, + "coordinates": { + "latitude": 14.595336748, + "longitude": -61.078167828999995 + } + } + ] + }, + { + "id": 36, + "iso_code": "MX", + "name": "Mexico", + "translations": { + "japanese": "メキシコ", + "english": "Mexico", + "french": "Mexique", + "german": "Mexiko", + "italian": "Messico", + "spanish": "México", + "chinese_simple": "墨西哥", + "korean": "멕시코", + "dutch": "Mexico", + "portuguese": "México", + "russian": "Мексика", + "chinese_traditional": "Mexico", + "unknown1": "Mexico", + "unknown2": "Mexico", + "unknown3": "Mexico", + "unknown4": "Mexico" + }, + "regions": [ + { + "id": 603979776, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": 19.429321068, + "longitude": -99.134911941 + } + }, + { + "id": 604110848, + "name": "Distrito Federal", + "translations": { + "japanese": "ディストリト・フェデラル連邦区", + "english": "Distrito Federal", + "french": "District Fédéral", + "german": "México D.F.", + "italian": "Distretto Federale", + "spanish": "Distrito Federal", + "chinese_simple": "联邦区", + "korean": "멕시코 연방구", + "dutch": "Federaal District", + "portuguese": "Distrito Federal", + "russian": "Федеральный округ", + "chinese_traditional": "Distrito Federal", + "unknown1": "Distrito Federal", + "unknown2": "Distrito Federal", + "unknown3": "Distrito Federal", + "unknown4": "Distrito Federal" + }, + "coordinates": { + "latitude": 19.429321068, + "longitude": -99.134911941 + } + }, + { + "id": 604176384, + "name": "Aguascalientes", + "translations": { + "japanese": "アグアスカリエンテス州", + "english": "Aguascalientes", + "french": "Aguascalientes", + "german": "Aguascalientes", + "italian": "Aguascalientes", + "spanish": "Aguascalientes", + "chinese_simple": "阿瓜斯卡连特斯州", + "korean": "아과스칼리엔테스 주", + "dutch": "Aguascalientes", + "portuguese": "Aguascalientes", + "russian": "Агуаскальентес", + "chinese_traditional": "Aguascalientes", + "unknown1": "Aguascalientes", + "unknown2": "Aguascalientes", + "unknown3": "Aguascalientes", + "unknown4": "Aguascalientes" + }, + "coordinates": { + "latitude": 21.879272212, + "longitude": -102.298983045 + } + }, + { + "id": 604241920, + "name": "Baja California", + "translations": { + "japanese": "バハ・カリフォルニア州", + "english": "Baja California", + "french": "Basse-Californie", + "german": "Niederkalifornien", + "italian": "Bassa California", + "spanish": "Baja California", + "chinese_simple": "下加里福尼亚州", + "korean": "바하칼리포르니아 주", + "dutch": "Neder-Californië", + "portuguese": "Baixa Califórnia", + "russian": "Нижняя Калифорния", + "chinese_traditional": "Baja California", + "unknown1": "Baja California", + "unknown2": "Baja California", + "unknown3": "Baja California", + "unknown4": "Baja California" + }, + "coordinates": { + "latitude": 32.651366816, + "longitude": -115.466133108 + } + }, + { + "id": 604307456, + "name": "Baja California Sur", + "translations": { + "japanese": "バハ・カリフォルニア・スル州", + "english": "Baja California Sur", + "french": "Basse-Californie du Sud", + "german": "Süd-Niederkalifornien", + "italian": "Bassa California del Sud", + "spanish": "Baja California Sur", + "chinese_simple": "南下加里福尼亚州", + "korean": "바하칼리포르니아수르 주", + "dutch": "Zuid-Neder-Californië", + "portuguese": "Baixa Califórnia do Sul", + "russian": "Южная Нижняя Калифорния", + "chinese_traditional": "Baja California Sur", + "unknown1": "Baja California Sur", + "unknown2": "Baja California Sur", + "unknown3": "Baja California Sur", + "unknown4": "Baja California Sur" + }, + "coordinates": { + "latitude": 24.164428436, + "longitude": -110.297051669 + } + }, + { + "id": 604372992, + "name": "Campeche", + "translations": { + "japanese": "カンペチェ州", + "english": "Campeche", + "french": "Campeche", + "german": "Campeche", + "italian": "Campeche", + "spanish": "Campeche", + "chinese_simple": "坎佩切州", + "korean": "캄페체 주", + "dutch": "Campeche", + "portuguese": "Campeche", + "russian": "Кампече", + "chinese_traditional": "Campeche", + "unknown1": "Campeche", + "unknown2": "Campeche", + "unknown3": "Campeche", + "unknown4": "Campeche" + }, + "coordinates": { + "latitude": 19.846801532, + "longitude": -90.532593627 + } + }, + { + "id": 604438528, + "name": "Chiapas", + "translations": { + "japanese": "チアパス州", + "english": "Chiapas", + "french": "Chiapas", + "german": "Chiapas", + "italian": "Chiapas", + "spanish": "Chiapas", + "chinese_simple": "恰帕斯州", + "korean": "치아파스 주", + "dutch": "Chiapas", + "portuguese": "Chiapas", + "russian": "Чьяпас", + "chinese_traditional": "Chiapas", + "unknown1": "Chiapas", + "unknown2": "Chiapas", + "unknown3": "Chiapas", + "unknown4": "Chiapas" + }, + "coordinates": { + "latitude": 16.748657036, + "longitude": -93.114387757 + } + }, + { + "id": 604504064, + "name": "Chihuahua", + "translations": { + "japanese": "チワワ州", + "english": "Chihuahua", + "french": "Chihuahua", + "german": "Chihuahua", + "italian": "Chihuahua", + "spanish": "Chihuahua", + "chinese_simple": "奇瓦瓦州", + "korean": "치와와 주", + "dutch": "Chihuahua", + "portuguese": "Chihuahua", + "russian": "Чиуауа", + "chinese_traditional": "Chihuahua", + "unknown1": "Chihuahua", + "unknown2": "Chihuahua", + "unknown3": "Chihuahua", + "unknown4": "Chihuahua" + }, + "coordinates": { + "latitude": 28.630370768, + "longitude": -106.078290197 + } + }, + { + "id": 604569600, + "name": "Coahuila de Zaragoza", + "translations": { + "japanese": "コアウイラ州", + "english": "Coahuila de Zaragoza", + "french": "Coahuila", + "german": "Coahuila de Zaragoza", + "italian": "Coahuila", + "spanish": "Coahuila", + "chinese_simple": "科阿韦拉州", + "korean": "코아우일라 주", + "dutch": "Coahuila de Zaragoza", + "portuguese": "Coahuila de Zaragoza", + "russian": "Коауила", + "chinese_traditional": "Coahuila de Zaragoza", + "unknown1": "Coahuila de Zaragoza", + "unknown2": "Coahuila de Zaragoza", + "unknown3": "Coahuila de Zaragoza", + "unknown4": "Coahuila de Zaragoza" + }, + "coordinates": { + "latitude": 25.411376664, + "longitude": -100.99709962200001 + } + }, + { + "id": 604635136, + "name": "Colima", + "translations": { + "japanese": "コリマ州", + "english": "Colima", + "french": "Colima", + "german": "Colima", + "italian": "Colima", + "spanish": "Colima", + "chinese_simple": "科利马州", + "korean": "콜리마 주", + "dutch": "Colima", + "portuguese": "Colima", + "russian": "Колима", + "chinese_traditional": "Colima", + "unknown1": "Colima", + "unknown2": "Colima", + "unknown3": "Colima", + "unknown4": "Colima" + }, + "coordinates": { + "latitude": 19.231567164, + "longitude": -103.716223227 + } + }, + { + "id": 604700672, + "name": "Durango", + "translations": { + "japanese": "ドゥランゴ州", + "english": "Durango", + "french": "Durango", + "german": "Durango", + "italian": "Durango", + "spanish": "Durango", + "chinese_simple": "杜兰戈州", + "korean": "두랑고 주", + "dutch": "Durango", + "portuguese": "Durango", + "russian": "Дуранго", + "chinese_traditional": "Durango", + "unknown1": "Durango", + "unknown2": "Durango", + "unknown3": "Durango", + "unknown4": "Durango" + }, + "coordinates": { + "latitude": 24.0325925, + "longitude": -104.661050015 + } + }, + { + "id": 604766208, + "name": "Guanajuato", + "translations": { + "japanese": "グアナフアト州", + "english": "Guanajuato", + "french": "Guanajuato", + "german": "Guanajuato", + "italian": "Guanajuato", + "spanish": "Guanajuato", + "chinese_simple": "瓜纳华托州", + "korean": "과나후아토 주", + "dutch": "Guanajuato", + "portuguese": "Guanajuato", + "russian": "Гуанахуато", + "chinese_traditional": "Guanajuato", + "unknown1": "Guanajuato", + "unknown2": "Guanajuato", + "unknown3": "Guanajuato", + "unknown4": "Guanajuato" + }, + "coordinates": { + "latitude": 21.0113523, + "longitude": -101.249785856 + } + }, + { + "id": 604831744, + "name": "Guerrero", + "translations": { + "japanese": "ゲレロ州", + "english": "Guerrero", + "french": "Guerrero", + "german": "Guerrero", + "italian": "Guerrero", + "spanish": "Guerrero", + "chinese_simple": "格雷罗州", + "korean": "게레로 주", + "dutch": "Guerrero", + "portuguese": "Guerrero", + "russian": "Герреро", + "chinese_traditional": "Guerrero", + "unknown1": "Guerrero", + "unknown2": "Guerrero", + "unknown3": "Guerrero", + "unknown4": "Guerrero" + }, + "coordinates": { + "latitude": 17.545165816, + "longitude": -99.497461755 + } + }, + { + "id": 604897280, + "name": "Hidalgo", + "translations": { + "japanese": "イダルゴ州", + "english": "Hidalgo", + "french": "Hidalgo", + "german": "Hidalgo", + "italian": "Hidalgo", + "spanish": "Hidalgo", + "chinese_simple": "伊达尔戈州", + "korean": "이달고 주", + "dutch": "Hidalgo", + "portuguese": "Hidalgo", + "russian": "Идальго", + "chinese_traditional": "Hidalgo", + "unknown1": "Hidalgo", + "unknown2": "Hidalgo", + "unknown3": "Hidalgo", + "unknown4": "Hidalgo" + }, + "coordinates": { + "latitude": 20.115966568, + "longitude": -98.728416695 + } + }, + { + "id": 604962816, + "name": "Jalisco", + "translations": { + "japanese": "ハリスコ州", + "english": "Jalisco", + "french": "Jalisco", + "german": "Jalisco", + "italian": "Jalisco", + "spanish": "Jalisco", + "chinese_simple": "哈利斯科州", + "korean": "할리스코 주", + "dutch": "Jalisco", + "portuguese": "Jalisco", + "russian": "Халиско", + "chinese_traditional": "Jalisco", + "unknown1": "Jalisco", + "unknown2": "Jalisco", + "unknown3": "Jalisco", + "unknown4": "Jalisco" + }, + "coordinates": { + "latitude": 20.665282968, + "longitude": -103.331700697 + } + }, + { + "id": 605028352, + "name": "México", + "translations": { + "japanese": "メヒコ州", + "english": "México", + "french": "Mexico", + "german": "México", + "italian": "Messico", + "spanish": "México", + "chinese_simple": "墨西哥州", + "korean": "메히코 주", + "dutch": "Mexico", + "portuguese": "México", + "russian": "Мехико", + "chinese_traditional": "México", + "unknown1": "México", + "unknown2": "México", + "unknown3": "México", + "unknown4": "México" + }, + "coordinates": { + "latitude": 19.286498804, + "longitude": -99.662257125 + } + }, + { + "id": 605093888, + "name": "Michoacán de Ocampo", + "translations": { + "japanese": "ミチョアカン州", + "english": "Michoacán de Ocampo", + "french": "Michoacán", + "german": "Michoacán", + "italian": "Michoacán de Ocampo", + "spanish": "Michoacán", + "chinese_simple": "米却肯州", + "korean": "미초아칸 주", + "dutch": "Michoacán de Ocampo", + "portuguese": "Michoacán de Ocampo", + "russian": "Мичоакан", + "chinese_traditional": "Michoacán de Ocampo", + "unknown1": "Michoacán de Ocampo", + "unknown2": "Michoacán de Ocampo", + "unknown3": "Michoacán de Ocampo", + "unknown4": "Michoacán de Ocampo" + }, + "coordinates": { + "latitude": 19.698486104, + "longitude": -101.112456381 + } + }, + { + "id": 605159424, + "name": "Morelos", + "translations": { + "japanese": "モレロス州", + "english": "Morelos", + "french": "Morelos", + "german": "Morelos", + "italian": "Morelos", + "spanish": "Morelos", + "chinese_simple": "莫雷洛斯州", + "korean": "모렐로스 주", + "dutch": "Morelos", + "portuguese": "Morelos", + "russian": "Морелос", + "chinese_traditional": "Morelos", + "unknown1": "Morelos", + "unknown2": "Morelos", + "unknown3": "Morelos", + "unknown4": "Morelos" + }, + "coordinates": { + "latitude": 18.912963652, + "longitude": -99.244775521 + } + }, + { + "id": 605224960, + "name": "Nayarit", + "translations": { + "japanese": "ナヤリット州", + "english": "Nayarit", + "french": "Nayarit", + "german": "Nayarit", + "italian": "Nayarit", + "spanish": "Nayarit", + "chinese_simple": "纳亚里特州", + "korean": "나야리트 주", + "dutch": "Nayarit", + "portuguese": "Nayarit", + "russian": "Наярит", + "chinese_traditional": "Nayarit", + "unknown1": "Nayarit", + "unknown2": "Nayarit", + "unknown3": "Nayarit", + "unknown4": "Nayarit" + }, + "coordinates": { + "latitude": 21.494750732, + "longitude": -104.897256712 + } + }, + { + "id": 605290496, + "name": "Nuevo León", + "translations": { + "japanese": "ヌエボ・レオン州", + "english": "Nuevo León", + "french": "Nuevo León", + "german": "Nuevo León", + "italian": "Nuevo León", + "spanish": "Nuevo León", + "chinese_simple": "新莱昂州", + "korean": "누에보레온 주", + "dutch": "Nieuw-León", + "portuguese": "Nuevo León", + "russian": "Нуэво-Леон", + "chinese_traditional": "Nuevo León", + "unknown1": "Nuevo León", + "unknown2": "Nuevo León", + "unknown3": "Nuevo León", + "unknown4": "Nuevo León" + }, + "coordinates": { + "latitude": 25.664062208, + "longitude": -100.315945426 + } + }, + { + "id": 605356032, + "name": "Oaxaca", + "translations": { + "japanese": "オアハカ州", + "english": "Oaxaca", + "french": "Oaxaca", + "german": "Oaxaca", + "italian": "Oaxaca", + "spanish": "Oaxaca", + "chinese_simple": "瓦哈卡州", + "korean": "오악사카 주", + "dutch": "Oaxaca", + "portuguese": "Oaxaca", + "russian": "Оахака", + "chinese_traditional": "Oaxaca", + "unknown1": "Oaxaca", + "unknown2": "Oaxaca", + "unknown3": "Oaxaca", + "unknown4": "Oaxaca" + }, + "coordinates": { + "latitude": 17.045287892, + "longitude": -96.712420002 + } + }, + { + "id": 605421568, + "name": "Puebla", + "translations": { + "japanese": "プエブラ州", + "english": "Puebla", + "french": "Puebla", + "german": "Puebla", + "italian": "Puebla", + "spanish": "Puebla", + "chinese_simple": "普埃布拉州", + "korean": "푸에블라 주", + "dutch": "Puebla", + "portuguese": "Puebla", + "russian": "Пуэбла", + "chinese_traditional": "Puebla", + "unknown1": "Puebla", + "unknown2": "Puebla", + "unknown3": "Puebla", + "unknown4": "Puebla" + }, + "coordinates": { + "latitude": 19.044799588, + "longitude": -98.195578332 + } + }, + { + "id": 605487104, + "name": "Querétaro de Arteaga", + "translations": { + "japanese": "ケレタロ州", + "english": "Querétaro de Arteaga", + "french": "Querétaro", + "german": "Querétaro", + "italian": "Querétaro de Arteaga", + "spanish": "Querétaro", + "chinese_simple": "克雷塔罗州", + "korean": "케레타로 주", + "dutch": "Querétaro de Arteaga", + "portuguese": "Querétaro de Arteaga", + "russian": "Керетаро", + "chinese_traditional": "Querétaro de Arteaga", + "unknown1": "Querétaro de Arteaga", + "unknown2": "Querétaro de Arteaga", + "unknown3": "Querétaro de Arteaga", + "unknown4": "Querétaro de Arteaga" + }, + "coordinates": { + "latitude": 20.599365, + "longitude": -100.38186357400001 + } + }, + { + "id": 605552640, + "name": "Quintana Roo", + "translations": { + "japanese": "キンタナ・ロー州", + "english": "Quintana Roo", + "french": "Quintana Roo", + "german": "Quintana Roo", + "italian": "Quintana Roo", + "spanish": "Quintana Roo", + "chinese_simple": "金塔纳罗奥州", + "korean": "킨타나로오 주", + "dutch": "Quintana Roo", + "portuguese": "Quintana Roo", + "russian": "Кинтана-Роо", + "chinese_traditional": "Quintana Roo", + "unknown1": "Quintana Roo", + "unknown2": "Quintana Roo", + "unknown3": "Quintana Roo", + "unknown4": "Quintana Roo" + }, + "coordinates": { + "latitude": 18.495483188, + "longitude": -88.296869774 + } + }, + { + "id": 605618176, + "name": "San Luis Potosí", + "translations": { + "japanese": "サン・ルイス・ポトシ州", + "english": "San Luis Potosí", + "french": "San Luis Potosí", + "german": "San Luis Potosí", + "italian": "San Luis Potosí", + "spanish": "San Luis Potosí", + "chinese_simple": "圣路易斯波托西州", + "korean": "산루이스포토시 주", + "dutch": "San Luis Potosí", + "portuguese": "San Luis Potosí", + "russian": "Сан-Луис-Потоси", + "chinese_traditional": "San Luis Potosí", + "unknown1": "San Luis Potosí", + "unknown2": "San Luis Potosí", + "unknown3": "San Luis Potosí", + "unknown4": "San Luis Potosí" + }, + "coordinates": { + "latitude": 22.148437248, + "longitude": -100.980620085 + } + }, + { + "id": 605683712, + "name": "Sinaloa", + "translations": { + "japanese": "シナロア州", + "english": "Sinaloa", + "french": "Sinaloa", + "german": "Sinaloa", + "italian": "Sinaloa", + "spanish": "Sinaloa", + "chinese_simple": "锡那罗亚州", + "korean": "시날로아 주", + "dutch": "Sinaloa", + "portuguese": "Sinaloa", + "russian": "Синалоа", + "chinese_traditional": "Sinaloa", + "unknown1": "Sinaloa", + "unknown2": "Sinaloa", + "unknown3": "Sinaloa", + "unknown4": "Sinaloa" + }, + "coordinates": { + "latitude": 24.796142296, + "longitude": -107.385666799 + } + }, + { + "id": 605749248, + "name": "Sonora", + "translations": { + "japanese": "ソノラ州", + "english": "Sonora", + "french": "Sonora", + "german": "Sonora", + "italian": "Sonora", + "spanish": "Sonora", + "chinese_simple": "索诺拉州", + "korean": "소노라 주", + "dutch": "Sonora", + "portuguese": "Sonora", + "russian": "Сонора", + "chinese_traditional": "Sonora", + "unknown1": "Sonora", + "unknown2": "Sonora", + "unknown3": "Sonora", + "unknown4": "Sonora" + }, + "coordinates": { + "latitude": 29.064330724, + "longitude": -110.961726328 + } + }, + { + "id": 605814784, + "name": "Tabasco", + "translations": { + "japanese": "タバスコ州", + "english": "Tabasco", + "french": "Tabasco", + "german": "Tabasco", + "italian": "Tabasco", + "spanish": "Tabasco", + "chinese_simple": "塔巴斯科州", + "korean": "타바스코 주", + "dutch": "Tabasco", + "portuguese": "Tabasco", + "russian": "Табаско", + "chinese_traditional": "Tabasco", + "unknown1": "Tabasco", + "unknown2": "Tabasco", + "unknown3": "Tabasco", + "unknown4": "Tabasco" + }, + "coordinates": { + "latitude": 17.979125772, + "longitude": -92.91114013400001 + } + }, + { + "id": 605880320, + "name": "Tamaulipas", + "translations": { + "japanese": "タマウリパス州", + "english": "Tamaulipas", + "french": "Tamaulipas", + "german": "Tamaulipas", + "italian": "Tamaulipas", + "spanish": "Tamaulipas", + "chinese_simple": "塔毛利帕斯州", + "korean": "타마울리파스 주", + "dutch": "Tamaulipas", + "portuguese": "Tamaulipas", + "russian": "Тамаулипас", + "chinese_traditional": "Tamaulipas", + "unknown1": "Tamaulipas", + "unknown2": "Tamaulipas", + "unknown3": "Tamaulipas", + "unknown4": "Tamaulipas" + }, + "coordinates": { + "latitude": 23.73046848, + "longitude": -99.129418762 + } + }, + { + "id": 605945856, + "name": "Tlaxcala", + "translations": { + "japanese": "トラスカラ州", + "english": "Tlaxcala", + "french": "Tlaxcala", + "german": "Tlaxcala", + "italian": "Tlaxcala", + "spanish": "Tlaxcala", + "chinese_simple": "特拉斯卡拉州", + "korean": "틀락스칼라 주", + "dutch": "Tlaxcala", + "portuguese": "Tlaxcala", + "russian": "Тласкала", + "chinese_traditional": "Tlaxcala", + "unknown1": "Tlaxcala", + "unknown2": "Tlaxcala", + "unknown3": "Tlaxcala", + "unknown4": "Tlaxcala" + }, + "coordinates": { + "latitude": 19.30847146, + "longitude": -98.239523764 + } + }, + { + "id": 606011392, + "name": "Veracruz-Llave", + "translations": { + "japanese": "ベラクルス州", + "english": "Veracruz-Llave", + "french": "Veracruz", + "german": "Veracruz", + "italian": "Veracruz", + "spanish": "Veracruz", + "chinese_simple": "韦拉克鲁斯州", + "korean": "베라크루스 주", + "dutch": "Veracruz", + "portuguese": "Veracruz-Llave", + "russian": "Веракрус", + "chinese_traditional": "Veracruz-Llave", + "unknown1": "Veracruz-Llave", + "unknown2": "Veracruz-Llave", + "unknown3": "Veracruz-Llave", + "unknown4": "Veracruz-Llave" + }, + "coordinates": { + "latitude": 19.52819802, + "longitude": -96.915667625 + } + }, + { + "id": 606076928, + "name": "Yucatán", + "translations": { + "japanese": "ユカタン州", + "english": "Yucatán", + "french": "Yucatán", + "german": "Yukatan", + "italian": "Yucatán", + "spanish": "Yucatán", + "chinese_simple": "尤卡坦州", + "korean": "유카탄 주", + "dutch": "Yucatán", + "portuguese": "Yucatán", + "russian": "Юкатан", + "chinese_traditional": "Yucatán", + "unknown1": "Yucatán", + "unknown2": "Yucatán", + "unknown3": "Yucatán", + "unknown4": "Yucatán" + }, + "coordinates": { + "latitude": 20.961913824, + "longitude": -89.615232734 + } + }, + { + "id": 606142464, + "name": "Zacatecas", + "translations": { + "japanese": "サカテカス州", + "english": "Zacatecas", + "french": "Zacatecas", + "german": "Zacatecas", + "italian": "Zacatecas", + "spanish": "Zacatecas", + "chinese_simple": "萨卡特卡斯州", + "korean": "사카테카스 주", + "dutch": "Zacatecas", + "portuguese": "Zacatecas", + "russian": "Сакатекас", + "chinese_traditional": "Zacatecas", + "unknown1": "Zacatecas", + "unknown2": "Zacatecas", + "unknown3": "Zacatecas", + "unknown4": "Zacatecas" + }, + "coordinates": { + "latitude": 22.780151108000002, + "longitude": -102.579135174 + } + } + ] + }, + { + "id": 37, + "iso_code": "MS", + "name": "Montserrat", + "translations": { + "japanese": "モントセラト", + "english": "Montserrat", + "french": "Montserrat", + "german": "Montserrat", + "italian": "Montserrat", + "spanish": "Montserrat", + "chinese_simple": "蒙特塞拉特", + "korean": "몬트세랫", + "dutch": "Montserrat", + "portuguese": "Montserrat", + "russian": "Монтсеррат", + "chinese_traditional": "Montserrat", + "unknown1": "Montserrat", + "unknown2": "Montserrat", + "unknown3": "Montserrat", + "unknown4": "Montserrat" + }, + "regions": [ + { + "id": 620756992, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": 16.69921856, + "longitude": -62.21525588200001 + } + }, + { + "id": 620822528, + "name": "Montserrat", + "translations": { + "japanese": "モントセラト", + "english": "Montserrat", + "french": "Montserrat", + "german": "Montserrat", + "italian": "Montserrat", + "spanish": "Montserrat", + "chinese_simple": "蒙特塞拉特", + "korean": "몬트세랫", + "dutch": "Montserrat", + "portuguese": "Montserrat", + "russian": "Монтсеррат", + "chinese_traditional": "Montserrat", + "unknown1": "Montserrat", + "unknown2": "Montserrat", + "unknown3": "Montserrat", + "unknown4": "Montserrat" + }, + "coordinates": { + "latitude": 16.69921856, + "longitude": -62.21525588200001 + } + } + ] + }, + { + "id": 38, + "iso_code": "AN", + "name": "Netherlands Antilles", + "translations": { + "japanese": "オランダ領アンティル", + "english": "Netherlands Antilles", + "french": "Antilles néerlandaises", + "german": "Niederländische Antillen", + "italian": "Antille Olandesi", + "spanish": "Antillas Neerlandesas", + "chinese_simple": "荷属安的列斯", + "korean": "네덜란드령 앤틸리스", + "dutch": "Nederlandse Antillen", + "portuguese": "Antilhas Holandesas", + "russian": "Нидерландские Антильские острова", + "chinese_traditional": "Netherlands Antilles", + "unknown1": "Netherlands Antilles", + "unknown2": "Netherlands Antilles", + "unknown3": "Netherlands Antilles", + "unknown4": "Netherlands Antilles" + }, + "regions": [ + { + "id": 637534208, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": 12.095947128, + "longitude": -68.911441083 + } + }, + { + "id": 637599744, + "name": "Netherlands Antilles", + "translations": { + "japanese": "オランダ領アンティル", + "english": "Netherlands Antilles", + "french": "Antilles néerlandaises", + "german": "Niederländische Antillen", + "italian": "Antille Olandesi", + "spanish": "Antillas Neerlandesas", + "chinese_simple": "荷属安的列斯", + "korean": "네덜란드령 앤틸리스", + "dutch": "Nederlandse Antillen", + "portuguese": "Antilhas Holandesas", + "russian": "Нидерландские Антильские острова", + "chinese_traditional": "Netherlands Antilles", + "unknown1": "Netherlands Antilles", + "unknown2": "Netherlands Antilles", + "unknown3": "Netherlands Antilles", + "unknown4": "Netherlands Antilles" + }, + "coordinates": { + "latitude": 12.095947128, + "longitude": -68.911441083 + } + } + ] + }, + { + "id": 39, + "iso_code": "NI", + "name": "Nicaragua", + "translations": { + "japanese": "ニカラグア", + "english": "Nicaragua", + "french": "Nicaragua", + "german": "Nicaragua", + "italian": "Nicaragua", + "spanish": "Nicaragua", + "chinese_simple": "尼加拉瓜", + "korean": "니카라과", + "dutch": "Nicaragua", + "portuguese": "Nicarágua", + "russian": "Никарагуа", + "chinese_traditional": "Nicaragua", + "unknown1": "Nicaragua", + "unknown2": "Nicaragua", + "unknown3": "Nicaragua", + "unknown4": "Nicaragua" + }, + "regions": [ + { + "id": 654311424, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": 12.145385604, + "longitude": -86.264393544 + } + }, + { + "id": 654442496, + "name": "Managua", + "translations": { + "japanese": "マナグア", + "english": "Managua", + "french": "Managua", + "german": "Managua", + "italian": "Managua", + "spanish": "Managua", + "chinese_simple": "马那瓜省", + "korean": "마나과", + "dutch": "Managua", + "portuguese": "Managua", + "russian": "Манагуа", + "chinese_traditional": "Managua", + "unknown1": "Managua", + "unknown2": "Managua", + "unknown3": "Managua", + "unknown4": "Managua" + }, + "coordinates": { + "latitude": 12.145385604, + "longitude": -86.264393544 + } + }, + { + "id": 654508032, + "name": "Boaco", + "translations": { + "japanese": "ボアコ", + "english": "Boaco", + "french": "Boaco", + "german": "Boaco", + "italian": "Boaco", + "spanish": "Boaco", + "chinese_simple": "博阿科省", + "korean": "보아코", + "dutch": "Boaco", + "portuguese": "Boaco", + "russian": "Боако", + "chinese_traditional": "Boaco", + "unknown1": "Boaco", + "unknown2": "Boaco", + "unknown3": "Boaco", + "unknown4": "Boaco" + }, + "coordinates": { + "latitude": 12.463989116, + "longitude": -85.665637033 + } + }, + { + "id": 654573568, + "name": "Carazo", + "translations": { + "japanese": "カラソ", + "english": "Carazo", + "french": "Carazo", + "german": "Carazo", + "italian": "Carazo", + "spanish": "Carazo", + "chinese_simple": "卡拉索省", + "korean": "카라소", + "dutch": "Carazo", + "portuguese": "Carazo", + "russian": "Карасо", + "chinese_traditional": "Carazo", + "unknown1": "Carazo", + "unknown2": "Carazo", + "unknown3": "Carazo", + "unknown4": "Carazo" + }, + "coordinates": { + "latitude": 11.848754748, + "longitude": -86.198475396 + } + }, + { + "id": 654639104, + "name": "Chinandega", + "translations": { + "japanese": "チナンデガ", + "english": "Chinandega", + "french": "Chinandega", + "german": "Chinandega", + "italian": "Chinandega", + "spanish": "Chinandega", + "chinese_simple": "奇南德加省", + "korean": "치난데가", + "dutch": "Chinandega", + "portuguese": "Chinandega", + "russian": "Чинандега", + "chinese_traditional": "Chinandega", + "unknown1": "Chinandega", + "unknown2": "Chinandega", + "unknown3": "Chinandega", + "unknown4": "Chinandega" + }, + "coordinates": { + "latitude": 12.612304544, + "longitude": -87.148795363 + } + }, + { + "id": 654704640, + "name": "Chontales", + "translations": { + "japanese": "チョンタレス", + "english": "Chontales", + "french": "Chontales", + "german": "Chontales", + "italian": "Chontales", + "spanish": "Chontales", + "chinese_simple": "琼塔莱斯省", + "korean": "촌탈레스", + "dutch": "Chontales", + "portuguese": "Chontales", + "russian": "Чонталес", + "chinese_traditional": "Chontales", + "unknown1": "Chontales", + "unknown2": "Chontales", + "unknown3": "Chontales", + "unknown4": "Chontales" + }, + "coordinates": { + "latitude": 12.079467636, + "longitude": -85.396471262 + } + }, + { + "id": 654770176, + "name": "Estelí", + "translations": { + "japanese": "エステリ", + "english": "Estelí", + "french": "Estelí", + "german": "Estelí", + "italian": "Estelí", + "spanish": "Estelí", + "chinese_simple": "埃斯特利省", + "korean": "에스텔리", + "dutch": "Estelí", + "portuguese": "Estelí", + "russian": "Эстели", + "chinese_traditional": "Estelí", + "unknown1": "Estelí", + "unknown2": "Estelí", + "unknown3": "Estelí", + "unknown4": "Estelí" + }, + "coordinates": { + "latitude": 13.079223484, + "longitude": -86.346791229 + } + }, + { + "id": 654835712, + "name": "Granada", + "translations": { + "japanese": "グラナダ", + "english": "Granada", + "french": "Granada", + "german": "Granada", + "italian": "Granada", + "spanish": "Granada", + "chinese_simple": "格拉纳达省", + "korean": "그라나다", + "dutch": "Granada", + "portuguese": "Granada", + "russian": "Гранада", + "chinese_traditional": "Granada", + "unknown1": "Granada", + "unknown2": "Granada", + "unknown3": "Granada", + "unknown4": "Granada" + }, + "coordinates": { + "latitude": 11.931152208, + "longitude": -85.945789162 + } + }, + { + "id": 654901248, + "name": "Jinotega", + "translations": { + "japanese": "ヒノテガ", + "english": "Jinotega", + "french": "Jinotega", + "german": "Jinotega", + "italian": "Jinotega", + "spanish": "Jinotega", + "chinese_simple": "希诺特加省", + "korean": "히노테가", + "dutch": "Jinotega", + "portuguese": "Jinotega", + "russian": "Хинотега", + "chinese_traditional": "Jinotega", + "unknown1": "Jinotega", + "unknown2": "Jinotega", + "unknown3": "Jinotega", + "unknown4": "Jinotega" + }, + "coordinates": { + "latitude": 13.095702976, + "longitude": -85.995227773 + } + }, + { + "id": 654966784, + "name": "León", + "translations": { + "japanese": "レオン", + "english": "León", + "french": "León", + "german": "León", + "italian": "León", + "spanish": "León", + "chinese_simple": "莱昂省", + "korean": "레온", + "dutch": "León", + "portuguese": "León", + "russian": "Леон", + "chinese_traditional": "León", + "unknown1": "León", + "unknown2": "León", + "unknown3": "León", + "unknown4": "León" + }, + "coordinates": { + "latitude": 12.431030132, + "longitude": -86.874136413 + } + }, + { + "id": 655032320, + "name": "Madriz", + "translations": { + "japanese": "マドリス", + "english": "Madriz", + "french": "Madriz", + "german": "Madriz", + "italian": "Madriz", + "spanish": "Madriz", + "chinese_simple": "马德里斯省", + "korean": "마드리스", + "dutch": "Madriz", + "portuguese": "Madriz", + "russian": "Мадрис", + "chinese_traditional": "Madriz", + "unknown1": "Madriz", + "unknown2": "Madriz", + "unknown3": "Madriz", + "unknown4": "Madriz" + }, + "coordinates": { + "latitude": 13.480224456, + "longitude": -86.582997926 + } + }, + { + "id": 655097856, + "name": "Masaya", + "translations": { + "japanese": "マサヤ", + "english": "Masaya", + "french": "Masaya", + "german": "Masaya", + "italian": "Masaya", + "spanish": "Masaya", + "chinese_simple": "马萨亚省", + "korean": "마사야", + "dutch": "Masaya", + "portuguese": "Masaya", + "russian": "Масая", + "chinese_traditional": "Masaya", + "unknown1": "Masaya", + "unknown2": "Masaya", + "unknown3": "Masaya", + "unknown4": "Masaya" + }, + "coordinates": { + "latitude": 11.964111192, + "longitude": -86.09959817400001 + } + }, + { + "id": 655163392, + "name": "Matagalpa", + "translations": { + "japanese": "マタガルパ", + "english": "Matagalpa", + "french": "Matagalpa", + "german": "Matagalpa", + "italian": "Matagalpa", + "spanish": "Matagalpa", + "chinese_simple": "马塔加尔帕省", + "korean": "마타갈파", + "dutch": "Matagalpa", + "portuguese": "Matagalpa", + "russian": "Матагальпа", + "chinese_traditional": "Matagalpa", + "unknown1": "Matagalpa", + "unknown2": "Matagalpa", + "unknown3": "Matagalpa", + "unknown4": "Matagalpa" + }, + "coordinates": { + "latitude": 12.914428564, + "longitude": -85.912830088 + } + }, + { + "id": 655228928, + "name": "Nueva Segovia", + "translations": { + "japanese": "ヌエバ・セゴビア", + "english": "Nueva Segovia", + "french": "Nueva Segovia", + "german": "Nueva Segovia", + "italian": "Nueva Segovia", + "spanish": "Nueva Segovia", + "chinese_simple": "新塞哥维亚省", + "korean": "누에바세고비아", + "dutch": "Nueva Segovia", + "portuguese": "Nueva Segovia", + "russian": "Нуэва-Сеговия", + "chinese_traditional": "Nueva Segovia", + "unknown1": "Nueva Segovia", + "unknown2": "Nueva Segovia", + "unknown3": "Nueva Segovia", + "unknown4": "Nueva Segovia" + }, + "coordinates": { + "latitude": 13.628539884, + "longitude": -86.47862752500001 + } + }, + { + "id": 655294464, + "name": "Río San Juan", + "translations": { + "japanese": "リオ・サン・フアン", + "english": "Río San Juan", + "french": "Río San Juan", + "german": "Río San Juan", + "italian": "Río San Juan", + "spanish": "Río San Juan", + "chinese_simple": "圣胡安河省", + "korean": "리오산후안", + "dutch": "Río San Juan", + "portuguese": "Río San Juan", + "russian": "Сан-Хуан", + "chinese_traditional": "Río San Juan", + "unknown1": "Río San Juan", + "unknown2": "Río San Juan", + "unknown3": "Río San Juan", + "unknown4": "Río San Juan" + }, + "coordinates": { + "latitude": 11.112670772, + "longitude": -84.781235214 + } + }, + { + "id": 655360000, + "name": "Rivas", + "translations": { + "japanese": "リバス", + "english": "Rivas", + "french": "Rivas", + "german": "Rivas", + "italian": "Rivas", + "spanish": "Rivas", + "chinese_simple": "里瓦斯省", + "korean": "리바스", + "dutch": "Rivas", + "portuguese": "Rivas", + "russian": "Ривас", + "chinese_traditional": "Rivas", + "unknown1": "Rivas", + "unknown2": "Rivas", + "unknown3": "Rivas", + "unknown4": "Rivas" + }, + "coordinates": { + "latitude": 11.431274284, + "longitude": -85.830432403 + } + }, + { + "id": 655425536, + "name": "Atlántico Norte", + "translations": { + "japanese": "北アトランティコ自治地域", + "english": "Atlántico Norte", + "french": "Región Autónoma del Atlántico Norte", + "german": "Región Autónoma del Atlántico Norte", + "italian": "Atlantico Settentrionale", + "spanish": "Atlántico Norte", + "chinese_simple": "北大西洋自治区", + "korean": "북아틀란티코 자치구", + "dutch": "Atlántico Norte", + "portuguese": "Atlântico Norte", + "russian": "Атлантический Северный автономный регион", + "chinese_traditional": "Atlántico Norte", + "unknown1": "Atlántico Norte", + "unknown2": "Atlántico Norte", + "unknown3": "Atlántico Norte", + "unknown4": "Atlántico Norte" + }, + "coordinates": { + "latitude": 14.029540856, + "longitude": -83.380474569 + } + }, + { + "id": 655491072, + "name": "Atlántico Sur", + "translations": { + "japanese": "南アトランティコ自治地域", + "english": "Atlántico Sur", + "french": "Región Autónoma del Atlántico Sur", + "german": "Región Autónoma del Atlántico Sur", + "italian": "Atlantico Meridionale", + "spanish": "Atlántico Sur", + "chinese_simple": "南大西洋自治区", + "korean": "남아틀란티코 자치구", + "dutch": "Atlántico Sur", + "portuguese": "Atlântico Sul", + "russian": "Атлантический Южный автономный регион", + "chinese_traditional": "Atlántico Sur", + "unknown1": "Atlántico Sur", + "unknown2": "Atlántico Sur", + "unknown3": "Atlántico Sur", + "unknown4": "Atlántico Sur" + }, + "coordinates": { + "latitude": 11.997070176, + "longitude": -83.748517562 + } + } + ] + }, + { + "id": 40, + "iso_code": "PA", + "name": "Panama", + "translations": { + "japanese": "パナマ", + "english": "Panama", + "french": "Panama", + "german": "Panama", + "italian": "Panamá", + "spanish": "Panamá", + "chinese_simple": "巴拿马", + "korean": "파나마", + "dutch": "Panama", + "portuguese": "Panamá", + "russian": "Панама", + "chinese_traditional": "Panama", + "unknown1": "Panama", + "unknown2": "Panama", + "unknown3": "Panama", + "unknown4": "Panama" + }, + "regions": [ + { + "id": 671088640, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": 8.964843648, + "longitude": -79.52975609 + } + }, + { + "id": 671219712, + "name": "Panamá", + "translations": { + "japanese": "パナマ", + "english": "Panamá", + "french": "Panama", + "german": "Panama", + "italian": "Panamá", + "spanish": "Panamá", + "chinese_simple": "巴拿马省", + "korean": "파나마", + "dutch": "Panama", + "portuguese": "Panamá", + "russian": "Панама", + "chinese_traditional": "Panamá", + "unknown1": "Panamá", + "unknown2": "Panamá", + "unknown3": "Panamá", + "unknown4": "Panamá" + }, + "coordinates": { + "latitude": 8.964843648, + "longitude": -79.52975609 + } + }, + { + "id": 671285248, + "name": "Bocas del Toro", + "translations": { + "japanese": "ボカズ・デル・トーロ", + "english": "Bocas del Toro", + "french": "Bocas del Toro", + "german": "Bocas del Toro", + "italian": "Bocas del Toro", + "spanish": "Bocas del Toro", + "chinese_simple": "博卡斯-德尔托罗省", + "korean": "보카스델토로", + "dutch": "Bocas del Toro", + "portuguese": "Bocas del Toro", + "russian": "Бокас-дель-Торо", + "chinese_traditional": "Bocas del Toro", + "unknown1": "Bocas del Toro", + "unknown2": "Bocas del Toro", + "unknown3": "Bocas del Toro", + "unknown4": "Bocas del Toro" + }, + "coordinates": { + "latitude": 9.332885636, + "longitude": -82.248879695 + } + }, + { + "id": 671350784, + "name": "Chiriquí", + "translations": { + "japanese": "チリキ", + "english": "Chiriquí", + "french": "Chiriquí", + "german": "Chiriquí", + "italian": "Chiriquí", + "spanish": "Chiriquí", + "chinese_simple": "奇里基省", + "korean": "치리키", + "dutch": "Chiriquí", + "portuguese": "Chiriquí", + "russian": "Чирики", + "chinese_traditional": "Chiriquí", + "unknown1": "Chiriquí", + "unknown2": "Chiriquí", + "unknown3": "Chiriquí", + "unknown4": "Chiriquí" + }, + "coordinates": { + "latitude": 8.43200674, + "longitude": -82.430154602 + } + }, + { + "id": 671416320, + "name": "Coclé", + "translations": { + "japanese": "コクレ", + "english": "Coclé", + "french": "Coclé", + "german": "Coclé", + "italian": "Coclé", + "spanish": "Coclé", + "chinese_simple": "科克莱省", + "korean": "코클레", + "dutch": "Coclé", + "portuguese": "Coclé", + "russian": "Кокле", + "chinese_traditional": "Coclé", + "unknown1": "Coclé", + "unknown2": "Coclé", + "unknown3": "Coclé", + "unknown4": "Coclé" + }, + "coordinates": { + "latitude": 8.5144042, + "longitude": -80.364719298 + } + }, + { + "id": 671481856, + "name": "Colón", + "translations": { + "japanese": "コロン", + "english": "Colón", + "french": "Colón", + "german": "Colón", + "italian": "Colón", + "spanish": "Colón", + "chinese_simple": "科隆省", + "korean": "콜론", + "dutch": "Colón", + "portuguese": "Colón", + "russian": "Колон", + "chinese_traditional": "Colón", + "unknown1": "Colón", + "unknown2": "Colón", + "unknown3": "Colón", + "unknown4": "Colón" + }, + "coordinates": { + "latitude": 9.354858292, + "longitude": -79.897799083 + } + }, + { + "id": 671547392, + "name": "Darién", + "translations": { + "japanese": "ダリエン", + "english": "Darién", + "french": "Darién", + "german": "Darién", + "italian": "Darién", + "spanish": "Darién", + "chinese_simple": "达连省", + "korean": "다리엔", + "dutch": "Darién", + "portuguese": "Darién", + "russian": "Дарьен", + "chinese_traditional": "Darién", + "unknown1": "Darién", + "unknown2": "Darién", + "unknown3": "Darién", + "unknown4": "Darién" + }, + "coordinates": { + "latitude": 8.399047756, + "longitude": -78.139981803 + } + }, + { + "id": 671612928, + "name": "Herrera", + "translations": { + "japanese": "エレーラ", + "english": "Herrera", + "french": "Herrera", + "german": "Herrera", + "italian": "Herrera", + "spanish": "Herrera", + "chinese_simple": "埃雷拉省", + "korean": "에레라", + "dutch": "Herrera", + "portuguese": "Herrera", + "russian": "Эррера", + "chinese_traditional": "Herrera", + "unknown1": "Herrera", + "unknown2": "Herrera", + "unknown3": "Herrera", + "unknown4": "Herrera" + }, + "coordinates": { + "latitude": 7.9650878, + "longitude": -80.430637446 + } + }, + { + "id": 671678464, + "name": "Los Santos", + "translations": { + "japanese": "ロス・サントス", + "english": "Los Santos", + "french": "Los Santos", + "german": "Los Santos", + "italian": "Los Santos", + "spanish": "Los Santos", + "chinese_simple": "洛斯桑托斯省", + "korean": "로스 산토스", + "dutch": "Los Santos", + "portuguese": "Los Santos", + "russian": "Лос-Сантос", + "chinese_traditional": "Los Santos", + "unknown1": "Los Santos", + "unknown2": "Los Santos", + "unknown3": "Los Santos", + "unknown4": "Los Santos" + }, + "coordinates": { + "latitude": 7.7618407320000005, + "longitude": -80.28232161300001 + } + }, + { + "id": 671744000, + "name": "Kuna Yala", + "translations": { + "japanese": "サンブラス諸島", + "english": "Kuna Yala", + "french": "Kuna Yala", + "german": "Kuna Yala", + "italian": "Kuna Yala", + "spanish": "Kuna Yala", + "chinese_simple": "圣布拉斯特区", + "korean": "산블라스 제도", + "dutch": "San Blas-eilanden", + "portuguese": "Kuna Yala", + "russian": "Куна-Яла", + "chinese_traditional": "Kuna Yala", + "unknown1": "Kuna Yala", + "unknown2": "Kuna Yala", + "unknown3": "Kuna Yala", + "unknown4": "Kuna Yala" + }, + "coordinates": { + "latitude": 9.563598524, + "longitude": -78.952972295 + } + }, + { + "id": 671809536, + "name": "Veraguas", + "translations": { + "japanese": "ベラグアス", + "english": "Veraguas", + "french": "Veraguas", + "german": "Veraguas", + "italian": "Veraguas", + "spanish": "Veraguas", + "chinese_simple": "贝拉瓜斯省", + "korean": "베라과스", + "dutch": "Veraguas", + "portuguese": "Veraguas", + "russian": "Верагуас", + "chinese_traditional": "Veraguas", + "unknown1": "Veraguas", + "unknown2": "Veraguas", + "unknown3": "Veraguas", + "unknown4": "Veraguas" + }, + "coordinates": { + "latitude": 8.096923736, + "longitude": -80.979955346 + } + } + ] + }, + { + "id": 41, + "iso_code": "PY", + "name": "Paraguay", + "translations": { + "japanese": "パラグアイ", + "english": "Paraguay", + "french": "Paraguay", + "german": "Paraguay", + "italian": "Paraguay", + "spanish": "Paraguay", + "chinese_simple": "巴拉圭", + "korean": "파라과이", + "dutch": "Paraguay", + "portuguese": "Paraguai", + "russian": "Парагвай", + "chinese_traditional": "Paraguay", + "unknown1": "Paraguay", + "unknown2": "Paraguay", + "unknown3": "Paraguay", + "unknown4": "Paraguay" + }, + "regions": [ + { + "id": 687865856, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": -25.263062259999998, + "longitude": -57.661410491 + } + }, + { + "id": 687996928, + "name": "Central", + "translations": { + "japanese": "セントラル県", + "english": "Central", + "french": "Central", + "german": "Central", + "italian": "Central", + "spanish": "Central", + "chinese_simple": "中央省", + "korean": "센트랄 주", + "dutch": "Central", + "portuguese": "Central", + "russian": "Сентраль", + "chinese_traditional": "Central", + "unknown1": "Central", + "unknown2": "Central", + "unknown3": "Central", + "unknown4": "Central" + }, + "coordinates": { + "latitude": -25.263062259999998, + "longitude": -57.661410491 + } + }, + { + "id": 688062464, + "name": "Alto Paraná", + "translations": { + "japanese": "アルト・パラナ県", + "english": "Alto Paraná", + "french": "Alto Paraná", + "german": "Alto Paraná", + "italian": "Alto Paraná", + "spanish": "Alto Paraná", + "chinese_simple": "上巴拉那省", + "korean": "알토파라나 주", + "dutch": "Alto Paraná", + "portuguese": "Alto Paraná", + "russian": "Альто-Парана", + "chinese_traditional": "Alto Paraná", + "unknown1": "Alto Paraná", + "unknown2": "Alto Paraná", + "unknown3": "Alto Paraná", + "unknown4": "Alto Paraná" + }, + "coordinates": { + "latitude": -25.499268311999998, + "longitude": -54.832423305999995 + } + }, + { + "id": 688128000, + "name": "Amambay", + "translations": { + "japanese": "アマンバイ県", + "english": "Amambay", + "french": "Amambay", + "german": "Amambay", + "italian": "Amambay", + "spanish": "Amambay", + "chinese_simple": "阿曼拜省", + "korean": "아맘바이 주", + "dutch": "Amambay", + "portuguese": "Amambay", + "russian": "Амамбай", + "chinese_traditional": "Amambay", + "unknown1": "Amambay", + "unknown2": "Amambay", + "unknown3": "Amambay", + "unknown4": "Amambay" + }, + "coordinates": { + "latitude": -22.999878691999996, + "longitude": -55.996977254 + } + }, + { + "id": 688193536, + "name": "Caaguazú", + "translations": { + "japanese": "カアグアスー県", + "english": "Caaguazú", + "french": "Caaguazú", + "german": "Caaguazú", + "italian": "Caaguazú", + "spanish": "Caaguazú", + "chinese_simple": "卡瓜苏省", + "korean": "카아과수 주", + "dutch": "Caaguazú", + "portuguese": "Caaguazú", + "russian": "Каагуасу", + "chinese_traditional": "Caaguazú", + "unknown1": "Caaguazú", + "unknown2": "Caaguazú", + "unknown3": "Caaguazú", + "unknown4": "Caaguazú" + }, + "coordinates": { + "latitude": -25.411377688, + "longitude": -56.44741793200001 + } + }, + { + "id": 688259072, + "name": "Caazapá", + "translations": { + "japanese": "カアサパ県", + "english": "Caazapá", + "french": "Caazapá", + "german": "Caazapá", + "italian": "Caazapá", + "spanish": "Caazapá", + "chinese_simple": "卡萨帕省", + "korean": "카아사파 주", + "dutch": "Caazapá", + "portuguese": "Caazapá", + "russian": "Каасапа", + "chinese_traditional": "Caazapá", + "unknown1": "Caazapá", + "unknown2": "Caazapá", + "unknown3": "Caazapá", + "unknown4": "Caazapá" + }, + "coordinates": { + "latitude": -26.163941156, + "longitude": -55.996977254 + } + }, + { + "id": 688324608, + "name": "Concepción", + "translations": { + "japanese": "コンセプシオン県", + "english": "Concepción", + "french": "Concepción", + "german": "Concepción", + "italian": "Concepción", + "spanish": "Concepción", + "chinese_simple": "康塞普西翁省", + "korean": "콘셉시온 주", + "dutch": "Concepción", + "portuguese": "Concepção", + "russian": "Консепсьон", + "chinese_traditional": "Concepción", + "unknown1": "Concepción", + "unknown2": "Concepción", + "unknown3": "Concepción", + "unknown4": "Concepción" + }, + "coordinates": { + "latitude": -23.406372828000002, + "longitude": -57.430696972999996 + } + }, + { + "id": 688390144, + "name": "Cordillera", + "translations": { + "japanese": "コルディリェラ県", + "english": "Cordillera", + "french": "Cordillera", + "german": "Cordillera", + "italian": "Cordillera", + "spanish": "Cordillera", + "chinese_simple": "科迪勒拉省", + "korean": "코르디예라 주", + "dutch": "Cordillera", + "portuguese": "Cordillera", + "russian": "Кордильера", + "chinese_traditional": "Cordillera", + "unknown1": "Cordillera", + "unknown2": "Cordillera", + "unknown3": "Cordillera", + "unknown4": "Cordillera" + }, + "coordinates": { + "latitude": -25.378418703999998, + "longitude": -57.145051665 + } + }, + { + "id": 688455680, + "name": "Guairá", + "translations": { + "japanese": "グアイラー県", + "english": "Guairá", + "french": "Guairá", + "german": "Guairá", + "italian": "Guairá", + "spanish": "Guairá", + "chinese_simple": "瓜伊拉省", + "korean": "과이라 주", + "dutch": "Guairá", + "portuguese": "Guairá", + "russian": "Гуайра", + "chinese_traditional": "Guairá", + "unknown1": "Guairá", + "unknown2": "Guairá", + "unknown3": "Guairá", + "unknown4": "Guairá" + }, + "coordinates": { + "latitude": -25.746460692, + "longitude": -56.430938395 + } + }, + { + "id": 688521216, + "name": "Itapúa", + "translations": { + "japanese": "イタプア県", + "english": "Itapúa", + "french": "Itapúa", + "german": "Itapúa", + "italian": "Itapúa", + "spanish": "Itapúa", + "chinese_simple": "伊塔普阿省", + "korean": "이타푸아 주", + "dutch": "Itapúa", + "portuguese": "Itapúa", + "russian": "Итапуа", + "chinese_traditional": "Itapúa", + "unknown1": "Itapúa", + "unknown2": "Itapúa", + "unknown3": "Itapúa", + "unknown4": "Itapúa" + }, + "coordinates": { + "latitude": -27.328491923999998, + "longitude": -55.898100032 + } + }, + { + "id": 688586752, + "name": "Misiones", + "translations": { + "japanese": "ミシオネス県", + "english": "Misiones", + "french": "Misiones", + "german": "Misiones", + "italian": "Misiones", + "spanish": "Misiones", + "chinese_simple": "米西奥内斯省", + "korean": "미시오네스 주", + "dutch": "Misiones", + "portuguese": "Misiones", + "russian": "Мисьонес", + "chinese_traditional": "Misiones", + "unknown1": "Misiones", + "unknown2": "Misiones", + "unknown3": "Misiones", + "unknown4": "Misiones" + }, + "coordinates": { + "latitude": -26.998902084, + "longitude": -56.996735832 + } + }, + { + "id": 688652288, + "name": "Ñeembucú", + "translations": { + "japanese": "ニェエンブク県", + "english": "Ñeembucú", + "french": "Ñeembucú", + "german": "Ñeembucú", + "italian": "Ñeembucú", + "spanish": "Ñeembucú", + "chinese_simple": "涅恩布库省", + "korean": "네엠부쿠 주", + "dutch": "Ñeembucú", + "portuguese": "Ñeembucú", + "russian": "Ньеэмбуку", + "chinese_traditional": "Ñeembucú", + "unknown1": "Ñeembucú", + "unknown2": "Ñeembucú", + "unknown3": "Ñeembucú", + "unknown4": "Ñeembucú" + }, + "coordinates": { + "latitude": -26.998902084, + "longitude": -57.99649441 + } + }, + { + "id": 688717824, + "name": "Paraguarí", + "translations": { + "japanese": "パラグアリ県", + "english": "Paraguarí", + "french": "Paraguarí", + "german": "Paraguarí", + "italian": "Paraguarí", + "spanish": "Paraguarí", + "chinese_simple": "巴拉瓜里省", + "korean": "파라과리 주", + "dutch": "Paraguarí", + "portuguese": "Paraguarí", + "russian": "Парагуари", + "chinese_traditional": "Paraguarí", + "unknown1": "Paraguarí", + "unknown2": "Paraguarí", + "unknown3": "Paraguarí", + "unknown4": "Paraguarí" + }, + "coordinates": { + "latitude": -25.631104248, + "longitude": -57.145051665 + } + }, + { + "id": 688783360, + "name": "Presidente Hayes", + "translations": { + "japanese": "プレジデンテ・アエス県", + "english": "Presidente Hayes", + "french": "Presidente Hayes", + "german": "Presidente Hayes", + "italian": "Presidente Hayes", + "spanish": "Presidente Hayes", + "chinese_simple": "阿耶斯总统省", + "korean": "프레시덴테아예스 주", + "dutch": "Presidente Hayes", + "portuguese": "Presidente Hayes", + "russian": "Пресиденте-Аес", + "chinese_traditional": "Presidente Hayes", + "unknown1": "Presidente Hayes", + "unknown2": "Presidente Hayes", + "unknown3": "Presidente Hayes", + "unknown4": "Presidente Hayes" + }, + "coordinates": { + "latitude": -23.499756616, + "longitude": -58.831457618 + } + }, + { + "id": 688848896, + "name": "San Pedro", + "translations": { + "japanese": "サン・ペドロ県", + "english": "San Pedro", + "french": "San Pedro", + "german": "San Pedro", + "italian": "San Pedro", + "spanish": "San Pedro", + "chinese_simple": "圣佩德罗省", + "korean": "산페드로 주", + "dutch": "San Pedro", + "portuguese": "San Pedro", + "russian": "Сан-Педро", + "chinese_traditional": "San Pedro", + "unknown1": "San Pedro", + "unknown2": "San Pedro", + "unknown3": "San Pedro", + "unknown4": "San Pedro" + }, + "coordinates": { + "latitude": -24.098511492, + "longitude": -57.079133517 + } + }, + { + "id": 688914432, + "name": "Canindeyú", + "translations": { + "japanese": "カニンデジュ県", + "english": "Canindeyú", + "french": "Canindeyú", + "german": "Canindeyú", + "italian": "Canindeyú", + "spanish": "Canindeyú", + "chinese_simple": "卡宁迪尤省", + "korean": "카넨디유 주", + "dutch": "Canindeyú", + "portuguese": "Canindeyú", + "russian": "Канендию", + "chinese_traditional": "Canindeyú", + "unknown1": "Canindeyú", + "unknown2": "Canindeyú", + "unknown3": "Canindeyú", + "unknown4": "Canindeyú" + }, + "coordinates": { + "latitude": -24.049073015999994, + "longitude": -54.349023554 + } + }, + { + "id": 688979968, + "name": "Asunción", + "translations": { + "japanese": "アスンシオン市", + "english": "Asunción", + "french": "Asunción", + "german": "Asunción D.C.", + "italian": "Asunción", + "spanish": "Asunción", + "chinese_simple": "亚松森首都特别区", + "korean": "아순시온", + "dutch": "Asunción", + "portuguese": "Assunção", + "russian": "Асунсьон", + "chinese_traditional": "Asunción", + "unknown1": "Asunción", + "unknown2": "Asunción", + "unknown3": "Asunción", + "unknown4": "Asunción" + }, + "coordinates": { + "latitude": -25.263062259999998, + "longitude": -57.661410491 + } + }, + { + "id": 689045504, + "name": "Alto Paraguay", + "translations": { + "japanese": "アルト・パラグアイ県", + "english": "Alto Paraguay", + "french": "Alto Paraguay", + "german": "Alto Paraguay", + "italian": "Alto Paraguay", + "spanish": "Alto Paraguay", + "chinese_simple": "上巴拉圭省", + "korean": "알토파라과이 주", + "dutch": "Alto Paraguay", + "portuguese": "Alto Paraguai", + "russian": "Альто-Парагвай", + "chinese_traditional": "Alto Paraguay", + "unknown1": "Alto Paraguay", + "unknown2": "Alto Paraguay", + "unknown3": "Alto Paraguay", + "unknown4": "Alto Paraguay" + }, + "coordinates": { + "latitude": -21.03332598, + "longitude": -57.897617188 + } + }, + { + "id": 689111040, + "name": "Boquerón", + "translations": { + "japanese": "ボケロン県", + "english": "Boquerón", + "french": "Boquerón", + "german": "Boquerón", + "italian": "Boquerón", + "spanish": "Boquerón", + "chinese_simple": "博克龙省", + "korean": "보케론 주", + "dutch": "Boquerón", + "portuguese": "Boquerón", + "russian": "Бокерон", + "chinese_traditional": "Boquerón", + "unknown1": "Boquerón", + "unknown2": "Boquerón", + "unknown3": "Boquerón", + "unknown4": "Boquerón" + }, + "coordinates": { + "latitude": -22.346192176000002, + "longitude": -60.02897064 + } + } + ] + }, + { + "id": 42, + "iso_code": "PE", + "name": "Peru", + "translations": { + "japanese": "ペルー", + "english": "Peru", + "french": "Pérou", + "german": "Peru", + "italian": "Perù", + "spanish": "Perú", + "chinese_simple": "秘鲁", + "korean": "페루", + "dutch": "Peru", + "portuguese": "Peru", + "russian": "Перу", + "chinese_traditional": "Peru", + "unknown1": "Peru", + "unknown2": "Peru", + "unknown3": "Peru", + "unknown4": "Peru" + }, + "regions": [ + { + "id": 704643072, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": -12.046509676, + "longitude": -77.046839182 + } + }, + { + "id": 704774144, + "name": "Lima", + "translations": { + "japanese": "リマ", + "english": "Lima", + "french": "Province de Lima", + "german": "Lima Metropolitana", + "italian": "Lima", + "spanish": "Lima", + "chinese_simple": "利马省", + "korean": "리마", + "dutch": "Lima", + "portuguese": "Lima", + "russian": "Лима", + "chinese_traditional": "Lima", + "unknown1": "Lima", + "unknown2": "Lima", + "unknown3": "Lima", + "unknown4": "Lima" + }, + "coordinates": { + "latitude": -12.046509676, + "longitude": -77.046839182 + } + }, + { + "id": 704839680, + "name": "Amazonas", + "translations": { + "japanese": "アマソナス", + "english": "Amazonas", + "french": "Amazone", + "german": "Amazonas", + "italian": "Amazonas", + "spanish": "Amazonas", + "chinese_simple": "亚马孙省", + "korean": "아마소나스", + "dutch": "Amazonas", + "portuguese": "Amazonas", + "russian": "Амасонас", + "chinese_traditional": "Amazonas", + "unknown1": "Amazonas", + "unknown2": "Amazonas", + "unknown3": "Amazonas", + "unknown4": "Amazonas" + }, + "coordinates": { + "latitude": -6.212769507999994, + "longitude": -77.848843316 + } + }, + { + "id": 704905216, + "name": "Ancash", + "translations": { + "japanese": "アンカッシュ", + "english": "Ancash", + "french": "Ancash", + "german": "Ancash", + "italian": "Ancash", + "spanish": "Ancash", + "chinese_simple": "安卡什省", + "korean": "앙카시", + "dutch": "Ancash", + "portuguese": "Ancash", + "russian": "Анкаш", + "chinese_traditional": "Ancash", + "unknown1": "Ancash", + "unknown2": "Ancash", + "unknown3": "Ancash", + "unknown4": "Ancash" + }, + "coordinates": { + "latitude": -9.530640563999995, + "longitude": -77.530238934 + } + }, + { + "id": 704970752, + "name": "Apurímac", + "translations": { + "japanese": "アプリマック", + "english": "Apurímac", + "french": "Apurímac", + "german": "Apurímac", + "italian": "Apurímac", + "spanish": "Apurímac", + "chinese_simple": "阿普里马克省", + "korean": "아푸리막", + "dutch": "Apurímac", + "portuguese": "Apurímac", + "russian": "Апуримак", + "chinese_traditional": "Apurímac", + "unknown1": "Apurímac", + "unknown2": "Apurímac", + "unknown3": "Apurímac", + "unknown4": "Apurímac" + }, + "coordinates": { + "latitude": -13.628540908000005, + "longitude": -72.877516321 + } + }, + { + "id": 705036288, + "name": "Arequipa", + "translations": { + "japanese": "アレキパ", + "english": "Arequipa", + "french": "Arequipa", + "german": "Arequipa", + "italian": "Arequipa", + "spanish": "Arequipa", + "chinese_simple": "阿雷基帕省", + "korean": "아레키파", + "dutch": "Arequipa", + "portuguese": "Arequipa", + "russian": "Арекипа", + "chinese_traditional": "Arequipa", + "unknown1": "Arequipa", + "unknown2": "Arequipa", + "unknown3": "Arequipa", + "unknown4": "Arequipa" + }, + "coordinates": { + "latitude": -16.397095563999997, + "longitude": -71.53168746600001 + } + }, + { + "id": 705101824, + "name": "Ayacucho", + "translations": { + "japanese": "アヤクーチョ", + "english": "Ayacucho", + "french": "Ayacucho", + "german": "Ayacucho", + "italian": "Ayacucho", + "spanish": "Ayacucho", + "chinese_simple": "阿亚库乔省", + "korean": "아야쿠초", + "dutch": "Ayacucho", + "portuguese": "Ayacucho", + "russian": "Аякучо", + "chinese_traditional": "Ayacucho", + "unknown1": "Ayacucho", + "unknown2": "Ayacucho", + "unknown3": "Ayacucho", + "unknown4": "Ayacucho" + }, + "coordinates": { + "latitude": -13.156128804000005, + "longitude": -74.223345176 + } + }, + { + "id": 705167360, + "name": "Cajamarca", + "translations": { + "japanese": "カハマルカ", + "english": "Cajamarca", + "french": "Cajamarca", + "german": "Cajamarca", + "italian": "Cajamarca", + "spanish": "Cajamarca", + "chinese_simple": "卡哈马卡省", + "korean": "카하마르카", + "dutch": "Cajamarca", + "portuguese": "Cajamarca", + "russian": "Кахамарка", + "chinese_traditional": "Cajamarca", + "unknown1": "Cajamarca", + "unknown2": "Cajamarca", + "unknown3": "Cajamarca", + "unknown4": "Cajamarca" + }, + "coordinates": { + "latitude": -7.1630868799999945, + "longitude": -78.513517975 + } + }, + { + "id": 705232896, + "name": "Callao", + "translations": { + "japanese": "カヤオ", + "english": "Callao", + "french": "Callao", + "german": "Callao", + "italian": "Callao", + "spanish": "Callao", + "chinese_simple": "卡亚俄区", + "korean": "카야오", + "dutch": "Callao", + "portuguese": "Callao", + "russian": "Кальяо", + "chinese_traditional": "Callao", + "unknown1": "Callao", + "unknown2": "Callao", + "unknown3": "Callao", + "unknown4": "Callao" + }, + "coordinates": { + "latitude": -12.062989168000001, + "longitude": -77.145716404 + } + }, + { + "id": 705298432, + "name": "Cuzco", + "translations": { + "japanese": "クスコ", + "english": "Cuzco", + "french": "Cuzco", + "german": "Cusco", + "italian": "Cusco", + "spanish": "Cuzco", + "chinese_simple": "库斯科省", + "korean": "쿠스코", + "dutch": "Cuzco", + "portuguese": "Cuzco", + "russian": "Куско", + "chinese_traditional": "Cuzco", + "unknown1": "Cuzco", + "unknown2": "Cuzco", + "unknown3": "Cuzco", + "unknown4": "Cuzco" + }, + "coordinates": { + "latitude": -13.513184464000005, + "longitude": -71.976634965 + } + }, + { + "id": 705363968, + "name": "Huancavelica", + "translations": { + "japanese": "ワンカベリカ", + "english": "Huancavelica", + "french": "Huancavelica", + "german": "Huancavelica", + "italian": "Huancavelica", + "spanish": "Huancavelica", + "chinese_simple": "万卡韦利卡省", + "korean": "우앙카벨리카", + "dutch": "Huancavelica", + "portuguese": "Huancavelica", + "russian": "Уанкавелика", + "chinese_traditional": "Huancavelica", + "unknown1": "Huancavelica", + "unknown2": "Huancavelica", + "unknown3": "Huancavelica", + "unknown4": "Huancavelica" + }, + "coordinates": { + "latitude": -12.76611416, + "longitude": -74.981403878 + } + }, + { + "id": 705429504, + "name": "Huánuco", + "translations": { + "japanese": "ワヌコ", + "english": "Huánuco", + "french": "Huánuco", + "german": "Huánuco", + "italian": "Huánuco", + "spanish": "Huánuco", + "chinese_simple": "瓦努科省", + "korean": "우아누코", + "dutch": "Huánuco", + "portuguese": "Huánuco", + "russian": "Уануко", + "chinese_traditional": "Huánuco", + "unknown1": "Huánuco", + "unknown2": "Huánuco", + "unknown3": "Huánuco", + "unknown4": "Huánuco" + }, + "coordinates": { + "latitude": -9.915162043999999, + "longitude": -76.228355511 + } + }, + { + "id": 705495040, + "name": "Ica", + "translations": { + "japanese": "イカ", + "english": "Ica", + "french": "Ica", + "german": "Ica", + "italian": "Ica", + "spanish": "Ica", + "chinese_simple": "伊卡省", + "korean": "이카", + "dutch": "Ica", + "portuguese": "Ica", + "russian": "Ика", + "chinese_traditional": "Ica", + "unknown1": "Ica", + "unknown2": "Ica", + "unknown3": "Ica", + "unknown4": "Ica" + }, + "coordinates": { + "latitude": -14.067994028000001, + "longitude": -75.722983043 + } + }, + { + "id": 705560576, + "name": "Junín", + "translations": { + "japanese": "フニン", + "english": "Junín", + "french": "Junín", + "german": "Junín", + "italian": "Junín", + "spanish": "Junín", + "chinese_simple": "胡宁省", + "korean": "후닌", + "dutch": "Junín", + "portuguese": "Junín", + "russian": "Хунин", + "chinese_traditional": "Junín", + "unknown1": "Junín", + "unknown2": "Junín", + "unknown3": "Junín", + "unknown4": "Junín" + }, + "coordinates": { + "latitude": -12.062989168000001, + "longitude": -75.228596933 + } + }, + { + "id": 705626112, + "name": "La Libertad", + "translations": { + "japanese": "ラ・リベルター", + "english": "La Libertad", + "french": "La Libertad", + "german": "La Libertad", + "italian": "La Libertad", + "spanish": "La Libertad", + "chinese_simple": "拉利伯塔德省", + "korean": "라리베르타드", + "dutch": "La Libertad", + "portuguese": "La Libertad", + "russian": "Ла-Либертад", + "chinese_traditional": "La Libertad", + "unknown1": "La Libertad", + "unknown2": "La Libertad", + "unknown3": "La Libertad", + "unknown4": "La Libertad" + }, + "coordinates": { + "latitude": -8.107911087999994, + "longitude": -79.024383622 + } + }, + { + "id": 705691648, + "name": "Lambayeque", + "translations": { + "japanese": "ランバイェケ", + "english": "Lambayeque", + "french": "Lambayeque", + "german": "Lambayeque", + "italian": "Lambayeque", + "spanish": "Lambayeque", + "chinese_simple": "兰巴耶克省", + "korean": "람바예케", + "dutch": "Lambayeque", + "portuguese": "Lambayeque", + "russian": "Ламбайеке", + "chinese_traditional": "Lambayeque", + "unknown1": "Lambayeque", + "unknown2": "Lambayeque", + "unknown3": "Lambayeque", + "unknown4": "Lambayeque" + }, + "coordinates": { + "latitude": -6.773072236000004, + "longitude": -79.837374114 + } + }, + { + "id": 705757184, + "name": "Loreto", + "translations": { + "japanese": "ロレト", + "english": "Loreto", + "french": "Loreto", + "german": "Loreto", + "italian": "Loreto", + "spanish": "Loreto", + "chinese_simple": "洛雷托省", + "korean": "로레토", + "dutch": "Loreto", + "portuguese": "Loreto", + "russian": "Лорето", + "chinese_traditional": "Loreto", + "unknown1": "Loreto", + "unknown2": "Loreto", + "unknown3": "Loreto", + "unknown4": "Loreto" + }, + "coordinates": { + "latitude": -3.7463388719999955, + "longitude": -73.245559314 + } + }, + { + "id": 705822720, + "name": "Madre de Dios", + "translations": { + "japanese": "マドレ・デ・ディオス", + "english": "Madre de Dios", + "french": "Madre de Dios", + "german": "Madre de Dios", + "italian": "Madre de Dios", + "spanish": "Madre de Dios", + "chinese_simple": "马德雷德迪奥斯省", + "korean": "마드레데디오스", + "dutch": "Madre de Dios", + "portuguese": "Madre de Dios", + "russian": "Мадре-де-Диос", + "chinese_traditional": "Madre de Dios", + "unknown1": "Madre de Dios", + "unknown2": "Madre de Dios", + "unknown3": "Madre de Dios", + "unknown4": "Madre de Dios" + }, + "coordinates": { + "latitude": -12.595826075999994, + "longitude": -69.180606854 + } + }, + { + "id": 705888256, + "name": "Moquegua", + "translations": { + "japanese": "モケグア", + "english": "Moquegua", + "french": "Moquegua", + "german": "Moquegua", + "italian": "Moquegua", + "spanish": "Moquegua", + "chinese_simple": "莫克瓜省", + "korean": "모케과", + "dutch": "Moquegua", + "portuguese": "Moquegua", + "russian": "Мокегуа", + "chinese_traditional": "Moquegua", + "unknown1": "Moquegua", + "unknown2": "Moquegua", + "unknown3": "Moquegua", + "unknown4": "Moquegua" + }, + "coordinates": { + "latitude": -17.193604343999993, + "longitude": -70.932930955 + } + }, + { + "id": 705953792, + "name": "Pasco", + "translations": { + "japanese": "パスコ", + "english": "Pasco", + "french": "Pasco", + "german": "Pasco", + "italian": "Pasco", + "spanish": "Pasco", + "chinese_simple": "帕斯科省", + "korean": "파스코", + "dutch": "Pasco", + "portuguese": "Pasco", + "russian": "Паско", + "chinese_traditional": "Pasco", + "unknown1": "Pasco", + "unknown2": "Pasco", + "unknown3": "Pasco", + "unknown4": "Pasco" + }, + "coordinates": { + "latitude": -10.678711840000005, + "longitude": -76.26131458500001 + } + }, + { + "id": 706019328, + "name": "Piura", + "translations": { + "japanese": "ピウラ", + "english": "Piura", + "french": "Piura", + "german": "Piura", + "italian": "Piura", + "spanish": "Piura", + "chinese_simple": "皮乌拉省", + "korean": "피우라", + "dutch": "Piura", + "portuguese": "Piura", + "russian": "Пиура", + "chinese_traditional": "Piura", + "unknown1": "Piura", + "unknown2": "Piura", + "unknown3": "Piura", + "unknown4": "Piura" + }, + "coordinates": { + "latitude": -5.1965341679999995, + "longitude": -80.62839189 + } + }, + { + "id": 706084864, + "name": "Puno", + "translations": { + "japanese": "プーノ", + "english": "Puno", + "french": "Puno", + "german": "Puno", + "italian": "Puno", + "spanish": "Puno", + "chinese_simple": "普诺省", + "korean": "푸노", + "dutch": "Puno", + "portuguese": "Puno", + "russian": "Пуно", + "chinese_traditional": "Puno", + "unknown1": "Puno", + "unknown2": "Puno", + "unknown3": "Puno", + "unknown4": "Puno" + }, + "coordinates": { + "latitude": -15.831299672, + "longitude": -70.032049599 + } + }, + { + "id": 706150400, + "name": "San Martín", + "translations": { + "japanese": "サン・マルティン", + "english": "San Martín", + "french": "San Martín", + "german": "San Martín", + "italian": "San Martín", + "spanish": "San Martín", + "chinese_simple": "圣马丁省", + "korean": "산마르틴", + "dutch": "San Martín", + "portuguese": "San Martín", + "russian": "Сан-Мартин", + "chinese_traditional": "San Martín", + "unknown1": "San Martín", + "unknown2": "San Martín", + "unknown3": "San Martín", + "unknown4": "San Martín" + }, + "coordinates": { + "latitude": -6.047974588000002, + "longitude": -76.964441497 + } + }, + { + "id": 706215936, + "name": "Tacna", + "translations": { + "japanese": "タクナ", + "english": "Tacna", + "french": "Tacna", + "german": "Tacna", + "italian": "Tacna", + "spanish": "Tacna", + "chinese_simple": "塔克纳省", + "korean": "타크나", + "dutch": "Tacna", + "portuguese": "Tacna", + "russian": "Такна", + "chinese_traditional": "Tacna", + "unknown1": "Tacna", + "unknown2": "Tacna", + "unknown3": "Tacna", + "unknown4": "Tacna" + }, + "coordinates": { + "latitude": -18.001099452000005, + "longitude": -70.24628358 + } + }, + { + "id": 706281472, + "name": "Tumbes", + "translations": { + "japanese": "トゥンベス", + "english": "Tumbes", + "french": "Tumbes", + "german": "Tumbes", + "italian": "Tumbes", + "spanish": "Tumbes", + "chinese_simple": "通贝斯省", + "korean": "툼베스", + "dutch": "Tumbes", + "portuguese": "Tumbes", + "russian": "Тумбес", + "chinese_traditional": "Tumbes", + "unknown1": "Tumbes", + "unknown2": "Tumbes", + "unknown3": "Tumbes", + "unknown4": "Tumbes" + }, + "coordinates": { + "latitude": -3.565064460000002, + "longitude": -80.436130625 + } + }, + { + "id": 706347008, + "name": "Ucayali", + "translations": { + "japanese": "ウカヤリ", + "english": "Ucayali", + "french": "Ucayali", + "german": "Ucayali", + "italian": "Ucayali", + "spanish": "Ucayali", + "chinese_simple": "乌卡亚利省", + "korean": "우카얄리", + "dutch": "Ucayali", + "portuguese": "Ucayali", + "russian": "Укаяли", + "chinese_traditional": "Ucayali", + "unknown1": "Ucayali", + "unknown2": "Ucayali", + "unknown3": "Ucayali", + "unknown4": "Ucayali" + }, + "coordinates": { + "latitude": -8.377076123999998, + "longitude": -74.536456379 + } + } + ] + }, + { + "id": 43, + "iso_code": "KN", + "name": "St. Kitts and Nevis", + "translations": { + "japanese": "セントキッツ・ネイビス", + "english": "St. Kitts and Nevis", + "french": "Saint-Christophe-et-Niévès", + "german": "St. Kitts und Nevis", + "italian": "Saint Kitts e Nevis", + "spanish": "San Cristóbal y Nieves", + "chinese_simple": "圣基茨和尼维斯", + "korean": "세인트키츠네비스", + "dutch": "Saint Kitts en Nevis", + "portuguese": "São Cristóvão e Nevis", + "russian": "Сент-Китс и Невис", + "chinese_traditional": "St. Kitts and Nevis", + "unknown1": "St. Kitts and Nevis", + "unknown2": "St. Kitts and Nevis", + "unknown3": "St. Kitts and Nevis", + "unknown4": "St. Kitts and Nevis" + }, + "regions": [ + { + "id": 721420288, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": 17.297973436, + "longitude": -62.715135171 + } + }, + { + "id": 721551360, + "name": "Saint George Basseterre", + "translations": { + "japanese": "セント・ジョージ・バセテール", + "english": "Saint George Basseterre", + "french": "Saint George Basseterre", + "german": "Saint George Basseterre", + "italian": "Saint George Basseterre", + "spanish": "Saint George Basseterre", + "chinese_simple": "圣乔治巴斯特尔区", + "korean": "세인트조지바스테르", + "dutch": "Saint George Basseterre", + "portuguese": "Saint George Basseterre", + "russian": "Сент-Джордж-Бастер", + "chinese_traditional": "Saint George Basseterre", + "unknown1": "Saint George Basseterre", + "unknown2": "Saint George Basseterre", + "unknown3": "Saint George Basseterre", + "unknown4": "Saint George Basseterre" + }, + "coordinates": { + "latitude": 17.297973436, + "longitude": -62.715135171 + } + }, + { + "id": 721616896, + "name": "Christ Church Nichola Town", + "translations": { + "japanese": "クライスト・チャーチ・ニコラタウン", + "english": "Christ Church Nichola Town", + "french": "Christ Church Nichola Town", + "german": "Christ Church Nichola Town", + "italian": "Christ Church Nichola Town", + "spanish": "Christ Church Nichola Town", + "chinese_simple": "克赖斯特彻奇尼古拉镇区", + "korean": "크라이스트처치니콜라타운", + "dutch": "Christ Church Nichola Town", + "portuguese": "Christ Church Nichola Town", + "russian": "Крайст-Чёрч-Никола-Таун", + "chinese_traditional": "Christ Church Nichola Town", + "unknown1": "Christ Church Nichola Town", + "unknown2": "Christ Church Nichola Town", + "unknown3": "Christ Church Nichola Town", + "unknown4": "Christ Church Nichola Town" + }, + "coordinates": { + "latitude": 17.363891404, + "longitude": -62.748094245000004 + } + }, + { + "id": 721682432, + "name": "Saint Anne Sandy Point", + "translations": { + "japanese": "セント・アン・サンディ・ポイント", + "english": "Saint Anne Sandy Point", + "french": "Saint Anne Sandy Point", + "german": "Saint Anne Sandy Point", + "italian": "Saint Anne Sandy Point", + "spanish": "Saint Anne Sandy Point", + "chinese_simple": "圣安妮桑迪波因特区", + "korean": "세인트앤샌디포인트", + "dutch": "Saint Anne Sandy Point", + "portuguese": "Saint Anne Sandy Point", + "russian": "Сент-Энн-Сэнди-Пойнт", + "chinese_traditional": "Saint Anne Sandy Point", + "unknown1": "Saint Anne Sandy Point", + "unknown2": "Saint Anne Sandy Point", + "unknown3": "Saint Anne Sandy Point", + "unknown4": "Saint Anne Sandy Point" + }, + "coordinates": { + "latitude": 17.347411912000002, + "longitude": -62.83049193000001 + } + }, + { + "id": 721747968, + "name": "Saint George Gingerland", + "translations": { + "japanese": "セント・ジョージ・ジンジャーランド", + "english": "Saint George Gingerland", + "french": "Saint George Gingerland", + "german": "Saint George Gingerland", + "italian": "Saint George Gingerland", + "spanish": "Saint George Gingerland", + "chinese_simple": "圣乔治金杰兰区", + "korean": "세인트조지진저랜드", + "dutch": "Saint George Gingerland", + "portuguese": "Saint George Gingerland", + "russian": "Сент-Джордж-Джинджерланд", + "chinese_traditional": "Saint George Gingerland", + "unknown1": "Saint George Gingerland", + "unknown2": "Saint George Gingerland", + "unknown3": "Saint George Gingerland", + "unknown4": "Saint George Gingerland" + }, + "coordinates": { + "latitude": 17.133178516, + "longitude": -62.544846621999994 + } + }, + { + "id": 721813504, + "name": "Saint James Windward", + "translations": { + "japanese": "セント・ジェームズ・ウィンドワード", + "english": "Saint James Windward", + "french": "Saint James Windward", + "german": "Saint James Windward", + "italian": "Saint James Windward", + "spanish": "Saint James Windward", + "chinese_simple": "圣詹姆斯温德沃德区", + "korean": "세인트제임스윈드워드", + "dutch": "Saint James Windward", + "portuguese": "Saint James Windward", + "russian": "Сент-Джеймс-Уиндуорд", + "chinese_traditional": "Saint James Windward", + "unknown1": "Saint James Windward", + "unknown2": "Saint James Windward", + "unknown3": "Saint James Windward", + "unknown4": "Saint James Windward" + }, + "coordinates": { + "latitude": 17.199096484000002, + "longitude": -62.577805696 + } + }, + { + "id": 721879040, + "name": "Saint John Capesterre", + "translations": { + "japanese": "セント・ジョン・カピステール", + "english": "Saint John Capesterre", + "french": "Saint John Capesterre", + "german": "Saint John Capisterre", + "italian": "Saint John Capisterre", + "spanish": "Saint John Capesterre", + "chinese_simple": "圣约翰卡皮斯特尔区", + "korean": "세인트존카페스테르", + "dutch": "Saint John Capesterre", + "portuguese": "Saint John Capesterre", + "russian": "Сент-Джон-Капистер", + "chinese_traditional": "Saint John Capesterre", + "unknown1": "Saint John Capesterre", + "unknown2": "Saint John Capesterre", + "unknown3": "Saint John Capesterre", + "unknown4": "Saint John Capesterre" + }, + "coordinates": { + "latitude": 17.396850388, + "longitude": -62.781053318999994 + } + }, + { + "id": 721944576, + "name": "Saint John Figtree", + "translations": { + "japanese": "セント・ジョン・フィッグトリー", + "english": "Saint John Figtree", + "french": "Saint John Figtree", + "german": "Saint John Figtree", + "italian": "Saint John Figtree", + "spanish": "Saint John Figtree", + "chinese_simple": "圣约翰菲格特里区", + "korean": "세인트존피그트리", + "dutch": "Saint John Figtree", + "portuguese": "Saint John Figtree", + "russian": "Сент-Джон-Фигтри", + "chinese_traditional": "Saint John Figtree", + "unknown1": "Saint John Figtree", + "unknown2": "Saint John Figtree", + "unknown3": "Saint John Figtree", + "unknown4": "Saint John Figtree" + }, + "coordinates": { + "latitude": 17.111205860000002, + "longitude": -62.59428523300001 + } + }, + { + "id": 722010112, + "name": "Saint Mary Cayon", + "translations": { + "japanese": "セント・メリー・ケーヨン", + "english": "Saint Mary Cayon", + "french": "Saint Mary Cayon", + "german": "Saint Mary Cayon", + "italian": "Saint Mary Cayon", + "spanish": "Saint Mary Cayon", + "chinese_simple": "圣玛丽卡永区", + "korean": "세인트메리케이언", + "dutch": "Saint Mary Cayon", + "portuguese": "Saint Mary Cayon", + "russian": "Сент-Мери-Кайон", + "chinese_traditional": "Saint Mary Cayon", + "unknown1": "Saint Mary Cayon", + "unknown2": "Saint Mary Cayon", + "unknown3": "Saint Mary Cayon", + "unknown4": "Saint Mary Cayon" + }, + "coordinates": { + "latitude": 17.347411912000002, + "longitude": -62.731614707999995 + } + }, + { + "id": 722075648, + "name": "Saint Paul Capesterre", + "translations": { + "japanese": "セント・ポール・カピステール", + "english": "Saint Paul Capesterre", + "french": "Saint Paul Capesterre", + "german": "Saint Paul Capisterre", + "italian": "Saint Paul Capisterre", + "spanish": "Saint Paul Capesterre", + "chinese_simple": "圣保罗卡皮斯特尔区", + "korean": "세인트폴카페스테르", + "dutch": "Saint Paul Capesterre", + "portuguese": "Saint Paul Capesterre", + "russian": "Сент-Пол-Капистер", + "chinese_traditional": "Saint Paul Capesterre", + "unknown1": "Saint Paul Capesterre", + "unknown2": "Saint Paul Capesterre", + "unknown3": "Saint Paul Capesterre", + "unknown4": "Saint Paul Capesterre" + }, + "coordinates": { + "latitude": 17.396850388, + "longitude": -62.814012393 + } + }, + { + "id": 722141184, + "name": "Saint Paul Charlestown", + "translations": { + "japanese": "セント・ポール・チャールズタウン", + "english": "Saint Paul Charlestown", + "french": "Saint Paul Charlestown", + "german": "Saint Paul Charlestown", + "italian": "Saint Paul Charlestown", + "spanish": "Saint Paul Charlestown", + "chinese_simple": "圣保罗查尔斯敦区", + "korean": "세인트폴찰스타운", + "dutch": "Saint Paul Charlestown", + "portuguese": "Saint Paul Charlestown", + "russian": "Сент-Пол-Чарлстаун", + "chinese_traditional": "Saint Paul Charlestown", + "unknown1": "Saint Paul Charlestown", + "unknown2": "Saint Paul Charlestown", + "unknown3": "Saint Paul Charlestown", + "unknown4": "Saint Paul Charlestown" + }, + "coordinates": { + "latitude": 17.133178516, + "longitude": -62.616257949 + } + }, + { + "id": 722206720, + "name": "Saint Peter Basseterre", + "translations": { + "japanese": "セント・ピーター・バセテール", + "english": "Saint Peter Basseterre", + "french": "Saint Peter Basseterre", + "german": "Saint Peter Basseterre", + "italian": "Saint Peter Basseterre", + "spanish": "Saint Peter Basseterre", + "chinese_simple": "圣彼得巴斯特尔区", + "korean": "세인트피터바스테르", + "dutch": "Saint Peter Basseterre", + "portuguese": "Saint Peter Basseterre", + "russian": "Сент-Питер-Бастер", + "chinese_traditional": "Saint Peter Basseterre", + "unknown1": "Saint Peter Basseterre", + "unknown2": "Saint Peter Basseterre", + "unknown3": "Saint Peter Basseterre", + "unknown4": "Saint Peter Basseterre" + }, + "coordinates": { + "latitude": 17.314452928, + "longitude": -62.715135171 + } + }, + { + "id": 722272256, + "name": "Saint Thomas Lowland", + "translations": { + "japanese": "セント・トーマス・ロウランド", + "english": "Saint Thomas Lowland", + "french": "Saint Thomas Lowland", + "german": "Saint Thomas Lowland", + "italian": "Saint Thomas Lowland", + "spanish": "Saint Thomas Lowland", + "chinese_simple": "圣托马斯洛兰区", + "korean": "세인트토머스롤랜드", + "dutch": "Saint Thomas Lowland", + "portuguese": "Saint Thomas Lowland", + "russian": "Сент-Томас-Лоуленд", + "chinese_traditional": "Saint Thomas Lowland", + "unknown1": "Saint Thomas Lowland", + "unknown2": "Saint Thomas Lowland", + "unknown3": "Saint Thomas Lowland", + "unknown4": "Saint Thomas Lowland" + }, + "coordinates": { + "latitude": 17.1661375, + "longitude": -62.616257949 + } + }, + { + "id": 722337792, + "name": "Saint Thomas Middle Island", + "translations": { + "japanese": "セント・トーマス・ミドルアイランド", + "english": "Saint Thomas Middle Island", + "french": "Saint Thomas Middle Island", + "german": "Saint Thomas Middle Island", + "italian": "Saint Thomas Middle Island", + "spanish": "Saint Thomas Middle Island", + "chinese_simple": "圣托马斯米德尔艾兰区", + "korean": "세인트토머스미들아일랜드", + "dutch": "Saint Thomas Middle Island", + "portuguese": "Saint Thomas Middle Island", + "russian": "Сент-Томас-Мидл-Айленд", + "chinese_traditional": "Saint Thomas Middle Island", + "unknown1": "Saint Thomas Middle Island", + "unknown2": "Saint Thomas Middle Island", + "unknown3": "Saint Thomas Middle Island", + "unknown4": "Saint Thomas Middle Island" + }, + "coordinates": { + "latitude": 17.314452928, + "longitude": -62.814012393 + } + }, + { + "id": 722403328, + "name": "Trinity Palmetto Point", + "translations": { + "japanese": "トリニティ・パルメット・ポイント", + "english": "Trinity Palmetto Point", + "french": "Trinity Palmetto Point", + "german": "Trinity Palmetto Point", + "italian": "Trinity Palmetto Point", + "spanish": "Trinity Palmetto Point", + "chinese_simple": "特里尼蒂帕尔梅托波因特区", + "korean": "트리니티팰머토포인트", + "dutch": "Trinity Palmetto Point", + "portuguese": "Trinity Palmetto Point", + "russian": "Тринити-Палметто-Пойнт", + "chinese_traditional": "Trinity Palmetto Point", + "unknown1": "Trinity Palmetto Point", + "unknown2": "Trinity Palmetto Point", + "unknown3": "Trinity Palmetto Point", + "unknown4": "Trinity Palmetto Point" + }, + "coordinates": { + "latitude": 17.281493944, + "longitude": -62.764573782 + } + } + ] + }, + { + "id": 44, + "iso_code": "LC", + "name": "St. Lucia", + "translations": { + "japanese": "セントルシア", + "english": "St. Lucia", + "french": "Sainte-Lucie", + "german": "St. Lucia", + "italian": "Santa Lucia", + "spanish": "Santa Lucía", + "chinese_simple": "圣卢西亚", + "korean": "세인트루시아", + "dutch": "Saint Lucia", + "portuguese": "Santa Lúcia", + "russian": "Сент-Люсия", + "chinese_traditional": "St. Lucia", + "unknown1": "St. Lucia", + "unknown2": "St. Lucia", + "unknown3": "St. Lucia", + "unknown4": "St. Lucia" + }, + "regions": [ + { + "id": 738197504, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": 13.996581872, + "longitude": -60.995770144000005 + } + }, + { + "id": 738263040, + "name": "St. Lucia", + "translations": { + "japanese": "セントルシア", + "english": "St. Lucia", + "french": "Sainte-Lucie", + "german": "St. Lucia", + "italian": "Santa Lucia", + "spanish": "Santa Lucía", + "chinese_simple": "圣卢西亚", + "korean": "세인트루시아", + "dutch": "Saint Lucia", + "portuguese": "Santa Lúcia", + "russian": "Сент-Люсия", + "chinese_traditional": "St. Lucia", + "unknown1": "St. Lucia", + "unknown2": "St. Lucia", + "unknown3": "St. Lucia", + "unknown4": "St. Lucia" + }, + "coordinates": { + "latitude": 13.996581872, + "longitude": -60.995770144000005 + } + } + ] + }, + { + "id": 45, + "iso_code": "VC", + "name": "St. Vincent and the Grenadines", + "translations": { + "japanese": "セントビンセント・グレナディーン", + "english": "St. Vincent and the Grenadines", + "french": "Saint-Vincent-et-les-Grenadines", + "german": "St. Vincent und die Grenadinen", + "italian": "Saint Vincent e Grenadine", + "spanish": "San Vicente y las Granadinas", + "chinese_simple": "圣文森特和格林纳丁斯", + "korean": "세인트빈센트 그레나딘", + "dutch": "Saint Vincent en de Grenadines", + "portuguese": "S. Vicente e as Granadinas", + "russian": "Сент-Винсент и Гренадины", + "chinese_traditional": "St. Vincent and the Grenadines", + "unknown1": "St. Vincent and the Grenadines", + "unknown2": "St. Vincent and the Grenadines", + "unknown3": "St. Vincent and the Grenadines", + "unknown4": "St. Vincent and the Grenadines" + }, + "regions": [ + { + "id": 754974720, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": 13.12866196, + "longitude": -61.215497303999996 + } + }, + { + "id": 755040256, + "name": "St. Vincent and the Grenadines", + "translations": { + "japanese": "セントビンセント・グレナディーン", + "english": "St. Vincent and the Grenadines", + "french": "Saint-Vincent-et-les-Grenadines", + "german": "St. Vincent und die Grenadinen", + "italian": "Saint Vincent e Grenadine", + "spanish": "San Vicente y las Granadinas", + "chinese_simple": "圣文森特和格林纳丁斯", + "korean": "세인트빈센트 그레나딘", + "dutch": "Saint Vincent en de Grenadines", + "portuguese": "S. Vicente e as Granadinas", + "russian": "Сент-Винсент и Гренадины", + "chinese_traditional": "St. Vincent and the Grenadines", + "unknown1": "St. Vincent and the Grenadines", + "unknown2": "St. Vincent and the Grenadines", + "unknown3": "St. Vincent and the Grenadines", + "unknown4": "St. Vincent and the Grenadines" + }, + "coordinates": { + "latitude": 13.12866196, + "longitude": -61.215497303999996 + } + } + ] + }, + { + "id": 46, + "iso_code": "SR", + "name": "Suriname", + "translations": { + "japanese": "スリナム", + "english": "Suriname", + "french": "Suriname", + "german": "Suriname", + "italian": "Suriname", + "spanish": "Surinam", + "chinese_simple": "苏里南", + "korean": "수리남", + "dutch": "Suriname", + "portuguese": "Suriname", + "russian": "Суринам", + "chinese_traditional": "Suriname", + "unknown1": "Suriname", + "unknown2": "Suriname", + "unknown3": "Suriname", + "unknown4": "Suriname" + }, + "regions": [ + { + "id": 771751936, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": 5.828247004, + "longitude": -55.162014045999996 + } + }, + { + "id": 771883008, + "name": "Paramaribo", + "translations": { + "japanese": "パラマリボ", + "english": "Paramaribo", + "french": "Paramaribo", + "german": "Paramaribo", + "italian": "Paramaribo", + "spanish": "Paramaribo", + "chinese_simple": "帕拉马里博市", + "korean": "파라마리보", + "dutch": "Paramaribo", + "portuguese": "Paramaribo", + "russian": "Парамарибо", + "chinese_traditional": "Paramaribo", + "unknown1": "Paramaribo", + "unknown2": "Paramaribo", + "unknown3": "Paramaribo", + "unknown4": "Paramaribo" + }, + "coordinates": { + "latitude": 5.828247004, + "longitude": -55.162014045999996 + } + }, + { + "id": 771948544, + "name": "Brokopondo", + "translations": { + "japanese": "ブロコポンド", + "english": "Brokopondo", + "french": "Brokopondo", + "german": "Brokopondo", + "italian": "Brokopondo", + "spanish": "Brokopondo", + "chinese_simple": "布罗科蓬多区", + "korean": "브로코폰도", + "dutch": "Brokopondo", + "portuguese": "Brokopondo", + "russian": "Брокопондо", + "chinese_traditional": "Brokopondo", + "unknown1": "Brokopondo", + "unknown2": "Brokopondo", + "unknown3": "Brokopondo", + "unknown4": "Brokopondo" + }, + "coordinates": { + "latitude": 5.064697208, + "longitude": -54.964259602 + } + }, + { + "id": 772014080, + "name": "Commewijne", + "translations": { + "japanese": "コメウィネ", + "english": "Commewijne", + "french": "Commewijne", + "german": "Commewijne", + "italian": "Commewijne", + "spanish": "Commewijne", + "chinese_simple": "科默韦讷区", + "korean": "코메베이너", + "dutch": "Commewijne", + "portuguese": "Commewijne", + "russian": "Коммевийн", + "chinese_traditional": "Commewijne", + "unknown1": "Commewijne", + "unknown2": "Commewijne", + "unknown3": "Commewijne", + "unknown4": "Commewijne" + }, + "coordinates": { + "latitude": 5.883178644, + "longitude": -55.07961636100001 + } + }, + { + "id": 772079616, + "name": "Coronie", + "translations": { + "japanese": "コロニー", + "english": "Coronie", + "french": "Coronie", + "german": "Coronie", + "italian": "Coronie", + "spanish": "Coronie", + "chinese_simple": "科罗尼区", + "korean": "코로니", + "dutch": "Coronie", + "portuguese": "Coronie", + "russian": "Корони", + "chinese_traditional": "Coronie", + "unknown1": "Coronie", + "unknown2": "Coronie", + "unknown3": "Coronie", + "unknown4": "Coronie" + }, + "coordinates": { + "latitude": 5.883178644, + "longitude": -56.315581636000005 + } + }, + { + "id": 772145152, + "name": "Marowijne", + "translations": { + "japanese": "マロウィネ", + "english": "Marowijne", + "french": "Marowijne", + "german": "Marowijne", + "italian": "Marowijne", + "spanish": "Marowijne", + "chinese_simple": "马罗韦讷区", + "korean": "마로베이너", + "dutch": "Marowijne", + "portuguese": "Marowijne", + "russian": "Маровийн", + "chinese_traditional": "Marowijne", + "unknown1": "Marowijne", + "unknown2": "Marowijne", + "unknown3": "Marowijne", + "unknown4": "Marowijne" + }, + "coordinates": { + "latitude": 5.498657164, + "longitude": -54.046898709000004 + } + }, + { + "id": 772210688, + "name": "Nickerie", + "translations": { + "japanese": "ニッケリー", + "english": "Nickerie", + "french": "Nickerie", + "german": "Nickerie", + "italian": "Nickerie", + "spanish": "Nickerie", + "chinese_simple": "尼克里区", + "korean": "니케리", + "dutch": "Nickerie", + "portuguese": "Nickerie", + "russian": "Никкери", + "chinese_traditional": "Nickerie", + "unknown1": "Nickerie", + "unknown2": "Nickerie", + "unknown3": "Nickerie", + "unknown4": "Nickerie" + }, + "coordinates": { + "latitude": 5.949096612, + "longitude": -56.980256295000004 + } + }, + { + "id": 772276224, + "name": "Para", + "translations": { + "japanese": "パラ", + "english": "Para", + "french": "Para", + "german": "Para", + "italian": "Para", + "spanish": "Para", + "chinese_simple": "帕拉区", + "korean": "파라", + "dutch": "Para", + "portuguese": "Para", + "russian": "Пара", + "chinese_traditional": "Para", + "unknown1": "Para", + "unknown2": "Para", + "unknown3": "Para", + "unknown4": "Para" + }, + "coordinates": { + "latitude": 5.581054624, + "longitude": -55.178493583000005 + } + }, + { + "id": 772341760, + "name": "Saramacca", + "translations": { + "japanese": "サラマッカ", + "english": "Saramacca", + "french": "Saramacca", + "german": "Saramacca", + "italian": "Saramacca", + "spanish": "Saramacca", + "chinese_simple": "萨拉马卡区", + "korean": "사라마카", + "dutch": "Saramacca", + "portuguese": "Saramacca", + "russian": "Сарамакка", + "chinese_traditional": "Saramacca", + "unknown1": "Saramacca", + "unknown2": "Saramacca", + "unknown3": "Saramacca", + "unknown4": "Saramacca" + }, + "coordinates": { + "latitude": 5.79528802, + "longitude": -55.464138891000005 + } + }, + { + "id": 772407296, + "name": "Sipaliwini", + "translations": { + "japanese": "シパリウィニ", + "english": "Sipaliwini", + "french": "Sipaliwini", + "german": "Sipaliwini", + "italian": "Sipaliwini", + "spanish": "Sipaliwini", + "chinese_simple": "西帕里维尼区", + "korean": "시팔리비니", + "dutch": "Sipaliwini", + "portuguese": "Sipaliwini", + "russian": "Сипалуини", + "chinese_traditional": "Sipaliwini", + "unknown1": "Sipaliwini", + "unknown2": "Sipaliwini", + "unknown3": "Sipaliwini", + "unknown4": "Sipaliwini" + }, + "coordinates": { + "latitude": 5.196533144, + "longitude": -57.161531202000006 + } + }, + { + "id": 772472832, + "name": "Wanica", + "translations": { + "japanese": "ワニカ", + "english": "Wanica", + "french": "Wanica", + "german": "Wanica", + "italian": "Wanica", + "spanish": "Wanica", + "chinese_simple": "瓦尼卡区", + "korean": "바니카", + "dutch": "Wanica", + "portuguese": "Wanica", + "russian": "Ваника", + "chinese_traditional": "Wanica", + "unknown1": "Wanica", + "unknown2": "Wanica", + "unknown3": "Wanica", + "unknown4": "Wanica" + }, + "coordinates": { + "latitude": 5.696411068, + "longitude": -55.227932194000005 + } + } + ] + }, + { + "id": 47, + "iso_code": "TT", + "name": "Trinidad and Tobago", + "translations": { + "japanese": "トリニダード・トバゴ", + "english": "Trinidad and Tobago", + "french": "Trinité-et-Tobago", + "german": "Trinidad und Tobago", + "italian": "Trinidad e Tobago", + "spanish": "Trinidad y Tobago", + "chinese_simple": "特立尼达和多巴哥", + "korean": "트리니다드토바고", + "dutch": "Trinidad en Tobago", + "portuguese": "Trinidade e Tobago", + "russian": "Тринидад и Тобаго", + "chinese_traditional": "Trinidad and Tobago", + "unknown1": "Trinidad and Tobago", + "unknown2": "Trinidad and Tobago", + "unknown3": "Trinidad and Tobago", + "unknown4": "Trinidad and Tobago" + }, + "regions": [ + { + "id": 788529152, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": 10.645751832, + "longitude": -61.512128970000006 + } + }, + { + "id": 788660224, + "name": "Port-of-Spain", + "translations": { + "japanese": "ポート・オブ・スペイン", + "english": "Port-of-Spain", + "french": "Port d'Espagne", + "german": "Port-of-Spain", + "italian": "Port of Spain", + "spanish": "Puerto España", + "chinese_simple": "西班牙港市", + "korean": "포트오브스페인", + "dutch": "Port of Spain", + "portuguese": "Porto de Espanha", + "russian": "Порт-оф-Спейн", + "chinese_traditional": "Port-of-Spain", + "unknown1": "Port-of-Spain", + "unknown2": "Port-of-Spain", + "unknown3": "Port-of-Spain", + "unknown4": "Port-of-Spain" + }, + "coordinates": { + "latitude": 10.645751832, + "longitude": -61.512128970000006 + } + }, + { + "id": 788725760, + "name": "Arima", + "translations": { + "japanese": "アリマ", + "english": "Arima", + "french": "Arima", + "german": "Arima", + "italian": "Arima", + "spanish": "Arima", + "chinese_simple": "阿里马市", + "korean": "아리마", + "dutch": "Arima", + "portuguese": "Arima", + "russian": "Арима", + "chinese_traditional": "Arima", + "unknown1": "Arima", + "unknown2": "Arima", + "unknown3": "Arima", + "unknown4": "Arima" + }, + "coordinates": { + "latitude": 10.62927234, + "longitude": -61.281415452000005 + } + }, + { + "id": 788791296, + "name": "Caroni", + "translations": { + "japanese": "カロニ州", + "english": "Caroni", + "french": "Caroni", + "german": "Caroni", + "italian": "Caroni", + "spanish": "Caroni", + "chinese_simple": "卡罗尼郡", + "korean": "카로니 주", + "dutch": "Caroni", + "portuguese": "Caroni", + "russian": "Карони", + "chinese_traditional": "Caroni", + "unknown1": "Caroni", + "unknown2": "Caroni", + "unknown3": "Caroni", + "unknown4": "Caroni" + }, + "coordinates": { + "latitude": 10.513915896, + "longitude": -61.41325174800001 + } + }, + { + "id": 788856832, + "name": "Mayaro", + "translations": { + "japanese": "マジャロ州", + "english": "Mayaro", + "french": "Mayaro", + "german": "Mayaro", + "italian": "Mayaro", + "spanish": "Mayaro", + "chinese_simple": "马亚罗郡", + "korean": "마야로 주", + "dutch": "Mayaro", + "portuguese": "Mayaro", + "russian": "Майяро", + "chinese_traditional": "Mayaro", + "unknown1": "Mayaro", + "unknown2": "Mayaro", + "unknown3": "Mayaro", + "unknown4": "Mayaro" + }, + "coordinates": { + "latitude": 10.211791876, + "longitude": -60.995770144000005 + } + }, + { + "id": 788922368, + "name": "Nariva", + "translations": { + "japanese": "ナリバ州", + "english": "Nariva", + "french": "Nariva", + "german": "Nariva", + "italian": "Nariva", + "spanish": "Nariva", + "chinese_simple": "纳里瓦郡", + "korean": "나리바 주", + "dutch": "Nariva", + "portuguese": "Nariva", + "russian": "Нарива", + "chinese_traditional": "Nariva", + "unknown1": "Nariva", + "unknown2": "Nariva", + "unknown3": "Nariva", + "unknown4": "Nariva" + }, + "coordinates": { + "latitude": 10.2996825, + "longitude": -61.182538230000006 + } + }, + { + "id": 788987904, + "name": "Saint Andrew", + "translations": { + "japanese": "セント・アンドリュー州", + "english": "Saint Andrew", + "french": "Saint Andrew", + "german": "Saint Andrew", + "italian": "Saint Andrew", + "spanish": "Saint Andrew", + "chinese_simple": "圣安德鲁郡", + "korean": "세인트앤드루 주", + "dutch": "Saint Andrew", + "portuguese": "Saint Andrew", + "russian": "Сейнт Эндрю", + "chinese_traditional": "Saint Andrew", + "unknown1": "Saint Andrew", + "unknown2": "Saint Andrew", + "unknown3": "Saint Andrew", + "unknown4": "Saint Andrew" + }, + "coordinates": { + "latitude": 10.579833864, + "longitude": -61.111126903 + } + }, + { + "id": 789053440, + "name": "Saint David", + "translations": { + "japanese": "セント・デビッド州", + "english": "Saint David", + "french": "Saint David", + "german": "Saint David", + "italian": "Saint David", + "spanish": "Saint David", + "chinese_simple": "圣戴维郡", + "korean": "세인트데이비드 주", + "dutch": "Saint David", + "portuguese": "Saint David", + "russian": "Сейнт Дэвид", + "chinese_traditional": "Saint David", + "unknown1": "Saint David", + "unknown2": "Saint David", + "unknown3": "Saint David", + "unknown4": "Saint David" + }, + "coordinates": { + "latitude": 10.799560424000001, + "longitude": -61.012249681 + } + }, + { + "id": 789118976, + "name": "Saint George", + "translations": { + "japanese": "セント・ジョージ州", + "english": "Saint George", + "french": "Saint George", + "german": "Saint George", + "italian": "Saint George", + "spanish": "Saint George", + "chinese_simple": "圣乔治郡", + "korean": "세인트조지 주", + "dutch": "Saint George", + "portuguese": "Saint George", + "russian": "Сейнт Джордж", + "chinese_traditional": "Saint George", + "unknown1": "Saint George", + "unknown2": "Saint George", + "unknown3": "Saint George", + "unknown4": "Saint George" + }, + "coordinates": { + "latitude": 10.62927234, + "longitude": -61.380292674 + } + }, + { + "id": 789184512, + "name": "Saint Patrick", + "translations": { + "japanese": "セント・パトリック州", + "english": "Saint Patrick", + "french": "Saint Patrick", + "german": "Saint Patrick", + "italian": "Saint Patrick", + "spanish": "Saint Patrick", + "chinese_simple": "圣帕特里克郡", + "korean": "세인트패트릭 주", + "dutch": "Saint Patrick", + "portuguese": "Saint Patrick", + "russian": "Сейнт Патрик", + "chinese_traditional": "Saint Patrick", + "unknown1": "Saint Patrick", + "unknown2": "Saint Patrick", + "unknown3": "Saint Patrick", + "unknown4": "Saint Patrick" + }, + "coordinates": { + "latitude": 10.178832892, + "longitude": -61.682417519 + } + }, + { + "id": 789250048, + "name": "San Fernando", + "translations": { + "japanese": "サン・フェルナンド", + "english": "San Fernando", + "french": "San Fernando", + "german": "San Fernando", + "italian": "San Fernando", + "spanish": "San Fernando", + "chinese_simple": "圣费尔南多市", + "korean": "산페르난도", + "dutch": "San Fernando", + "portuguese": "San Fernando", + "russian": "Сан-Фернандо", + "chinese_traditional": "San Fernando", + "unknown1": "San Fernando", + "unknown2": "San Fernando", + "unknown3": "San Fernando", + "unknown4": "San Fernando" + }, + "coordinates": { + "latitude": 10.283203008, + "longitude": -61.46269035900001 + } + }, + { + "id": 789315584, + "name": "Tobago", + "translations": { + "japanese": "トバゴ島", + "english": "Tobago", + "french": "Tobago", + "german": "Tobago", + "italian": "Tobago", + "spanish": "Tobago", + "chinese_simple": "多巴哥岛", + "korean": "토바고 섬", + "dutch": "Tobago", + "portuguese": "Tobago", + "russian": "Тобаго", + "chinese_traditional": "Tobago", + "unknown1": "Tobago", + "unknown2": "Tobago", + "unknown3": "Tobago", + "unknown4": "Tobago" + }, + "coordinates": { + "latitude": 11.17858874, + "longitude": -60.732097552 + } + }, + { + "id": 789381120, + "name": "Victoria", + "translations": { + "japanese": "ビクトリア州", + "english": "Victoria", + "french": "Victoria", + "german": "Victoria", + "italian": "Victoria", + "spanish": "Victoria", + "chinese_simple": "维多利亚郡", + "korean": "빅토리아 주", + "dutch": "Victoria", + "portuguese": "Vitória", + "russian": "Виктория", + "chinese_traditional": "Victoria", + "unknown1": "Victoria", + "unknown2": "Victoria", + "unknown3": "Victoria", + "unknown4": "Victoria" + }, + "coordinates": { + "latitude": 10.261230352, + "longitude": -61.380292674 + } + }, + { + "id": 789446656, + "name": "Point Fortin", + "translations": { + "japanese": "ポイントフォーティン", + "english": "Point Fortin", + "french": "Point Fortin", + "german": "Point Fortin", + "italian": "Point Fortin", + "spanish": "Point Fortin", + "chinese_simple": "福廷岬市", + "korean": "포인트포르틴", + "dutch": "Point Fortin", + "portuguese": "Ponto Fortin", + "russian": "Пойнт Фортин", + "chinese_traditional": "Point Fortin", + "unknown1": "Point Fortin", + "unknown2": "Point Fortin", + "unknown3": "Point Fortin", + "unknown4": "Point Fortin" + }, + "coordinates": { + "latitude": 10.167846564, + "longitude": -61.67692434 + } + } + ] + }, + { + "id": 48, + "iso_code": "TC", + "name": "Turks and Caicos Islands", + "translations": { + "japanese": "タークス・カイコス諸島", + "english": "Turks and Caicos Islands", + "french": "Îles Turques-et-Caïques", + "german": "Turks- und Caicosinseln", + "italian": "Isole Turks e Caicos", + "spanish": "Islas Turcas y Caicos", + "chinese_simple": "特克斯和凯科斯群岛", + "korean": "터크스 케이커스 제도", + "dutch": "Turks- en Caicoseilanden", + "portuguese": "Ilhas Turcas e Caicos", + "russian": "Тёркс и Кайкос", + "chinese_traditional": "Turks and Caicos Islands", + "unknown1": "Turks and Caicos Islands", + "unknown2": "Turks and Caicos Islands", + "unknown3": "Turks and Caicos Islands", + "unknown4": "Turks and Caicos Islands" + }, + "regions": [ + { + "id": 805306368, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": 21.461791748, + "longitude": -71.130685399 + } + }, + { + "id": 805371904, + "name": "Turks and Caicos Islands", + "translations": { + "japanese": "タークス・カイコス諸島", + "english": "Turks and Caicos Islands", + "french": "Îles Turques-et-Caïques", + "german": "Turks- und Caicosinseln", + "italian": "Isole Turks e Caicos", + "spanish": "Islas Turcas y Caicos", + "chinese_simple": "特克斯和凯科斯群岛", + "korean": "터크스 케이커스 제도", + "dutch": "Turks- en Caicoseilanden", + "portuguese": "Ilhas Turcas e Caicos", + "russian": "Тёркс и Кайкос", + "chinese_traditional": "Turks and Caicos Islands", + "unknown1": "Turks and Caicos Islands", + "unknown2": "Turks and Caicos Islands", + "unknown3": "Turks and Caicos Islands", + "unknown4": "Turks and Caicos Islands" + }, + "coordinates": { + "latitude": 21.461791748, + "longitude": -71.130685399 + } + } + ] + }, + { + "id": 49, + "iso_code": "US", + "name": "United States", + "translations": { + "japanese": "アメリカ", + "english": "United States", + "french": "États-Unis d’Amérique", + "german": "Vereinigte Staaten", + "italian": "Stati Uniti d'America", + "spanish": "Estados Unidos de América", + "chinese_simple": "美国", + "korean": "미국", + "dutch": "Verenigde Staten", + "portuguese": "Estados Unidos", + "russian": "Соединённые Штаты Америки", + "chinese_traditional": "United States", + "unknown1": "United States", + "unknown2": "United States", + "unknown3": "United States", + "unknown4": "United States" + }, + "regions": [ + { + "id": 822083584, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": 38.89160112, + "longitude": -77.035852824 + } + }, + { + "id": 822214656, + "name": "District of Columbia", + "translations": { + "japanese": "コロンビア特別区", + "english": "District of Columbia", + "french": "Washington (District de Columbia)", + "german": "District of Columbia", + "italian": "Distretto di Columbia", + "spanish": "Distrito de Columbia", + "chinese_simple": "哥伦比亚特区", + "korean": "컬럼비아 특별구", + "dutch": "District of Columbia", + "portuguese": "Distrito de Colúmbia", + "russian": "Округ Колумбия", + "chinese_traditional": "District of Columbia", + "unknown1": "District of Columbia", + "unknown2": "District of Columbia", + "unknown3": "District of Columbia", + "unknown4": "District of Columbia" + }, + "coordinates": { + "latitude": 38.89160112, + "longitude": -77.035852824 + } + }, + { + "id": 822280192, + "name": "Alaska", + "translations": { + "japanese": "アラスカ州", + "english": "Alaska", + "french": "Alaska", + "german": "Alaska", + "italian": "Alaska", + "spanish": "Alaska", + "chinese_simple": "阿拉斯加州", + "korean": "알래스카 주", + "dutch": "Alaska", + "portuguese": "Alasca", + "russian": "Аляска", + "chinese_traditional": "Alaska", + "unknown1": "Alaska", + "unknown2": "Alaska", + "unknown3": "Alaska", + "unknown4": "Alaska" + }, + "coordinates": { + "latitude": 58.298949532, + "longitude": -134.417600658 + } + }, + { + "id": 822345728, + "name": "Alabama", + "translations": { + "japanese": "アラバマ州", + "english": "Alabama", + "french": "Alabama", + "german": "Alabama", + "italian": "Alabama", + "spanish": "Alabama", + "chinese_simple": "亚拉巴马州", + "korean": "앨라배마 주", + "dutch": "Alabama", + "portuguese": "Alabama", + "russian": "Алабама", + "chinese_traditional": "Alabama", + "unknown1": "Alabama", + "unknown2": "Alabama", + "unknown3": "Alabama", + "unknown4": "Alabama" + }, + "coordinates": { + "latitude": 32.365722288, + "longitude": -86.297352618 + } + }, + { + "id": 822411264, + "name": "Arkansas", + "translations": { + "japanese": "アーカンソー州", + "english": "Arkansas", + "french": "Arkansas", + "german": "Arkansas", + "italian": "Arkansas", + "spanish": "Arkansas", + "chinese_simple": "阿肯色州", + "korean": "아칸소 주", + "dutch": "Arkansas", + "portuguese": "Arkansas", + "russian": "Арканзас", + "chinese_traditional": "Arkansas", + "unknown1": "Arkansas", + "unknown2": "Arkansas", + "unknown3": "Arkansas", + "unknown4": "Arkansas" + }, + "coordinates": { + "latitude": 34.7442623, + "longitude": -92.284917728 + } + }, + { + "id": 822476800, + "name": "Arizona", + "translations": { + "japanese": "アリゾナ州", + "english": "Arizona", + "french": "Arizona", + "german": "Arizona", + "italian": "Arizona", + "spanish": "Arizona", + "chinese_simple": "亚利桑那州", + "korean": "애리조나 주", + "dutch": "Arizona", + "portuguese": "Arizona", + "russian": "Аризона", + "chinese_traditional": "Arizona", + "unknown1": "Arizona", + "unknown2": "Arizona", + "unknown3": "Arizona", + "unknown4": "Arizona" + }, + "coordinates": { + "latitude": 33.447875596, + "longitude": -112.071348486 + } + }, + { + "id": 822542336, + "name": "California", + "translations": { + "japanese": "カリフォルニア州", + "english": "California", + "french": "Californie", + "german": "Kalifornien", + "italian": "California", + "spanish": "California", + "chinese_simple": "加利福尼亚州", + "korean": "캘리포니아 주", + "dutch": "Californië", + "portuguese": "Califórnia", + "russian": "Калифорния", + "chinese_traditional": "California", + "unknown1": "California", + "unknown2": "California", + "unknown3": "California", + "unknown4": "California" + }, + "coordinates": { + "latitude": 38.578490772, + "longitude": -121.492150471 + } + }, + { + "id": 822607872, + "name": "Colorado", + "translations": { + "japanese": "コロラド州", + "english": "Colorado", + "french": "Colorado", + "german": "Colorado", + "italian": "Colorado", + "spanish": "Colorado", + "chinese_simple": "科罗拉多州", + "korean": "콜로라도 주", + "dutch": "Colorado", + "portuguese": "Colorado", + "russian": "Колорадо", + "chinese_traditional": "Colorado", + "unknown1": "Colorado", + "unknown2": "Colorado", + "unknown3": "Colorado", + "unknown4": "Colorado" + }, + "coordinates": { + "latitude": 39.737548376, + "longitude": -104.979654397 + } + }, + { + "id": 822673408, + "name": "Connecticut", + "translations": { + "japanese": "コネティカット州", + "english": "Connecticut", + "french": "Connecticut", + "german": "Connecticut", + "italian": "Connecticut", + "spanish": "Connecticut", + "chinese_simple": "康涅狄格州", + "korean": "코네티컷 주", + "dutch": "Connecticut", + "portuguese": "Connecticut", + "russian": "Коннектикут", + "chinese_traditional": "Connecticut", + "unknown1": "Connecticut", + "unknown2": "Connecticut", + "unknown3": "Connecticut", + "unknown4": "Connecticut" + }, + "coordinates": { + "latitude": 41.759032728, + "longitude": -72.685255056 + } + }, + { + "id": 822738944, + "name": "Delaware", + "translations": { + "japanese": "デラウェア州", + "english": "Delaware", + "french": "Delaware", + "german": "Delaware", + "italian": "Delaware", + "spanish": "Delaware", + "chinese_simple": "特拉华州", + "korean": "델라웨어 주", + "dutch": "Delaware", + "portuguese": "Delaware", + "russian": "Делавэр", + "chinese_traditional": "Delaware", + "unknown1": "Delaware", + "unknown2": "Delaware", + "unknown3": "Delaware", + "unknown4": "Delaware" + }, + "coordinates": { + "latitude": 39.155272992, + "longitude": -75.51973542 + } + }, + { + "id": 822804480, + "name": "Florida", + "translations": { + "japanese": "フロリダ州", + "english": "Florida", + "french": "Floride", + "german": "Florida", + "italian": "Florida", + "spanish": "Florida", + "chinese_simple": "佛罗里达州", + "korean": "플로리다 주", + "dutch": "Florida", + "portuguese": "Flórida", + "russian": "Флорида", + "chinese_traditional": "Florida", + "unknown1": "Florida", + "unknown2": "Florida", + "unknown3": "Florida", + "unknown4": "Florida" + }, + "coordinates": { + "latitude": 30.437621724, + "longitude": -84.275862746 + } + }, + { + "id": 822870016, + "name": "Georgia", + "translations": { + "japanese": "ジョージア州", + "english": "Georgia", + "french": "Géorgie", + "german": "Georgia", + "italian": "Georgia", + "spanish": "Georgia", + "chinese_simple": "佐治亚州", + "korean": "조지아 주", + "dutch": "Georgia", + "portuguese": "Geórgia", + "russian": "Джорджия", + "chinese_traditional": "Georgia", + "unknown1": "Georgia", + "unknown2": "Georgia", + "unknown3": "Georgia", + "unknown4": "Georgia" + }, + "coordinates": { + "latitude": 33.744506452, + "longitude": -84.385726326 + } + }, + { + "id": 822935552, + "name": "Hawaii", + "translations": { + "japanese": "ハワイ州", + "english": "Hawaii", + "french": "Hawaï", + "german": "Hawaii", + "italian": "Hawaii", + "spanish": "Hawái", + "chinese_simple": "夏威夷州", + "korean": "하와이 주", + "dutch": "Hawaï", + "portuguese": "Havaí", + "russian": "Гавайи", + "chinese_traditional": "Hawaii", + "unknown1": "Hawaii", + "unknown2": "Hawaii", + "unknown3": "Hawaii", + "unknown4": "Hawaii" + }, + "coordinates": { + "latitude": 21.302489992, + "longitude": -157.856995451 + } + }, + { + "id": 823001088, + "name": "Iowa", + "translations": { + "japanese": "アイオワ州", + "english": "Iowa", + "french": "Iowa", + "german": "Iowa", + "italian": "Iowa", + "spanish": "Iowa", + "chinese_simple": "艾奥瓦州", + "korean": "아이오와 주", + "dutch": "Iowa", + "portuguese": "Iowa", + "russian": "Айова", + "chinese_traditional": "Iowa", + "unknown1": "Iowa", + "unknown2": "Iowa", + "unknown3": "Iowa", + "unknown4": "Iowa" + }, + "coordinates": { + "latitude": 41.599730972, + "longitude": -93.603280688 + } + }, + { + "id": 823066624, + "name": "Idaho", + "translations": { + "japanese": "アイダホ州", + "english": "Idaho", + "french": "Idaho", + "german": "Idaho", + "italian": "Idaho", + "spanish": "Idaho", + "chinese_simple": "爱达荷州", + "korean": "아이다호 주", + "dutch": "Idaho", + "portuguese": "Idaho", + "russian": "Айдахо", + "chinese_traditional": "Idaho", + "unknown1": "Idaho", + "unknown2": "Idaho", + "unknown3": "Idaho", + "unknown4": "Idaho" + }, + "coordinates": { + "latitude": 43.610228996000004, + "longitude": -116.202219094 + } + }, + { + "id": 823132160, + "name": "Illinois", + "translations": { + "japanese": "イリノイ州", + "english": "Illinois", + "french": "Illinois", + "german": "Illinois", + "italian": "Illinois", + "spanish": "Illinois", + "chinese_simple": "伊利诺伊州", + "korean": "일리노이 주", + "dutch": "Illinois", + "portuguese": "Illinois", + "russian": "Иллинойс", + "chinese_traditional": "Illinois", + "unknown1": "Illinois", + "unknown2": "Illinois", + "unknown3": "Illinois", + "unknown4": "Illinois" + }, + "coordinates": { + "latitude": 39.79797318, + "longitude": -89.642698629 + } + }, + { + "id": 823197696, + "name": "Indiana", + "translations": { + "japanese": "インディアナ州", + "english": "Indiana", + "french": "Indiana", + "german": "Indiana", + "italian": "Indiana", + "spanish": "Indiana", + "chinese_simple": "印第安纳州", + "korean": "인디애나 주", + "dutch": "Indiana", + "portuguese": "Indiana", + "russian": "Индиана", + "chinese_traditional": "Indiana", + "unknown1": "Indiana", + "unknown2": "Indiana", + "unknown3": "Indiana", + "unknown4": "Indiana" + }, + "coordinates": { + "latitude": 39.765014196, + "longitude": -86.154529964 + } + }, + { + "id": 823263232, + "name": "Kansas", + "translations": { + "japanese": "カンザス州", + "english": "Kansas", + "french": "Kansas", + "german": "Kansas", + "italian": "Kansas", + "spanish": "Kansas", + "chinese_simple": "堪萨斯州", + "korean": "캔자스 주", + "dutch": "Kansas", + "portuguese": "Kansas", + "russian": "Канзас", + "chinese_traditional": "Kansas", + "unknown1": "Kansas", + "unknown2": "Kansas", + "unknown3": "Kansas", + "unknown4": "Kansas" + }, + "coordinates": { + "latitude": 39.045409712, + "longitude": -95.674209171 + } + }, + { + "id": 823328768, + "name": "Kentucky", + "translations": { + "japanese": "ケンタッキー州", + "english": "Kentucky", + "french": "Kentucky", + "german": "Kentucky", + "italian": "Kentucky", + "spanish": "Kentucky", + "chinese_simple": "肯塔基州", + "korean": "켄터키 주", + "dutch": "Kentucky", + "portuguese": "Kentucky", + "russian": "Кентукки", + "chinese_traditional": "Kentucky", + "unknown1": "Kentucky", + "unknown2": "Kentucky", + "unknown3": "Kentucky", + "unknown4": "Kentucky" + }, + "coordinates": { + "latitude": 38.199462456, + "longitude": -84.86912607800001 + } + }, + { + "id": 823394304, + "name": "Louisiana", + "translations": { + "japanese": "ルイジアナ州", + "english": "Louisiana", + "french": "Louisiane", + "german": "Louisiana", + "italian": "Luisiana", + "spanish": "Luisiana", + "chinese_simple": "路易斯安那州", + "korean": "루이지애나 주", + "dutch": "Louisiana", + "portuguese": "Luisiana", + "russian": "Луизиана", + "chinese_traditional": "Louisiana", + "unknown1": "Louisiana", + "unknown2": "Louisiana", + "unknown3": "Louisiana", + "unknown4": "Louisiana" + }, + "coordinates": { + "latitude": 30.448608052, + "longitude": -91.153322854 + } + }, + { + "id": 823459840, + "name": "Massachusetts", + "translations": { + "japanese": "マサチューセッツ州", + "english": "Massachusetts", + "french": "Massachusetts", + "german": "Massachusetts", + "italian": "Massachusetts", + "spanish": "Massachusetts", + "chinese_simple": "马萨诸塞州", + "korean": "매사추세츠 주", + "dutch": "Massachusetts", + "portuguese": "Massachusetts", + "russian": "Массачусетс", + "chinese_traditional": "Massachusetts", + "unknown1": "Massachusetts", + "unknown2": "Massachusetts", + "unknown3": "Massachusetts", + "unknown4": "Massachusetts" + }, + "coordinates": { + "latitude": 42.357787604, + "longitude": -71.05927407200001 + } + }, + { + "id": 823525376, + "name": "Maryland", + "translations": { + "japanese": "メリーランド州", + "english": "Maryland", + "french": "Maryland", + "german": "Maryland", + "italian": "Maryland", + "spanish": "Maryland", + "chinese_simple": "马里兰州", + "korean": "메릴랜드 주", + "dutch": "Maryland", + "portuguese": "Maryland", + "russian": "Мэриленд", + "chinese_traditional": "Maryland", + "unknown1": "Maryland", + "unknown2": "Maryland", + "unknown3": "Maryland", + "unknown4": "Maryland" + }, + "coordinates": { + "latitude": 38.97399858, + "longitude": -76.492028103 + } + }, + { + "id": 823590912, + "name": "Maine", + "translations": { + "japanese": "メーン州", + "english": "Maine", + "french": "Maine", + "german": "Maine", + "italian": "Maine", + "spanish": "Maine", + "chinese_simple": "缅因州", + "korean": "메인 주", + "dutch": "Maine", + "portuguese": "Maine", + "russian": "Мэн", + "chinese_traditional": "Maine", + "unknown1": "Maine", + "unknown2": "Maine", + "unknown3": "Maine", + "unknown4": "Maine" + }, + "coordinates": { + "latitude": 44.307860824, + "longitude": -69.779363365 + } + }, + { + "id": 823656448, + "name": "Michigan", + "translations": { + "japanese": "ミシガン州", + "english": "Michigan", + "french": "Michigan", + "german": "Michigan", + "italian": "Michigan", + "spanish": "Míchigan", + "chinese_simple": "密歇根州", + "korean": "미시간 주", + "dutch": "Michigan", + "portuguese": "Michigan", + "russian": "Мичиган", + "chinese_traditional": "Michigan", + "unknown1": "Michigan", + "unknown2": "Michigan", + "unknown3": "Michigan", + "unknown4": "Michigan" + }, + "coordinates": { + "latitude": 42.731322756, + "longitude": -84.550521696 + } + }, + { + "id": 823721984, + "name": "Minnesota", + "translations": { + "japanese": "ミネソタ州", + "english": "Minnesota", + "french": "Minnesota", + "german": "Minnesota", + "italian": "Minnesota", + "spanish": "Minnesota", + "chinese_simple": "明尼苏达州", + "korean": "미네소타 주", + "dutch": "Minnesota", + "portuguese": "Minnesota", + "russian": "Миннесота", + "chinese_traditional": "Minnesota", + "unknown1": "Minnesota", + "unknown2": "Minnesota", + "unknown3": "Minnesota", + "unknown4": "Minnesota" + }, + "coordinates": { + "latitude": 44.939574684, + "longitude": -93.092415041 + } + }, + { + "id": 823787520, + "name": "Missouri", + "translations": { + "japanese": "ミズーリ州", + "english": "Missouri", + "french": "Missouri", + "german": "Missouri", + "italian": "Missouri", + "spanish": "Misuri", + "chinese_simple": "密苏里州", + "korean": "미주리 주", + "dutch": "Missouri", + "portuguese": "Missouri", + "russian": "Миссури", + "chinese_traditional": "Missouri", + "unknown1": "Missouri", + "unknown2": "Missouri", + "unknown3": "Missouri", + "unknown4": "Missouri" + }, + "coordinates": { + "latitude": 38.572997608, + "longitude": -92.169560969 + } + }, + { + "id": 823853056, + "name": "Mississippi", + "translations": { + "japanese": "ミシシッピ州", + "english": "Mississippi", + "french": "Mississippi", + "german": "Mississippi", + "italian": "Mississippi", + "spanish": "Misisipi", + "chinese_simple": "密西西比州", + "korean": "미시시피 주", + "dutch": "Mississippi", + "portuguese": "Mississippi", + "russian": "Миссисипи", + "chinese_traditional": "Mississippi", + "unknown1": "Mississippi", + "unknown2": "Mississippi", + "unknown3": "Mississippi", + "unknown4": "Mississippi" + }, + "coordinates": { + "latitude": 32.294311156, + "longitude": -90.181030171 + } + }, + { + "id": 823918592, + "name": "Montana", + "translations": { + "japanese": "モンタナ州", + "english": "Montana", + "french": "Montana", + "german": "Montana", + "italian": "Montana", + "spanish": "Montana", + "chinese_simple": "蒙大拿州", + "korean": "몬태나 주", + "dutch": "Montana", + "portuguese": "Montana", + "russian": "Монтана", + "chinese_traditional": "Montana", + "unknown1": "Montana", + "unknown2": "Montana", + "unknown3": "Montana", + "unknown4": "Montana" + }, + "coordinates": { + "latitude": 46.587523884, + "longitude": -112.032896233 + } + }, + { + "id": 823984128, + "name": "North Carolina", + "translations": { + "japanese": "ノースカロライナ州", + "english": "North Carolina", + "french": "Caroline du Nord", + "german": "North Carolina", + "italian": "Carolina del Nord", + "spanish": "Carolina del Norte", + "chinese_simple": "北卡罗来纳州", + "korean": "노스캐롤라이나 주", + "dutch": "North Carolina", + "portuguese": "Carolina do Norte", + "russian": "Северная Каролина", + "chinese_traditional": "North Carolina", + "unknown1": "North Carolina", + "unknown2": "North Carolina", + "unknown3": "North Carolina", + "unknown4": "North Carolina" + }, + "coordinates": { + "latitude": 35.771483968, + "longitude": -78.634367913 + } + }, + { + "id": 824049664, + "name": "North Dakota", + "translations": { + "japanese": "ノースダコタ州", + "english": "North Dakota", + "french": "Dakota du Nord", + "german": "North Dakota", + "italian": "Dakota del Nord", + "spanish": "Dakota del Norte", + "chinese_simple": "北达科他州", + "korean": "노스다코타 주", + "dutch": "North Dakota", + "portuguese": "Dakota do Norte", + "russian": "Северная Дакота", + "chinese_traditional": "North Dakota", + "unknown1": "North Dakota", + "unknown2": "North Dakota", + "unknown3": "North Dakota", + "unknown4": "North Dakota" + }, + "coordinates": { + "latitude": 46.807250444, + "longitude": -100.782865641 + } + }, + { + "id": 824115200, + "name": "Nebraska", + "translations": { + "japanese": "ネブラスカ州", + "english": "Nebraska", + "french": "Nebraska", + "german": "Nebraska", + "italian": "Nebraska", + "spanish": "Nebraska", + "chinese_simple": "内布拉斯加州", + "korean": "네브래스카 주", + "dutch": "Nebraska", + "portuguese": "Nebrasca", + "russian": "Небраска", + "chinese_traditional": "Nebraska", + "unknown1": "Nebraska", + "unknown2": "Nebraska", + "unknown3": "Nebraska", + "unknown4": "Nebraska" + }, + "coordinates": { + "latitude": 40.797729028, + "longitude": -96.662981391 + } + }, + { + "id": 824180736, + "name": "New Hampshire", + "translations": { + "japanese": "ニューハンプシャー州", + "english": "New Hampshire", + "french": "New Hampshire", + "german": "New Hampshire", + "italian": "New Hampshire", + "spanish": "Nuevo Hampshire", + "chinese_simple": "新罕布什尔州", + "korean": "뉴햄프셔 주", + "dutch": "New Hampshire", + "portuguese": "Nova Hampshire", + "russian": "Нью-Гэмпшир", + "chinese_traditional": "New Hampshire", + "unknown1": "New Hampshire", + "unknown2": "New Hampshire", + "unknown3": "New Hampshire", + "unknown4": "New Hampshire" + }, + "coordinates": { + "latitude": 43.20373486, + "longitude": -71.537180645 + } + }, + { + "id": 824246272, + "name": "New Jersey", + "translations": { + "japanese": "ニュージャージー州", + "english": "New Jersey", + "french": "New Jersey", + "german": "New Jersey", + "italian": "New Jersey", + "spanish": "Nueva Jersey", + "chinese_simple": "新泽西州", + "korean": "뉴저지 주", + "dutch": "New Jersey", + "portuguese": "Nova Jérsei", + "russian": "Нью-Джерси", + "chinese_traditional": "New Jersey", + "unknown1": "New Jersey", + "unknown2": "New Jersey", + "unknown3": "New Jersey", + "unknown4": "New Jersey" + }, + "coordinates": { + "latitude": 40.215453644, + "longitude": -74.739704002 + } + }, + { + "id": 824311808, + "name": "New Mexico", + "translations": { + "japanese": "ニューメキシコ州", + "english": "New Mexico", + "french": "Nouveau-Mexique", + "german": "New Mexico", + "italian": "Nuovo Messico", + "spanish": "Nuevo México", + "chinese_simple": "新墨西哥州", + "korean": "뉴멕시코 주", + "dutch": "New Mexico", + "portuguese": "Novo México", + "russian": "Нью-Мексико", + "chinese_traditional": "New Mexico", + "unknown1": "New Mexico", + "unknown2": "New Mexico", + "unknown3": "New Mexico", + "unknown4": "New Mexico" + }, + "coordinates": { + "latitude": 35.683593344, + "longitude": -105.935467543 + } + }, + { + "id": 824377344, + "name": "Nevada", + "translations": { + "japanese": "ネバダ州", + "english": "Nevada", + "french": "Nevada", + "german": "Nevada", + "italian": "Nevada", + "spanish": "Nevada", + "chinese_simple": "内华达州", + "korean": "네바다 주", + "dutch": "Nevada", + "portuguese": "Nevada", + "russian": "Невада", + "chinese_traditional": "Nevada", + "unknown1": "Nevada", + "unknown2": "Nevada", + "unknown3": "Nevada", + "unknown4": "Nevada" + }, + "coordinates": { + "latitude": 39.160766156, + "longitude": -119.761799086 + } + }, + { + "id": 824442880, + "name": "New York", + "translations": { + "japanese": "ニューヨーク州", + "english": "New York", + "french": "New York", + "german": "New York", + "italian": "New York", + "spanish": "Nueva York", + "chinese_simple": "纽约州", + "korean": "뉴욕 주", + "dutch": "New York", + "portuguese": "Nova Iorque", + "russian": "Нью-Йорк", + "chinese_traditional": "New York", + "unknown1": "New York", + "unknown2": "New York", + "unknown3": "New York", + "unknown4": "New York" + }, + "coordinates": { + "latitude": 42.648925296, + "longitude": -73.750931782 + } + }, + { + "id": 824508416, + "name": "Ohio", + "translations": { + "japanese": "オハイオ州", + "english": "Ohio", + "french": "Ohio", + "german": "Ohio", + "italian": "Ohio", + "spanish": "Ohio", + "chinese_simple": "俄亥俄州", + "korean": "오하이오 주", + "dutch": "Ohio", + "portuguese": "Ohio", + "russian": "Огайо", + "chinese_traditional": "Ohio", + "unknown1": "Ohio", + "unknown2": "Ohio", + "unknown3": "Ohio", + "unknown4": "Ohio" + }, + "coordinates": { + "latitude": 39.957274936, + "longitude": -82.995952039 + } + }, + { + "id": 824573952, + "name": "Oklahoma", + "translations": { + "japanese": "オクラホマ州", + "english": "Oklahoma", + "french": "Oklahoma", + "german": "Oklahoma", + "italian": "Oklahoma", + "spanish": "Oklahoma", + "chinese_simple": "俄克拉何马州", + "korean": "오클라호마 주", + "dutch": "Oklahoma", + "portuguese": "Oklahoma", + "russian": "Оклахома", + "chinese_traditional": "Oklahoma", + "unknown1": "Oklahoma", + "unknown2": "Oklahoma", + "unknown3": "Oklahoma", + "unknown4": "Oklahoma" + }, + "coordinates": { + "latitude": 35.463866784, + "longitude": -97.514424136 + } + }, + { + "id": 824639488, + "name": "Oregon", + "translations": { + "japanese": "オレゴン州", + "english": "Oregon", + "french": "Oregon", + "german": "Oregon", + "italian": "Oregon", + "spanish": "Oregón", + "chinese_simple": "俄勒冈州", + "korean": "오리건 주", + "dutch": "Oregon", + "portuguese": "Oregon", + "russian": "Орегон", + "chinese_traditional": "Oregon", + "unknown1": "Oregon", + "unknown2": "Oregon", + "unknown3": "Oregon", + "unknown4": "Oregon" + }, + "coordinates": { + "latitude": 44.939574684, + "longitude": -123.030240591 + } + }, + { + "id": 824705024, + "name": "Pennsylvania", + "translations": { + "japanese": "ペンシルベニア州", + "english": "Pennsylvania", + "french": "Pennsylvanie", + "german": "Pennsylvania", + "italian": "Pennsylvania", + "spanish": "Pensilvania", + "chinese_simple": "宾夕法尼亚州", + "korean": "펜실베이니아 주", + "dutch": "Pennsylvania", + "portuguese": "Pensilvânia", + "russian": "Пенсильвания", + "chinese_traditional": "Pennsylvania", + "unknown1": "Pennsylvania", + "unknown2": "Pennsylvania", + "unknown3": "Pennsylvania", + "unknown4": "Pennsylvania" + }, + "coordinates": { + "latitude": 40.270385284, + "longitude": -76.882043812 + } + }, + { + "id": 824770560, + "name": "Rhode Island", + "translations": { + "japanese": "ロードアイランド州", + "english": "Rhode Island", + "french": "Rhode Island", + "german": "Rhode Island", + "italian": "Rhode Island", + "spanish": "Rhode Island", + "chinese_simple": "罗得岛州", + "korean": "로드아일랜드 주", + "dutch": "Rhode Island", + "portuguese": "Rhode Island", + "russian": "Род-Айленд", + "chinese_traditional": "Rhode Island", + "unknown1": "Rhode Island", + "unknown2": "Rhode Island", + "unknown3": "Rhode Island", + "unknown4": "Rhode Island" + }, + "coordinates": { + "latitude": 41.819457532, + "longitude": -71.410837528 + } + }, + { + "id": 824836096, + "name": "South Carolina", + "translations": { + "japanese": "サウスカロライナ州", + "english": "South Carolina", + "french": "Caroline du Sud", + "german": "South Carolina", + "italian": "Carolina del Sud", + "spanish": "Carolina del Sur", + "chinese_simple": "南卡罗来纳州", + "korean": "사우스캐롤라이나 주", + "dutch": "South Carolina", + "portuguese": "Carolina do Sul", + "russian": "Южная Каролина", + "chinese_traditional": "South Carolina", + "unknown1": "South Carolina", + "unknown2": "South Carolina", + "unknown3": "South Carolina", + "unknown4": "South Carolina" + }, + "coordinates": { + "latitude": 33.997191996, + "longitude": -81.029393957 + } + }, + { + "id": 824901632, + "name": "South Dakota", + "translations": { + "japanese": "サウスダコタ州", + "english": "South Dakota", + "french": "Dakota du Sud", + "german": "South Dakota", + "italian": "Dakota del Sud", + "spanish": "Dakota del Sur", + "chinese_simple": "南达科他州", + "korean": "사우스다코타 주", + "dutch": "South Dakota", + "portuguese": "Dakota do Sul", + "russian": "Южная Дакота", + "chinese_traditional": "South Dakota", + "unknown1": "South Dakota", + "unknown2": "South Dakota", + "unknown3": "South Dakota", + "unknown4": "South Dakota" + }, + "coordinates": { + "latitude": 44.368285628, + "longitude": -100.3489045 + } + }, + { + "id": 824967168, + "name": "Tennessee", + "translations": { + "japanese": "テネシー州", + "english": "Tennessee", + "french": "Tennessee", + "german": "Tennessee", + "italian": "Tennessee", + "spanish": "Tennessee", + "chinese_simple": "田纳西州", + "korean": "테네시 주", + "dutch": "Tennessee", + "portuguese": "Tennessee", + "russian": "Теннесси", + "chinese_traditional": "Tennessee", + "unknown1": "Tennessee", + "unknown2": "Tennessee", + "unknown3": "Tennessee", + "unknown4": "Tennessee" + }, + "coordinates": { + "latitude": 36.161498612, + "longitude": -86.78075237 + } + }, + { + "id": 825032704, + "name": "Texas", + "translations": { + "japanese": "テキサス州", + "english": "Texas", + "french": "Texas", + "german": "Texas", + "italian": "Texas", + "spanish": "Texas", + "chinese_simple": "得克萨斯州", + "korean": "텍사스 주", + "dutch": "Texas", + "portuguese": "Texas", + "russian": "Техас", + "chinese_traditional": "Texas", + "unknown1": "Texas", + "unknown2": "Texas", + "unknown3": "Texas", + "unknown4": "Texas" + }, + "coordinates": { + "latitude": 30.261840476, + "longitude": -97.739644475 + } + }, + { + "id": 825098240, + "name": "Utah", + "translations": { + "japanese": "ユタ州", + "english": "Utah", + "french": "Utah", + "german": "Utah", + "italian": "Utah", + "spanish": "Utah", + "chinese_simple": "犹他州", + "korean": "유타 주", + "dutch": "Utah", + "portuguese": "Utah", + "russian": "Юта", + "chinese_traditional": "Utah", + "unknown1": "Utah", + "unknown2": "Utah", + "unknown3": "Utah", + "unknown4": "Utah" + }, + "coordinates": { + "latitude": 40.75927688, + "longitude": -111.890073579 + } + }, + { + "id": 825163776, + "name": "Virginia", + "translations": { + "japanese": "バージニア州", + "english": "Virginia", + "french": "Virginie", + "german": "Virginia", + "italian": "Virginia", + "spanish": "Virginia", + "chinese_simple": "弗吉尼亚州", + "korean": "버지니아 주", + "dutch": "Virginia", + "portuguese": "Virgínia", + "russian": "Виргиния", + "chinese_traditional": "Virginia", + "unknown1": "Virginia", + "unknown2": "Virginia", + "unknown3": "Virginia", + "unknown4": "Virginia" + }, + "coordinates": { + "latitude": 37.551269104, + "longitude": -77.458827607 + } + }, + { + "id": 825229312, + "name": "Vermont", + "translations": { + "japanese": "バーモント州", + "english": "Vermont", + "french": "Vermont", + "german": "Vermont", + "italian": "Vermont", + "spanish": "Vermont", + "chinese_simple": "佛蒙特州", + "korean": "버몬트 주", + "dutch": "Vermont", + "portuguese": "Vermont", + "russian": "Вермонт", + "chinese_traditional": "Vermont", + "unknown1": "Vermont", + "unknown2": "Vermont", + "unknown3": "Vermont", + "unknown4": "Vermont" + }, + "coordinates": { + "latitude": 44.258422348, + "longitude": -72.57539147600001 + } + }, + { + "id": 825294848, + "name": "Washington", + "translations": { + "japanese": "ワシントン州", + "english": "Washington", + "french": "Washington", + "german": "Washington", + "italian": "Stato di Washington", + "spanish": "Washington", + "chinese_simple": "华盛顿州", + "korean": "워싱턴 주", + "dutch": "Washington", + "portuguese": "Washington", + "russian": "Вашингтон (штат)", + "chinese_traditional": "Washington", + "unknown1": "Washington", + "unknown2": "Washington", + "unknown3": "Washington", + "unknown4": "Washington" + }, + "coordinates": { + "latitude": 47.037963332, + "longitude": -122.898404295 + } + }, + { + "id": 825360384, + "name": "Wisconsin", + "translations": { + "japanese": "ウィスコンシン州", + "english": "Wisconsin", + "french": "Wisconsin", + "german": "Wisconsin", + "italian": "Wisconsin", + "spanish": "Wisconsin", + "chinese_simple": "威斯康星州", + "korean": "위스콘신 주", + "dutch": "Wisconsin", + "portuguese": "Wisconsin", + "russian": "Висконсин", + "chinese_traditional": "Wisconsin", + "unknown1": "Wisconsin", + "unknown2": "Wisconsin", + "unknown3": "Wisconsin", + "unknown4": "Wisconsin" + }, + "coordinates": { + "latitude": 43.071898924, + "longitude": -89.395505574 + } + }, + { + "id": 825425920, + "name": "West Virginia", + "translations": { + "japanese": "ウェストバージニア州", + "english": "West Virginia", + "french": "Virginie Occidentale", + "german": "West Virginia", + "italian": "Virginia Occidentale", + "spanish": "Virginia Occidental", + "chinese_simple": "西弗吉尼亚州", + "korean": "웨스트버지니아 주", + "dutch": "West Virginia", + "portuguese": "Virgínia Ocidental", + "russian": "Западная Виргиния", + "chinese_traditional": "West Virginia", + "unknown1": "West Virginia", + "unknown2": "West Virginia", + "unknown3": "West Virginia", + "unknown4": "West Virginia" + }, + "coordinates": { + "latitude": 38.347777884, + "longitude": -81.628150468 + } + }, + { + "id": 825491456, + "name": "Wyoming", + "translations": { + "japanese": "ワイオミング州", + "english": "Wyoming", + "french": "Wyoming", + "german": "Wyoming", + "italian": "Wyoming", + "spanish": "Wyoming", + "chinese_simple": "怀俄明州", + "korean": "와이오밍 주", + "dutch": "Wyoming", + "portuguese": "Wyoming", + "russian": "Вайоминг", + "chinese_traditional": "Wyoming", + "unknown1": "Wyoming", + "unknown2": "Wyoming", + "unknown3": "Wyoming", + "unknown4": "Wyoming" + }, + "coordinates": { + "latitude": 41.138305196, + "longitude": -104.814859027 + } + }, + { + "id": 825556992, + "name": "Puerto Rico", + "translations": { + "japanese": "プエルトリコ", + "english": "Puerto Rico", + "french": "Porto Rico", + "german": "Puerto Rico", + "italian": "Porto Rico", + "spanish": "Puerto Rico", + "chinese_simple": "波多黎各", + "korean": "푸에르토리코", + "dutch": "Puerto Rico", + "portuguese": "Porto Rico", + "russian": "Пуэрто-Рико", + "chinese_traditional": "Puerto Rico", + "unknown1": "Puerto Rico", + "unknown2": "Puerto Rico", + "unknown3": "Puerto Rico", + "unknown4": "Puerto Rico" + }, + "coordinates": { + "latitude": 18.468017368, + "longitude": -66.104426614 + } + } + ] + }, + { + "id": 50, + "iso_code": "UY", + "name": "Uruguay", + "translations": { + "japanese": "ウルグアイ", + "english": "Uruguay", + "french": "Uruguay", + "german": "Uruguay", + "italian": "Uruguay", + "spanish": "Uruguay", + "chinese_simple": "乌拉圭", + "korean": "우루과이", + "dutch": "Uruguay", + "portuguese": "Uruguai", + "russian": "Уругвай", + "chinese_traditional": "Uruguay", + "unknown1": "Uruguay", + "unknown2": "Uruguay", + "unknown3": "Uruguay", + "unknown4": "Uruguay" + }, + "regions": [ + { + "id": 838860800, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": -34.854126604, + "longitude": -56.16726580300001 + } + }, + { + "id": 838991872, + "name": "Montevideo", + "translations": { + "japanese": "モンテビデオ", + "english": "Montevideo", + "french": "Montevideo", + "german": "Montevideo", + "italian": "Montevideo", + "spanish": "Montevideo", + "chinese_simple": "蒙得维的亚省", + "korean": "몬테비데오", + "dutch": "Montevideo", + "portuguese": "Montevideu", + "russian": "Монтевидео", + "chinese_traditional": "Montevideo", + "unknown1": "Montevideo", + "unknown2": "Montevideo", + "unknown3": "Montevideo", + "unknown4": "Montevideo" + }, + "coordinates": { + "latitude": -34.854126604, + "longitude": -56.16726580300001 + } + }, + { + "id": 839057408, + "name": "Artigas", + "translations": { + "japanese": "アルティガス", + "english": "Artigas", + "french": "Artigas", + "german": "Artigas", + "italian": "Artigas", + "spanish": "Artigas", + "chinese_simple": "阿蒂加斯省", + "korean": "아르티가스", + "dutch": "Artigas", + "portuguese": "Artigas", + "russian": "Артигас", + "chinese_traditional": "Artigas", + "unknown1": "Artigas", + "unknown2": "Artigas", + "unknown3": "Artigas", + "unknown4": "Artigas" + }, + "coordinates": { + "latitude": -30.399170599999998, + "longitude": -56.463897469 + } + }, + { + "id": 839122944, + "name": "Canelones", + "translations": { + "japanese": "カネロネス", + "english": "Canelones", + "french": "Canelones", + "german": "Canelones", + "italian": "Canelones", + "spanish": "Canelones", + "chinese_simple": "卡内洛内斯省", + "korean": "카넬로네스", + "dutch": "Canelones", + "portuguese": "Canelones", + "russian": "Канелонес", + "chinese_traditional": "Canelones", + "unknown1": "Canelones", + "unknown2": "Canelones", + "unknown3": "Canelones", + "unknown4": "Canelones" + }, + "coordinates": { + "latitude": -34.519043599999996, + "longitude": -56.277129383 + } + }, + { + "id": 839188480, + "name": "Cerro Largo", + "translations": { + "japanese": "セロ・ラルゴ", + "english": "Cerro Largo", + "french": "Cerro Largo", + "german": "Cerro Largo", + "italian": "Cerro Largo", + "spanish": "Cerro Largo", + "chinese_simple": "塞罗拉尔戈省", + "korean": "세로라르고", + "dutch": "Cerro Largo", + "portuguese": "Cerro Largo", + "russian": "Серро-Ларго", + "chinese_traditional": "Cerro Largo", + "unknown1": "Cerro Largo", + "unknown2": "Cerro Largo", + "unknown3": "Cerro Largo", + "unknown4": "Cerro Largo" + }, + "coordinates": { + "latitude": -32.365723312, + "longitude": -54.17873500500001 + } + }, + { + "id": 839254016, + "name": "Colonia", + "translations": { + "japanese": "コロニア", + "english": "Colonia", + "french": "Colonia", + "german": "Colonia", + "italian": "Colonia", + "spanish": "Colonia", + "chinese_simple": "科洛尼亚省", + "korean": "콜로니아", + "dutch": "Colonia", + "portuguese": "Colônia", + "russian": "Колония", + "chinese_traditional": "Colonia", + "unknown1": "Colonia", + "unknown2": "Colonia", + "unknown3": "Colonia", + "unknown4": "Colonia" + }, + "coordinates": { + "latitude": -34.46411196, + "longitude": -57.848178577 + } + }, + { + "id": 839319552, + "name": "Durazno", + "translations": { + "japanese": "ドゥラスノ", + "english": "Durazno", + "french": "Durazno", + "german": "Durazno", + "italian": "Durazno", + "spanish": "Durazno", + "chinese_simple": "杜拉斯诺省", + "korean": "두라스노", + "dutch": "Durazno", + "portuguese": "Durazno", + "russian": "Дурасно", + "chinese_traditional": "Durazno", + "unknown1": "Durazno", + "unknown2": "Durazno", + "unknown3": "Durazno", + "unknown4": "Durazno" + }, + "coordinates": { + "latitude": -33.409424472, + "longitude": -56.49685654300001 + } + }, + { + "id": 839385088, + "name": "Flores", + "translations": { + "japanese": "フロレス", + "english": "Flores", + "french": "Flores", + "german": "Flores", + "italian": "Flores", + "spanish": "Flores", + "chinese_simple": "弗洛雷斯省", + "korean": "플로레스", + "dutch": "Flores", + "portuguese": "Flores", + "russian": "Флорес", + "chinese_traditional": "Flores", + "unknown1": "Flores", + "unknown2": "Flores", + "unknown3": "Flores", + "unknown4": "Flores" + }, + "coordinates": { + "latitude": -33.535767244, + "longitude": -56.886872252 + } + }, + { + "id": 839450624, + "name": "Florida", + "translations": { + "japanese": "フロリダ", + "english": "Florida", + "french": "Florida", + "german": "Florida", + "italian": "Florida", + "spanish": "Florida", + "chinese_simple": "佛罗里达省", + "korean": "플로리다", + "dutch": "Florida", + "portuguese": "Florida", + "russian": "Флорида", + "chinese_traditional": "Florida", + "unknown1": "Florida", + "unknown2": "Florida", + "unknown3": "Florida", + "unknown4": "Florida" + }, + "coordinates": { + "latitude": -34.090576808, + "longitude": -56.21121123500001 + } + }, + { + "id": 839516160, + "name": "Lavalleja", + "translations": { + "japanese": "ラバジェハ", + "english": "Lavalleja", + "french": "Lavalleja", + "german": "Lavalleja", + "italian": "Lavalleja", + "spanish": "Lavalleja", + "chinese_simple": "拉瓦耶哈省", + "korean": "라바예하", + "dutch": "Lavalleja", + "portuguese": "Lavalleja", + "russian": "Лавальеха", + "chinese_traditional": "Lavalleja", + "unknown1": "Lavalleja", + "unknown2": "Lavalleja", + "unknown3": "Lavalleja", + "unknown4": "Lavalleja" + }, + "coordinates": { + "latitude": -34.365235008, + "longitude": -55.222439015000006 + } + }, + { + "id": 839581696, + "name": "Maldonado", + "translations": { + "japanese": "マルドナド", + "english": "Maldonado", + "french": "Maldonado", + "german": "Maldonado", + "italian": "Maldonado", + "spanish": "Maldonado", + "chinese_simple": "马尔多纳多省", + "korean": "말도나도", + "dutch": "Maldonado", + "portuguese": "Maldonado", + "russian": "Мальдонадо", + "chinese_traditional": "Maldonado", + "unknown1": "Maldonado", + "unknown2": "Maldonado", + "unknown3": "Maldonado", + "unknown4": "Maldonado" + }, + "coordinates": { + "latitude": -34.898071916, + "longitude": -54.947780065 + } + }, + { + "id": 839647232, + "name": "Paysandú", + "translations": { + "japanese": "パイサンドゥ", + "english": "Paysandú", + "french": "Paysandú", + "german": "Paysandú", + "italian": "Paysandú", + "spanish": "Paysandú", + "chinese_simple": "派桑杜省", + "korean": "파이산두", + "dutch": "Paysandú", + "portuguese": "Paysandú", + "russian": "Пайсанду", + "chinese_traditional": "Paysandú", + "unknown1": "Paysandú", + "unknown2": "Paysandú", + "unknown3": "Paysandú", + "unknown4": "Paysandú" + }, + "coordinates": { + "latitude": -32.316284836, + "longitude": -58.073398916 + } + }, + { + "id": 839712768, + "name": "Río Negro", + "translations": { + "japanese": "リオ・ネグロ", + "english": "Río Negro", + "french": "Río Negro", + "german": "Río Negro", + "italian": "Río Negro", + "spanish": "Río Negro", + "chinese_simple": "内格罗河省", + "korean": "리오네그로", + "dutch": "Río Negro", + "portuguese": "Río Negro", + "russian": "Рио-Негро", + "chinese_traditional": "Río Negro", + "unknown1": "Río Negro", + "unknown2": "Río Negro", + "unknown3": "Río Negro", + "unknown4": "Río Negro" + }, + "coordinates": { + "latitude": -33.129273108, + "longitude": -58.29312607600001 + } + }, + { + "id": 839778304, + "name": "Rivera", + "translations": { + "japanese": "リベラ", + "english": "Rivera", + "french": "Rivera", + "german": "Rivera", + "italian": "Rivera", + "spanish": "Rivera", + "chinese_simple": "里韦拉省", + "korean": "리베라", + "dutch": "Rivera", + "portuguese": "Rivera", + "russian": "Ривера", + "chinese_traditional": "Rivera", + "unknown1": "Rivera", + "unknown2": "Rivera", + "unknown3": "Rivera", + "unknown4": "Rivera" + }, + "coordinates": { + "latitude": -30.899048524, + "longitude": -55.513577502000004 + } + }, + { + "id": 839843840, + "name": "Rocha", + "translations": { + "japanese": "ロチャ", + "english": "Rocha", + "french": "Rocha", + "german": "Rocha", + "italian": "Rocha", + "spanish": "Rocha", + "chinese_simple": "罗恰省", + "korean": "로차", + "dutch": "Rocha", + "portuguese": "Rocha", + "russian": "Роча", + "chinese_traditional": "Rocha", + "unknown1": "Rocha", + "unknown2": "Rocha", + "unknown3": "Rocha", + "unknown4": "Rocha" + }, + "coordinates": { + "latitude": -34.480591452, + "longitude": -54.332544017000004 + } + }, + { + "id": 839909376, + "name": "Salto", + "translations": { + "japanese": "サルト", + "english": "Salto", + "french": "Salto", + "german": "Salto", + "italian": "Salto", + "spanish": "Salto", + "chinese_simple": "萨尔托省", + "korean": "살토", + "dutch": "Salto", + "portuguese": "Salto", + "russian": "Сальто", + "chinese_traditional": "Salto", + "unknown1": "Salto", + "unknown2": "Salto", + "unknown3": "Salto", + "unknown4": "Salto" + }, + "coordinates": { + "latitude": -31.382446956000003, + "longitude": -57.96353533600001 + } + }, + { + "id": 839974912, + "name": "San José", + "translations": { + "japanese": "サン・ホセ", + "english": "San José", + "french": "San José", + "german": "San José", + "italian": "San José", + "spanish": "San José", + "chinese_simple": "圣何塞省", + "korean": "산호세", + "dutch": "San José", + "portuguese": "San José", + "russian": "Сан-Хосе", + "chinese_traditional": "San José", + "unknown1": "San José", + "unknown2": "San José", + "unknown3": "San José", + "unknown4": "San José" + }, + "coordinates": { + "latitude": -34.332276024, + "longitude": -56.711090524 + } + }, + { + "id": 840040448, + "name": "Soriano", + "translations": { + "japanese": "ソリアノ", + "english": "Soriano", + "french": "Soriano", + "german": "Soriano", + "italian": "Soriano", + "spanish": "Soriano", + "chinese_simple": "索里亚诺省", + "korean": "소리아노", + "dutch": "Soriano", + "portuguese": "Soriano", + "russian": "Сорьяно", + "chinese_traditional": "Soriano", + "unknown1": "Soriano", + "unknown2": "Soriano", + "unknown3": "Soriano", + "unknown4": "Soriano" + }, + "coordinates": { + "latitude": -33.25561588, + "longitude": -58.018467126000004 + } + }, + { + "id": 840105984, + "name": "Tacuarembó", + "translations": { + "japanese": "タクアレンボ", + "english": "Tacuarembó", + "french": "Tacuarembó", + "german": "Tacuarembó", + "italian": "Tacuarembó", + "spanish": "Tacuarembó", + "chinese_simple": "塔夸伦博省", + "korean": "타쿠아렘보", + "dutch": "Tacuarembó", + "portuguese": "Tacuarembó", + "russian": "Такуарембо", + "chinese_traditional": "Tacuarembó", + "unknown1": "Tacuarembó", + "unknown2": "Tacuarembó", + "unknown3": "Tacuarembó", + "unknown4": "Tacuarembó" + }, + "coordinates": { + "latitude": -31.728516288, + "longitude": -55.980497717000006 + } + }, + { + "id": 840171520, + "name": "Treinta y Tres", + "translations": { + "japanese": "トレインタ・イ・トレス", + "english": "Treinta y Tres", + "french": "Treinta y Tres", + "german": "Treinta y Tres", + "italian": "Treinta y Tres", + "spanish": "Treinta y Tres", + "chinese_simple": "三十三人省", + "korean": "트레인타이트레스", + "dutch": "Treinta y Tres", + "portuguese": "Treinta y Tres", + "russian": "Трейнта-и-Трес", + "chinese_traditional": "Treinta y Tres", + "unknown1": "Treinta y Tres", + "unknown2": "Treinta y Tres", + "unknown3": "Treinta y Tres", + "unknown4": "Treinta y Tres" + }, + "coordinates": { + "latitude": -33.22815006, + "longitude": -54.381982628 + } + } + ] + }, + { + "id": 51, + "iso_code": "VI", + "name": "US Virgin Islands", + "translations": { + "japanese": "米領バージン諸島", + "english": "US Virgin Islands", + "french": "Îles Vierges américaines", + "german": "Amerikanische Jungferninseln", + "italian": "Isole Vergini Statunitensi", + "spanish": "Islas Vírgenes de los EE. UU.", + "chinese_simple": "美属维尔京群岛", + "korean": "미국령 버진아일랜드", + "dutch": "Amerikaanse Maagdeneilanden", + "portuguese": "Ilhas Virgens Americanas", + "russian": "Американские Виргинские острова", + "chinese_traditional": "US Virgin Islands", + "unknown1": "US Virgin Islands", + "unknown2": "US Virgin Islands", + "unknown3": "US Virgin Islands", + "unknown4": "US Virgin Islands" + }, + "regions": [ + { + "id": 855638016, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": 18.341674596, + "longitude": -64.928886308 + } + }, + { + "id": 855703552, + "name": "US Virgin Islands", + "translations": { + "japanese": "米領バージン諸島", + "english": "US Virgin Islands", + "french": "Îles Vierges américaines", + "german": "Amerikanische Jungferninseln", + "italian": "Isole Vergini Statunitensi", + "spanish": "Islas Vírgenes de los EE. UU.", + "chinese_simple": "美属维尔京群岛", + "korean": "미국령 버진아일랜드", + "dutch": "Amerikaanse Maagdeneilanden", + "portuguese": "Ilhas Virgens Americanas", + "russian": "Американские Виргинские острова", + "chinese_traditional": "US Virgin Islands", + "unknown1": "US Virgin Islands", + "unknown2": "US Virgin Islands", + "unknown3": "US Virgin Islands", + "unknown4": "US Virgin Islands" + }, + "coordinates": { + "latitude": 18.341674596, + "longitude": -64.928886308 + } + } + ] + }, + { + "id": 52, + "iso_code": "VE", + "name": "Venezuela", + "translations": { + "japanese": "ベネズエラ", + "english": "Venezuela", + "french": "Venezuela", + "german": "Venezuela", + "italian": "Venezuela", + "spanish": "Venezuela", + "chinese_simple": "委内瑞拉", + "korean": "베네수엘라", + "dutch": "Venezuela", + "portuguese": "Venezuela", + "russian": "Венесуэла", + "chinese_traditional": "Venezuela", + "unknown1": "Venezuela", + "unknown2": "Venezuela", + "unknown3": "Venezuela", + "unknown4": "Venezuela" + }, + "regions": [ + { + "id": 872415232, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": 10.497436404, + "longitude": -66.911923927 + } + }, + { + "id": 872546304, + "name": "Distrito Federal", + "translations": { + "japanese": "ディストリト首都地区", + "english": "Distrito Federal", + "french": "District Fédéral", + "german": "Caracas D.F.", + "italian": "Distretto Capitale", + "spanish": "Distrito Capital", + "chinese_simple": "首都区", + "korean": "베네수엘라 연방구", + "dutch": "Hoofdstedelijk District", + "portuguese": "Distrito Federal", + "russian": "Федеральный округ", + "chinese_traditional": "Distrito Federal", + "unknown1": "Distrito Federal", + "unknown2": "Distrito Federal", + "unknown3": "Distrito Federal", + "unknown4": "Distrito Federal" + }, + "coordinates": { + "latitude": 10.497436404, + "longitude": -66.911923927 + } + }, + { + "id": 872611840, + "name": "Amazonas", + "translations": { + "japanese": "アマソナス", + "english": "Amazonas", + "french": "Amazone", + "german": "Amazonas", + "italian": "Amazonas", + "spanish": "Amazonas", + "chinese_simple": "亚马孙边疆区", + "korean": "아마소나스", + "dutch": "Amazonas", + "portuguese": "Amazonas", + "russian": "Амасонас", + "chinese_traditional": "Amazonas", + "unknown1": "Amazonas", + "unknown2": "Amazonas", + "unknown3": "Amazonas", + "unknown4": "Amazonas" + }, + "coordinates": { + "latitude": 5.663452084, + "longitude": -67.620544018 + } + }, + { + "id": 872677376, + "name": "Anzoátegui", + "translations": { + "japanese": "アンソアテギ", + "english": "Anzoátegui", + "french": "Anzoátegui", + "german": "Anzoátegui", + "italian": "Anzoátegui", + "spanish": "Anzoátegui", + "chinese_simple": "安索阿特吉州", + "korean": "안소아테기", + "dutch": "Anzoátegui", + "portuguese": "Anzoátegui", + "russian": "Ансоатеги", + "chinese_traditional": "Anzoátegui", + "unknown1": "Anzoátegui", + "unknown2": "Anzoátegui", + "unknown3": "Anzoátegui", + "unknown4": "Anzoátegui" + }, + "coordinates": { + "latitude": 10.129394416, + "longitude": -64.69817279 + } + }, + { + "id": 872742912, + "name": "Apure", + "translations": { + "japanese": "アプレ", + "english": "Apure", + "french": "Apure", + "german": "Apure", + "italian": "Apure", + "spanish": "Apure", + "chinese_simple": "阿普雷州", + "korean": "아푸레", + "dutch": "Apure", + "portuguese": "Apure", + "russian": "Апуре", + "chinese_traditional": "Apure", + "unknown1": "Apure", + "unknown2": "Apure", + "unknown3": "Apure", + "unknown4": "Apure" + }, + "coordinates": { + "latitude": 7.893676668, + "longitude": -67.46673500600001 + } + }, + { + "id": 872808448, + "name": "Aragua", + "translations": { + "japanese": "アラグア", + "english": "Aragua", + "french": "Aragua", + "german": "Aragua", + "italian": "Aragua", + "spanish": "Aragua", + "chinese_simple": "阿拉瓜州", + "korean": "아라과", + "dutch": "Aragua", + "portuguese": "Aragua", + "russian": "Арагуа", + "chinese_traditional": "Aragua", + "unknown1": "Aragua", + "unknown2": "Aragua", + "unknown3": "Aragua", + "unknown4": "Aragua" + }, + "coordinates": { + "latitude": 10.24475086, + "longitude": -67.593078123 + } + }, + { + "id": 872873984, + "name": "Barinas", + "translations": { + "japanese": "バリナス", + "english": "Barinas", + "french": "Barinas", + "german": "Barinas", + "italian": "Barinas", + "spanish": "Barinas", + "chinese_simple": "巴里纳斯州", + "korean": "바리나스", + "dutch": "Barinas", + "portuguese": "Barinas", + "russian": "Баринас", + "chinese_traditional": "Barinas", + "unknown1": "Barinas", + "unknown2": "Barinas", + "unknown3": "Barinas", + "unknown4": "Barinas" + }, + "coordinates": { + "latitude": 8.62426748, + "longitude": -70.202338148 + } + }, + { + "id": 872939520, + "name": "Bolívar", + "translations": { + "japanese": "ボリーバル", + "english": "Bolívar", + "french": "Bolívar", + "german": "Bolívar", + "italian": "Bolívar", + "spanish": "Bolívar", + "chinese_simple": "玻利瓦尔州", + "korean": "볼리바르", + "dutch": "Bolívar", + "portuguese": "Bolívar", + "russian": "Боливар", + "chinese_traditional": "Bolívar", + "unknown1": "Bolívar", + "unknown2": "Bolívar", + "unknown3": "Bolívar", + "unknown4": "Bolívar" + }, + "coordinates": { + "latitude": 8.118896392, + "longitude": -63.54460520000001 + } + }, + { + "id": 873005056, + "name": "Carabobo", + "translations": { + "japanese": "カラボボ", + "english": "Carabobo", + "french": "Carabobo", + "german": "Carabobo", + "italian": "Carabobo", + "spanish": "Carabobo", + "chinese_simple": "卡拉沃沃州", + "korean": "카라보보", + "dutch": "Carabobo", + "portuguese": "Carabobo", + "russian": "Карабобо", + "chinese_traditional": "Carabobo", + "unknown1": "Carabobo", + "unknown2": "Carabobo", + "unknown3": "Carabobo", + "unknown4": "Carabobo" + }, + "coordinates": { + "latitude": 10.178832892, + "longitude": -67.999573369 + } + }, + { + "id": 873070592, + "name": "Cojedes", + "translations": { + "japanese": "コヘデス", + "english": "Cojedes", + "french": "Cojedes", + "german": "Cojedes", + "italian": "Cojedes", + "spanish": "Cojedes", + "chinese_simple": "科赫德斯州", + "korean": "코헤데스", + "dutch": "Cojedes", + "portuguese": "Cojedes", + "russian": "Кохедес", + "chinese_traditional": "Cojedes", + "unknown1": "Cojedes", + "unknown2": "Cojedes", + "unknown3": "Cojedes", + "unknown4": "Cojedes" + }, + "coordinates": { + "latitude": 9.662475476000001, + "longitude": -68.59832988 + } + }, + { + "id": 873136128, + "name": "Delta Amacuro", + "translations": { + "japanese": "デルタ・アマクロ", + "english": "Delta Amacuro", + "french": "Delta Amacuro", + "german": "Delta Amacuro", + "italian": "Delta Amacuro", + "spanish": "Delta Amacuro", + "chinese_simple": "阿马库罗三角洲边疆区", + "korean": "델타아마쿠로", + "dutch": "Delta Amacuro", + "portuguese": "Delta Amacuro", + "russian": "Дельта-Амакуро", + "chinese_traditional": "Delta Amacuro", + "unknown1": "Delta Amacuro", + "unknown2": "Delta Amacuro", + "unknown3": "Delta Amacuro", + "unknown4": "Delta Amacuro" + }, + "coordinates": { + "latitude": 9.0637206, + "longitude": -62.044967333 + } + }, + { + "id": 873201664, + "name": "Falcón", + "translations": { + "japanese": "ファルコン", + "english": "Falcón", + "french": "Falcón", + "german": "Falcón", + "italian": "Falcón", + "spanish": "Falcón", + "chinese_simple": "法尔孔州", + "korean": "팔콘", + "dutch": "Falcón", + "portuguese": "Falcón", + "russian": "Фалькон", + "chinese_traditional": "Falcón", + "unknown1": "Falcón", + "unknown2": "Falcón", + "unknown3": "Falcón", + "unknown4": "Falcón" + }, + "coordinates": { + "latitude": 11.403808464, + "longitude": -69.664006606 + } + }, + { + "id": 873267200, + "name": "Guárico", + "translations": { + "japanese": "グアリコ", + "english": "Guárico", + "french": "Guárico", + "german": "Guárico", + "italian": "Guárico", + "spanish": "Guárico", + "chinese_simple": "瓜里科州", + "korean": "과리코", + "dutch": "Guárico", + "portuguese": "Guárico", + "russian": "Гуарико", + "chinese_traditional": "Guárico", + "unknown1": "Guárico", + "unknown2": "Guárico", + "unknown3": "Guárico", + "unknown4": "Guárico" + }, + "coordinates": { + "latitude": 9.909667856, + "longitude": -67.356871426 + } + }, + { + "id": 873332736, + "name": "Lara", + "translations": { + "japanese": "ララ", + "english": "Lara", + "french": "Lara", + "german": "Lara", + "italian": "Lara", + "spanish": "Lara", + "chinese_simple": "拉腊州", + "korean": "라라", + "dutch": "Lara", + "portuguese": "Lara", + "russian": "Лара", + "chinese_traditional": "Lara", + "unknown1": "Lara", + "unknown2": "Lara", + "unknown3": "Lara", + "unknown4": "Lara" + }, + "coordinates": { + "latitude": 10.068969612, + "longitude": -69.317936329 + } + }, + { + "id": 873398272, + "name": "Mérida", + "translations": { + "japanese": "メリダ", + "english": "Mérida", + "french": "Mérida", + "german": "Mérida", + "italian": "Mérida", + "spanish": "Mérida", + "chinese_simple": "梅里达州", + "korean": "메리다", + "dutch": "Mérida", + "portuguese": "Mérida", + "russian": "Мерида", + "chinese_traditional": "Mérida", + "unknown1": "Mérida", + "unknown2": "Mérida", + "unknown3": "Mérida", + "unknown4": "Mérida" + }, + "coordinates": { + "latitude": 8.59680166, + "longitude": -71.141671757 + } + }, + { + "id": 873463808, + "name": "Miranda", + "translations": { + "japanese": "ミランダ", + "english": "Miranda", + "french": "Miranda", + "german": "Miranda", + "italian": "Miranda", + "spanish": "Miranda", + "chinese_simple": "米兰达州", + "korean": "미란다", + "dutch": "Miranda", + "portuguese": "Miranda", + "russian": "Миранда", + "chinese_traditional": "Miranda", + "unknown1": "Miranda", + "unknown2": "Miranda", + "unknown3": "Miranda", + "unknown4": "Miranda" + }, + "coordinates": { + "latitude": 10.338134648, + "longitude": -67.038267044 + } + }, + { + "id": 873529344, + "name": "Monagas", + "translations": { + "japanese": "モナガス", + "english": "Monagas", + "french": "Monagas", + "german": "Monagas", + "italian": "Monagas", + "spanish": "Monagas", + "chinese_simple": "莫纳加斯州", + "korean": "모나가스", + "dutch": "Monagas", + "portuguese": "Monagas", + "russian": "Монагас", + "chinese_traditional": "Monagas", + "unknown1": "Monagas", + "unknown2": "Monagas", + "unknown3": "Monagas", + "unknown4": "Monagas" + }, + "coordinates": { + "latitude": 9.744872936, + "longitude": -63.171069028000005 + } + }, + { + "id": 873594880, + "name": "Nueva Esparta", + "translations": { + "japanese": "ヌエバエスパルタ", + "english": "Nueva Esparta", + "french": "Nueva Esparta", + "german": "Nueva Esparta", + "italian": "Nueva Esparta", + "spanish": "Nueva Esparta", + "chinese_simple": "新埃斯帕塔州", + "korean": "누에바에스파르타", + "dutch": "Nueva Esparta", + "portuguese": "Nova Esparta", + "russian": "Нуэва-Эспарта", + "chinese_traditional": "Nueva Esparta", + "unknown1": "Nueva Esparta", + "unknown2": "Nueva Esparta", + "unknown3": "Nueva Esparta", + "unknown4": "Nueva Esparta" + }, + "coordinates": { + "latitude": 11.030273312, + "longitude": -63.857716403 + } + }, + { + "id": 873660416, + "name": "Portuguesa", + "translations": { + "japanese": "ポルトゥゲサ", + "english": "Portuguesa", + "french": "Portuguesa", + "german": "Portuguesa", + "italian": "Portuguesa", + "spanish": "Portuguesa", + "chinese_simple": "波图格萨州", + "korean": "포르투게사", + "dutch": "Portuguesa", + "portuguese": "Portuguesa", + "russian": "Португеса", + "chinese_traditional": "Portuguesa", + "unknown1": "Portuguesa", + "unknown2": "Portuguesa", + "unknown3": "Portuguesa", + "unknown4": "Portuguesa" + }, + "coordinates": { + "latitude": 9.047241108, + "longitude": -69.746404291 + } + }, + { + "id": 873725952, + "name": "Sucre", + "translations": { + "japanese": "スクレ", + "english": "Sucre", + "french": "Sucre", + "german": "Sucre", + "italian": "Sucre", + "spanish": "Sucre", + "chinese_simple": "苏克雷州", + "korean": "수크레", + "dutch": "Sucre", + "portuguese": "Sucre", + "russian": "Сукре", + "chinese_traditional": "Sucre", + "unknown1": "Sucre", + "unknown2": "Sucre", + "unknown3": "Sucre", + "unknown4": "Sucre" + }, + "coordinates": { + "latitude": 10.46447742, + "longitude": -64.165334427 + } + }, + { + "id": 873791488, + "name": "Táchira", + "translations": { + "japanese": "タチラ", + "english": "Táchira", + "french": "Táchira", + "german": "Táchira", + "italian": "Táchira", + "spanish": "Táchira", + "chinese_simple": "塔奇拉州", + "korean": "타치라", + "dutch": "Táchira", + "portuguese": "Táchira", + "russian": "Тачира", + "chinese_traditional": "Táchira", + "unknown1": "Táchira", + "unknown2": "Táchira", + "unknown3": "Táchira", + "unknown4": "Táchira" + }, + "coordinates": { + "latitude": 7.7618407320000005, + "longitude": -72.22382802 + } + }, + { + "id": 873857024, + "name": "Trujillo", + "translations": { + "japanese": "トルヒーヨ", + "english": "Trujillo", + "french": "Trujillo", + "german": "Trujillo", + "italian": "Trujillo", + "spanish": "Trujillo", + "chinese_simple": "特鲁希略州", + "korean": "트루히요", + "dutch": "Trujillo", + "portuguese": "Trujillo", + "russian": "Трухильо", + "chinese_traditional": "Trujillo", + "unknown1": "Trujillo", + "unknown2": "Trujillo", + "unknown3": "Trujillo", + "unknown4": "Trujillo" + }, + "coordinates": { + "latitude": 9.36584462, + "longitude": -70.427558487 + } + }, + { + "id": 873922560, + "name": "Yaracuy", + "translations": { + "japanese": "ヤラクイ", + "english": "Yaracuy", + "french": "Yaracuy", + "german": "Yaracuy", + "italian": "Yaracuy", + "spanish": "Yaracuy", + "chinese_simple": "亚拉奎州", + "korean": "야라쿠이", + "dutch": "Yaracuy", + "portuguese": "Yaracuy", + "russian": "Яракуй", + "chinese_traditional": "Yaracuy", + "unknown1": "Yaracuy", + "unknown2": "Yaracuy", + "unknown3": "Yaracuy", + "unknown4": "Yaracuy" + }, + "coordinates": { + "latitude": 10.338134648, + "longitude": -68.735659355 + } + }, + { + "id": 873988096, + "name": "Zulia", + "translations": { + "japanese": "スリア", + "english": "Zulia", + "french": "Zulia", + "german": "Zulia", + "italian": "Zulia", + "spanish": "Zulia", + "chinese_simple": "苏利亚州", + "korean": "술리아", + "dutch": "Zulia", + "portuguese": "Zulia", + "russian": "Сулия", + "chinese_traditional": "Zulia", + "unknown1": "Zulia", + "unknown2": "Zulia", + "unknown3": "Zulia", + "unknown4": "Zulia" + }, + "coordinates": { + "latitude": 10.62927234, + "longitude": -71.636057867 + } + }, + { + "id": 874053632, + "name": "Dependencias Federales", + "translations": { + "japanese": "連邦保護領", + "english": "Dependencias Federales", + "french": "Dépendances Fédérales", + "german": "Dependencias Federales", + "italian": "Dipendenze Federali", + "spanish": "Dependencias Federales", + "chinese_simple": "联邦属地", + "korean": "베네수엘라 연방자치령", + "dutch": "Federale gebieden", + "portuguese": "Dependências Federais", + "russian": "Федеральное владение", + "chinese_traditional": "Dependencias Federales", + "unknown1": "Dependencias Federales", + "unknown2": "Dependencias Federales", + "unknown3": "Dependencias Federales", + "unknown4": "Dependencias Federales" + }, + "coordinates": { + "latitude": 11.997070176, + "longitude": -65.994563034 + } + }, + { + "id": 874119168, + "name": "Vargas", + "translations": { + "japanese": "バルガス", + "english": "Vargas", + "french": "Vargas", + "german": "Vargas", + "italian": "Vargas", + "spanish": "Vargas", + "chinese_simple": "巴尔加斯州", + "korean": "바르가스", + "dutch": "Vargas", + "portuguese": "Vargas", + "russian": "Варгас", + "chinese_traditional": "Vargas", + "unknown1": "Vargas", + "unknown2": "Vargas", + "unknown3": "Vargas", + "unknown4": "Vargas" + }, + "coordinates": { + "latitude": 10.596313356, + "longitude": -66.928403464 + } + } + ] + }, + { + "id": 64, + "iso_code": "AL", + "name": "Albania", + "translations": { + "japanese": "アルバニア", + "english": "Albania", + "french": "Albanie", + "german": "Albanien", + "italian": "Albania", + "spanish": "Albania", + "chinese_simple": "阿尔巴尼亚", + "korean": "알바니아", + "dutch": "Albanië", + "portuguese": "Albânia", + "russian": "Албания", + "chinese_traditional": "Albania", + "unknown1": "Albania", + "unknown2": "Albania", + "unknown3": "Albania", + "unknown4": "Albania" + }, + "regions": [ + { + "id": 1073741824, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": 41.325072772, + "longitude": 19.824883011 + } + }, + { + "id": 1073872896, + "name": "Tirana", + "translations": { + "japanese": "ティラナ州", + "english": "Tirana", + "french": "Tirana", + "german": "Tirana", + "italian": "Tirana", + "spanish": "Tirana", + "chinese_simple": "地拉那州", + "korean": "티라나 주", + "dutch": "Tirana", + "portuguese": "Tirana", + "russian": "Тирана", + "chinese_traditional": "Tirana", + "unknown1": "Tirana", + "unknown2": "Tirana", + "unknown3": "Tirana", + "unknown4": "Tirana" + }, + "coordinates": { + "latitude": 41.325072772, + "longitude": 19.824883011 + } + }, + { + "id": 1073938432, + "name": "Berat", + "translations": { + "japanese": "ベラト州", + "english": "Berat", + "french": "Berat", + "german": "Berat", + "italian": "Berat", + "spanish": "Berat", + "chinese_simple": "培拉特州", + "korean": "베라트 주", + "dutch": "Berat", + "portuguese": "Berat", + "russian": "Берат", + "chinese_traditional": "Berat", + "unknown1": "Berat", + "unknown2": "Berat", + "unknown3": "Berat", + "unknown4": "Berat" + }, + "coordinates": { + "latitude": 40.70434524, + "longitude": 19.967705665 + } + }, + { + "id": 1074003968, + "name": "Dibër", + "translations": { + "japanese": "ディブラ州", + "english": "Dibër", + "french": "Dibër", + "german": "Dibra", + "italian": "Dibër", + "spanish": "Dibër", + "chinese_simple": "迪勃拉州", + "korean": "디브라 주", + "dutch": "Dibër", + "portuguese": "Dibër", + "russian": "Дибра", + "chinese_traditional": "Dibër", + "unknown1": "Dibër", + "unknown2": "Dibër", + "unknown3": "Dibër", + "unknown4": "Dibër" + }, + "coordinates": { + "latitude": 41.682128432, + "longitude": 20.440119059 + } + }, + { + "id": 1074069504, + "name": "Durrës", + "translations": { + "japanese": "デュラス州", + "english": "Durrës", + "french": "Durrës", + "german": "Durrës", + "italian": "Durazzo", + "spanish": "Durrës", + "chinese_simple": "都拉斯州", + "korean": "두러스 주", + "dutch": "Durrës", + "portuguese": "Durrës", + "russian": "Дуррес", + "chinese_traditional": "Durrës", + "unknown1": "Durrës", + "unknown2": "Durrës", + "unknown3": "Durrës", + "unknown4": "Durrës" + }, + "coordinates": { + "latitude": 41.319579608, + "longitude": 19.451346839 + } + }, + { + "id": 1074135040, + "name": "Elbasan", + "translations": { + "japanese": "エルバサン州", + "english": "Elbasan", + "french": "Elbasan", + "german": "Elbasan", + "italian": "Elbasan", + "spanish": "Elbasan", + "chinese_simple": "爱尔巴桑州", + "korean": "엘바산 주", + "dutch": "Elbasan", + "portuguese": "Elbasan", + "russian": "Эльбасан", + "chinese_traditional": "Elbasan", + "unknown1": "Elbasan", + "unknown2": "Elbasan", + "unknown3": "Elbasan", + "unknown4": "Elbasan" + }, + "coordinates": { + "latitude": 41.044921408, + "longitude": 19.99517156 + } + }, + { + "id": 1074200576, + "name": "Fier", + "translations": { + "japanese": "フィエル州", + "english": "Fier", + "french": "Fier", + "german": "Fier", + "italian": "Fier", + "spanish": "Fier", + "chinese_simple": "费里州", + "korean": "피에르 주", + "dutch": "Fier", + "portuguese": "Fier", + "russian": "Фиери", + "chinese_traditional": "Fier", + "unknown1": "Fier", + "unknown2": "Fier", + "unknown3": "Fier", + "unknown4": "Fier" + }, + "coordinates": { + "latitude": 40.720824732, + "longitude": 19.566703598 + } + }, + { + "id": 1074266112, + "name": "Gjirokastër", + "translations": { + "japanese": "ギロカストラ州", + "english": "Gjirokastër", + "french": "Gjirokastër", + "german": "Gjirokastra", + "italian": "Argirocastro", + "spanish": "Gjirokastra", + "chinese_simple": "吉诺卡斯特州", + "korean": "지로카스터르 주", + "dutch": "Gjirokastër", + "portuguese": "Gjirokastra", + "russian": "Гирокастра", + "chinese_traditional": "Gjirokastër", + "unknown1": "Gjirokastër", + "unknown2": "Gjirokastër", + "unknown3": "Gjirokastër", + "unknown4": "Gjirokastër" + }, + "coordinates": { + "latitude": 40.07263138, + "longitude": 20.143487393 + } + }, + { + "id": 1074331648, + "name": "Korçë", + "translations": { + "japanese": "コルチャ州", + "english": "Korçë", + "french": "Korçë", + "german": "Korça", + "italian": "Coriza", + "spanish": "Korçë", + "chinese_simple": "科尔察州", + "korean": "코르처 주", + "dutch": "Korçë", + "portuguese": "Korçë", + "russian": "Корча", + "chinese_traditional": "Korçë", + "unknown1": "Korçë", + "unknown2": "Korçë", + "unknown3": "Korçë", + "unknown4": "Korçë" + }, + "coordinates": { + "latitude": 40.616454616, + "longitude": 20.786189336 + } + }, + { + "id": 1074397184, + "name": "Kukës", + "translations": { + "japanese": "クケス州", + "english": "Kukës", + "french": "Kukës", + "german": "Kukës", + "italian": "Kukës", + "spanish": "Kukës", + "chinese_simple": "库克斯州", + "korean": "쿠커스 주", + "dutch": "Kukës", + "portuguese": "Kukës", + "russian": "Кукес", + "chinese_traditional": "Kukës", + "unknown1": "Kukës", + "unknown2": "Kukës", + "unknown3": "Kukës", + "unknown4": "Kukës" + }, + "coordinates": { + "latitude": 42.07763624, + "longitude": 20.429132701 + } + }, + { + "id": 1074462720, + "name": "Lezhë", + "translations": { + "japanese": "レジャ州", + "english": "Lezhë", + "french": "Lezhë", + "german": "Lezha", + "italian": "Alessio", + "spanish": "Lezhë", + "chinese_simple": "莱什州", + "korean": "레저 주", + "dutch": "Lezhë", + "portuguese": "Lezhë", + "russian": "Лежа", + "chinese_traditional": "Lezhë", + "unknown1": "Lezhë", + "unknown2": "Lezhë", + "unknown3": "Lezhë", + "unknown4": "Lezhë" + }, + "coordinates": { + "latitude": 41.781005384000004, + "longitude": 19.649101283 + } + }, + { + "id": 1074528256, + "name": "Shkodër", + "translations": { + "japanese": "シュコドラ州", + "english": "Shkodër", + "french": "Shkodër", + "german": "Shkodra", + "italian": "Scutari", + "spanish": "Shkodër", + "chinese_simple": "斯库台州", + "korean": "슈코더르 주", + "dutch": "Shkodër", + "portuguese": "Shkodër", + "russian": "Шкодер", + "chinese_traditional": "Shkodër", + "unknown1": "Shkodër", + "unknown2": "Shkodër", + "unknown3": "Shkodër", + "unknown4": "Shkodër" + }, + "coordinates": { + "latitude": 42.072143076, + "longitude": 19.522758166 + } + }, + { + "id": 1074593792, + "name": "Vlorë", + "translations": { + "japanese": "ヴロラ州", + "english": "Vlorë", + "french": "Vlorë", + "german": "Vlora", + "italian": "Valona", + "spanish": "Vlorë", + "chinese_simple": "发罗拉州", + "korean": "블로러 주", + "dutch": "Vlorë", + "portuguese": "Vlorë", + "russian": "Влёра", + "chinese_traditional": "Vlorë", + "unknown1": "Vlorë", + "unknown2": "Vlorë", + "unknown3": "Vlorë", + "unknown4": "Vlorë" + }, + "coordinates": { + "latitude": 40.468139188, + "longitude": 19.484305913 + } + } + ] + }, + { + "id": 65, + "iso_code": "AU", + "name": "Australia", + "translations": { + "japanese": "オーストラリア", + "english": "Australia", + "french": "Australie", + "german": "Australien", + "italian": "Australia", + "spanish": "Australia", + "chinese_simple": "澳大利亚", + "korean": "오스트레일리아", + "dutch": "Australië", + "portuguese": "Austrália", + "russian": "Австралия", + "chinese_traditional": "Australia", + "unknown1": "Australia", + "unknown2": "Australia", + "unknown3": "Australia", + "unknown4": "Australia" + }, + "regions": [ + { + "id": 1090519040, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": -33.88732974, + "longitude": 151.238204228 + } + }, + { + "id": 1090650112, + "name": "Australian Capital Territory", + "translations": { + "japanese": "オーストラリア首都特別地域", + "english": "Australian Capital Territory", + "french": "Territoire de la capitale australienne", + "german": "Australisches Hauptstadtterritorium", + "italian": "Territorio della Capitale Australiana", + "spanish": "Territorio de la Capital Australiana", + "chinese_simple": "澳大利亚首都直辖区", + "korean": "오스트레일리아캐피털테리토리", + "dutch": "Australisch Hoofdstedelijk Territorium", + "portuguese": "Território da Capital Australiana", + "russian": "Австралийская столичная территория", + "chinese_traditional": "Australian Capital Territory", + "unknown1": "Australian Capital Territory", + "unknown2": "Australian Capital Territory", + "unknown3": "Australian Capital Territory", + "unknown4": "Australian Capital Territory" + }, + "coordinates": { + "latitude": -35.348511364, + "longitude": 149.040932628 + } + }, + { + "id": 1090715648, + "name": "New South Wales", + "translations": { + "japanese": "ニューサウスウェールズ州", + "english": "New South Wales", + "french": "Nouvelle-Galles du Sud", + "german": "Neusüdwales", + "italian": "Nuovo Galles del Sud", + "spanish": "Nueva Gales del Sur", + "chinese_simple": "新南威尔士州", + "korean": "뉴사우스웨일스 주", + "dutch": "Nieuw-Zuid-Wales", + "portuguese": "Nova Gales do Sul", + "russian": "Новый Южный Уэльс", + "chinese_traditional": "New South Wales", + "unknown1": "New South Wales", + "unknown2": "New South Wales", + "unknown3": "New South Wales", + "unknown4": "New South Wales" + }, + "coordinates": { + "latitude": -33.88732974, + "longitude": 151.238204228 + } + }, + { + "id": 1090781184, + "name": "Northern Territory", + "translations": { + "japanese": "ノーザンテリトリー", + "english": "Northern Territory", + "french": "Territoire du Nord", + "german": "Nördliches Territorium", + "italian": "Territorio del Nord", + "spanish": "Territorio del Norte", + "chinese_simple": "北部地区", + "korean": "노던테리토리 주", + "dutch": "Noordelijk Territorium", + "portuguese": "Território do Norte", + "russian": "Северная территория", + "chinese_traditional": "Northern Territory", + "unknown1": "Northern Territory", + "unknown2": "Northern Territory", + "unknown3": "Northern Territory", + "unknown4": "Northern Territory" + }, + "coordinates": { + "latitude": -12.431031156000003, + "longitude": 130.842030601 + } + }, + { + "id": 1090846720, + "name": "Queensland", + "translations": { + "japanese": "クィーンズランド州", + "english": "Queensland", + "french": "Queensland", + "german": "Queensland", + "italian": "Queensland", + "spanish": "Queensland", + "chinese_simple": "昆士兰州", + "korean": "퀸즐랜드 주", + "dutch": "Queensland", + "portuguese": "Queensland", + "russian": "Квинсленд", + "chinese_traditional": "Queensland", + "unknown1": "Queensland", + "unknown2": "Queensland", + "unknown3": "Queensland", + "unknown4": "Queensland" + }, + "coordinates": { + "latitude": -27.449341532, + "longitude": 153.023487403 + } + }, + { + "id": 1090912256, + "name": "South Australia", + "translations": { + "japanese": "南オーストラリア州", + "english": "South Australia", + "french": "Australie-Méridionale", + "german": "Südaustralien", + "italian": "Australia Meridionale", + "spanish": "Australia Meridional", + "chinese_simple": "南澳大利亚州", + "korean": "사우스오스트레일리아 주", + "dutch": "Zuid-Australië", + "portuguese": "Austrália Meridional", + "russian": "Южная Австралия", + "chinese_traditional": "South Australia", + "unknown1": "South Australia", + "unknown2": "South Australia", + "unknown3": "South Australia", + "unknown4": "South Australia" + }, + "coordinates": { + "latitude": -34.914551408, + "longitude": 138.609385707 + } + }, + { + "id": 1090977792, + "name": "Tasmania", + "translations": { + "japanese": "タスマニア州", + "english": "Tasmania", + "french": "Tasmanie", + "german": "Tasmanien", + "italian": "Tasmania", + "spanish": "Tasmania", + "chinese_simple": "塔斯马尼亚州", + "korean": "태즈메이니아 주", + "dutch": "Tasmanië", + "portuguese": "Tasmânia", + "russian": "Тасмания", + "chinese_traditional": "Tasmania", + "unknown1": "Tasmania", + "unknown2": "Tasmania", + "unknown3": "Tasmania", + "unknown4": "Tasmania" + }, + "coordinates": { + "latitude": -42.846680223999996, + "longitude": 147.288608527 + } + }, + { + "id": 1091043328, + "name": "Victoria", + "translations": { + "japanese": "ヴィクトリア州", + "english": "Victoria", + "french": "Victoria", + "german": "Victoria", + "italian": "Victoria", + "spanish": "Victoria", + "chinese_simple": "维多利亚州", + "korean": "빅토리아 주", + "dutch": "Victoria", + "portuguese": "Victoria", + "russian": "Виктория", + "chinese_traditional": "Victoria", + "unknown1": "Victoria", + "unknown2": "Victoria", + "unknown3": "Victoria", + "unknown4": "Victoria" + }, + "coordinates": { + "latitude": -37.809448836, + "longitude": 144.96499381 + } + }, + { + "id": 1091108864, + "name": "Western Australia", + "translations": { + "japanese": "西オーストラリア州", + "english": "Western Australia", + "french": "Australie-Occidentale", + "german": "Westaustralien", + "italian": "Australia Occidentale", + "spanish": "Australia Occidental", + "chinese_simple": "西澳大利亚州", + "korean": "웨스턴오스트레일리아 주", + "dutch": "West-Australië", + "portuguese": "Austrália Ocidental", + "russian": "Западная Австралия", + "chinese_traditional": "Western Australia", + "unknown1": "Western Australia", + "unknown2": "Western Australia", + "unknown3": "Western Australia", + "unknown4": "Western Australia" + }, + "coordinates": { + "latitude": -31.931763355999998, + "longitude": 115.862131468 + } + } + ] + }, + { + "id": 66, + "iso_code": "AT", + "name": "Austria", + "translations": { + "japanese": "オーストリア", + "english": "Austria", + "french": "Autriche", + "german": "Österreich", + "italian": "Austria", + "spanish": "Austria", + "chinese_simple": "奥地利", + "korean": "오스트리아", + "dutch": "Oostenrijk", + "portuguese": "Áustria", + "russian": "Австрия", + "chinese_traditional": "Austria", + "unknown1": "Austria", + "unknown2": "Austria", + "unknown3": "Austria", + "unknown4": "Austria" + }, + "regions": [ + { + "id": 1107296256, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": 48.2025141, + "longitude": 16.36967342 + } + }, + { + "id": 1107427328, + "name": "Vienna", + "translations": { + "japanese": "ウィーン", + "english": "Vienna", + "french": "Vienne", + "german": "Wien", + "italian": "Vienna", + "spanish": "Viena", + "chinese_simple": "维也纳州", + "korean": "빈", + "dutch": "Wenen", + "portuguese": "Viena", + "russian": "Вена", + "chinese_traditional": "Vienna", + "unknown1": "Vienna", + "unknown2": "Vienna", + "unknown3": "Vienna", + "unknown4": "Vienna" + }, + "coordinates": { + "latitude": 48.2025141, + "longitude": 16.36967342 + } + }, + { + "id": 1107492864, + "name": "Burgenland", + "translations": { + "japanese": "ブルゲンラント州", + "english": "Burgenland", + "french": "Burgenland", + "german": "Burgenland", + "italian": "Burgenland", + "spanish": "Burgenland", + "chinese_simple": "布尔根兰州", + "korean": "부르겐란트 주", + "dutch": "Burgenland", + "portuguese": "Burgenland", + "russian": "Бургенланд", + "chinese_traditional": "Burgenland", + "unknown1": "Burgenland", + "unknown2": "Burgenland", + "unknown3": "Burgenland", + "unknown4": "Burgenland" + }, + "coordinates": { + "latitude": 47.84545844, + "longitude": 16.512496074 + } + }, + { + "id": 1107558400, + "name": "Carinthia", + "translations": { + "japanese": "ケルンテン州", + "english": "Carinthia", + "french": "Carinthie", + "german": "Kärnten", + "italian": "Carinzia", + "spanish": "Carintia", + "chinese_simple": "克恩顿州", + "korean": "케른텐 주", + "dutch": "Karinthië", + "portuguese": "Caríntia", + "russian": "Каринтия", + "chinese_traditional": "Carinthia", + "unknown1": "Carinthia", + "unknown2": "Carinthia", + "unknown3": "Carinthia", + "unknown4": "Carinthia" + }, + "coordinates": { + "latitude": 46.614989704, + "longitude": 14.298744937 + } + }, + { + "id": 1107623936, + "name": "Lower Austria", + "translations": { + "japanese": "ニーダー・エスターライヒ州", + "english": "Lower Austria", + "french": "Basse-Autriche", + "german": "Niederösterreich", + "italian": "Bassa Austria", + "spanish": "Baja Austria", + "chinese_simple": "下奥地利州", + "korean": "니더외스터라이히 주", + "dutch": "Neder-Oostenrijk", + "portuguese": "Baixa Áustria", + "russian": "Нижняя Австрия", + "chinese_traditional": "Lower Austria", + "unknown1": "Lower Austria", + "unknown2": "Lower Austria", + "unknown3": "Lower Austria", + "unknown4": "Lower Austria" + }, + "coordinates": { + "latitude": 48.197020936, + "longitude": 15.617107897 + } + }, + { + "id": 1107689472, + "name": "Upper Austria", + "translations": { + "japanese": "オーバー・エスターライヒ州", + "english": "Upper Austria", + "french": "Haute-Autriche", + "german": "Oberösterreich", + "italian": "Alta Austria", + "spanish": "Alta Austria", + "chinese_simple": "上奥地利州", + "korean": "오버외스터라이히 주", + "dutch": "Opper-Oostenrijk", + "portuguese": "Alta Áustria", + "russian": "Верхняя Австрия", + "chinese_traditional": "Upper Austria", + "unknown1": "Upper Austria", + "unknown2": "Upper Austria", + "unknown3": "Upper Austria", + "unknown4": "Upper Austria" + }, + "coordinates": { + "latitude": 48.295897888, + "longitude": 14.287758579 + } + }, + { + "id": 1107755008, + "name": "Salzburg", + "translations": { + "japanese": "ザルツブルク州", + "english": "Salzburg", + "french": "Salzbourg", + "german": "Salzburg", + "italian": "Salisburghese", + "spanish": "Salzburgo", + "chinese_simple": "萨尔茨堡州", + "korean": "잘츠부르크 주", + "dutch": "Salzburg", + "portuguese": "Salzburgo", + "russian": "Зальцбург", + "chinese_traditional": "Salzburg", + "unknown1": "Salzburg", + "unknown2": "Salzburg", + "unknown3": "Salzburg", + "unknown4": "Salzburg" + }, + "coordinates": { + "latitude": 47.796019964, + "longitude": 13.029820588 + } + }, + { + "id": 1107820544, + "name": "Styria", + "translations": { + "japanese": "シュタイアーマルク州", + "english": "Styria", + "french": "Styrie", + "german": "Steiermark", + "italian": "Stiria", + "spanish": "Estiria", + "chinese_simple": "施蒂利亚州", + "korean": "슈타이어마르크 주", + "dutch": "Stiermarken", + "portuguese": "Estíria", + "russian": "Штирия", + "chinese_traditional": "Styria", + "unknown1": "Styria", + "unknown2": "Styria", + "unknown3": "Styria", + "unknown4": "Styria" + }, + "coordinates": { + "latitude": 47.065429152, + "longitude": 15.43583299 + } + }, + { + "id": 1107886080, + "name": "Tyrol", + "translations": { + "japanese": "ティロル州", + "english": "Tyrol", + "french": "Tyrol", + "german": "Tirol", + "italian": "Tirolo", + "spanish": "Tirol", + "chinese_simple": "蒂罗尔州", + "korean": "티롤 주", + "dutch": "Tirol", + "portuguese": "Tirol", + "russian": "Тироль", + "chinese_traditional": "Tyrol", + "unknown1": "Tyrol", + "unknown2": "Tyrol", + "unknown3": "Tyrol", + "unknown4": "Tyrol" + }, + "coordinates": { + "latitude": 47.263183056, + "longitude": 11.381866888 + } + }, + { + "id": 1107951616, + "name": "Vorarlberg", + "translations": { + "japanese": "フォアアールベルク州", + "english": "Vorarlberg", + "french": "Vorarlberg", + "german": "Vorarlberg", + "italian": "Vorarlberg", + "spanish": "Vorarlberg", + "chinese_simple": "福拉尔贝格州", + "korean": "포어아를베르크 주", + "dutch": "Vorarlberg", + "portuguese": "Vorarlberg", + "russian": "Форарльберг", + "chinese_traditional": "Vorarlberg", + "unknown1": "Vorarlberg", + "unknown2": "Vorarlberg", + "unknown3": "Vorarlberg", + "unknown4": "Vorarlberg" + }, + "coordinates": { + "latitude": 47.499389108, + "longitude": 9.744899546 + } + } + ] + }, + { + "id": 67, + "iso_code": "BE", + "name": "Belgium", + "translations": { + "japanese": "ベルギー", + "english": "Belgium", + "french": "Belgique", + "german": "Belgien", + "italian": "Belgio", + "spanish": "Bélgica", + "chinese_simple": "比利时", + "korean": "벨기에", + "dutch": "België", + "portuguese": "Bélgica", + "russian": "Бельгия", + "chinese_traditional": "Belgium", + "unknown1": "Belgium", + "unknown2": "Belgium", + "unknown3": "Belgium", + "unknown4": "Belgium" + }, + "regions": [ + { + "id": 1124073472, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": 50.83923282, + "longitude": 4.367077305 + } + }, + { + "id": 1124204544, + "name": "Brussels Region", + "translations": { + "japanese": "ブリュッセル首都地域圏", + "english": "Brussels Region", + "french": "Région de Bruxelles-Capitale", + "german": "Region Brüssel-Hauptstadt", + "italian": "Regione di Bruxelles", + "spanish": "Región de Bruselas-Capital", + "chinese_simple": "布鲁塞尔首都大区", + "korean": "브뤼셀 지역", + "dutch": "Brussels Hoofdstedelijk Gewest", + "portuguese": "Região de Bruxelas - Capital", + "russian": "Брюссельский столичный округ", + "chinese_traditional": "Brussels Region", + "unknown1": "Brussels Region", + "unknown2": "Brussels Region", + "unknown3": "Brussels Region", + "unknown4": "Brussels Region" + }, + "coordinates": { + "latitude": 50.83923282, + "longitude": 4.367077305 + } + }, + { + "id": 1124270080, + "name": "Flanders", + "translations": { + "japanese": "フランデレン地域圏", + "english": "Flanders", + "french": "Région flamande", + "german": "Flandern", + "italian": "Fiandre", + "spanish": "Región de Flandes", + "chinese_simple": "佛兰德大区", + "korean": "플랑드르 지역", + "dutch": "Vlaanderen", + "portuguese": "Flandres", + "russian": "Фламандский регион", + "chinese_traditional": "Flanders", + "unknown1": "Flanders", + "unknown2": "Flanders", + "unknown3": "Flanders", + "unknown4": "Flanders" + }, + "coordinates": { + "latitude": 50.83923282, + "longitude": 4.367077305 + } + }, + { + "id": 1124335616, + "name": "Wallonia", + "translations": { + "japanese": "ワロン地域圏", + "english": "Wallonia", + "french": "Région wallonne", + "german": "Wallonien", + "italian": "Vallonia", + "spanish": "Región de Valonia", + "chinese_simple": "瓦隆大区", + "korean": "왈롱 지역", + "dutch": "Wallonië", + "portuguese": "Valónia", + "russian": "Валлонский регион", + "chinese_traditional": "Wallonia", + "unknown1": "Wallonia", + "unknown2": "Wallonia", + "unknown3": "Wallonia", + "unknown4": "Wallonia" + }, + "coordinates": { + "latitude": 50.465697668, + "longitude": 4.861463415 + } + } + ] + }, + { + "id": 68, + "iso_code": "BA", + "name": "Bosnia and Herzegovina", + "translations": { + "japanese": "ボスニア・ヘルツェゴビナ", + "english": "Bosnia and Herzegovina", + "french": "Bosnie-Herzégovine", + "german": "Bosnien-Herzegowina", + "italian": "Bosnia-Erzegovina", + "spanish": "Bosnia-Herzegovina", + "chinese_simple": "波斯尼亚和黑塞哥维那", + "korean": "보스니아헤르체고비나", + "dutch": "Bosnië en Herzegovina", + "portuguese": "Bósnia-Herzegovina", + "russian": "Босния и Герцеговина", + "chinese_traditional": "Bosnia and Herzegovina", + "unknown1": "Bosnia and Herzegovina", + "unknown2": "Bosnia and Herzegovina", + "unknown3": "Bosnia and Herzegovina", + "unknown4": "Bosnia and Herzegovina" + }, + "regions": [ + { + "id": 1140850688, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": 43.851928212, + "longitude": 18.407642829 + } + }, + { + "id": 1140981760, + "name": "Federation of Bosnia and Herzegovina", + "translations": { + "japanese": "ボスニア・ヘルツェゴビナ連邦", + "english": "Federation of Bosnia and Herzegovina", + "french": "Fédération de Bosnie-Herzégovine", + "german": "Föderation Bosnien und Herzegowina", + "italian": "Federazione di Bosnia-Erzegovina", + "spanish": "Federación de Bosnia-Herzegovina", + "chinese_simple": "波黑联邦", + "korean": "보스니아헤르체고비나 연방", + "dutch": "Moslim-Kroatische Federatie", + "portuguese": "Federação da Bósnia-Herzegovina", + "russian": "Федерация Боснии и Герцеговины", + "chinese_traditional": "Federation of Bosnia and Herzegovina", + "unknown1": "Federation of Bosnia and Herzegovina", + "unknown2": "Federation of Bosnia and Herzegovina", + "unknown3": "Federation of Bosnia and Herzegovina", + "unknown4": "Federation of Bosnia and Herzegovina" + }, + "coordinates": { + "latitude": 43.851928212, + "longitude": 18.407642829 + } + }, + { + "id": 1141047296, + "name": "Republika Srpska", + "translations": { + "japanese": "セルビア人共和国", + "english": "Republika Srpska", + "french": "République serbe de Bosnie", + "german": "Serbische Republik", + "italian": "Repubblica Serba di Bosnia-Erzegovina", + "spanish": "República Srpska", + "chinese_simple": "塞族共和国", + "korean": "스릅스카 공화국", + "dutch": "Servische Republiek", + "portuguese": "República Sérvia", + "russian": "Республика Сербская", + "chinese_traditional": "Republika Srpska", + "unknown1": "Republika Srpska", + "unknown2": "Republika Srpska", + "unknown3": "Republika Srpska", + "unknown4": "Republika Srpska" + }, + "coordinates": { + "latitude": 44.7692866, + "longitude": 17.19365027 + } + }, + { + "id": 1141112832, + "name": "Brčko District", + "translations": { + "japanese": "ブルチュコ", + "english": "Brčko District", + "french": "Brčko (district)", + "german": "Brčko-Distrikt", + "italian": "Distretto di Brčko", + "spanish": "Distrito de Brčko", + "chinese_simple": "布尔奇科特区", + "korean": "브르치코", + "dutch": "Brčko", + "portuguese": "Distrito de Brčko", + "russian": "Округ Брчко", + "chinese_traditional": "Brčko District", + "unknown1": "Brčko District", + "unknown2": "Brčko District", + "unknown3": "Brčko District", + "unknown4": "Brčko District" + }, + "coordinates": { + "latitude": 44.868163552, + "longitude": 18.803151717 + } + } + ] + }, + { + "id": 69, + "iso_code": "BW", + "name": "Botswana", + "translations": { + "japanese": "ボツワナ", + "english": "Botswana", + "french": "Botswana", + "german": "Botsuana", + "italian": "Botswana", + "spanish": "Botsuana", + "chinese_simple": "博茨瓦纳", + "korean": "보츠와나", + "dutch": "Botswana", + "portuguese": "Botsuana", + "russian": "Ботсвана", + "chinese_traditional": "Botswana", + "unknown1": "Botswana", + "unknown2": "Botswana", + "unknown3": "Botswana", + "unknown4": "Botswana" + }, + "regions": [ + { + "id": 1157627904, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": -24.647827891999995, + "longitude": 25.900338985 + } + }, + { + "id": 1157693440, + "name": "Botswana", + "translations": { + "japanese": "ボツワナ", + "english": "Botswana", + "french": "Botswana", + "german": "Botsuana", + "italian": "Botswana", + "spanish": "Botsuana", + "chinese_simple": "博茨瓦纳", + "korean": "보츠와나", + "dutch": "Botswana", + "portuguese": "Botsuana", + "russian": "Ботсвана", + "chinese_traditional": "Botswana", + "unknown1": "Botswana", + "unknown2": "Botswana", + "unknown3": "Botswana", + "unknown4": "Botswana" + }, + "coordinates": { + "latitude": -24.647827891999995, + "longitude": 25.900338985 + } + } + ] + }, + { + "id": 70, + "iso_code": "BG", + "name": "Bulgaria", + "translations": { + "japanese": "ブルガリア", + "english": "Bulgaria", + "french": "Bulgarie", + "german": "Bulgarien", + "italian": "Bulgaria", + "spanish": "Bulgaria", + "chinese_simple": "保加利亚", + "korean": "불가리아", + "dutch": "Bulgarije", + "portuguese": "Bulgária", + "russian": "Болгария", + "chinese_traditional": "Bulgaria", + "unknown1": "Bulgaria", + "unknown2": "Bulgaria", + "unknown3": "Bulgaria", + "unknown4": "Bulgaria" + }, + "regions": [ + { + "id": 1174405120, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": 42.703856936, + "longitude": 23.302065318 + } + }, + { + "id": 1174536192, + "name": "Sofia City", + "translations": { + "japanese": "ソフィア市", + "english": "Sofia City", + "french": "Sofia (ville)", + "german": "Sofia (Stadt)", + "italian": "Sofia", + "spanish": "Ciudad de Sofía", + "chinese_simple": "索非亚市", + "korean": "소피아", + "dutch": "Sofia (stad)", + "portuguese": "Sófia (cidade)", + "russian": "София", + "chinese_traditional": "Sofia City", + "unknown1": "Sofia City", + "unknown2": "Sofia City", + "unknown3": "Sofia City", + "unknown4": "Sofia City" + }, + "coordinates": { + "latitude": 42.703856936, + "longitude": 23.302065318 + } + }, + { + "id": 1174601728, + "name": "Sofia Province", + "translations": { + "japanese": "ソフィア州", + "english": "Sofia Province", + "french": "Sofia (oblast)", + "german": "Sofia (Region)", + "italian": "Regione di Sofia", + "spanish": "Provincia de Sofía", + "chinese_simple": "索非亚州", + "korean": "소피아 주", + "dutch": "Sofia (oblast)", + "portuguese": "Província de Sófia", + "russian": "Софийская область", + "chinese_traditional": "Sofia Province", + "unknown1": "Sofia Province", + "unknown2": "Sofia Province", + "unknown3": "Sofia Province", + "unknown4": "Sofia Province" + }, + "coordinates": { + "latitude": 42.703856936, + "longitude": 23.302065318 + } + }, + { + "id": 1174667264, + "name": "Blagoevgrad", + "translations": { + "japanese": "ブラゴエブグラト州", + "english": "Blagoevgrad", + "french": "Blagoevgrad", + "german": "Blagoewgrad", + "italian": "Blagoevgrad", + "spanish": "Blagoevgrad", + "chinese_simple": "布拉格耶夫格勒州", + "korean": "블라고예브그라드 주", + "dutch": "Blagoëvgrad", + "portuguese": "Blagoevgrad", + "russian": "Благоевград", + "chinese_traditional": "Blagoevgrad", + "unknown1": "Blagoevgrad", + "unknown2": "Blagoevgrad", + "unknown3": "Blagoevgrad", + "unknown4": "Blagoevgrad" + }, + "coordinates": { + "latitude": 42.017211436000004, + "longitude": 23.098817695 + } + }, + { + "id": 1174732800, + "name": "Pleven", + "translations": { + "japanese": "プレベン州", + "english": "Pleven", + "french": "Pleven", + "german": "Plewen", + "italian": "Pleven", + "spanish": "Pleven", + "chinese_simple": "普列文州", + "korean": "플레벤 주", + "dutch": "Pleven", + "portuguese": "Pleven", + "russian": "Плевен", + "chinese_traditional": "Pleven", + "unknown1": "Pleven", + "unknown2": "Pleven", + "unknown3": "Pleven", + "unknown4": "Pleven" + }, + "coordinates": { + "latitude": 43.417968256, + "longitude": 24.620428278 + } + }, + { + "id": 1174798336, + "name": "Vidin", + "translations": { + "japanese": "ビディン州", + "english": "Vidin", + "french": "Vidin", + "german": "Widin", + "italian": "Vidin", + "spanish": "Vidin", + "chinese_simple": "维丁州", + "korean": "비딘 주", + "dutch": "Vidin", + "portuguese": "Vidin", + "russian": "Видин", + "chinese_traditional": "Vidin", + "unknown1": "Vidin", + "unknown2": "Vidin", + "unknown3": "Vidin", + "unknown4": "Vidin" + }, + "coordinates": { + "latitude": 43.989257312, + "longitude": 22.873597356 + } + }, + { + "id": 1174863872, + "name": "Varna", + "translations": { + "japanese": "バルナ州", + "english": "Varna", + "french": "Varna", + "german": "Warna", + "italian": "Varna", + "spanish": "Varna", + "chinese_simple": "瓦尔纳州", + "korean": "바르나 주", + "dutch": "Varna", + "portuguese": "Varna", + "russian": "Варна", + "chinese_traditional": "Varna", + "unknown1": "Varna", + "unknown2": "Varna", + "unknown3": "Varna", + "unknown4": "Varna" + }, + "coordinates": { + "latitude": 43.20373486, + "longitude": 27.899856141 + } + }, + { + "id": 1174929408, + "name": "Burgas", + "translations": { + "japanese": "ブルガス州", + "english": "Burgas", + "french": "Bourgas", + "german": "Burgas", + "italian": "Burgas", + "spanish": "Burgas", + "chinese_simple": "布尔加斯州", + "korean": "부르가스 주", + "dutch": "Boergas", + "portuguese": "Burgas", + "russian": "Бургас", + "chinese_traditional": "Burgas", + "unknown1": "Burgas", + "unknown2": "Burgas", + "unknown3": "Burgas", + "unknown4": "Burgas" + }, + "coordinates": { + "latitude": 42.522582524, + "longitude": 27.454908642 + } + }, + { + "id": 1174994944, + "name": "Dobrich", + "translations": { + "japanese": "ドブリチ州", + "english": "Dobrich", + "french": "Dobritch", + "german": "Dobritsch", + "italian": "Dobrich", + "spanish": "Dobrich", + "chinese_simple": "多布里奇州", + "korean": "도브리치 주", + "dutch": "Dobritsj", + "portuguese": "Dobric", + "russian": "Добрич", + "chinese_traditional": "Dobrich", + "unknown1": "Dobrich", + "unknown2": "Dobrich", + "unknown3": "Dobrich", + "unknown4": "Dobrich" + }, + "coordinates": { + "latitude": 43.571776848, + "longitude": 27.833937993 + } + }, + { + "id": 1175060480, + "name": "Gabrovo", + "translations": { + "japanese": "ガブロボ州", + "english": "Gabrovo", + "french": "Gabrovo", + "german": "Gabrowo", + "italian": "Gabrovo", + "spanish": "Gabrovo", + "chinese_simple": "加布罗沃州", + "korean": "가브로보 주", + "dutch": "Gabrovo", + "portuguese": "Gabrovo", + "russian": "Габрово", + "chinese_traditional": "Gabrovo", + "unknown1": "Gabrovo", + "unknown2": "Gabrovo", + "unknown3": "Gabrovo", + "unknown4": "Gabrovo" + }, + "coordinates": { + "latitude": 42.890624512, + "longitude": 25.307075653 + } + }, + { + "id": 1175126016, + "name": "Haskovo", + "translations": { + "japanese": "ハスコボ州", + "english": "Haskovo", + "french": "Khaskovo", + "german": "Chaskowo", + "italian": "Haskovo", + "spanish": "Haskovo", + "chinese_simple": "哈斯科沃州", + "korean": "하스코보 주", + "dutch": "Chaskovo", + "portuguese": "Haskovo", + "russian": "Хасково", + "chinese_traditional": "Haskovo", + "unknown1": "Haskovo", + "unknown2": "Haskovo", + "unknown3": "Haskovo", + "unknown4": "Haskovo" + }, + "coordinates": { + "latitude": 41.934813976, + "longitude": 25.554268708 + } + }, + { + "id": 1175191552, + "name": "Yambol", + "translations": { + "japanese": "ヤンボル州", + "english": "Yambol", + "french": "Yambol", + "german": "Jambol", + "italian": "Jambol", + "spanish": "Yambol", + "chinese_simple": "扬博尔州", + "korean": "얌볼 주", + "dutch": "Jambol", + "portuguese": "Yambol", + "russian": "Ямбол", + "chinese_traditional": "Yambol", + "unknown1": "Yambol", + "unknown2": "Yambol", + "unknown3": "Yambol", + "unknown4": "Yambol" + }, + "coordinates": { + "latitude": 42.484130376, + "longitude": 26.515575033 + } + }, + { + "id": 1175257088, + "name": "Kardzhali", + "translations": { + "japanese": "クルジャリ州", + "english": "Kardzhali", + "french": "Kardjali", + "german": "Kardschali", + "italian": "Kardzhali", + "spanish": "Kardzhali", + "chinese_simple": "克尔贾利州", + "korean": "쿠르잘리 주", + "dutch": "Kardzjali", + "portuguese": "Kardzhali", + "russian": "Кырджали", + "chinese_traditional": "Kardzhali", + "unknown1": "Kardzhali", + "unknown2": "Kardzhali", + "unknown3": "Kardzhali", + "unknown4": "Kardzhali" + }, + "coordinates": { + "latitude": 41.63818312, + "longitude": 25.378486979999998 + } + }, + { + "id": 1175322624, + "name": "Kyustendil", + "translations": { + "japanese": "キュステンディル州", + "english": "Kyustendil", + "french": "Kyoustendil", + "german": "Kjustendil", + "italian": "Kjustendil", + "spanish": "Kyustendil", + "chinese_simple": "丘斯滕迪尔州", + "korean": "큐스텐딜 주", + "dutch": "Kjoestendil", + "portuguese": "Kyustendil", + "russian": "Кюстендил", + "chinese_traditional": "Kyustendil", + "unknown1": "Kyustendil", + "unknown2": "Kyustendil", + "unknown3": "Kyustendil", + "unknown4": "Kyustendil" + }, + "coordinates": { + "latitude": 42.286376472, + "longitude": 22.68682927 + } + }, + { + "id": 1175388160, + "name": "Lovech", + "translations": { + "japanese": "ロベチ州", + "english": "Lovech", + "french": "Lovetch", + "german": "Lowetsch", + "italian": "Lovech", + "spanish": "Lovech", + "chinese_simple": "洛维奇州", + "korean": "로베치 주", + "dutch": "Lovetsj", + "portuguese": "Lovech", + "russian": "Ловеч", + "chinese_traditional": "Lovech", + "unknown1": "Lovech", + "unknown2": "Lovech", + "unknown3": "Lovech", + "unknown4": "Lovech" + }, + "coordinates": { + "latitude": 43.132323728, + "longitude": 24.713812321 + } + }, + { + "id": 1175453696, + "name": "Montana", + "translations": { + "japanese": "モンタナ州", + "english": "Montana", + "french": "Montana", + "german": "Montana", + "italian": "Montana", + "spanish": "Montana", + "chinese_simple": "蒙塔纳州", + "korean": "몬타나 주", + "dutch": "Montana", + "portuguese": "Montana", + "russian": "Монтана", + "chinese_traditional": "Montana", + "unknown1": "Montana", + "unknown2": "Montana", + "unknown3": "Montana", + "unknown4": "Montana" + }, + "coordinates": { + "latitude": 43.412475092, + "longitude": 23.23614717 + } + }, + { + "id": 1175519232, + "name": "Pazardzhik", + "translations": { + "japanese": "パザルジク州", + "english": "Pazardzhik", + "french": "Pazardjik", + "german": "Pasardschik", + "italian": "Pazardzhik", + "spanish": "Pazardzhik", + "chinese_simple": "帕扎尔吉克州", + "korean": "파자르지크 주", + "dutch": "Pazardzjik", + "portuguese": "Pazardzhik", + "russian": "Пазарджик", + "chinese_traditional": "Pazardzhik", + "unknown1": "Pazardzhik", + "unknown2": "Pazardzhik", + "unknown3": "Pazardzhik", + "unknown4": "Pazardzhik" + }, + "coordinates": { + "latitude": 42.18749952, + "longitude": 24.33478297 + } + }, + { + "id": 1175584768, + "name": "Pernik", + "translations": { + "japanese": "ペルニク州", + "english": "Pernik", + "french": "Pernik", + "german": "Pernik", + "italian": "Pernik", + "spanish": "Pernik", + "chinese_simple": "佩尔尼克州", + "korean": "페르니크 주", + "dutch": "Pernik", + "portuguese": "Pernik", + "russian": "Перник", + "chinese_traditional": "Pernik", + "unknown1": "Pernik", + "unknown2": "Pernik", + "unknown3": "Pernik", + "unknown4": "Pernik" + }, + "coordinates": { + "latitude": 42.583007328, + "longitude": 23.005433652 + } + }, + { + "id": 1175650304, + "name": "Plovdiv", + "translations": { + "japanese": "プロブディフ州", + "english": "Plovdiv", + "french": "Plovdiv", + "german": "Plowdiw", + "italian": "Plovdiv", + "spanish": "Plovdiv", + "chinese_simple": "普罗夫迪夫州", + "korean": "플로브디브 주", + "dutch": "Plovdiv", + "portuguese": "Plovdiv", + "russian": "Пловдив", + "chinese_traditional": "Plovdiv", + "unknown1": "Plovdiv", + "unknown2": "Plovdiv", + "unknown3": "Plovdiv", + "unknown4": "Plovdiv" + }, + "coordinates": { + "latitude": 42.13256788, + "longitude": 24.757757753 + } + }, + { + "id": 1175715840, + "name": "Razgrad", + "translations": { + "japanese": "ラズグラド州", + "english": "Razgrad", + "french": "Razgrad", + "german": "Rasgrad", + "italian": "Razgrad", + "spanish": "Razgrad", + "chinese_simple": "拉兹格勒州", + "korean": "라즈그라드 주", + "dutch": "Razgrad", + "portuguese": "Razgrad", + "russian": "Разград", + "chinese_traditional": "Razgrad", + "unknown1": "Razgrad", + "unknown2": "Razgrad", + "unknown3": "Razgrad", + "unknown4": "Razgrad" + }, + "coordinates": { + "latitude": 43.5333247, + "longitude": 26.53205457 + } + }, + { + "id": 1175781376, + "name": "Ruse", + "translations": { + "japanese": "ルセ州", + "english": "Ruse", + "french": "Roussé", + "german": "Russe", + "italian": "Ruse", + "spanish": "Ruse", + "chinese_simple": "鲁塞州", + "korean": "루세 주", + "dutch": "Roese", + "portuguese": "Ruse", + "russian": "Русе", + "chinese_traditional": "Ruse", + "unknown1": "Ruse", + "unknown2": "Ruse", + "unknown3": "Ruse", + "unknown4": "Ruse" + }, + "coordinates": { + "latitude": 43.846435048000004, + "longitude": 25.966257133 + } + }, + { + "id": 1175846912, + "name": "Silistra", + "translations": { + "japanese": "シリストラ州", + "english": "Silistra", + "french": "Silistra", + "german": "Silistra", + "italian": "Silistra", + "spanish": "Silistra", + "chinese_simple": "锡利斯特拉州", + "korean": "실리스트라 주", + "dutch": "Silistra", + "portuguese": "Silistra", + "russian": "Силистра", + "chinese_traditional": "Silistra", + "unknown1": "Silistra", + "unknown2": "Silistra", + "unknown3": "Silistra", + "unknown4": "Silistra" + }, + "coordinates": { + "latitude": 44.082641100000004, + "longitude": 27.251661019 + } + }, + { + "id": 1175912448, + "name": "Sliven", + "translations": { + "japanese": "スリベン州", + "english": "Sliven", + "french": "Sliven", + "german": "Sliwen", + "italian": "Sliven", + "spanish": "Sliven", + "chinese_simple": "斯利文州", + "korean": "슬리벤 주", + "dutch": "Sliven", + "portuguese": "Sliven", + "russian": "Сливен", + "chinese_traditional": "Sliven", + "unknown1": "Sliven", + "unknown2": "Sliven", + "unknown3": "Sliven", + "unknown4": "Sliven" + }, + "coordinates": { + "latitude": 42.687377444, + "longitude": 26.328806947 + } + }, + { + "id": 1175977984, + "name": "Smolyan", + "translations": { + "japanese": "スモリャン州", + "english": "Smolyan", + "french": "Smolyan", + "german": "Smoljan", + "italian": "Smoljan", + "spanish": "Smolyan", + "chinese_simple": "斯莫梁州", + "korean": "스몰랸 주", + "dutch": "Smoljan", + "portuguese": "Smolyan", + "russian": "Смолян", + "chinese_traditional": "Smolyan", + "unknown1": "Smolyan", + "unknown2": "Smolyan", + "unknown3": "Smolyan", + "unknown4": "Smolyan" + }, + "coordinates": { + "latitude": 41.58325148, + "longitude": 24.697332784 + } + }, + { + "id": 1176043520, + "name": "Stara Zagora", + "translations": { + "japanese": "スタラ・ザゴラ州", + "english": "Stara Zagora", + "french": "Stara Zagora", + "german": "Stara Sagora", + "italian": "Stara Zagora", + "spanish": "Stara Zagora", + "chinese_simple": "旧扎戈拉州", + "korean": "스타라자고라 주", + "dutch": "Stara Zagora", + "portuguese": "Stara Zagora", + "russian": "Стара-Загора", + "chinese_traditional": "Stara Zagora", + "unknown1": "Stara Zagora", + "unknown2": "Stara Zagora", + "unknown3": "Stara Zagora", + "unknown4": "Stara Zagora" + }, + "coordinates": { + "latitude": 42.423705572, + "longitude": 25.636666393 + } + }, + { + "id": 1176109056, + "name": "Shumen", + "translations": { + "japanese": "シュメン州", + "english": "Shumen", + "french": "Choumen", + "german": "Schumen", + "italian": "Shumen", + "spanish": "Shumen", + "chinese_simple": "舒门州", + "korean": "슈멘 주", + "dutch": "Sjoemen", + "portuguese": "Shumen", + "russian": "Шумен", + "chinese_traditional": "Shumen", + "unknown1": "Shumen", + "unknown2": "Shumen", + "unknown3": "Shumen", + "unknown4": "Shumen" + }, + "coordinates": { + "latitude": 43.269652828, + "longitude": 26.922070279 + } + }, + { + "id": 1176174592, + "name": "Targovishte", + "translations": { + "japanese": "トゥルゴビシュテ州", + "english": "Targovishte", + "french": "Targovichte", + "german": "Targowischte", + "italian": "Targovishte", + "spanish": "Targovishte", + "chinese_simple": "特尔戈维什特州", + "korean": "투르고비슈테 주", + "dutch": "Targovisjte", + "portuguese": "Targovishte", + "russian": "Тырговиште", + "chinese_traditional": "Targovishte", + "unknown1": "Targovishte", + "unknown2": "Targovishte", + "unknown3": "Targovishte", + "unknown4": "Targovishte" + }, + "coordinates": { + "latitude": 43.247680172, + "longitude": 26.570506823 + } + }, + { + "id": 1176240128, + "name": "Veliko Tarnovo", + "translations": { + "japanese": "ベリコ・トゥルノボ州", + "english": "Veliko Tarnovo", + "french": "Veliko Tarnovo", + "german": "Weliko Tarnowo", + "italian": "Veliko Tarnovo", + "spanish": "Veliko Tarnovo", + "chinese_simple": "大特尔诺沃州", + "korean": "벨리코투르노보 주", + "dutch": "Veliko Tarnovo", + "portuguese": "Veliko Tarnovo", + "russian": "Велико-Тырново", + "chinese_traditional": "Veliko Tarnovo", + "unknown1": "Veliko Tarnovo", + "unknown2": "Veliko Tarnovo", + "unknown3": "Veliko Tarnovo", + "unknown4": "Veliko Tarnovo" + }, + "coordinates": { + "latitude": 43.082885252, + "longitude": 25.631173214 + } + }, + { + "id": 1176305664, + "name": "Vratsa", + "translations": { + "japanese": "ブラツァ州", + "english": "Vratsa", + "french": "Vratsa", + "german": "Wraza", + "italian": "Vratsa", + "spanish": "Vratsa", + "chinese_simple": "弗拉察州", + "korean": "브라차 주", + "dutch": "Vratsa", + "portuguese": "Vratsa", + "russian": "Враца", + "chinese_traditional": "Vratsa", + "unknown1": "Vratsa", + "unknown2": "Vratsa", + "unknown3": "Vratsa", + "unknown4": "Vratsa" + }, + "coordinates": { + "latitude": 43.20373486, + "longitude": 23.549258373 + } + } + ] + }, + { + "id": 71, + "iso_code": "HR", + "name": "Croatia", + "translations": { + "japanese": "クロアチア", + "english": "Croatia", + "french": "Croatie", + "german": "Kroatien", + "italian": "Croazia", + "spanish": "Croacia", + "chinese_simple": "克罗地亚", + "korean": "크로아티아", + "dutch": "Kroatië", + "portuguese": "Croácia", + "russian": "Хорватия", + "chinese_traditional": "Croatia", + "unknown1": "Croatia", + "unknown2": "Croatia", + "unknown3": "Croatia", + "unknown4": "Croatia" + }, + "regions": [ + { + "id": 1191182336, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": 45.81298776, + "longitude": 15.979657711 + } + }, + { + "id": 1191575552, + "name": "Zagreb", + "translations": { + "japanese": "ザグレブ直轄市", + "english": "Zagreb", + "french": "Zagreb (ville)", + "german": "Zagreb (Stadt)", + "italian": "Zagabria", + "spanish": "Ciudad de Zagreb", + "chinese_simple": "萨格勒布市", + "korean": "자그레브", + "dutch": "Zagreb (stad)", + "portuguese": "Zagreb (cidade)", + "russian": "Загреб", + "chinese_traditional": "Zagreb", + "unknown1": "Zagreb", + "unknown2": "Zagreb", + "unknown3": "Zagreb", + "unknown4": "Zagreb" + }, + "coordinates": { + "latitude": 45.81298776, + "longitude": 15.979657711 + } + }, + { + "id": 1191641088, + "name": "Bjelovar-Bilogora County", + "translations": { + "japanese": "ビェロヴァル=ビロゴラ郡", + "english": "Bjelovar-Bilogora County", + "french": "Bjelovar-Bilogora", + "german": "Bjelovar-Bilogora", + "italian": "Regione di Bjelovar e della Bilogora", + "spanish": "Condado de Bjelovar-Bilogora", + "chinese_simple": "别洛瓦尔-比洛戈拉县", + "korean": "벨로바르-빌로고라 군", + "dutch": "Bjelovar-Bilogora", + "portuguese": "Bjelovar-Bilogora (condado)", + "russian": "Беловарско-Билогорская жупания", + "chinese_traditional": "Bjelovar-Bilogora County", + "unknown1": "Bjelovar-Bilogora County", + "unknown2": "Bjelovar-Bilogora County", + "unknown3": "Bjelovar-Bilogora County", + "unknown4": "Bjelovar-Bilogora County" + }, + "coordinates": { + "latitude": 45.89538522, + "longitude": 16.831100456 + } + }, + { + "id": 1191706624, + "name": "Brod-Posavina County", + "translations": { + "japanese": "ブロド=ポサヴィナ郡", + "english": "Brod-Posavina County", + "french": "Brod-Posavina", + "german": "Brod-Posavina", + "italian": "Regione di Brod e della Posavina", + "spanish": "Condado de Brod-Posavina", + "chinese_simple": "布罗德-波萨维纳县", + "korean": "브로드-포사비나 군", + "dutch": "Brod-Posavina", + "portuguese": "Brod-Posavina (condado)", + "russian": "Бродско-Посавская жупания", + "chinese_traditional": "Brod-Posavina County", + "unknown1": "Brod-Posavina County", + "unknown2": "Brod-Posavina County", + "unknown3": "Brod-Posavina County", + "unknown4": "Brod-Posavina County" + }, + "coordinates": { + "latitude": 45.164794408, + "longitude": 18.012133941 + } + }, + { + "id": 1191772160, + "name": "Dubrovnik-Neretva County", + "translations": { + "japanese": "ドゥブロヴニク=ネレトヴァ郡", + "english": "Dubrovnik-Neretva County", + "french": "Dubrovnik-Neretva", + "german": "Dubrovnik-Neretva", + "italian": "Regione Raguseo-Narentana", + "spanish": "Condado de Dubrovnik-Neretva", + "chinese_simple": "杜布罗夫斯克-内雷特瓦县", + "korean": "두브로브니크-네레트바 군", + "dutch": "Dubrovnik-Neretva", + "portuguese": "Dubrovnik-Neretva (condado)", + "russian": "Дубровницко-Неретванская жупания", + "chinese_traditional": "Dubrovnik-Neretva County", + "unknown1": "Dubrovnik-Neretva County", + "unknown2": "Dubrovnik-Neretva County", + "unknown3": "Dubrovnik-Neretva County", + "unknown4": "Dubrovnik-Neretva County" + }, + "coordinates": { + "latitude": 42.637938968, + "longitude": 18.105517984 + } + }, + { + "id": 1191837696, + "name": "Istria County", + "translations": { + "japanese": "イストラ郡", + "english": "Istria County", + "french": "Istrie", + "german": "Istrien", + "italian": "Regione Istriana", + "spanish": "Condado de Istria", + "chinese_simple": "伊斯特拉县", + "korean": "이스트라 군", + "dutch": "Istrië", + "portuguese": "Istria (condado)", + "russian": "Истрийская жупания", + "chinese_traditional": "Istria County", + "unknown1": "Istria County", + "unknown2": "Istria County", + "unknown3": "Istria County", + "unknown4": "Istria County" + }, + "coordinates": { + "latitude": 45.230712376, + "longitude": 13.930701943999999 + } + }, + { + "id": 1191903232, + "name": "Karlovac County", + "translations": { + "japanese": "カルロヴァツ郡", + "english": "Karlovac County", + "french": "Karlovac", + "german": "Karlovac", + "italian": "Regione di Karlovac", + "spanish": "Condado de Karlovac", + "chinese_simple": "卡尔洛瓦茨县", + "korean": "카를로바츠 군", + "dutch": "Karlovac", + "portuguese": "Karlovac (condado)", + "russian": "Карловацкая жупания", + "chinese_traditional": "Karlovac County", + "unknown1": "Karlovac County", + "unknown2": "Karlovac County", + "unknown3": "Karlovac County", + "unknown4": "Karlovac County" + }, + "coordinates": { + "latitude": 45.477904756, + "longitude": 15.54569657 + } + }, + { + "id": 1191968768, + "name": "Koprivnica-Križevci County", + "translations": { + "japanese": "コプリヴニツァ=クリジェヴツィ郡", + "english": "Koprivnica-Križevci County", + "french": "Koprivnica-Križevci", + "german": "Koprivnica-Križevci", + "italian": "Regione di Koprivnica e Križevci", + "spanish": "Condado de Koprivnica-Križevci", + "chinese_simple": "科普里夫尼察-克里热夫齐县", + "korean": "코프리브니차-크리제브치 군", + "dutch": "Koprivnica-Križevci", + "portuguese": "Koprivnica-Križevci (condado)", + "russian": "Копривницко-Крижевацкая жупания", + "chinese_traditional": "Koprivnica-Križevci County", + "unknown1": "Koprivnica-Križevci County", + "unknown2": "Koprivnica-Križevci County", + "unknown3": "Koprivnica-Križevci County", + "unknown4": "Koprivnica-Križevci County" + }, + "coordinates": { + "latitude": 46.148070764, + "longitude": 16.814620919 + } + }, + { + "id": 1192034304, + "name": "Krapina-Zagorje County", + "translations": { + "japanese": "クラピナ=ザゴリエ郡", + "english": "Krapina-Zagorje County", + "french": "Krapina-Zagorje", + "german": "Krapina-Zagorje", + "italian": "Regione di Krapina e dello Zagorje", + "spanish": "Condado de Krapina-Zagorje", + "chinese_simple": "克拉皮纳-扎戈列县", + "korean": "크라피나-자고례 군", + "dutch": "Krapina-Zagorje", + "portuguese": "Krapina-Zagorje (condado)", + "russian": "Крапинско-Загорская жупания", + "chinese_traditional": "Krapina-Zagorje County", + "unknown1": "Krapina-Zagorje County", + "unknown2": "Krapina-Zagorje County", + "unknown3": "Krapina-Zagorje County", + "unknown4": "Krapina-Zagorje County" + }, + "coordinates": { + "latitude": 46.164550256, + "longitude": 15.880780489 + } + }, + { + "id": 1192099840, + "name": "Lika-Senj County", + "translations": { + "japanese": "リカ=セニ郡", + "english": "Lika-Senj County", + "french": "Lika-Senj", + "german": "Lika-Senj", + "italian": "Regione della Lika e di Segna", + "spanish": "Condado de Lika-Senj", + "chinese_simple": "利卡-塞尼县", + "korean": "리카-센 군", + "dutch": "Lika-Senj", + "portuguese": "Lika-Senj (condado)", + "russian": "Лицко-Сеньская жупания", + "chinese_traditional": "Lika-Senj County", + "unknown1": "Lika-Senj County", + "unknown2": "Lika-Senj County", + "unknown3": "Lika-Senj County", + "unknown4": "Lika-Senj County" + }, + "coordinates": { + "latitude": 44.538573712, + "longitude": 15.369914842 + } + }, + { + "id": 1192165376, + "name": "Međimurje County", + "translations": { + "japanese": "メジムリェ郡", + "english": "Međimurje County", + "french": "Međimurje", + "german": "Medimurje", + "italian": "Regione del Medjimurje", + "spanish": "Condado de Medimurje", + "chinese_simple": "梅吉穆列县", + "korean": "메지무례 군", + "dutch": "Medimurje", + "portuguese": "Medimurje (condado)", + "russian": "Меджимурская жупания", + "chinese_traditional": "Međimurje County", + "unknown1": "Međimurje County", + "unknown2": "Međimurje County", + "unknown3": "Međimurje County", + "unknown4": "Međimurje County" + }, + "coordinates": { + "latitude": 46.384276816, + "longitude": 16.430098389 + } + }, + { + "id": 1192230912, + "name": "Osijek-Baranja County", + "translations": { + "japanese": "オシエク=バラニャ郡", + "english": "Osijek-Baranja County", + "french": "Osijek-Baranja", + "german": "Osijek-Baranja", + "italian": "Regione di Osijek e della Baranja", + "spanish": "Condado de Osijek-Baranja", + "chinese_simple": "奥西耶克-巴拉尼亚县", + "korean": "오시예크-바라냐 군", + "dutch": "Osijek-Baranja", + "portuguese": "Osijek-Baranja (condado)", + "russian": "Осиецко-Бараньская жупания", + "chinese_traditional": "Osijek-Baranja County", + "unknown1": "Osijek-Baranja County", + "unknown2": "Osijek-Baranja County", + "unknown3": "Osijek-Baranja County", + "unknown4": "Osijek-Baranja County" + }, + "coordinates": { + "latitude": 45.554809052, + "longitude": 18.6768086 + } + }, + { + "id": 1192296448, + "name": "Požega-Slavonia County", + "translations": { + "japanese": "ポジェガ=スラヴォニア郡", + "english": "Požega-Slavonia County", + "french": "Požega-Slavonie", + "german": "Požega-Slawonien", + "italian": "Regione di Požega e della Slavonia", + "spanish": "Condado de Požega-Eslavonia", + "chinese_simple": "波热加-斯拉沃尼亚县", + "korean": "포제가-슬라보니아 군", + "dutch": "Požega-Slavonië", + "portuguese": "Pozega-Slavonia (condado)", + "russian": "Пожежско-Славонская жупания", + "chinese_traditional": "Požega-Slavonia County", + "unknown1": "Požega-Slavonia County", + "unknown2": "Požega-Slavonia County", + "unknown3": "Požega-Slavonia County", + "unknown4": "Požega-Slavonia County" + }, + "coordinates": { + "latitude": 45.335082492, + "longitude": 17.68803638 + } + }, + { + "id": 1192361984, + "name": "Primorje-Gorski Kotar County", + "translations": { + "japanese": "プリモリェ=ゴルスキ・コタル郡", + "english": "Primorje-Gorski Kotar County", + "french": "Primorje-Gorski Kotar", + "german": "Primorje-Gorski kotar", + "italian": "Regione Litoraneo-Montana", + "spanish": "Condado de Primorje-Gorski Kotar", + "chinese_simple": "滨海和山区县", + "korean": "프리모례-고르스키코타르 군", + "dutch": "Primorje-Gorski Kotar", + "portuguese": "Primorje-Gorski Kotar (condado)", + "russian": "Приморско-Горанская жупания", + "chinese_traditional": "Primorje-Gorski Kotar County", + "unknown1": "Primorje-Gorski Kotar County", + "unknown2": "Primorje-Gorski Kotar County", + "unknown3": "Primorje-Gorski Kotar County", + "unknown4": "Primorje-Gorski Kotar County" + }, + "coordinates": { + "latitude": 45.313109836, + "longitude": 14.414101696 + } + }, + { + "id": 1192427520, + "name": "Sisak-Moslavina County", + "translations": { + "japanese": "シサク=モスラヴィナ郡", + "english": "Sisak-Moslavina County", + "french": "Sisak-Moslavina", + "german": "Sisak-Moslavina", + "italian": "Regione di Sisak e della Moslavina", + "spanish": "Condado de Sisak-Moslavina", + "chinese_simple": "锡萨克-莫斯拉维纳县", + "korean": "시사크-모슬라비나 군", + "dutch": "Sisak-Moslavina", + "portuguese": "Sisak-Moslavina (condado)", + "russian": "Сисацко-Мославинская жупания", + "chinese_traditional": "Sisak-Moslavina County", + "unknown1": "Sisak-Moslavina County", + "unknown2": "ᑻ۱埣ἴk-Moslavina County", + "unknown3": "Sisak-Moslavina County", + "unknown4": "Sisak-Moslavina County" + }, + "coordinates": { + "latitude": 45.461425264, + "longitude": 16.380659778 + } + }, + { + "id": 1192493056, + "name": "Split-Dalmatia County", + "translations": { + "japanese": "スプリト=ダルマチア郡", + "english": "Split-Dalmatia County", + "french": "Split-Dalmatie", + "german": "Split-Dalmatien", + "italian": "Regione Spalatino-Dalmata", + "spanish": "Condado de Split-Dalmacia", + "chinese_simple": "斯普利特-达尔马提亚县", + "korean": "스플리트-달마티아 군", + "dutch": "Split-Dalmatië", + "portuguese": "Split-Dalmácia (condado)", + "russian": "Сплитско-Далматинская жупания", + "chinese_traditional": "Split-Dalmatia County", + "unknown1": "Split-Dalmatia County", + "unknown2": "Split-Dalmatia County", + "unknown3": "Split-Dalmatia County", + "unknown4": "Split-Dalmatia County" + }, + "coordinates": { + "latitude": 43.494872552, + "longitude": 16.430098389 + } + }, + { + "id": 1192558592, + "name": "Šibenik-Knin County", + "translations": { + "japanese": "シベニク=クニン郡", + "english": "Šibenik-Knin County", + "french": "Šibenik-Knin", + "german": "Šibenik-Knin", + "italian": "Regione di Sebenico e Tenin", + "spanish": "Condado de Šibenik-Knin", + "chinese_simple": "希贝尼克-克宁县", + "korean": "시베니크-크닌 군", + "dutch": "Šibenik-Knin", + "portuguese": "Šibenik-Knin (condado)", + "russian": "Шибенско-Книнская жупания", + "chinese_traditional": "Šibenik-Knin County", + "unknown1": "Šibenik-Knin County", + "unknown2": "Šibenik-Knin County", + "unknown3": "Šibenik-Knin County", + "unknown4": "Šibenik-Knin County" + }, + "coordinates": { + "latitude": 43.731078604, + "longitude": 15.886273668 + } + }, + { + "id": 1192624128, + "name": "Varaždin County", + "translations": { + "japanese": "ヴァラジュディン郡", + "english": "Varaždin County", + "french": "Varaždin", + "german": "Varaždin", + "italian": "Regione di Varasdino", + "spanish": "Condado de Varaždin", + "chinese_simple": "瓦拉日丁县", + "korean": "바라주딘 군", + "dutch": "Varaždin", + "portuguese": "Varaždin (condado)", + "russian": "Вараждинская жупания", + "chinese_traditional": "Varaždin County", + "unknown1": "Varaždin County", + "unknown2": "Varaždin County", + "unknown3": "Varaždin County", + "unknown4": "Varaždin County" + }, + "coordinates": { + "latitude": 46.296386192, + "longitude": 16.331221167 + } + }, + { + "id": 1192689664, + "name": "Virovitica-Podravina County", + "translations": { + "japanese": "ヴィロヴィティツァ=ポドラヴィナ郡", + "english": "Virovitica-Podravina County", + "french": "Virovitica-Podravina", + "german": "Virovitica-Podravina", + "italian": "Regione di Virovitica e della Podravina", + "spanish": "Condado de Virovitica-Podravina", + "chinese_simple": "维罗维蒂察-波德拉维纳县", + "korean": "비로비티차-포드라비나 군", + "dutch": "Virovitica-Podravina", + "portuguese": "Virovitica-Podravina (condado)", + "russian": "Вировитицко-Подравская жупания", + "chinese_traditional": "Virovitica-Podravina County", + "unknown1": "Virovitica-Podravina County", + "unknown2": "Virovitica-Podravina County", + "unknown3": "Virovitica-Podravina County", + "unknown4": "Virovitica-Podravina County" + }, + "coordinates": { + "latitude": 45.829467252, + "longitude": 17.380418356 + } + }, + { + "id": 1192755200, + "name": "Vukovar-Syrmia County", + "translations": { + "japanese": "ヴコヴァル=スリイェム郡", + "english": "Vukovar-Syrmia County", + "french": "Vukovar-Syrmie", + "german": "Vukovar-Srijem", + "italian": "Regione di Vukovar e della Sirmia", + "spanish": "Condado de Vukovar-Srijem", + "chinese_simple": "武科瓦尔-斯里耶姆县", + "korean": "부코바르-시르미아 군", + "dutch": "Vukovar-Srijem", + "portuguese": "Vukovar-Srijem (condado)", + "russian": "Вуковарско-Сремская жупания", + "chinese_traditional": "Vukovar-Syrmia County", + "unknown1": "Vukovar-Syrmia County", + "unknown2": "Vukovar-Syrmia County", + "unknown3": "Vukovar-Syrmia County", + "unknown4": "Vukovar-Syrmia County" + }, + "coordinates": { + "latitude": 45.34606882, + "longitude": 18.995412982 + } + }, + { + "id": 1192820736, + "name": "Zadar County", + "translations": { + "japanese": "ザダル郡", + "english": "Zadar County", + "french": "Zadar", + "german": "Zadar", + "italian": "Regione Zaratina", + "spanish": "Condado de Zadar", + "chinese_simple": "扎达尔县", + "korean": "자다르 군", + "dutch": "Zadar", + "portuguese": "Zadar (condado)", + "russian": "Задарская жупания", + "chinese_traditional": "Zadar County", + "unknown1": "Zadar County", + "unknown2": "Zadar County", + "unknown3": "Zadar County", + "unknown4": "Zadar County" + }, + "coordinates": { + "latitude": 44.11010692, + "longitude": 15.227092188 + } + }, + { + "id": 1192886272, + "name": "Zagreb County", + "translations": { + "japanese": "ザグレブ郡", + "english": "Zagreb County", + "french": "Zagreb (comitat)", + "german": "Zagreb", + "italian": "Regione Zagabrese", + "spanish": "Condado de Zagreb", + "chinese_simple": "萨格勒布县", + "korean": "자그레브 군", + "dutch": "Zagreb (provincie)", + "portuguese": "Zagrebe (condado)", + "russian": "Загребская жупания", + "chinese_traditional": "Zagreb County", + "unknown1": "Zagreb County", + "unknown2": "Zagreb County", + "unknown3": "Zagreb County", + "unknown4": "Zagreb County" + }, + "coordinates": { + "latitude": 45.81298776, + "longitude": 15.979657711 + } + } + ] + }, + { + "id": 72, + "iso_code": "CY", + "name": "Cyprus", + "translations": { + "japanese": "キプロス", + "english": "Cyprus", + "french": "Chypre", + "german": "Zypern", + "italian": "Cipro", + "spanish": "Chipre", + "chinese_simple": "塞浦路斯", + "korean": "키프로스", + "dutch": "Cyprus", + "portuguese": "Chipre", + "russian": "Кипр", + "chinese_traditional": "Cyprus", + "unknown1": "Cyprus", + "unknown2": "Cyprus", + "unknown3": "Cyprus", + "unknown4": "Cyprus" + }, + "regions": [ + { + "id": 1207959552, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": 35.161742764, + "longitude": 33.371062425 + } + }, + { + "id": 1208025088, + "name": "Cyprus", + "translations": { + "japanese": "キプロス", + "english": "Cyprus", + "french": "Chypre", + "german": "Zypern", + "italian": "Cipro", + "spanish": "Chipre", + "chinese_simple": "塞浦路斯", + "korean": "키프로스", + "dutch": "Cyprus", + "portuguese": "Chipre", + "russian": "Кипр", + "chinese_traditional": "Cyprus", + "unknown1": "Cyprus", + "unknown2": "Cyprus", + "unknown3": "Cyprus", + "unknown4": "Cyprus" + }, + "coordinates": { + "latitude": 35.161742764, + "longitude": 33.371062425 + } + } + ] + }, + { + "id": 73, + "iso_code": "CZ", + "name": "Czech Republic", + "translations": { + "japanese": "チェコ", + "english": "Czech Republic", + "french": "République tchèque", + "german": "Tschechische Republik", + "italian": "Repubblica Ceca", + "spanish": "República Checa", + "chinese_simple": "捷克共和国", + "korean": "체코", + "dutch": "Tsjechië", + "portuguese": "República Checa", + "russian": "Чешская Республика", + "chinese_traditional": "Czech Republic", + "unknown1": "Czech Republic", + "unknown2": "Czech Republic", + "unknown3": "Czech Republic", + "unknown4": "Czech Republic" + }, + "regions": [ + { + "id": 1224736768, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": 50.07018986, + "longitude": 14.430581233 + } + }, + { + "id": 1224867840, + "name": "Prague", + "translations": { + "japanese": "プラハ", + "english": "Prague", + "french": "Prague", + "german": "Prag", + "italian": "Praga", + "spanish": "Praga", + "chinese_simple": "布拉格市", + "korean": "프라하", + "dutch": "Praag", + "portuguese": "Praga", + "russian": "Прага", + "chinese_traditional": "Prague", + "unknown1": "Prague", + "unknown2": "Prague", + "unknown3": "Prague", + "unknown4": "Prague" + }, + "coordinates": { + "latitude": 50.07018986, + "longitude": 14.430581233 + } + }, + { + "id": 1224933376, + "name": "Central Bohemian Region", + "translations": { + "japanese": "中部ボヘミア地方", + "english": "Central Bohemian Region", + "french": "Bohême centrale", + "german": "Mittelböhmische Region", + "italian": "Boemia Centrale", + "spanish": "Región de Bohemia Central", + "chinese_simple": "中捷克州", + "korean": "스트르제도체스키 지방", + "dutch": "Midden-Bohemen", + "portuguese": "Boémia Central (região)", + "russian": "Среднечешский край", + "chinese_traditional": "Central Bohemian Region", + "unknown1": "Central Bohemian Region", + "unknown2": "Central Bohemian Region", + "unknown3": "Central Bohemian Region", + "unknown4": "Central Bohemian Region" + }, + "coordinates": { + "latitude": 50.07018986, + "longitude": 14.430581233 + } + }, + { + "id": 1224998912, + "name": "South Bohemian Region", + "translations": { + "japanese": "南ボヘミア地方", + "english": "South Bohemian Region", + "french": "Bohême du Sud", + "german": "Südböhmische Region", + "italian": "Boemia Meridionale", + "spanish": "Región de Bohemia Meridional", + "chinese_simple": "南捷克州", + "korean": "이호체스키 지방", + "dutch": "Zuid-Bohemen", + "portuguese": "Boémia do Sul (região)", + "russian": "Южночешский край", + "chinese_traditional": "South Bohemian Region", + "unknown1": "South Bohemian Region", + "unknown2": "South Bohemian Region", + "unknown3": "South Bohemian Region", + "unknown4": "South Bohemian Region" + }, + "coordinates": { + "latitude": 48.97155706, + "longitude": 14.469033486 + } + }, + { + "id": 1225064448, + "name": "Plzeň Region", + "translations": { + "japanese": "プルゼニ地方", + "english": "Plzeň Region", + "french": "Région de Pilsen", + "german": "Region Pilsen", + "italian": "Regione di Plseň", + "spanish": "Región de Pilsen", + "chinese_simple": "比尔森州", + "korean": "플젠 지방", + "dutch": "Pilsen", + "portuguese": "Plzen (região)", + "russian": "Пльзенский край", + "chinese_traditional": "Plzeň Region", + "unknown1": "Plzeň Region", + "unknown2": "Plzeň Region", + "unknown3": "Plzeň Region", + "unknown4": "Plzeň Region" + }, + "coordinates": { + "latitude": 49.735106856, + "longitude": 13.370397686 + } + }, + { + "id": 1225129984, + "name": "Karlovy Vary Region", + "translations": { + "japanese": "カールスバート地方", + "english": "Karlovy Vary Region", + "french": "Région de Karlovy Vary", + "german": "Region Karlsbad", + "italian": "Regione di Karlovy Vary", + "spanish": "Región de Karlovy Vary", + "chinese_simple": "卡罗维发利州", + "korean": "카를로비바리 지방", + "dutch": "Karlsbad", + "portuguese": "Karlovy Vary (região)", + "russian": "Карловарский край", + "chinese_traditional": "Karlovy Vary Region", + "unknown1": "Karlovy Vary Region", + "unknown2": "Karlovy Vary Region", + "unknown3": "Karlovy Vary Region", + "unknown4": "Karlovy Vary Region" + }, + "coordinates": { + "latitude": 50.223998452000004, + "longitude": 12.870518397 + } + }, + { + "id": 1225195520, + "name": "Ústí nad Labem Region", + "translations": { + "japanese": "ウースチー・ナド・ラベム地方", + "english": "Ústí nad Labem Region", + "french": "Région d'Ústí nad Labem", + "german": "Region Ústí", + "italian": "Regione di Ústí nad Labem", + "spanish": "Región de Ústí nad Labem", + "chinese_simple": "拉贝河畔乌斯季州", + "korean": "우스티나트라벰 지방", + "dutch": "Ústí nad Labem", + "portuguese": "Ústí nad Labem (região)", + "russian": "Устецкий край", + "chinese_traditional": "Ústí nad Labem Region", + "unknown1": "Ústí nad Labem Region", + "unknown2": "Ústí nad Labem Region", + "unknown3": "Ústí nad Labem Region", + "unknown4": "Ústí nad Labem Region" + }, + "coordinates": { + "latitude": 50.668944736, + "longitude": 14.084510955999999 + } + }, + { + "id": 1225261056, + "name": "Liberec Region", + "translations": { + "japanese": "リベレツ地方", + "english": "Liberec Region", + "french": "Région de Liberec", + "german": "Region Liberec", + "italian": "Regione di Liberec", + "spanish": "Región de Liberec", + "chinese_simple": "利贝雷克州", + "korean": "리베레츠 지방", + "dutch": "Liberec", + "portuguese": "Liberec (região)", + "russian": "Либерецкий край", + "chinese_traditional": "Liberec Region", + "unknown1": "Liberec Region", + "unknown2": "Liberec Region", + "unknown3": "Liberec Region", + "unknown4": "Liberec Region" + }, + "coordinates": { + "latitude": 50.756835360000004, + "longitude": 15.040324102 + } + }, + { + "id": 1225326592, + "name": "Hradec Králové Region", + "translations": { + "japanese": "フラデツ・クラロベ地方", + "english": "Hradec Králové Region", + "french": "Région de Hradec Králové", + "german": "Region Hradec Králové", + "italian": "Regione di Hradec Králové", + "spanish": "Región de Hradec Králové", + "chinese_simple": "赫拉德茨-克拉洛韦州", + "korean": "흐라데츠크랄로베 지방", + "dutch": "Hradec Králové", + "portuguese": "Hradec Králové (região)", + "russian": "Краловеградецкий край", + "chinese_traditional": "Hradec Králové Region", + "unknown1": "Hradec Králové Region", + "unknown2": "Hradec Králové Region", + "unknown3": "Hradec Králové Region", + "unknown4": "Hradec Králové Region" + }, + "coordinates": { + "latitude": 50.202025796, + "longitude": 15.803875983 + } + }, + { + "id": 1225392128, + "name": "Pardubice Region", + "translations": { + "japanese": "パルドゥビツェ地方", + "english": "Pardubice Region", + "french": "Région de Pardubice", + "german": "Region Pardubice", + "italian": "Regione di Pardubice", + "spanish": "Región de Pardubice", + "chinese_simple": "帕尔杜比采州", + "korean": "파르두비체 지방", + "dutch": "Pardubice", + "portuguese": "Pardubice (região)", + "russian": "Пардубицкий край", + "chinese_traditional": "Pardubice Region", + "unknown1": "Pardubice Region", + "unknown2": "Pardubice Region", + "unknown3": "Pardubice Region", + "unknown4": "Pardubice Region" + }, + "coordinates": { + "latitude": 50.020751384, + "longitude": 15.76542373 + } + }, + { + "id": 1225457664, + "name": "Olomouc Region", + "translations": { + "japanese": "オロモウツ地方", + "english": "Olomouc Region", + "french": "Région d'Olomouc", + "german": "Region Olmütz", + "italian": "Regione di Olomouc", + "spanish": "Región de Olomouc", + "chinese_simple": "奥洛穆茨州", + "korean": "올로모우츠 지방", + "dutch": "Olomouc", + "portuguese": "Olomouc (região)", + "russian": "Оломоуцкий край", + "chinese_traditional": "Olomouc Region", + "unknown1": "Olomouc Region", + "unknown2": "Olomouc Region", + "unknown3": "Olomouc Region", + "unknown4": "Olomouc Region" + }, + "coordinates": { + "latitude": 49.586791428, + "longitude": 17.254075239 + } + }, + { + "id": 1225523200, + "name": "Moravian-Silesian Region", + "translations": { + "japanese": "モラビア・シレジア地方", + "english": "Moravian-Silesian Region", + "french": "Moravie-Silésie", + "german": "Region Mährisch-Schlesien", + "italian": "Regione di Moravia-Slesia", + "spanish": "Región de Moravia-Silesia", + "chinese_simple": "摩拉维亚-西里西亚州", + "korean": "모라비아실레지아 지방", + "dutch": "Moravië-Silezië", + "portuguese": "Morávia-Silésia (região)", + "russian": "Моравскосилезский край", + "chinese_traditional": "Moravian-Silesian Region", + "unknown1": "Moravian-Silesian Region", + "unknown2": "Moravian-Silesian Region", + "unknown3": "Moravian-Silesian Region", + "unknown4": "Moravian-Silesian Region" + }, + "coordinates": { + "latitude": 49.817504316, + "longitude": 18.253833817 + } + }, + { + "id": 1225588736, + "name": "South Moravian Region", + "translations": { + "japanese": "南モラビア地方", + "english": "South Moravian Region", + "french": "Moravie du Sud", + "german": "Südmährische Region", + "italian": "Moravia Meridionale", + "spanish": "Región de Moravia Meridional", + "chinese_simple": "南摩拉维亚州", + "korean": "이호모라프스키 지방", + "dutch": "Zuid-Moravië", + "portuguese": "Morávia do Sul (região)", + "russian": "Южноморавский край", + "chinese_traditional": "South Moravian Region", + "unknown1": "South Moravian Region", + "unknown2": "South Moravian Region", + "unknown3": "South Moravian Region", + "unknown4": "South Moravian Region" + }, + "coordinates": { + "latitude": 49.196776784, + "longitude": 16.605880116999998 + } + }, + { + "id": 1225654272, + "name": "Zlín Region", + "translations": { + "japanese": "ズリン地方", + "english": "Zlín Region", + "french": "Région de Zlín", + "german": "Region Zlin", + "italian": "Regione di Zlín", + "spanish": "Región de Zlín", + "chinese_simple": "兹林州", + "korean": "즐린 지방", + "dutch": "Zlín", + "portuguese": "Zlín (região)", + "russian": "Злинский край", + "chinese_traditional": "Zlín Region", + "unknown1": "Zlín Region", + "unknown2": "Zlín Region", + "unknown3": "Zlín Region", + "unknown4": "Zlín Region" + }, + "coordinates": { + "latitude": 49.21874944, + "longitude": 17.649584127 + } + }, + { + "id": 1225719808, + "name": "Vysočina Region", + "translations": { + "japanese": "ヴィソチナ地方", + "english": "Vysočina Region", + "french": "Vysočina", + "german": "Region Vysočina", + "italian": "Regione di Vysočina", + "spanish": "Región de Vysočina", + "chinese_simple": "维索基纳州", + "korean": "비소치나 지방", + "dutch": "Vysočina", + "portuguese": "Vysočina (região)", + "russian": "Край Высочина", + "chinese_traditional": "Vysočina Region", + "unknown1": "Vysočina Region", + "unknown2": "Vysočina Region", + "unknown3": "Vysočina Region", + "unknown4": "Vysočina Region" + }, + "coordinates": { + "latitude": 49.389037524, + "longitude": 15.584148823 + } + } + ] + }, + { + "id": 74, + "iso_code": "DK", + "name": "Denmark (Kingdom of)", + "translations": { + "japanese": "デンマーク", + "english": "Denmark (Kingdom of)", + "french": "Danemark (Royaume)", + "german": "Dänemark (Königreich)", + "italian": "Danimarca (Regno di)", + "spanish": "Dinamarca (Reino)", + "chinese_simple": "丹麦", + "korean": "덴마크", + "dutch": "Denemarken (koninkrijk)", + "portuguese": "Dinamarca (Reino)", + "russian": "Дания (Королевство)", + "chinese_traditional": "Denmark (Kingdom of)", + "unknown1": "Denmark (Kingdom of)", + "unknown2": "Denmark (Kingdom of)", + "unknown3": "Denmark (Kingdom of)", + "unknown4": "Denmark (Kingdom of)" + }, + "regions": [ + { + "id": 1241513984, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": 55.931395848, + "longitude": 12.315707318 + } + }, + { + "id": 1242693632, + "name": "Greenland", + "translations": { + "japanese": "グリーンランド", + "english": "Greenland", + "french": "Groenland", + "german": "Grönland", + "italian": "Groenlandia", + "spanish": "Groenlandia", + "chinese_simple": "格陵兰", + "korean": "그린란드", + "dutch": "Groenland", + "portuguese": "Gronelândia", + "russian": "Гренландия", + "chinese_traditional": "Greenland", + "unknown1": "Greenland", + "unknown2": "Greenland", + "unknown3": "Greenland", + "unknown4": "Greenland" + }, + "coordinates": { + "latitude": 64.182128176, + "longitude": -51.71779081299999 + } + }, + { + "id": 1242759168, + "name": "Capital Region of Denmark", + "translations": { + "japanese": "デンマーク首都地域", + "english": "Capital Region of Denmark", + "french": "Hovedstaden", + "german": "Hauptstadtregion", + "italian": "Hovedstaden", + "spanish": "Hovedstaden", + "chinese_simple": "首都大区", + "korean": "덴마크 수도권 지역", + "dutch": "Hovedstaden", + "portuguese": "Hovedstaden", + "russian": "Столичная область", + "chinese_traditional": "Capital Region of Denmark", + "unknown1": "Capital Region of Denmark", + "unknown2": "Capital Region of Denmark", + "unknown3": "Capital Region of Denmark", + "unknown4": "Capital Region of Denmark" + }, + "coordinates": { + "latitude": 55.931395848, + "longitude": 12.315707318 + } + }, + { + "id": 1242824704, + "name": "Central Denmark Region", + "translations": { + "japanese": "中央ユラン地域", + "english": "Central Denmark Region", + "french": "Jutland-Central", + "german": "Mitteljütland", + "italian": "Jutland Centrale", + "spanish": "Jutlandia Central", + "chinese_simple": "中日德兰大区", + "korean": "중부 덴마크 지역", + "dutch": "Midden-Jutland", + "portuguese": "Jutlândia Central", + "russian": "Центральная Ютландия", + "chinese_traditional": "Central Denmark Region", + "unknown1": "Central Denmark Region", + "unknown2": "Central Denmark Region", + "unknown3": "Central Denmark Region", + "unknown4": "Central Denmark Region" + }, + "coordinates": { + "latitude": 56.447753264, + "longitude": 9.398829269 + } + }, + { + "id": 1242890240, + "name": "North Denmark Region", + "translations": { + "japanese": "北ユラン地域", + "english": "North Denmark Region", + "french": "Jutland-du-Nord", + "german": "Nordjütland", + "italian": "Jutland Settentrionale", + "spanish": "Jutlandia Septentrional", + "chinese_simple": "北日德兰大区", + "korean": "북부 덴마크 지역", + "dutch": "Noord-Jutland", + "portuguese": "Jutlândia do Norte", + "russian": "Северная Ютландия", + "chinese_traditional": "North Denmark Region", + "unknown1": "North Denmark Region", + "unknown2": "North Denmark Region", + "unknown3": "North Denmark Region", + "unknown4": "North Denmark Region" + }, + "coordinates": { + "latitude": 57.04650814, + "longitude": 9.915188095 + } + }, + { + "id": 1242955776, + "name": "Region Zealand", + "translations": { + "japanese": "シェラン地域", + "english": "Region Zealand", + "french": "Zélande-du-Nord", + "german": "Seeland", + "italian": "Sjælland", + "spanish": "Selandia", + "chinese_simple": "西兰大区", + "korean": "셸란 지역", + "dutch": "Seeland", + "portuguese": "Zelândia", + "russian": "Зеландия", + "chinese_traditional": "Region Zealand", + "unknown1": "Region Zealand", + "unknown2": "Region Zealand", + "unknown3": "Region Zealand", + "unknown4": "Region Zealand" + }, + "coordinates": { + "latitude": 55.431517924, + "longitude": 11.563141795 + } + }, + { + "id": 1243021312, + "name": "Region of Southern Denmark", + "translations": { + "japanese": "南デンマーク地域", + "english": "Region of Southern Denmark", + "french": "Danemark-du-Sud", + "german": "Süddänemark", + "italian": "Syddanmark", + "spanish": "Dinamarca Meridional", + "chinese_simple": "南丹麦大区", + "korean": "남부 덴마크 지역", + "dutch": "Zuid-Denemarken", + "portuguese": "Dinamarca do Sul", + "russian": "Южная Дания", + "chinese_traditional": "Region of Southern Denmark", + "unknown1": "Region of Southern Denmark", + "unknown2": "Region of Southern Denmark", + "unknown3": "Region of Southern Denmark", + "unknown4": "씒⭤ꥠ軷on of Southern Denmark" + }, + "coordinates": { + "latitude": 55.711669288, + "longitude": 9.530665565 + } + }, + { + "id": 1243086848, + "name": "Faroe Islands", + "translations": { + "japanese": "フェロー諸島", + "english": "Faroe Islands", + "french": "Îles Féroé", + "german": "Färöer", + "italian": "Isole Fær Øer", + "spanish": "Islas Feroe", + "chinese_simple": "法罗群岛", + "korean": "페로 제도", + "dutch": "Faeröer", + "portuguese": "Ilhas Faroé", + "russian": "Фарерские острова", + "chinese_traditional": "Faroe Islands", + "unknown1": "Faroe Islands", + "unknown2": "Faroe Islands", + "unknown3": "Faroe Islands", + "unknown4": "Faroe Islands" + }, + "coordinates": { + "latitude": 62.006835232, + "longitude": -6.761613877000002 + } + } + ] + }, + { + "id": 75, + "iso_code": "EE", + "name": "Estonia", + "translations": { + "japanese": "エストニア", + "english": "Estonia", + "french": "Estonie", + "german": "Estland", + "italian": "Estonia", + "spanish": "Estonia", + "chinese_simple": "爱沙尼亚", + "korean": "에스토니아", + "dutch": "Estland", + "portuguese": "Estónia", + "russian": "Эстония", + "chinese_traditional": "Estonia", + "unknown1": "Estonia", + "unknown2": "Estonia", + "unknown3": "Estonia", + "unknown4": "Estonia" + }, + "regions": [ + { + "id": 1258291200, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": 59.430541316, + "longitude": 24.730291858 + } + }, + { + "id": 1258356736, + "name": "Estonia", + "translations": { + "japanese": "エストニア", + "english": "Estonia", + "french": "Estonie", + "german": "Estland", + "italian": "Estonia", + "spanish": "Estonia", + "chinese_simple": "爱沙尼亚", + "korean": "에스토니아", + "dutch": "Estland", + "portuguese": "Estónia", + "russian": "Эстония", + "chinese_traditional": "Estonia", + "unknown1": "Estonia", + "unknown2": "Estonia", + "unknown3": "Estonia", + "unknown4": "Estonia" + }, + "coordinates": { + "latitude": 59.430541316, + "longitude": 24.730291858 + } + } + ] + }, + { + "id": 76, + "iso_code": "FI", + "name": "Finland", + "translations": { + "japanese": "フィンランド", + "english": "Finland", + "french": "Finlande", + "german": "Finnland", + "italian": "Finlandia", + "spanish": "Finlandia", + "chinese_simple": "芬兰", + "korean": "핀란드", + "dutch": "Finland", + "portuguese": "Finlândia", + "russian": "Финляндия", + "chinese_traditional": "Finland", + "unknown1": "Finland", + "unknown2": "Finland", + "unknown3": "Finland", + "unknown4": "Finland" + }, + "regions": [ + { + "id": 1275068416, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": 60.166625292, + "longitude": 24.933539481 + } + }, + { + "id": 1275592704, + "name": "Uusimaa / Nyland", + "translations": { + "japanese": "ウーシマー県", + "english": "Uusimaa / Nyland", + "french": "Uusimaa", + "german": "Uusimaa", + "italian": "Uusimaa", + "spanish": "Uusimaa", + "chinese_simple": "新地区", + "korean": "우시마 주", + "dutch": "Uusimaa", + "portuguese": "Uusimaa", + "russian": "Уусимаа", + "chinese_traditional": "Uusimaa / Nyland", + "unknown1": "Uusimaa / Nyland", + "unknown2": "Uusimaa / Nyland", + "unknown3": "Uusimaa / Nyland", + "unknown4": "Uusimaa / Nyland" + }, + "coordinates": { + "latitude": 60.166625292, + "longitude": 24.933539481 + } + }, + { + "id": 1275658240, + "name": "Lappi / Lapland", + "translations": { + "japanese": "ラッピ州", + "english": "Lappi / Lapland", + "french": "Laponie", + "german": "Lappland", + "italian": "Lapponia", + "spanish": "Laponia finlandesa", + "chinese_simple": "拉普兰省", + "korean": "라피 주", + "dutch": "Lapland", + "portuguese": "Lapónia", + "russian": "Лапландия", + "chinese_traditional": "Lappi / Lapland", + "unknown1": "Lappi / Lapland", + "unknown2": "Lappi / Lapland", + "unknown3": "Lappi / Lapland", + "unknown4": "Lappi / Lapland" + }, + "coordinates": { + "latitude": 66.49475022, + "longitude": 25.730050436 + } + }, + { + "id": 1275723776, + "name": "Pohjois-Pohjanmaa / Norra Österbotten", + "translations": { + "japanese": "北ポフヤンマー県", + "english": "Pohjois-Pohjanmaa / Norra Österbotten", + "french": "Ostrobotnie du Nord", + "german": "Nordösterbotten", + "italian": "Ostrobotnia Settentrionale", + "spanish": "Ostrobothnia del Norte", + "chinese_simple": "北博滕区", + "korean": "북오스트로보트니아 주", + "dutch": "Noord-Österbotten", + "portuguese": "Ostrobótnia do Norte", + "russian": "Северная Остроботния", + "chinese_traditional": "Pohjois-Pohjanmaa / Norra Österbotten", + "unknown1": "Pohjois-Pohjanmaa / Norra Österbotten", + "unknown2": "Pohjois-Pohjanmaa / Norra Österbotten", + "unknown3": "Pohjois-Pohjanmaa / Norra Österbotten", + "unknown4": "Pohjois-Pohjanmaa / Norra Österbotten" + }, + "coordinates": { + "latitude": 65.01159594, + "longitude": 25.466377844 + } + }, + { + "id": 1275789312, + "name": "Kainuu / Kajanaland", + "translations": { + "japanese": "カイヌー県", + "english": "Kainuu / Kajanaland", + "french": "Kainuu", + "german": "Kainuu", + "italian": "Kainuu", + "spanish": "Kainuu", + "chinese_simple": "凯努区", + "korean": "카이누 주", + "dutch": "Kainuu", + "portuguese": "Kainuu", + "russian": "Кайнуу", + "chinese_traditional": "Kainuu / Kajanaland", + "unknown1": "Kainuu / Kajanaland", + "unknown2": "Kainuu / Kajanaland", + "unknown3": "Kainuu / Kajanaland", + "unknown4": "Kainuu / Kajanaland" + }, + "coordinates": { + "latitude": 64.220580324, + "longitude": 27.729567592 + } + }, + { + "id": 1275854848, + "name": "Pohjois-Karjala / Norra Karelen", + "translations": { + "japanese": "北カレリア県", + "english": "Pohjois-Karjala / Norra Karelen", + "french": "Carélie du Nord", + "german": "Nordkarelien", + "italian": "Carelia Settentrionale", + "spanish": "Karelia del Norte", + "chinese_simple": "北卡累利阿区", + "korean": "북카렐리아 주", + "dutch": "Noord-Karelië", + "portuguese": "Carélia do Norte", + "russian": "Северная Карелия", + "chinese_traditional": "Pohjois-Karjala / Norra Karelen", + "unknown1": "Pohjois-Karjala / Norra Karelen", + "unknown2": "Pohjois-Karjala / Norra Karelen", + "unknown3": "Pohjois-Karjala / Norra Karelen", + "unknown4": "Pohjois-Karjala / Norra Karelen" + }, + "coordinates": { + "latitude": 62.59460378, + "longitude": 29.745564285 + } + }, + { + "id": 1275920384, + "name": "Pohjois-Savo / Norra Savolax", + "translations": { + "japanese": "北サヴォ県", + "english": "Pohjois-Savo / Norra Savolax", + "french": "Savonie du Nord", + "german": "Nordsavo", + "italian": "Savo Settentrionale", + "spanish": "Savonia del Norte", + "chinese_simple": "北萨沃区", + "korean": "북사보 주", + "dutch": "Noord-Savo", + "portuguese": "Savónia do Norte", + "russian": "Северное Саво", + "chinese_traditional": "Pohjois-Savo / Norra Savolax", + "unknown1": "Pohjois-Savo / Norra Savolax", + "unknown2": "Pohjois-Savo / Norra Savolax", + "unknown3": "Pohjois-Savo / Norra Savolax", + "unknown4": "Pohjois-Savo / Norra Savolax" + }, + "coordinates": { + "latitude": 62.891234636, + "longitude": 27.674635802 + } + }, + { + "id": 1275985920, + "name": "Etelä-Savo / Södra Savolax", + "translations": { + "japanese": "南サヴォ県", + "english": "Etelä-Savo / Södra Savolax", + "french": "Savonie du Sud", + "german": "Südsavo", + "italian": "Savo Meridionale", + "spanish": "Savonia del Sur", + "chinese_simple": "南萨沃区", + "korean": "남사보 주", + "dutch": "Zuid-Savo", + "portuguese": "Savónia do Sul", + "russian": "Южное Саво", + "chinese_traditional": "Etelä-Savo / Södra Savolax", + "unknown1": "Etelä-Savo / Södra Savolax", + "unknown2": "Etelä-Savo / Södra Savolax", + "unknown3": "Etelä-Savo / Södra Savolax", + "unknown4": "Etelä-Savo / Södra Savolax" + }, + "coordinates": { + "latitude": 61.682738556000004, + "longitude": 27.262647377 + } + }, + { + "id": 1276051456, + "name": "Etelä-Pohjanmaa / Södra Österbotten", + "translations": { + "japanese": "南ポフヤンマー県", + "english": "Etelä-Pohjanmaa / Södra Österbotten", + "french": "Ostrobotnie du Sud", + "german": "Südösterbotten", + "italian": "Ostrobotnia Meridionale", + "spanish": "Ostrobothnia del Sur", + "chinese_simple": "南博滕区", + "korean": "남오스트로보트니아 주", + "dutch": "Zuid-Österbotten", + "portuguese": "Ostrobótnia do Sul", + "russian": "Южная Остроботния", + "chinese_traditional": "Etelä-Pohjanmaa / Södra Österbotten", + "unknown1": "Etelä-Pohjanmaa / Södra Österbotten", + "unknown2": "Etelä-Pohjanmaa / Södra Österbotten", + "unknown3": "Etelä-Pohjanmaa / Södra Österbotten", + "unknown4": "Etelä-Pohjanmaa / Södra Österbotten" + }, + "coordinates": { + "latitude": 62.78686452, + "longitude": 22.840638282 + } + }, + { + "id": 1276116992, + "name": "Pohjanmaa / Österbotten", + "translations": { + "japanese": "ポフヤンマー県", + "english": "Pohjanmaa / Österbotten", + "french": "Ostrobotnie", + "german": "Österbotten", + "italian": "Ostrobotnia", + "spanish": "Ostrobothnia", + "chinese_simple": "博滕区", + "korean": "오스트로보트니아 주", + "dutch": "Österbotten", + "portuguese": "Ostrobótnia", + "russian": "Остроботния", + "chinese_traditional": "Pohjanmaa / Österbotten", + "unknown1": "Pohjanmaa / Österbotten", + "unknown2": "Pohjanmaa / Österbotten", + "unknown3": "Pohjanmaa / Österbotten", + "unknown4": "Pohjanmaa / Österbotten" + }, + "coordinates": { + "latitude": 63.08898854, + "longitude": 21.308041341 + } + }, + { + "id": 1276182528, + "name": "Pirkanmaa / Birkaland", + "translations": { + "japanese": "ピルカンマー県", + "english": "Pirkanmaa / Birkaland", + "french": "Pirkanmaa", + "german": "Pirkanmaa", + "italian": "Pirkanmaa", + "spanish": "Pirkanmaa", + "chinese_simple": "皮尔卡区", + "korean": "피르칸마 주", + "dutch": "Pirkanmaa", + "portuguese": "Pirkanmaa", + "russian": "Пирканмаа", + "chinese_traditional": "Pirkanmaa / Birkaland", + "unknown1": "Pirkanmaa / Birkaland", + "unknown2": "Pirkanmaa / Birkaland", + "unknown3": "Pirkanmaa / Birkaland", + "unknown4": "Pirkanmaa / Birkaland" + }, + "coordinates": { + "latitude": 61.49597098, + "longitude": 23.763492354 + } + }, + { + "id": 1276248064, + "name": "Satakunta / Satakunda", + "translations": { + "japanese": "サタクンタ県", + "english": "Satakunta / Satakunda", + "french": "Satakunta", + "german": "Satakunta", + "italian": "Satakunta", + "spanish": "Satakunta", + "chinese_simple": "萨塔昆塔区", + "korean": "사타쿤타 주", + "dutch": "Satakunta", + "portuguese": "Satakunta", + "russian": "Сатакунта", + "chinese_traditional": "Satakunta / Satakunda", + "unknown1": "Satakunta / Satakunda", + "unknown2": "Satakunta / Satakunda", + "unknown3": "Satakunta / Satakunda", + "unknown4": "Satakunta / Satakunda" + }, + "coordinates": { + "latitude": 61.479491488, + "longitude": 21.796934272 + } + }, + { + "id": 1276313600, + "name": "Keski-Pohjanmaa / Mellersta Österbotten", + "translations": { + "japanese": "中部ポフヤンマー県", + "english": "Keski-Pohjanmaa / Mellersta Österbotten", + "french": "Ostrobotnie-Centrale", + "german": "Mittelösterbotten", + "italian": "Ostrobotnia Centrale", + "spanish": "Ostrobothnia Central", + "chinese_simple": "中博滕区", + "korean": "중앙오스트로보트니아 주", + "dutch": "Centraal-Österbotten", + "portuguese": "Ostrobótnia Central", + "russian": "Центральная Остроботния", + "chinese_traditional": "Keski-Pohjanmaa / Mellersta Österbotten", + "unknown1": "Keski-Pohjanmaa / Mellersta Österbotten", + "unknown2": "Keski-Pohjanmaa / Mellersta Österbotten", + "unknown3": "Keski-Pohjanmaa / Mellersta Österbotten", + "unknown4": "Keski-Pohjanmaa / Mellersta Österbotten" + }, + "coordinates": { + "latitude": 63.836058844, + "longitude": 23.131776769 + } + }, + { + "id": 1276379136, + "name": "Keski-Suomi / Mellersta Finland", + "translations": { + "japanese": "中央スオミ県", + "english": "Keski-Suomi / Mellersta Finland", + "french": "Finlande-Centrale", + "german": "Mittelfinnland", + "italian": "Finlandia Centrale", + "spanish": "Finlandia Central", + "chinese_simple": "中芬兰区", + "korean": "케스키수오미 주", + "dutch": "Centraal-Finland", + "portuguese": "Finlândia Central", + "russian": "Центральная Финляндия", + "chinese_traditional": "Keski-Suomi / Mellersta Finland", + "unknown1": "Keski-Suomi / Mellersta Finland", + "unknown2": "Keski-Suomi / Mellersta Finland", + "unknown3": "Keski-Suomi / Mellersta Finland", + "unknown4": "Keski-Suomi / Mellersta Finland" + }, + "coordinates": { + "latitude": 62.23754812, + "longitude": 25.741036794 + } + }, + { + "id": 1276444672, + "name": "Varsinais-Suomi / Egentliga Finland", + "translations": { + "japanese": "ヴァルシナイス=スオミ県", + "english": "Varsinais-Suomi / Egentliga Finland", + "french": "Finlande propre", + "german": "Varsinais-Suomi", + "italian": "Finlandia Sud-Occidentale", + "spanish": "Finlandia Propia", + "chinese_simple": "西南芬兰区", + "korean": "바르시나이스수오미 주", + "dutch": "Varsinais-Suomi", + "portuguese": "Finlândia Própria", + "russian": "Собственно Финляндия", + "chinese_traditional": "Varsinais-Suomi / Egentliga Finland", + "unknown1": "Varsinais-Suomi / Egentliga Finland", + "unknown2": "Varsinais-Suomi / Egentliga Finland", + "unknown3": "Varsinais-Suomi / Egentliga Finland", + "unknown4": "Varsinais-Suomi / Egentliga Finland" + }, + "coordinates": { + "latitude": 60.446776656, + "longitude": 22.263854487 + } + }, + { + "id": 1276510208, + "name": "Etelä-Karjala / Södra Karelen", + "translations": { + "japanese": "南カレリア県", + "english": "Etelä-Karjala / Södra Karelen", + "french": "Carélie du Sud", + "german": "Südkarelien", + "italian": "Carelia Meridionale", + "spanish": "Karelia del Sur", + "chinese_simple": "南卡累利阿区", + "korean": "남카렐리아 주", + "dutch": "Zuid-Karelië", + "portuguese": "Carélia do Sul", + "russian": "Южная Карелия", + "chinese_traditional": "Etelä-Karjala / Södra Karelen", + "unknown1": "Etelä-Karjala / Södra Karelen", + "unknown2": "Etelä-Karjala / Södra Karelen", + "unknown3": "Etelä-Karjala / Södra Karelen", + "unknown4": "Etelä-Karjala / Södra Karelen" + }, + "coordinates": { + "latitude": 61.062011024, + "longitude": 28.18000827 + } + }, + { + "id": 1276575744, + "name": "Päijät-Häme / Päijänne Tavastland", + "translations": { + "japanese": "パイヤト=ハメ県", + "english": "Päijät-Häme / Päijänne Tavastland", + "french": "Päijät-Häme", + "german": "Päijät-Häme", + "italian": "Päijät-Häme", + "spanish": "Päijänne Tavastia", + "chinese_simple": "派亚特海梅区", + "korean": "파이얏하메 주", + "dutch": "Päijät-Häme", + "portuguese": "Päijänne Tavastia", + "russian": "Пяйят-Хяме", + "chinese_traditional": "Päijät-Häme / Päijänne Tavastland", + "unknown1": "Päijät-Häme / Päijänne Tavastland", + "unknown2": "Päijät-Häme / Päijänne Tavastland", + "unknown3": "Päijät-Häme / Päijänne Tavastland", + "unknown4": "Päijät-Häme / Päijänne Tavastland" + }, + "coordinates": { + "latitude": 60.979613564, + "longitude": 25.647652751 + } + }, + { + "id": 1276641280, + "name": "Kanta-Häme / Egentliga Tavastland", + "translations": { + "japanese": "カンタ=ハメ県", + "english": "Kanta-Häme / Egentliga Tavastland", + "french": "Kanta-Häme", + "german": "Kanta-Häme", + "italian": "Kanta-Häme", + "spanish": "Tavastia Propia", + "chinese_simple": "坎塔海梅区", + "korean": "칸타하메 주", + "dutch": "Kanta-Häme", + "portuguese": "Tavastia Própria", + "russian": "Канта-Хяме", + "chinese_traditional": "Kanta-Häme / Egentliga Tavastland", + "unknown1": "Kanta-Häme / Egentliga Tavastland", + "unknown2": "Kanta-Häme / Egentliga Tavastland", + "unknown3": "Kanta-Häme / Egentliga Tavastland", + "unknown4": "Kanta-Häme / Egentliga Tavastland" + }, + "coordinates": { + "latitude": 60.996093056, + "longitude": 24.466619266 + } + }, + { + "id": 1276772352, + "name": "Kymenlaakso / Kymmenedalen", + "translations": { + "japanese": "キュメンラークソ県", + "english": "Kymenlaakso / Kymmenedalen", + "french": "Vallée de la Kymi", + "german": "Kymenlaakso", + "italian": "Kymenlaakso", + "spanish": "Kymenlaakso", + "chinese_simple": "屈米区", + "korean": "퀴멘락소 주", + "dutch": "Kymenlaakso", + "portuguese": "Kymenlaakso", + "russian": "Кюменлааксо", + "chinese_traditional": "Kymenlaakso / Kymmenedalen", + "unknown1": "Kymenlaakso / Kymmenedalen", + "unknown2": "Kymenlaakso / Kymmenedalen", + "unknown3": "Kymenlaakso / Kymmenedalen", + "unknown4": "Kymenlaakso / Kymmenedalen" + }, + "coordinates": { + "latitude": 60.86425712, + "longitude": 26.702343119 + } + }, + { + "id": 1276837888, + "name": "Ahvenanmaa / Åland", + "translations": { + "japanese": "アハベナンマー州", + "english": "Ahvenanmaa / Åland", + "french": "Åland", + "german": "Åland", + "italian": "Isole Åland", + "spanish": "Islas de Åland", + "chinese_simple": "奥兰岛自治区", + "korean": "아베난마 주", + "dutch": "Åland", + "portuguese": "Ilhas de Åland", + "russian": "Аландские острова", + "chinese_traditional": "Ahvenanmaa / Åland", + "unknown1": "Ahvenanmaa / Åland", + "unknown2": "Ahvenanmaa / Åland", + "unknown3": "Ahvenanmaa / Åland", + "unknown4": "Ahvenanmaa / Åland" + }, + "coordinates": { + "latitude": 60.089720996, + "longitude": 19.934746591 + } + } + ] + }, + { + "id": 77, + "iso_code": "FR", + "name": "France", + "translations": { + "japanese": "フランス", + "english": "France", + "french": "France", + "german": "Frankreich", + "italian": "Francia", + "spanish": "Francia", + "chinese_simple": "法国", + "korean": "프랑스", + "dutch": "Frankrijk", + "portuguese": "França", + "russian": "Франция", + "chinese_traditional": "France", + "unknown1": "France", + "unknown2": "France", + "unknown3": "France", + "unknown4": "France" + }, + "regions": [ + { + "id": 1291845632, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": 48.850707452, + "longitude": 2.345587433 + } + }, + { + "id": 1291976704, + "name": "Île-de-France", + "translations": { + "japanese": "イール・ド・フランス", + "english": "Île-de-France", + "french": "Île-de-France", + "german": "Île-de-France", + "italian": "Île-de-France", + "spanish": "Isla de Francia", + "chinese_simple": "法兰西岛大区", + "korean": "일드프랑스", + "dutch": "Île-de-France", + "portuguese": "Ilha de França", + "russian": "Иль-де-Франс", + "chinese_traditional": "Île-de-France", + "unknown1": "Île-de-France", + "unknown2": "Île-de-France", + "unknown3": "Île-de-France", + "unknown4": "Île-de-France" + }, + "coordinates": { + "latitude": 48.850707452, + "longitude": 2.345587433 + } + }, + { + "id": 1292042240, + "name": "Alsace", + "translations": { + "japanese": "アルザス", + "english": "Alsace", + "french": "Alsace", + "german": "Elsass", + "italian": "Alsazia", + "spanish": "Alsacia", + "chinese_simple": "阿尔萨斯大区", + "korean": "알자스", + "dutch": "Elzas", + "portuguese": "Alsácia", + "russian": "Эльзас", + "chinese_traditional": "Alsace", + "unknown1": "Alsace", + "unknown2": "Alsace", + "unknown3": "Alsace", + "unknown4": "Alsace" + }, + "coordinates": { + "latitude": 48.581542416, + "longitude": 7.7398892109999995 + } + }, + { + "id": 1292107776, + "name": "Aquitaine", + "translations": { + "japanese": "アキテーヌ", + "english": "Aquitaine", + "french": "Aquitaine", + "german": "Aquitanien", + "italian": "Aquitania", + "spanish": "Aquitania", + "chinese_simple": "阿基坦大区", + "korean": "아키텐", + "dutch": "Aquitanië", + "portuguese": "Aquitânia", + "russian": "Аквитания", + "chinese_traditional": "Aquitaine", + "unknown1": "Aquitaine", + "unknown2": "Aquitaine", + "unknown3": "Aquitaine", + "unknown4": "Aquitaine" + }, + "coordinates": { + "latitude": 44.835204568, + "longitude": -0.5817875019999974 + } + }, + { + "id": 1292173312, + "name": "Auvergne", + "translations": { + "japanese": "オーベルニュ", + "english": "Auvergne", + "french": "Auvergne", + "german": "Auvergne", + "italian": "Alvernia", + "spanish": "Auvernia", + "chinese_simple": "奥弗涅大区", + "korean": "오베르뉴", + "dutch": "Auvergne", + "portuguese": "Auvérnia", + "russian": "Овернь", + "chinese_traditional": "Auvergne", + "unknown1": "Auvergne", + "unknown2": "Auvergne", + "unknown3": "Auvergne", + "unknown4": "Auvergne" + }, + "coordinates": { + "latitude": 45.774535612, + "longitude": 3.081673419 + } + }, + { + "id": 1292238848, + "name": "Lower Normandy", + "translations": { + "japanese": "バス・ノルマンディ", + "english": "Lower Normandy", + "french": "Basse-Normandie", + "german": "Basse-Normandie", + "italian": "Bassa Normandia", + "spanish": "Baja Normandía", + "chinese_simple": "下诺曼底大区", + "korean": "바스노르망디", + "dutch": "Laag-Normandië", + "portuguese": "Baixa Normandia", + "russian": "Нижняя Нормандия", + "chinese_traditional": "Lower Normandy", + "unknown1": "Lower Normandy", + "unknown2": "Lower Normandy", + "unknown3": "Lower Normandy", + "unknown4": "Lower Normandy" + }, + "coordinates": { + "latitude": 49.180297292, + "longitude": -0.356567162999994 + } + }, + { + "id": 1292304384, + "name": "Burgundy", + "translations": { + "japanese": "ブルゴーニュ", + "english": "Burgundy", + "french": "Bourgogne", + "german": "Burgund", + "italian": "Borgogna", + "spanish": "Borgoña", + "chinese_simple": "勃艮第大区", + "korean": "부르고뉴", + "dutch": "Bourgondië", + "portuguese": "Borgonha", + "russian": "Бургундия", + "chinese_traditional": "Burgundy", + "unknown1": "Burgundy", + "unknown2": "Burgundy", + "unknown3": "Burgundy", + "unknown4": "Burgundy" + }, + "coordinates": { + "latitude": 47.318114696, + "longitude": 5.037245143 + } + }, + { + "id": 1292369920, + "name": "Brittany", + "translations": { + "japanese": "ブルターニュ", + "english": "Brittany", + "french": "Bretagne", + "german": "Bretagne", + "italian": "Bretagna", + "spanish": "Bretaña", + "chinese_simple": "布列塔尼大区", + "korean": "브르타뉴", + "dutch": "Bretagne", + "portuguese": "Bretanha", + "russian": "Бретань", + "chinese_traditional": "Brittany", + "unknown1": "Brittany", + "unknown2": "Brittany", + "unknown3": "Brittany", + "unknown4": "Brittany" + }, + "coordinates": { + "latitude": 48.109130312, + "longitude": -1.6804233020000083 + } + }, + { + "id": 1292435456, + "name": "Centre", + "translations": { + "japanese": "サントル", + "english": "Centre", + "french": "Centre", + "german": "Centre", + "italian": "Centro", + "spanish": "Centro", + "chinese_simple": "中央大区", + "korean": "상트르", + "dutch": "Centre", + "portuguese": "Centro", + "russian": "Центр", + "chinese_traditional": "Centre", + "unknown1": "Centre", + "unknown2": "Centre", + "unknown3": "Centre", + "unknown4": "Centre" + }, + "coordinates": { + "latitude": 47.90039008, + "longitude": 1.900639934 + } + }, + { + "id": 1292500992, + "name": "Champagne-Ardenne", + "translations": { + "japanese": "シャンパーニュ・アルデンヌ", + "english": "Champagne-Ardenne", + "french": "Champagne-Ardenne", + "german": "Champagne-Ardenne", + "italian": "Champagne-Ardenne", + "spanish": "Champaña-Ardenas", + "chinese_simple": "香槟-阿登大区", + "korean": "샹파뉴아르덴", + "dutch": "Champagne-Ardennen", + "portuguese": "Champanha-Ardenas", + "russian": "Шампань-Арденны", + "chinese_traditional": "Champagne-Ardenne", + "unknown1": "Champagne-Ardenne", + "unknown2": "Champagne-Ardenne", + "unknown3": "Champagne-Ardenne", + "unknown4": "Champagne-Ardenne" + }, + "coordinates": { + "latitude": 48.960570732, + "longitude": 4.361584126 + } + }, + { + "id": 1292566528, + "name": "Corsica", + "translations": { + "japanese": "コルシカ", + "english": "Corsica", + "french": "Corse", + "german": "Korsika", + "italian": "Corsica", + "spanish": "Córcega", + "chinese_simple": "科西嘉大区", + "korean": "코르스", + "dutch": "Corsica", + "portuguese": "Córsega", + "russian": "Корсика", + "chinese_traditional": "Corsica", + "unknown1": "Corsica", + "unknown2": "Corsica", + "unknown3": "Corsica", + "unknown4": "Corsica" + }, + "coordinates": { + "latitude": 41.918334484, + "longitude": 8.73415461 + } + }, + { + "id": 1292632064, + "name": "Franche-Comté", + "translations": { + "japanese": "フランシュ・コンテ", + "english": "Franche-Comté", + "french": "Franche-Comté", + "german": "Franche-Comté", + "italian": "Franca Contea", + "spanish": "Franco Condado", + "chinese_simple": "弗朗什孔泰大区", + "korean": "프랑슈콩테", + "dutch": "Franche-Comté", + "portuguese": "Franco Condado", + "russian": "Франш-Конте", + "chinese_traditional": "Franche-Comté", + "unknown1": "Franche-Comté", + "unknown2": "Franche-Comté", + "unknown3": "Franche-Comté", + "unknown4": "Franche-Comté" + }, + "coordinates": { + "latitude": 47.235717236, + "longitude": 6.015031005 + } + }, + { + "id": 1292697600, + "name": "Upper Normandy", + "translations": { + "japanese": "オート・ノルマンディ", + "english": "Upper Normandy", + "french": "Haute-Normandie", + "german": "Haute-Normandie", + "italian": "Alta Normandia", + "spanish": "Alta Normandía", + "chinese_simple": "上诺曼底大区", + "korean": "오트노르망디", + "dutch": "Hoog-Normandië", + "portuguese": "Alta Normandia", + "russian": "Верхняя Нормандия", + "chinese_traditional": "Upper Normandy", + "unknown1": "Upper Normandy", + "unknown2": "Upper Normandy", + "unknown3": "Upper Normandy", + "unknown4": "Upper Normandy" + }, + "coordinates": { + "latitude": 49.438476, + "longitude": 1.0986358 + } + }, + { + "id": 1292763136, + "name": "Languedoc-Roussillon", + "translations": { + "japanese": "ラングドック・ルシヨン", + "english": "Languedoc-Roussillon", + "french": "Languedoc-Roussillon", + "german": "Languedoc-Roussillon", + "italian": "Linguadoca-Rossiglione", + "spanish": "Languedoc-Rosellón", + "chinese_simple": "朗格多克-鲁西永大区", + "korean": "랑그도크루시용", + "dutch": "Languedoc-Roussillon", + "portuguese": "Languedoque-Rossilhão", + "russian": "Лангедок-Руссильон", + "chinese_traditional": "Languedoc-Roussillon", + "unknown1": "Languedoc-Roussillon", + "unknown2": "Languedoc-Roussillon", + "unknown3": "Languedoc-Roussillon", + "unknown4": "Languedoc-Roussillon" + }, + "coordinates": { + "latitude": 43.604735832, + "longitude": 3.872691195 + } + }, + { + "id": 1292828672, + "name": "Limousin", + "translations": { + "japanese": "リムーザン", + "english": "Limousin", + "french": "Limousin", + "german": "Limousin", + "italian": "Limosino", + "spanish": "Lemosín", + "chinese_simple": "利穆赞大区", + "korean": "리무쟁", + "dutch": "Limousin", + "portuguese": "Limusino", + "russian": "Лимузен", + "chinese_traditional": "Limousin", + "unknown1": "Limousin", + "unknown2": "Limousin", + "unknown3": "Limousin", + "unknown4": "Limousin" + }, + "coordinates": { + "latitude": 45.829467252, + "longitude": 1.252444812 + } + }, + { + "id": 1292894208, + "name": "Lorraine", + "translations": { + "japanese": "ロレーヌ", + "english": "Lorraine", + "french": "Lorraine", + "german": "Lothringen", + "italian": "Lorena", + "spanish": "Lorena", + "chinese_simple": "洛林大区", + "korean": "로렌", + "dutch": "Lotharingen", + "portuguese": "Lorena", + "russian": "Лотарингия", + "chinese_traditional": "Lorraine", + "unknown1": "Lorraine", + "unknown2": "Lorraine", + "unknown3": "Lorraine", + "unknown4": "Lorraine" + }, + "coordinates": { + "latitude": 49.114379324, + "longitude": 6.168840017 + } + }, + { + "id": 1292959744, + "name": "Midi-Pyrénées", + "translations": { + "japanese": "ミディ・ピレネー", + "english": "Midi-Pyrénées", + "french": "Midi-Pyrénées", + "german": "Midi-Pyrénées", + "italian": "Mezzogiorno-Pirenei", + "spanish": "Mediodía-Pirineos", + "chinese_simple": "南部-比利牛斯大区", + "korean": "미디피레네", + "dutch": "Zuid-Frankrijk-Pyreneeën", + "portuguese": "Médio Pirenéus", + "russian": "Юг-Пиренеи", + "chinese_traditional": "Midi-Pyrénées", + "unknown1": "Midi-Pyrénées", + "unknown2": "Midi-Pyrénées", + "unknown3": "Midi-Pyrénées", + "unknown4": "Midi-Pyrénées" + }, + "coordinates": { + "latitude": 43.599242668, + "longitude": 1.433719719 + } + }, + { + "id": 1293025280, + "name": "Nord-Pas-de-Calais", + "translations": { + "japanese": "ノール・パ・ド・カレー", + "english": "Nord-Pas-de-Calais", + "french": "Nord-Pas de Calais", + "german": "Nord-Pas-de-Calais", + "italian": "Nord-Passo di Calais", + "spanish": "Norte-Paso de Calais", + "chinese_simple": "北部-加来海峡大区", + "korean": "노르파드칼레", + "dutch": "Noord-Nauw van Calais", + "portuguese": "Norte Pas de Calais", + "russian": "Нор-Па-де-Кале", + "chinese_traditional": "Nord-Pas-de-Calais", + "unknown1": "Nord-Pas-de-Calais", + "unknown2": "Nord-Pas-de-Calais", + "unknown3": "Nord-Pas-de-Calais", + "unknown4": "Nord-Pas-de-Calais" + }, + "coordinates": { + "latitude": 50.630492588, + "longitude": 3.059700703 + } + }, + { + "id": 1293090816, + "name": "Pays de la Loire", + "translations": { + "japanese": "ペイ・ド・ラ・ロワール", + "english": "Pays de la Loire", + "french": "Pays de la Loire", + "german": "Pays de la Loire", + "italian": "Paesi della Loira", + "spanish": "Países del Loira", + "chinese_simple": "卢瓦尔河地区", + "korean": "페이드라루아르", + "dutch": "Landen van de Loire", + "portuguese": "País do Loire", + "russian": "Земли Луары", + "chinese_traditional": "Pays de la Loire", + "unknown1": "Pays de la Loire", + "unknown2": "Pays de la Loire", + "unknown3": "Pays de la Loire", + "unknown4": "Pays de la Loire" + }, + "coordinates": { + "latitude": 47.213744580000004, + "longitude": -1.548587005999991 + } + }, + { + "id": 1293156352, + "name": "Picardy", + "translations": { + "japanese": "ピカルディー", + "english": "Picardy", + "french": "Picardie", + "german": "Picardie", + "italian": "Piccardia", + "spanish": "Picardía", + "chinese_simple": "皮卡第大区", + "korean": "피카르디", + "dutch": "Picardië", + "portuguese": "Picardia", + "russian": "Пикардия", + "chinese_traditional": "Picardy", + "unknown1": "Picardy", + "unknown2": "Picardy", + "unknown3": "Picardy", + "unknown4": "Picardy" + }, + "coordinates": { + "latitude": 49.883422284, + "longitude": 2.290655643 + } + }, + { + "id": 1293221888, + "name": "Poitou-Charentes", + "translations": { + "japanese": "ポワトゥー・シャラント", + "english": "Poitou-Charentes", + "french": "Poitou-Charentes", + "german": "Poitou-Charentes", + "italian": "Poitou-Charentes", + "spanish": "Poitou-Charentes", + "chinese_simple": "普瓦图-夏朗德大区", + "korean": "푸아투샤랑트", + "dutch": "Poitou-Charentes", + "portuguese": "Poitou-Charentes", + "russian": "Пуату-Шаранта", + "chinese_traditional": "Poitou-Charentes", + "unknown1": "Poitou-Charentes", + "unknown2": "Poitou-Charentes", + "unknown3": "Poitou-Charentes", + "unknown4": "Poitou-Charentes" + }, + "coordinates": { + "latitude": 46.58203072, + "longitude": 0.335083919 + } + }, + { + "id": 1293287424, + "name": "Provence-Alpes-Côte d'Azur", + "translations": { + "japanese": "プロヴァンス・アルプ・コート・ダジュール", + "english": "Provence-Alpes-Côte d'Azur", + "french": "Provence-Alpes-Côte d'Azur", + "german": "Provence-Alpes-Côte d'Azur", + "italian": "Provenza-Alpi-Costa Azzurra", + "spanish": "Provenza-Alpes-Costa Azul", + "chinese_simple": "普罗旺斯-阿尔卑斯-蓝色海岸大区", + "korean": "프로방스알프코트다쥐르", + "dutch": "Provence-Alpen-Côte d'Azur", + "portuguese": "Provença-Alpes-Costa Azul", + "russian": "Прованс-Альпы-Лазурный Берег", + "chinese_traditional": "Provence-Alpes-Côte d'Azur", + "unknown1": "Provence-Alpes-Côte d'Azur", + "unknown2": "Provence-Alpes-Côte d'Azur", + "unknown3": "Provence-Alpes-Côte d'Azur", + "unknown4": "Provence-Alpes-Côte d'Azur" + }, + "coordinates": { + "latitude": 43.291625484, + "longitude": 5.372329062 + } + }, + { + "id": 1293352960, + "name": "Rhône-Alpes", + "translations": { + "japanese": "ローヌ・アルプ", + "english": "Rhône-Alpes", + "french": "Rhône-Alpes", + "german": "Rhône-Alpes", + "italian": "Rodano-Alpi", + "spanish": "Ródano-Alpes", + "chinese_simple": "罗讷-阿尔卑斯大区", + "korean": "론알프", + "dutch": "Rhône-Alpen", + "portuguese": "Ródano-Alpes", + "russian": "Рона-Альпы", + "chinese_traditional": "Rhône-Alpes", + "unknown1": "Rhône-Alpes", + "unknown2": "Rhône-Alpes", + "unknown3": "Rhône-Alpes", + "unknown4": "Rhône-Alpes" + }, + "coordinates": { + "latitude": 45.752562956, + "longitude": 4.828504341 + } + }, + { + "id": 1293418496, + "name": "Guadeloupe", + "translations": { + "japanese": "グアドループ", + "english": "Guadeloupe", + "french": "Guadeloupe", + "german": "Guadeloupe", + "italian": "Guadalupa", + "spanish": "Guadalupe", + "chinese_simple": "瓜德罗普省", + "korean": "과들루프", + "dutch": "Guadeloupe", + "portuguese": "Guadalupe", + "russian": "Гваделупа", + "chinese_traditional": "Guadeloupe", + "unknown1": "Guadeloupe", + "unknown2": "Guadeloupe", + "unknown3": "Guadeloupe", + "unknown4": "Guadeloupe" + }, + "coordinates": { + "latitude": 15.996093568000001, + "longitude": -61.715376593 + } + }, + { + "id": 1293484032, + "name": "Martinique", + "translations": { + "japanese": "マルチニーク", + "english": "Martinique", + "french": "Martinique", + "german": "Martinique", + "italian": "Martinica", + "spanish": "Martinica", + "chinese_simple": "马提尼克省", + "korean": "마르티니크", + "dutch": "Martinique", + "portuguese": "Martinica", + "russian": "Мартиника", + "chinese_traditional": "Martinique", + "unknown1": "Martinique", + "unknown2": "Martinique", + "unknown3": "Martinique", + "unknown4": "Martinique" + }, + "coordinates": { + "latitude": 14.600829912, + "longitude": -61.072674649999996 + } + }, + { + "id": 1293549568, + "name": "French Guiana", + "translations": { + "japanese": "フランス領ギアナ", + "english": "French Guiana", + "french": "Guyane", + "german": "Französisch-Guayana", + "italian": "Guyana Francese", + "spanish": "Guayana Francesa", + "chinese_simple": "法属圭亚那省", + "korean": "프랑스령 기아나", + "dutch": "Frans-Guyana", + "portuguese": "Guiana Francesa", + "russian": "Французская Гвиана", + "chinese_traditional": "French Guiana", + "unknown1": "French Guiana", + "unknown2": "French Guiana", + "unknown3": "French Guiana", + "unknown4": "French Guiana" + }, + "coordinates": { + "latitude": 4.932861272, + "longitude": -52.327533681999995 + } + }, + { + "id": 1293615104, + "name": "Réunion", + "translations": { + "japanese": "レユニオン", + "english": "Réunion", + "french": "Réunion", + "german": "Réunion", + "italian": "Riunione", + "spanish": "Reunión", + "chinese_simple": "留尼汪省", + "korean": "레위니옹", + "dutch": "Réunion", + "portuguese": "Reunião", + "russian": "Реюньон", + "chinese_traditional": "Réunion", + "unknown1": "Réunion", + "unknown2": "Réunion", + "unknown3": "Réunion", + "unknown4": "Réunion" + }, + "coordinates": { + "latitude": -20.874024223999996, + "longitude": 55.448148826 + } + }, + { + "id": 1293680640, + "name": "Mayotte", + "translations": { + "japanese": "マヨット島", + "english": "Mayotte", + "french": "Mayotte", + "german": "Mayotte", + "italian": "Mayotte", + "spanish": "Mayotte", + "chinese_simple": "马约特省", + "korean": "마요트", + "dutch": "Mayotte", + "portuguese": "Maiote", + "russian": "Майотта", + "chinese_traditional": "Mayotte", + "unknown1": "Mayotte", + "unknown2": "Mayotte", + "unknown3": "Mayotte", + "unknown4": "Mayotte" + }, + "coordinates": { + "latitude": -12.777100488000002, + "longitude": 45.225342707 + } + } + ] + }, + { + "id": 78, + "iso_code": "DE", + "name": "Germany", + "translations": { + "japanese": "ドイツ", + "english": "Germany", + "french": "Allemagne", + "german": "Deutschland", + "italian": "Germania", + "spanish": "Alemania", + "chinese_simple": "德国", + "korean": "독일", + "dutch": "Duitsland", + "portuguese": "Alemanha", + "russian": "Германия", + "chinese_traditional": "Germany", + "unknown1": "Germany", + "unknown2": "Germany", + "unknown3": "Germany", + "unknown4": "Germany" + }, + "regions": [ + { + "id": 1308622848, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": 52.520141004, + "longitude": 13.40335676 + } + }, + { + "id": 1308753920, + "name": "Berlin", + "translations": { + "japanese": "ベルリン", + "english": "Berlin", + "french": "Berlin", + "german": "Berlin", + "italian": "Berlino", + "spanish": "Berlín", + "chinese_simple": "柏林市", + "korean": "베를린", + "dutch": "Berlijn", + "portuguese": "Berlim", + "russian": "Берлин", + "chinese_traditional": "Berlin", + "unknown1": "Berlin", + "unknown2": "Berlin", + "unknown3": "Berlin", + "unknown4": "Berlin" + }, + "coordinates": { + "latitude": 52.520141004, + "longitude": 13.40335676 + } + }, + { + "id": 1308819456, + "name": "Hesse", + "translations": { + "japanese": "ヘッセン州", + "english": "Hesse", + "french": "Hesse", + "german": "Hessen", + "italian": "Assia", + "spanish": "Hesse", + "chinese_simple": "黑森州", + "korean": "헤센 주", + "dutch": "Hessen", + "portuguese": "Hesse", + "russian": "Гессен", + "chinese_traditional": "Hesse", + "unknown1": "Hesse", + "unknown2": "Hesse", + "unknown3": "Hesse", + "unknown4": "Hesse" + }, + "coordinates": { + "latitude": 50.07018986, + "longitude": 8.234275321 + } + }, + { + "id": 1308884992, + "name": "Baden-Württemberg", + "translations": { + "japanese": "バーデン・ビュルテンベルク州", + "english": "Baden-Württemberg", + "french": "Bade-Wurtemberg", + "german": "Baden-Württemberg", + "italian": "Baden-Württemberg", + "spanish": "Baden-Wurtemberg", + "chinese_simple": "巴登-符腾堡州", + "korean": "바덴뷔르템베르크 주", + "dutch": "Baden-Württemberg", + "portuguese": "Baden-Württemberg", + "russian": "Баден-Вюртемберг", + "chinese_traditional": "Baden-Württemberg", + "unknown1": "Baden-Württemberg", + "unknown2": "Baden-Württemberg", + "unknown3": "Baden-Württemberg", + "unknown4": "Baden-Württemberg" + }, + "coordinates": { + "latitude": 48.768309992, + "longitude": 9.17360893 + } + }, + { + "id": 1308950528, + "name": "Bavaria", + "translations": { + "japanese": "バイエルン州", + "english": "Bavaria", + "french": "Bavière", + "german": "Bayern", + "italian": "Baviera", + "spanish": "Baviera", + "chinese_simple": "巴伐利亚州", + "korean": "바이에른 주", + "dutch": "Beieren", + "portuguese": "Baviera", + "russian": "Бавария", + "chinese_traditional": "Bavaria", + "unknown1": "Bavaria", + "unknown2": "Bavaria", + "unknown3": "Bavaria", + "unknown4": "Bavaria" + }, + "coordinates": { + "latitude": 48.131102968, + "longitude": 11.552155437 + } + }, + { + "id": 1309016064, + "name": "Brandenburg", + "translations": { + "japanese": "ブランデンブルク州", + "english": "Brandenburg", + "french": "Brandebourg", + "german": "Brandenburg", + "italian": "Brandeburgo", + "spanish": "Brandeburgo", + "chinese_simple": "勃兰登堡州", + "korean": "브란덴부르크 주", + "dutch": "Brandenburg", + "portuguese": "Brandeburgo", + "russian": "Бранденбург", + "chinese_traditional": "Brandenburg", + "unknown1": "Brandenburg", + "unknown2": "Brandenburg", + "unknown3": "Brandenburg", + "unknown4": "Brandenburg" + }, + "coordinates": { + "latitude": 52.388305068, + "longitude": 13.035313767 + } + }, + { + "id": 1309081600, + "name": "Bremen", + "translations": { + "japanese": "ブレーメン", + "english": "Bremen", + "french": "Brême", + "german": "Bremen", + "italian": "Brema", + "spanish": "Bremen", + "chinese_simple": "不来梅市", + "korean": "브레멘 주", + "dutch": "Bremen", + "portuguese": "Bremen", + "russian": "Бремен", + "chinese_traditional": "Bremen", + "unknown1": "Bremen", + "unknown2": "Bremen", + "unknown3": "Bremen", + "unknown4": "Bremen" + }, + "coordinates": { + "latitude": 53.06396424, + "longitude": 8.805565937 + } + }, + { + "id": 1309147136, + "name": "Hamburg", + "translations": { + "japanese": "ハンブルク", + "english": "Hamburg", + "french": "Hambourg", + "german": "Hamburg", + "italian": "Amburgo", + "spanish": "Hamburgo", + "chinese_simple": "汉堡市", + "korean": "함부르크 주", + "dutch": "Hamburg", + "portuguese": "Hamburgo", + "russian": "Гамбург", + "chinese_traditional": "Hamburg", + "unknown1": "Hamburg", + "unknown2": "Hamburg", + "unknown3": "Hamburg", + "unknown4": "Hamburg" + }, + "coordinates": { + "latitude": 53.547362672, + "longitude": 9.986599422 + } + }, + { + "id": 1309212672, + "name": "Mecklenburg-Vorpommern", + "translations": { + "japanese": "メクレンブルク・フォアポンメルン州", + "english": "Mecklenburg-Vorpommern", + "french": "Mecklembourg-Poméranie antérieure", + "german": "Mecklenburg-Vorpommern", + "italian": "Meclemburgo-Pomerania Anteriore", + "spanish": "Mecklemburgo-Pomerania Occidental", + "chinese_simple": "梅克伦堡-前波莫瑞州", + "korean": "메클렌부르크포어포메른 주", + "dutch": "Mecklenburg-Voor-Pommeren", + "portuguese": "Mecklemburgo-Pomerânia Ocidental", + "russian": "Мекленбург-Передняя Померания", + "chinese_traditional": "Mecklenburg-Vorpommern", + "unknown1": "Mecklenburg-Vorpommern", + "unknown2": "Mecklenburg-Vorpommern", + "unknown3": "Mecklenburg-Vorpommern", + "unknown4": "Mecklenburg-Vorpommern" + }, + "coordinates": { + "latitude": 53.61328064, + "longitude": 11.414825962 + } + }, + { + "id": 1309278208, + "name": "Lower Saxony", + "translations": { + "japanese": "ニーダーザクセン州", + "english": "Lower Saxony", + "french": "Basse-Saxe", + "german": "Niedersachsen", + "italian": "Bassa Sassonia", + "spanish": "Baja Sajonia", + "chinese_simple": "下萨克森州", + "korean": "니더작센 주", + "dutch": "Nedersaksen", + "portuguese": "Baixa Saxónia", + "russian": "Нижняя Саксония", + "chinese_traditional": "Lower Saxony", + "unknown1": "Lower Saxony", + "unknown2": "Lower Saxony", + "unknown3": "Lower Saxony", + "unknown4": "Lower Saxony" + }, + "coordinates": { + "latitude": 52.366332412, + "longitude": 9.733913188 + } + }, + { + "id": 1309343744, + "name": "North Rhine-Westphalia", + "translations": { + "japanese": "ノルトライン・ウェストファーレン州", + "english": "North Rhine-Westphalia", + "french": "Rhénanie-du-Nord-Westphalie", + "german": "Nordrhein-Westfalen", + "italian": "Renania Settentrionale-Vestfalia", + "spanish": "Renania del Norte-Westfalia", + "chinese_simple": "北莱茵-威斯特法伦州", + "korean": "노르트라인베스트팔렌 주", + "dutch": "Noord-Rijnland-Westfalen", + "portuguese": "Renânia do Norte-Vestefália", + "russian": "Северный Рейн-Вестфалия", + "chinese_traditional": "North Rhine-Westphalia", + "unknown1": "North Rhine-Westphalia", + "unknown2": "North Rhine-Westphalia", + "unknown3": "North Rhine-Westphalia", + "unknown4": "North Rhine-Westphalia" + }, + "coordinates": { + "latitude": 51.240233792, + "longitude": 6.773089707 + } + }, + { + "id": 1309409280, + "name": "Rhineland-Palatinate", + "translations": { + "japanese": "ラインラント・ファルツ州", + "english": "Rhineland-Palatinate", + "french": "Rhénanie-Palatinat", + "german": "Rh쳐歮覀㞂ʮ∎d-Pfalz", + "italian": "Renania-Palatinato", + "spanish": "Renania-Palatinado", + "chinese_simple": "莱茵兰-普法尔茨州", + "korean": "라인란트팔츠 주", + "dutch": "Rijnland-Palts", + "portuguese": "Renânia-Palatinado", + "russian": "Рейнланд-Пфальц", + "chinese_traditional": "Rhineland-Palatinate", + "unknown1": "Rhineland-Palatinate", + "unknown2": "Rhineland-Palatinate", + "unknown3": "Rhineland-Palatinate", + "unknown4": "Rhineland-Palatinate" + }, + "coordinates": { + "latitude": 49.998778728, + "longitude": 8.256248037 + } + }, + { + "id": 1309474816, + "name": "Saarland", + "translations": { + "japanese": "ザールラント州", + "english": "Saarland", + "french": "Sarre", + "german": "Saarland", + "italian": "Saarland", + "spanish": "Sarre", + "chinese_simple": "萨尔州", + "korean": "자를란트 주", + "dutch": "Saarland", + "portuguese": "Sarre", + "russian": "Саар", + "chinese_traditional": "Saarland", + "unknown1": "Saarland", + "unknown2": "Saarland", + "unknown3": "Saarland", + "unknown4": "Saarland" + }, + "coordinates": { + "latitude": 49.229735768, + "longitude": 6.998310046 + } + }, + { + "id": 1309540352, + "name": "Saxony", + "translations": { + "japanese": "ザクセン州", + "english": "Saxony", + "french": "Saxe", + "german": "Sachsen", + "italian": "Sassonia", + "spanish": "Sajonia", + "chinese_simple": "萨克森州", + "korean": "작센 주", + "dutch": "Saksen", + "portuguese": "Saxónia", + "russian": "Саксония", + "chinese_traditional": "Saxony", + "unknown1": "Saxony", + "unknown2": "Saxony", + "unknown3": "Saxony", + "unknown4": "Saxony" + }, + "coordinates": { + "latitude": 51.03149356, + "longitude": 13.7329475 + } + }, + { + "id": 1309605888, + "name": "Saxony-Anhalt", + "translations": { + "japanese": "ザクセン・アンハルト州", + "english": "Saxony-Anhalt", + "french": "Saxe-Anhalt", + "german": "Sachsen-Anhalt", + "italian": "Sassonia-Anhalt", + "spanish": "Sajonia-Anhalt", + "chinese_simple": "萨克森-安哈特州", + "korean": "작센안할트 주", + "dutch": "Saksen-Anhalt", + "portuguese": "Saxónia-Anhalt", + "russian": "Саксония-Анхальт", + "chinese_traditional": "Saxony-Anhalt", + "unknown1": "Saxony-Anhalt", + "unknown2": "Saxony-Anhalt", + "unknown3": "Saxony-Anhalt", + "unknown4": "Saxony-Anhalt" + }, + "coordinates": { + "latitude": 52.124633196, + "longitude": 11.612580406 + } + }, + { + "id": 1309671424, + "name": "Schleswig-Holstein", + "translations": { + "japanese": "シュレスビヒ・ホルシュタイン州", + "english": "Schleswig-Holstein", + "french": "Schleswig-Holstein", + "german": "Schleswig-Holstein", + "italian": "Schleswig-Holstein", + "spanish": "Schleswig-Holstein", + "chinese_simple": "石勒苏益格-荷尔斯泰因州", + "korean": "슐레스비히홀슈타인 주", + "dutch": "Sleeswijk-Holstein", + "portuguese": "Schleswig-Holstein", + "russian": "Шлезвиг-Гольштейн", + "chinese_traditional": "Schleswig-Holstein", + "unknown1": "Schleswig-Holstein", + "unknown2": "Schleswig-Holstein", + "unknown3": "Schleswig-Holstein", + "unknown4": "Schleswig-Holstein" + }, + "coordinates": { + "latitude": 54.316405632, + "longitude": 10.118435718 + } + }, + { + "id": 1309736960, + "name": "Thuringia", + "translations": { + "japanese": "テューリンゲン州", + "english": "Thuringia", + "french": "Thuringe", + "german": "Thüringen", + "italian": "Turingia", + "spanish": "Turingia", + "chinese_simple": "图林根州", + "korean": "튀링겐 주", + "dutch": "Thüringen", + "portuguese": "Turíngia", + "russian": "Тюрингия", + "chinese_traditional": "Thuringia", + "unknown1": "Thuringia", + "unknown2": "Thuringia", + "unknown3": "Thuringia", + "unknown4": "Thuringia" + }, + "coordinates": { + "latitude": 50.971068756, + "longitude": 11.019317074 + } + } + ] + }, + { + "id": 79, + "iso_code": "GR", + "name": "Greece", + "translations": { + "japanese": "ギリシャ", + "english": "Greece", + "french": "Grèce", + "german": "Griechenland", + "italian": "Grecia", + "spanish": "Grecia", + "chinese_simple": "希腊", + "korean": "그리스", + "dutch": "Griekenland", + "portuguese": "Grécia", + "russian": "Греция", + "chinese_traditional": "Greece", + "unknown1": "Greece", + "unknown2": "Greece", + "unknown3": "Greece", + "unknown4": "Greece" + }, + "regions": [ + { + "id": 1325400064, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": 37.974242732, + "longitude": 23.719546922 + } + }, + { + "id": 1325531136, + "name": "Attica", + "translations": { + "japanese": "アッティカ", + "english": "Attica", + "french": "Attique", + "german": "Attika", + "italian": "Attica", + "spanish": "Ática", + "chinese_simple": "阿提卡大区", + "korean": "아티카", + "dutch": "Attika", + "portuguese": "Ática", + "russian": "Аттика", + "chinese_traditional": "Attica", + "unknown1": "Attica", + "unknown2": "Attica", + "unknown3": "Attica", + "unknown4": "Attica" + }, + "coordinates": { + "latitude": 37.974242732, + "longitude": 23.719546922 + } + }, + { + "id": 1325596672, + "name": "Central Greece", + "translations": { + "japanese": "中央ギリシャ", + "english": "Central Greece", + "french": "Grèce-Centrale", + "german": "Mittelgriechenland", + "italian": "Grecia Centrale", + "spanish": "Grecia Central", + "chinese_simple": "中希腊大区", + "korean": "중부 그리스", + "dutch": "Centraal-Griekenland", + "portuguese": "Grécia Central", + "russian": "Центральная Греция", + "chinese_traditional": "Central Greece", + "unknown1": "Central Greece", + "unknown2": "Central Greece", + "unknown3": "Central Greece", + "unknown4": "Central Greece" + }, + "coordinates": { + "latitude": 38.89160112, + "longitude": 22.428649857 + } + }, + { + "id": 1325662208, + "name": "Central Macedonia", + "translations": { + "japanese": "中央マケドニア", + "english": "Central Macedonia", + "french": "Macédoine-Centrale", + "german": "Zentralmakedonien", + "italian": "Macedonia Centrale", + "spanish": "Macedonia Central", + "chinese_simple": "中马其顿大区", + "korean": "중부 마케도니아", + "dutch": "Centraal-Macedonië", + "portuguese": "Macedónia Central", + "russian": "Центральная Македония", + "chinese_traditional": "Central Macedonia", + "unknown1": "Central Macedonia", + "unknown2": "Central Macedonia", + "unknown3": "Central Macedonia", + "unknown4": "Central Macedonia" + }, + "coordinates": { + "latitude": 40.616454616, + "longitude": 22.966981398999998 + } + }, + { + "id": 1325727744, + "name": "Crete", + "translations": { + "japanese": "クレタ", + "english": "Crete", + "french": "Crète", + "german": "Kreta", + "italian": "Creta", + "spanish": "Creta", + "chinese_simple": "克里特大区", + "korean": "크레타", + "dutch": "Kreta", + "portuguese": "Creta", + "russian": "Крит", + "chinese_traditional": "Crete", + "unknown1": "Crete", + "unknown2": "Crete", + "unknown3": "Crete", + "unknown4": "Crete" + }, + "coordinates": { + "latitude": 35.332030848, + "longitude": 25.131293925 + } + }, + { + "id": 1325793280, + "name": "East Macedonia and Thrace", + "translations": { + "japanese": "東マケドニア・トラキア", + "english": "East Macedonia and Thrace", + "french": "Macédoine-Orientale-et-Thrace", + "german": "Ostmakedonien und Thrakien", + "italian": "Macedonia Orientale e Tracia", + "spanish": "Macedonia Oriental y Tracia", + "chinese_simple": "东马其顿和色雷斯大区", + "korean": "동부 마케도니아트라키아", + "dutch": "Oost-Macedonië en Thracië", + "portuguese": "Macedónia Oriental e Trácia", + "russian": "Восточная Македония и Фракия", + "chinese_traditional": "East Macedonia and Thrace", + "unknown1": "East Macedonia and Thrace", + "unknown2": "East Macedonia and Thrace", + "unknown3": "East Macedonia and Thrace", + "unknown4": "East Macedonia and Thrace" + }, + "coordinates": { + "latitude": 41.121825704, + "longitude": 25.416939233 + } + }, + { + "id": 1325858816, + "name": "Epirus", + "translations": { + "japanese": "イピロス", + "english": "Epirus", + "french": "Épire", + "german": "Epirus", + "italian": "Epiro", + "spanish": "Epiro", + "chinese_simple": "伊庇鲁斯大区", + "korean": "에피루스", + "dutch": "Epirus", + "portuguese": "Epiro", + "russian": "Эпир", + "chinese_traditional": "Epirus", + "unknown1": "Epirus", + "unknown2": "Epirus", + "unknown3": "Epirus", + "unknown4": "Epirus" + }, + "coordinates": { + "latitude": 39.649657752, + "longitude": 20.846614305 + } + }, + { + "id": 1325924352, + "name": "Ionian Islands", + "translations": { + "japanese": "イオニア", + "english": "Ionian Islands", + "french": "Îles Ioniennes", + "german": "Ionische Inseln", + "italian": "Isole Ionie", + "spanish": "Islas Jónicas", + "chinese_simple": "爱奥尼亚群岛大区", + "korean": "이오니아 제도", + "dutch": "Ionische Eilanden", + "portuguese": "Ilhas Jónicas", + "russian": "Ионические острова", + "chinese_traditional": "Ionian Islands", + "unknown1": "Ionian Islands", + "unknown2": "Ionian Islands", + "unknown3": "Ionian Islands", + "unknown4": "Ionian Islands" + }, + "coordinates": { + "latitude": 39.616698768, + "longitude": 19.912773875 + } + }, + { + "id": 1325989888, + "name": "North Aegean", + "translations": { + "japanese": "北エーゲ", + "english": "North Aegean", + "french": "Égée-Septentrionale", + "german": "Nördliche Ägäis", + "italian": "Egeo Settentrionale", + "spanish": "Egeo Septentrional", + "chinese_simple": "北爱琴海大区", + "korean": "북부 에게", + "dutch": "Noord-Egeïsche Eilanden", + "portuguese": "Egeu Setentrional", + "russian": "Северные Эгейские острова", + "chinese_traditional": "North Aegean", + "unknown1": "North Aegean", + "unknown2": "North Aegean", + "unknown3": "North Aegean", + "unknown4": "North Aegean" + }, + "coordinates": { + "latitude": 39.100341352, + "longitude": 26.548534107 + } + }, + { + "id": 1326055424, + "name": "Peloponnese", + "translations": { + "japanese": "ペロポネソス", + "english": "Peloponnese", + "french": "Péloponnèse", + "german": "Peloponnes", + "italian": "Peloponneso", + "spanish": "Peloponeso", + "chinese_simple": "伯罗奔尼撒大区", + "korean": "펠로폰네소스", + "dutch": "Peloponnesos", + "portuguese": "Peloponeso", + "russian": "Пелопоннес", + "chinese_traditional": "Peloponnese", + "unknown1": "Peloponnese", + "unknown2": "Peloponnese", + "unknown3": "Peloponnese", + "unknown4": "Peloponnese" + }, + "coordinates": { + "latitude": 37.501830628, + "longitude": 22.362731709 + } + }, + { + "id": 1326120960, + "name": "South Aegean", + "translations": { + "japanese": "南エーゲ", + "english": "South Aegean", + "french": "Égée-Méridionale", + "german": "Südliche Ägäis", + "italian": "Egeo Meridionale", + "spanish": "Egeo Meridional", + "chinese_simple": "南爱琴海大区", + "korean": "남부 에게", + "dutch": "Zuid-Egeïsche Eilanden", + "portuguese": "Egeu Meridional", + "russian": "Южные Эгейские острова", + "chinese_traditional": "South Aegean", + "unknown1": "South Aegean", + "unknown2": "South Aegean", + "unknown3": "South Aegean", + "unknown4": "South Aegean" + }, + "coordinates": { + "latitude": 36.436156812, + "longitude": 28.218460523 + } + }, + { + "id": 1326186496, + "name": "Thessaly", + "translations": { + "japanese": "テッサリーア", + "english": "Thessaly", + "french": "Thessalie", + "german": "Thessalien", + "italian": "Tessaglia", + "spanish": "Tesalia", + "chinese_simple": "色萨利大区", + "korean": "테살리아", + "dutch": "Thessalië", + "portuguese": "Tessália", + "russian": "Фессалия", + "chinese_traditional": "Thessaly", + "unknown1": "Thessaly", + "unknown2": "Thessaly", + "unknown3": "Thessaly", + "unknown4": "Thessaly" + }, + "coordinates": { + "latitude": 39.63317826, + "longitude": 22.417663499 + } + }, + { + "id": 1326252032, + "name": "West Greece", + "translations": { + "japanese": "西ギリシャ", + "english": "West Greece", + "french": "Grèce-Occidentale", + "german": "Westgriechenland", + "italian": "Grecia Occidentale", + "spanish": "Grecia Occidental", + "chinese_simple": "西希腊大区", + "korean": "서부 그리스", + "dutch": "West-Griekenland", + "portuguese": "Grécia Ocidental", + "russian": "Западная Греция", + "chinese_traditional": "West Greece", + "unknown1": "West Greece", + "unknown2": "West Greece", + "unknown3": "West Greece", + "unknown4": "West Greece" + }, + "coordinates": { + "latitude": 38.23242144, + "longitude": 21.736509303 + } + }, + { + "id": 1326317568, + "name": "West Macedonia", + "translations": { + "japanese": "西マケドニア", + "english": "West Macedonia", + "french": "Macédoine-Occidentale", + "german": "Westmakedonien", + "italian": "Macedonia Occidentale", + "spanish": "Macedonia Occidental", + "chinese_simple": "西马其顿大区", + "korean": "서부 마케도니아", + "dutch": "West-Macedonië", + "portuguese": "Macedónia Ocidental", + "russian": "Западная Македония", + "chinese_traditional": "West Macedonia", + "unknown1": "West Macedonia", + "unknown2": "West Macedonia", + "unknown3": "West Macedonia", + "unknown4": "West Macedonia" + }, + "coordinates": { + "latitude": 40.308837432, + "longitude": 21.796934272 + } + } + ] + }, + { + "id": 80, + "iso_code": "HU", + "name": "Hungary", + "translations": { + "japanese": "ハンガリー", + "english": "Hungary", + "french": "Hongrie", + "german": "Ungarn", + "italian": "Ungheria", + "spanish": "Hungría", + "chinese_simple": "匈牙利", + "korean": "헝가리", + "dutch": "Hongarije", + "portuguese": "Hungria", + "russian": "Венгрия", + "chinese_traditional": "Hungary", + "unknown1": "Hungary", + "unknown2": "Hungary", + "unknown3": "Hungary", + "unknown4": "Hungary" + }, + "regions": [ + { + "id": 1342177280, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": 47.48840278, + "longitude": 19.077810667 + } + }, + { + "id": 1342308352, + "name": "Budapest", + "translations": { + "japanese": "ブダペスト", + "english": "Budapest", + "french": "Budapest", + "german": "Budapest", + "italian": "Budapest", + "spanish": "Budapest", + "chinese_simple": "布达佩斯市", + "korean": "부다페스트", + "dutch": "Boedapest", + "portuguese": "Budapeste", + "russian": "Будапешт", + "chinese_traditional": "Budapest", + "unknown1": "Budapest", + "unknown2": "Budapest", + "unknown3": "Budapest", + "unknown4": "Budapest" + }, + "coordinates": { + "latitude": 47.48840278, + "longitude": 19.077810667 + } + }, + { + "id": 1342373888, + "name": "Bács-Kiskun County", + "translations": { + "japanese": "バーチ・キシュクン州", + "english": "Bács-Kiskun County", + "french": "Bács-Kiskun", + "german": "Bács-Kiskun", + "italian": "Bács-Kiskun", + "spanish": "Bács-Kiskun", + "chinese_simple": "巴奇-基什孔州", + "korean": "바치키슈쿤 주", + "dutch": "Bács-Kiskun", + "portuguese": "Bács-Kiskun (condado)", + "russian": "Бач-Кишкун", + "chinese_traditional": "Bács-Kiskun County", + "unknown1": "Bács-Kiskun County", + "unknown2": "Bács-Kiskun County", + "unknown3": "Bács-Kiskun County", + "unknown4": "Bács-Kiskun County" + }, + "coordinates": { + "latitude": 46.900634232, + "longitude": 19.687553536 + } + }, + { + "id": 1342439424, + "name": "Baranya County", + "translations": { + "japanese": "バラニャ州", + "english": "Baranya County", + "french": "Baranya", + "german": "Baranya", + "italian": "Baranya", + "spanish": "Baranya", + "chinese_simple": "巴兰尼亚州", + "korean": "버러녀 주", + "dutch": "Baranya", + "portuguese": "Baranya (condado)", + "russian": "Баранья", + "chinese_traditional": "Baranya County", + "unknown1": "Baranya County", + "unknown2": "Baranya County", + "unknown3": "Baranya County", + "unknown4": "Baranya County" + }, + "coordinates": { + "latitude": 46.071166468, + "longitude": 18.215381564 + } + }, + { + "id": 1342504960, + "name": "Békés County", + "translations": { + "japanese": "ベーケーシュ州", + "english": "Békés County", + "french": "Békés", + "german": "Békés", + "italian": "Békés", + "spanish": "Békés", + "chinese_simple": "贝凯什州", + "korean": "베케시 주", + "dutch": "Békés", + "portuguese": "Békés (condado)", + "russian": "Бекеш", + "chinese_traditional": "Békés County", + "unknown1": "Békés County", + "unknown2": "Békés County", + "unknown3": "Békés County", + "unknown4": "Békés County" + }, + "coordinates": { + "latitude": 46.669921344, + "longitude": 21.099300539 + } + }, + { + "id": 1342570496, + "name": "Borsod-Abaúj-Zemplén County", + "translations": { + "japanese": "ボルショド・アバウーイ・ゼンプレーン州", + "english": "Borsod-Abaúj-Zemplén County", + "french": "Borsod-Abaúj-Zemplén", + "german": "Borsod-Abaúj-Zemplén", + "italian": "Borsod-Abaúj-Zemplén", + "spanish": "Borsod-Abaúj-Zemplén", + "chinese_simple": "包尔绍德-奥包乌伊-曾普伦州", + "korean": "보르쇼드어버우이젬플렌 주", + "dutch": "Borsod-Abaúj-Zemplén", + "portuguese": "Borsod-Abaúj-Zemplén (condado)", + "russian": "Боршод-Абауй-Земплен", + "chinese_traditional": "Borsod-Abaúj-Zemplén County", + "unknown1": "Borsod-Abaúj-Zemplén County", + "unknown2": "Borsod-Abaúj-Zemplén County", + "unknown3": "Borsod-Abaúj-Zemplén County", + "unknown4": "Borsod-Abaúj-Zemplén County" + }, + "coordinates": { + "latitude": 48.098143984000004, + "longitude": 20.786189336 + } + }, + { + "id": 1342636032, + "name": "Csongrád County", + "translations": { + "japanese": "チョングラード州", + "english": "Csongrád County", + "french": "Csongrád", + "german": "Csongrád", + "italian": "Csongrád", + "spanish": "Csongrád", + "chinese_simple": "琼格拉德州", + "korean": "촌그라드 주", + "dutch": "Csongrád", + "portuguese": "Csongrád (condado)", + "russian": "Чонград", + "chinese_traditional": "Csongrád County", + "unknown1": "Csongrád County", + "unknown2": "Csongrád County", + "unknown3": "Csongrád County", + "unknown4": "Csongrád County" + }, + "coordinates": { + "latitude": 46.25244088, + "longitude": 20.154473751 + } + }, + { + "id": 1342701568, + "name": "Fejér County", + "translations": { + "japanese": "フェイェール州", + "english": "Fejér County", + "french": "Fejér", + "german": "Fejér", + "italian": "Fejér", + "spanish": "Fejér", + "chinese_simple": "费耶尔州", + "korean": "페예르 주", + "dutch": "Fejér", + "portuguese": "Fejér (condado)", + "russian": "Фейер", + "chinese_traditional": "Fejér County", + "unknown1": "Fejér County", + "unknown2": "Fejér County", + "unknown3": "Fejér County", + "unknown4": "Fejér County" + }, + "coordinates": { + "latitude": 47.197265088, + "longitude": 18.40214965 + } + }, + { + "id": 1342767104, + "name": "Győr-Moson-Sopron County", + "translations": { + "japanese": "ジェール・モション・ショプロン州", + "english": "Győr-Moson-Sopron County", + "french": "Győr-Moson-Sopron", + "german": "Győr-Moson-Sopron", + "italian": "Győr-Moson-Sopron", + "spanish": "Győr-Moson-Sopron", + "chinese_simple": "杰尔-莫松-肖普朗州", + "korean": "죄르모숀쇼프론 주", + "dutch": "Győr-Moson-Sopron", + "portuguese": "Gyor-Moson-Sopron (condado)", + "russian": "Дьёр-Мошон-Шопрон", + "chinese_traditional": "Győr-Moson-Sopron County", + "unknown1": "Győr-Moson-Sopron County", + "unknown2": "Győr-Moson-Sopron County", + "unknown3": "Győr-Moson-Sopron County", + "unknown4": "Győr-Moson-Sopron County" + }, + "coordinates": { + "latitude": 47.675170356, + "longitude": 17.63310459 + } + }, + { + "id": 1342832640, + "name": "Hajdú-Bihar County", + "translations": { + "japanese": "ハイドゥー・ヒバル州", + "english": "Hajdú-Bihar County", + "french": "Hajdú-Bihar", + "german": "Hajdú-Bihar", + "italian": "Hajdú-Bihar", + "spanish": "Hajdú-Bihar", + "chinese_simple": "豪伊杜-比豪尔州", + "korean": "허이두 비허르 주", + "dutch": "Hajdú-Bihar", + "portuguese": "Hajdú-Bihar (condado)", + "russian": "Хайду-Бихар", + "chinese_traditional": "Hajdú-Bihar County", + "unknown1": "Hajdú-Bihar County", + "unknown2": "Hajdú-Bihar County", + "unknown3": "Hajdú-Bihar County", + "unknown4": "Hajdú-Bihar County" + }, + "coordinates": { + "latitude": 47.532348092, + "longitude": 21.621152544 + } + }, + { + "id": 1342898176, + "name": "Heves County", + "translations": { + "japanese": "ヘヴェシュ州", + "english": "Heves County", + "french": "Heves", + "german": "Heves", + "italian": "Heves", + "spanish": "Heves", + "chinese_simple": "赫维什州", + "korean": "헤베시 주", + "dutch": "Heves", + "portuguese": "Heves (condado)", + "russian": "Хевеш", + "chinese_traditional": "Heves County", + "unknown1": "Heves County", + "unknown2": "Heves County", + "unknown3": "Heves County", + "unknown4": "Heves County" + }, + "coordinates": { + "latitude": 47.894896916, + "longitude": 20.37969409 + } + }, + { + "id": 1342963712, + "name": "Jász-Nagykun-Szolnok County", + "translations": { + "japanese": "ヤース・ナチクン・ソルノク州", + "english": "Jász-Nagykun-Szolnok County", + "french": "Jász-Nagykun-Szolnok", + "german": "Jász-Nagykun-Szolnok", + "italian": "Jász-Nagykun-Szolnok", + "spanish": "Jász-Nagyk쳐歮覀㞂ʮ∎辝硂旱✑櫙᰺͖ﻩ", + "chinese_simple": "加兹-纳杰孔-索尔诺克州", + "korean": "야스너지쿤솔노크 주", + "dutch": "Jász-Nagykun-Szolnok", + "portuguese": "Jász-Nagykun-Szolnok (condado)", + "russian": "Яс-Надькун-Сольнок", + "chinese_traditional": "Jász-Nagykun-Szolnok County", + "unknown1": "Jász-Nagykun-Szolnok County", + "unknown2": "Jász-Nagykun-Szolnok County", + "unknown3": "Jász-Nagykun-Szolnok County", + "unknown4": "Jász-Nagykun-Szolnok County" + }, + "coordinates": { + "latitude": 47.169799268, + "longitude": 20.181939646 + } + }, + { + "id": 1343029248, + "name": "Komárom-Esztergom County", + "translations": { + "japanese": "コマーロム・エステルゴム州", + "english": "Komárom-Esztergom County", + "french": "Komárom-Esztergom", + "german": "Komárom-Esztergom", + "italian": "Komárom-Esztergom", + "spanish": "Komárom-Esztergom", + "chinese_simple": "科马罗姆-埃斯泰尔戈姆州", + "korean": "코마롬에스테르곰 주", + "dutch": "Komárom-Esztergom", + "portuguese": "Komárom-Esztergom (condado)", + "russian": "Комаром-Эстергом", + "chinese_traditional": "Komárom-Esztergom County", + "unknown1": "Komárom-Esztergom County", + "unknown2": "Komárom-Esztergom County", + "unknown3": "Komárom-Esztergom County", + "unknown4": "Komárom-Esztergom County" + }, + "coordinates": { + "latitude": 47.548827584, + "longitude": 18.435108724 + } + }, + { + "id": 1343094784, + "name": "Nógrád County", + "translations": { + "japanese": "ノーグラード州", + "english": "Nógrád County", + "french": "Nógrád", + "german": "Nógrád", + "italian": "Nógrád", + "spanish": "Nógrád", + "chinese_simple": "诺格拉德州", + "korean": "노그라드 주", + "dutch": "Nógrád", + "portuguese": "Nógrád (condado)", + "russian": "Ноград", + "chinese_traditional": "Nógrád County", + "unknown1": "Nógrád County", + "unknown2": "Nógrád County", + "unknown3": "Nógrád County", + "unknown4": "Nógrád County" + }, + "coordinates": { + "latitude": 48.103637148, + "longitude": 19.813896653 + } + }, + { + "id": 1343160320, + "name": "Pest County", + "translations": { + "japanese": "ペシュト州", + "english": "Pest County", + "french": "Pest", + "german": "Pest", + "italian": "Pest", + "spanish": "Pest", + "chinese_simple": "佩斯州", + "korean": "페슈트 주", + "dutch": "Pest", + "portuguese": "Pest (condado)", + "russian": "Пешт", + "chinese_traditional": "Pest County", + "unknown1": "Pest County", + "unknown2": "Pest County", + "unknown3": "Pest County", + "unknown4": "Pest County" + }, + "coordinates": { + "latitude": 47.48840278, + "longitude": 19.077810667 + } + }, + { + "id": 1343225856, + "name": "Somogy County", + "translations": { + "japanese": "ショモジ州", + "english": "Somogy County", + "french": "Somogy", + "german": "Somogy", + "italian": "Somogy", + "spanish": "Somogy", + "chinese_simple": "绍莫吉州", + "korean": "쇼모지 주", + "dutch": "Somogy", + "portuguese": "Somogy (condado)", + "russian": "Шомодь", + "chinese_traditional": "Somogy County", + "unknown1": "Somogy County", + "unknown2": "Somogy County", + "unknown3": "Somogy County", + "unknown4": "Somogy County" + }, + "coordinates": { + "latitude": 46.351317832, + "longitude": 17.786913602 + } + }, + { + "id": 1343291392, + "name": "Szabolcs-Szatmár-Bereg County", + "translations": { + "japanese": "サボルチ・サトマール・ベレグ州", + "english": "Szabolcs-Szatmár-Bereg County", + "french": "Szabolcs-Szatmár-Bereg", + "german": "Szabolcs-Szatmár-Bereg", + "italian": "Szabolcs-Szatmár-Bereg", + "spanish": "Szabolcs-Szatmár-Bereg", + "chinese_simple": "索博尔奇-索特马尔-贝拉格州", + "korean": "서볼츠서트마르베레그 주", + "dutch": "Szabolcs-Szatmár-Bereg", + "portuguese": "Szabolcs-Szatmár-Bereg (condado)", + "russian": "Сабольч-Сатмар-Берег", + "chinese_traditional": "Szabolcs-Szatmár-Bereg County", + "unknown1": "Szabolcs-Szatmár-Bereg County", + "unknown2": "Szabolcs-Szatmár-Bereg County", + "unknown3": "Szabolcs-Szatmár-Bereg County", + "unknown4": "Szabolcs-Szatmár-Bereg County" + }, + "coordinates": { + "latitude": 47.95532172, + "longitude": 21.720029766 + } + }, + { + "id": 1343356928, + "name": "Tolna County", + "translations": { + "japanese": "トルナ州", + "english": "Tolna County", + "french": "Tolna", + "german": "Tolna", + "italian": "Tolna", + "spanish": "Tolna", + "chinese_simple": "托尔瑙州", + "korean": "톨너 주", + "dutch": "Tolna", + "portuguese": "Tolna (condado)", + "russian": "Тольна", + "chinese_traditional": "Tolna County", + "unknown1": "Tolna County", + "unknown2": "Tolna County", + "unknown3": "Tolna County", + "unknown4": "Tolna County" + }, + "coordinates": { + "latitude": 46.345824668, + "longitude": 18.704274495 + } + }, + { + "id": 1343422464, + "name": "Vas County", + "translations": { + "japanese": "ヴァシュ州", + "english": "Vas County", + "french": "Vas", + "german": "Vas", + "italian": "Vas", + "spanish": "Vas", + "chinese_simple": "沃什州", + "korean": "버시 주", + "dutch": "Vas", + "portuguese": "Vas (condado)", + "russian": "Ваш", + "chinese_traditional": "Vas County", + "unknown1": "Vas County", + "unknown2": "Vas County", + "unknown3": "Vas County", + "unknown4": "Vas County" + }, + "coordinates": { + "latitude": 47.230224072, + "longitude": 16.622359654 + } + }, + { + "id": 1343488000, + "name": "Veszprém County", + "translations": { + "japanese": "ベスプレーム州", + "english": "Veszprém County", + "french": "Veszprém", + "german": "Veszprém", + "italian": "Veszprém", + "spanish": "Veszprém", + "chinese_simple": "维斯普雷姆州", + "korean": "베스프렘 주", + "dutch": "Veszprém", + "portuguese": "Veszprém (condado)", + "russian": "Веспрем", + "chinese_traditional": "Veszprém County", + "unknown1": "Veszprém County", + "unknown2": "Veszprém County", + "unknown3": "Veszprém County", + "unknown4": "Veszprém County" + }, + "coordinates": { + "latitude": 47.098388136, + "longitude": 17.913256719 + } + }, + { + "id": 1343553536, + "name": "Zala County", + "translations": { + "japanese": "ザラ州", + "english": "Zala County", + "french": "Zala", + "german": "Zala", + "italian": "Zala", + "spanish": "Zala", + "chinese_simple": "佐洛州", + "korean": "절러 주", + "dutch": "Zala", + "portuguese": "Zala (condado)", + "russian": "Зала", + "chinese_traditional": "Zala County", + "unknown1": "Zala County", + "unknown2": "Zala County", + "unknown3": "Zala County", + "unknown4": "Zala County" + }, + "coordinates": { + "latitude": 46.840209428, + "longitude": 16.836593635 + } + } + ] + }, + { + "id": 81, + "iso_code": "IS", + "name": "Iceland", + "translations": { + "japanese": "アイスランド", + "english": "Iceland", + "french": "Islande", + "german": "Island", + "italian": "Islanda", + "spanish": "Islandia", + "chinese_simple": "冰岛", + "korean": "아이슬란드", + "dutch": "IJsland", + "portuguese": "Islândia", + "russian": "Исландия", + "chinese_traditional": "Iceland", + "unknown1": "Iceland", + "unknown2": "Iceland", + "unknown3": "Iceland", + "unknown4": "Iceland" + }, + "regions": [ + { + "id": 1358954496, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": 64.1326897, + "longitude": -21.889828843000004 + } + }, + { + "id": 1359020032, + "name": "Iceland", + "translations": { + "japanese": "アイスランド", + "english": "Iceland", + "french": "Islande", + "german": "Island", + "italian": "Islanda", + "spanish": "Islandia", + "chinese_simple": "冰岛", + "korean": "아이슬란드", + "dutch": "IJsland", + "portuguese": "Islândia", + "russian": "Исландия", + "chinese_traditional": "Iceland", + "unknown1": "Iceland", + "unknown2": "Iceland", + "unknown3": "Iceland", + "unknown4": "Iceland" + }, + "coordinates": { + "latitude": 64.1326897, + "longitude": -21.889828843000004 + } + } + ] + }, + { + "id": 82, + "iso_code": "IE", + "name": "Ireland", + "translations": { + "japanese": "アイルランド", + "english": "Ireland", + "french": "Irlande", + "german": "Irland", + "italian": "Irlanda", + "spanish": "Irlanda", + "chinese_simple": "爱尔兰", + "korean": "아일랜드", + "dutch": "Ierland", + "portuguese": "Irlanda", + "russian": "Ирландия", + "chinese_traditional": "Ireland", + "unknown1": "Ireland", + "unknown2": "Ireland", + "unknown3": "Ireland", + "unknown4": "Ireland" + }, + "regions": [ + { + "id": 1375731712, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": 53.33862244, + "longitude": -6.256241409000012 + } + }, + { + "id": 1375862784, + "name": "Dublin", + "translations": { + "japanese": "ダブリン州", + "english": "Dublin", + "french": "Dublin", + "german": "Dublin", + "italian": "Dublino", + "spanish": "Dublín", + "chinese_simple": "都柏林地区", + "korean": "더블린", + "dutch": "Dublin", + "portuguese": "Dublin", + "russian": "Дублин", + "chinese_traditional": "Dublin", + "unknown1": "Dublin", + "unknown2": "Dublin", + "unknown3": "Dublin", + "unknown4": "Dublin" + }, + "coordinates": { + "latitude": 53.33862244, + "longitude": -6.256241409000012 + } + }, + { + "id": 1376387072, + "name": "County Carlow", + "translations": { + "japanese": "カーロウ州", + "english": "County Carlow", + "french": "Carlow", + "german": "Carlow", + "italian": "Carlow", + "spanish": "Carlow", + "chinese_simple": "卡洛郡", + "korean": "칼로우 주", + "dutch": "Carlow", + "portuguese": "Carlow (condado)", + "russian": "Карлоу", + "chinese_traditional": "County Carlow", + "unknown1": "County Carlow", + "unknown2": "County Carlow", + "unknown3": "County Carlow", + "unknown4": "County Carlow" + }, + "coordinates": { + "latitude": 52.827758188, + "longitude": -6.926409247000009 + } + }, + { + "id": 1376452608, + "name": "County Cavan", + "translations": { + "japanese": "キャバン州", + "english": "County Cavan", + "french": "Cavan", + "german": "Cavan", + "italian": "Cavan", + "spanish": "Cavan", + "chinese_simple": "卡文郡", + "korean": "캐번 주", + "dutch": "Cavan", + "portuguese": "Cavan (condado)", + "russian": "Каван", + "chinese_traditional": "County Cavan", + "unknown1": "County Cavan", + "unknown2": "County Cavan", + "unknown3": "County Cavan", + "unknown4": "County Cavan" + }, + "coordinates": { + "latitude": 53.986815792, + "longitude": -7.354877208999994 + } + }, + { + "id": 1376518144, + "name": "County Clare", + "translations": { + "japanese": "クレア州", + "english": "County Clare", + "french": "Clare", + "german": "Clare", + "italian": "Clare", + "spanish": "Clare", + "chinese_simple": "克莱尔郡", + "korean": "클레어 주", + "dutch": "Clare", + "portuguese": "Clare (condado)", + "russian": "Клэр", + "chinese_traditional": "County Clare", + "unknown1": "County Clare", + "unknown2": "County Clare", + "unknown3": "County Clare", + "unknown4": "County Clare" + }, + "coordinates": { + "latitude": 52.84423768, + "longitude": -8.975365014000005 + } + }, + { + "id": 1376583680, + "name": "County Cork", + "translations": { + "japanese": "コーク州", + "english": "County Cork", + "french": "Cork", + "german": "Cork", + "italian": "Cork", + "spanish": "Cork", + "chinese_simple": "科克郡", + "korean": "코크 주", + "dutch": "Cork", + "portuguese": "Cork (condado)", + "russian": "Корк", + "chinese_traditional": "County Cork", + "unknown1": "County Cork", + "unknown2": "County Cork", + "unknown3": "County Cork", + "unknown4": "County Cork" + }, + "coordinates": { + "latitude": 51.893920308, + "longitude": -8.464499367000002 + } + }, + { + "id": 1376649216, + "name": "County Donegal", + "translations": { + "japanese": "ドニゴール州", + "english": "County Donegal", + "french": "Donegal", + "german": "Donegal", + "italian": "Donegal", + "spanish": "Donegal", + "chinese_simple": "多内加尔郡", + "korean": "도니골 주", + "dutch": "Donegal", + "portuguese": "Donegal (condado)", + "russian": "Донегол", + "chinese_traditional": "County Donegal", + "unknown1": "County Donegal", + "unknown2": "County Donegal", + "unknown3": "County Donegal", + "unknown4": "County Donegal" + }, + "coordinates": { + "latitude": 54.832763048, + "longitude": -7.475727147000015 + } + }, + { + "id": 1376714752, + "name": "County Galway", + "translations": { + "japanese": "ゴールウェイ州", + "english": "County Galway", + "french": "Galway", + "german": "Galway", + "italian": "Galway", + "spanish": "Galway", + "chinese_simple": "戈尔韦郡", + "korean": "골웨이 주", + "dutch": "Galway", + "portuguese": "Galway (condado)", + "russian": "Голуэй", + "chinese_traditional": "County Galway", + "unknown1": "County Galway", + "unknown2": "County Galway", + "unknown3": "County Galway", + "unknown4": "County Galway" + }, + "coordinates": { + "latitude": 53.272704472, + "longitude": -9.041283162000013 + } + }, + { + "id": 1376780288, + "name": "County Kerry", + "translations": { + "japanese": "ケリー州", + "english": "County Kerry", + "french": "Kerry", + "german": "Kerry", + "italian": "Kerry", + "spanish": "Kerry", + "chinese_simple": "凯里郡", + "korean": "케리 주", + "dutch": "Kerry", + "portuguese": "Kerry (condado)", + "russian": "Керри", + "chinese_traditional": "County Kerry", + "unknown1": "County Kerry", + "unknown2": "County Kerry", + "unknown3": "County Kerry", + "unknown4": "County Kerry" + }, + "coordinates": { + "latitude": 52.26745546, + "longitude": -9.694971463000002 + } + }, + { + "id": 1376845824, + "name": "County Kildare", + "translations": { + "japanese": "キルデア州", + "english": "County Kildare", + "french": "Kildare", + "german": "Kildare", + "italian": "Kildare", + "spanish": "Kildare", + "chinese_simple": "基尔代尔郡", + "korean": "킬데어 주", + "dutch": "Kildare", + "portuguese": "Kildare (condado)", + "russian": "Килдэр", + "chinese_traditional": "County Kildare", + "unknown1": "County Kildare", + "unknown2": "County Kildare", + "unknown3": "County Kildare", + "unknown4": "County Kildare" + }, + "coordinates": { + "latitude": 53.212279668, + "longitude": -6.662736655000003 + } + }, + { + "id": 1376911360, + "name": "County Kilkenny", + "translations": { + "japanese": "キルケニー州", + "english": "County Kilkenny", + "french": "Kilkenny", + "german": "Kilkenny", + "italian": "Kilkenny", + "spanish": "Kilkenny", + "chinese_simple": "基尔肯尼郡", + "korean": "킬케니 주", + "dutch": "Kilkenny", + "portuguese": "Kilkenny (condado)", + "russian": "Килкенни", + "chinese_traditional": "County Kilkenny", + "unknown1": "County Kilkenny", + "unknown2": "County Kilkenny", + "unknown3": "County Kilkenny", + "unknown4": "County Kilkenny" + }, + "coordinates": { + "latitude": 52.646483776000004, + "longitude": -7.2505068080000115 + } + }, + { + "id": 1376976896, + "name": "County Laois", + "translations": { + "japanese": "リーシュ州", + "english": "County Laois", + "french": "Laois", + "german": "Laois", + "italian": "Laois", + "spanish": "Laois", + "chinese_simple": "莱伊什郡", + "korean": "리시 주", + "dutch": "Laois", + "portuguese": "Laois (condado)", + "russian": "Лиишь", + "chinese_traditional": "County Laois", + "unknown1": "County Laois", + "unknown2": "County Laois", + "unknown3": "County Laois", + "unknown4": "County Laois" + }, + "coordinates": { + "latitude": 53.025512092, + "longitude": -7.299945419000011 + } + }, + { + "id": 1377042432, + "name": "County Leitrim", + "translations": { + "japanese": "リートリム州", + "english": "County Leitrim", + "french": "Leitrim", + "german": "Leitrim", + "italian": "Leitrim", + "spanish": "Leitrim", + "chinese_simple": "利特里姆郡", + "korean": "리트림 주", + "dutch": "Leitrim", + "portuguese": "Leitrim (condado)", + "russian": "Литрим", + "chinese_traditional": "County Leitrim", + "unknown1": "County Leitrim", + "unknown2": "County Leitrim", + "unknown3": "County Leitrim", + "unknown4": "County Leitrim" + }, + "coordinates": { + "latitude": 53.94287048, + "longitude": -8.085470015999988 + } + }, + { + "id": 1377107968, + "name": "County Limerick", + "translations": { + "japanese": "リムリック州", + "english": "County Limerick", + "french": "Limerick", + "german": "Limerick", + "italian": "Limerick", + "spanish": "Limerick", + "chinese_simple": "利默里克郡", + "korean": "리머릭 주", + "dutch": "Limerick", + "portuguese": "Limerick (condado)", + "russian": "Лимерик", + "chinese_traditional": "County Limerick", + "unknown1": "County Limerick", + "unknown2": "County Limerick", + "unknown3": "County Limerick", + "unknown4": "County Limerick" + }, + "coordinates": { + "latitude": 52.662963268, + "longitude": -8.618308379000013 + } + }, + { + "id": 1377173504, + "name": "County Longford", + "translations": { + "japanese": "ロングフォード州", + "english": "County Longford", + "french": "Longford", + "german": "Longford", + "italian": "Longford", + "spanish": "Longford", + "chinese_simple": "朗福德郡", + "korean": "롱퍼드 주", + "dutch": "Longford", + "portuguese": "Longford (condado)", + "russian": "Лонгфорд", + "chinese_traditional": "County Longford", + "unknown1": "County Longford", + "unknown2": "County Longford", + "unknown3": "County Longford", + "unknown4": "County Longford" + }, + "coordinates": { + "latitude": 53.72314392, + "longitude": -7.794331529000004 + } + }, + { + "id": 1377239040, + "name": "County Louth", + "translations": { + "japanese": "ラウス州", + "english": "County Louth", + "french": "Louth", + "german": "Louth", + "italian": "Louth", + "spanish": "Louth", + "chinese_simple": "劳斯郡", + "korean": "라우스 주", + "dutch": "Louth", + "portuguese": "Louth (condado)", + "russian": "Лаут", + "chinese_traditional": "County Louth", + "unknown1": "County Louth", + "unknown2": "County Louth", + "unknown3": "County Louth", + "unknown4": "County Louth" + }, + "coordinates": { + "latitude": 54.008788448, + "longitude": -6.399064062999997 + } + }, + { + "id": 1377304576, + "name": "County Mayo", + "translations": { + "japanese": "メイヨー州", + "english": "County Mayo", + "french": "Mayo", + "german": "Mayo", + "italian": "Mayo", + "spanish": "Mayo", + "chinese_simple": "梅奥郡", + "korean": "메이오 주", + "dutch": "Mayo", + "portuguese": "Mayo (condado)", + "russian": "Мейо", + "chinese_traditional": "County Mayo", + "unknown1": "County Mayo", + "unknown2": "County Mayo", + "unknown3": "County Mayo", + "unknown4": "County Mayo" + }, + "coordinates": { + "latitude": 53.86047302, + "longitude": -9.293969395999994 + } + }, + { + "id": 1377370112, + "name": "County Meath", + "translations": { + "japanese": "ミース州", + "english": "County Meath", + "french": "Meath", + "german": "Meath", + "italian": "Meath", + "spanish": "Meath", + "chinese_simple": "米斯郡", + "korean": "미스 주", + "dutch": "Meath", + "portuguese": "Meath (condado)", + "russian": "Мит", + "chinese_traditional": "County Meath", + "unknown1": "County Meath", + "unknown2": "County Meath", + "unknown3": "County Meath", + "unknown4": "County Meath" + }, + "coordinates": { + "latitude": 53.651732788, + "longitude": -6.679216192000013 + } + }, + { + "id": 1377435648, + "name": "County Monaghan", + "translations": { + "japanese": "モナハン州", + "english": "County Monaghan", + "french": "Monaghan", + "german": "Monaghan", + "italian": "Monaghan", + "spanish": "Monaghan", + "chinese_simple": "莫纳亨郡", + "korean": "모너핸 주", + "dutch": "Monaghan", + "portuguese": "Monaghan (condado)", + "russian": "Монахан", + "chinese_traditional": "County Monaghan", + "unknown1": "County Monaghan", + "unknown2": "County Monaghan", + "unknown3": "County Monaghan", + "unknown4": "County Monaghan" + }, + "coordinates": { + "latitude": 54.2449945, + "longitude": -6.964861500000012 + } + }, + { + "id": 1377501184, + "name": "County Offaly", + "translations": { + "japanese": "オファリー州", + "english": "County Offaly", + "french": "Offaly", + "german": "Offaly", + "italian": "Offaly", + "spanish": "Offaly", + "chinese_simple": "奥法利郡", + "korean": "오펄리 주", + "dutch": "Offaly", + "portuguese": "Offaly (condado)", + "russian": "Оффали", + "chinese_traditional": "County Offaly", + "unknown1": "County Offaly", + "unknown2": "County Offaly", + "unknown3": "County Offaly", + "unknown4": "County Offaly" + }, + "coordinates": { + "latitude": 53.261718144, + "longitude": -7.497699863000008 + } + }, + { + "id": 1377566720, + "name": "County Roscommon", + "translations": { + "japanese": "ロスコモン州", + "english": "County Roscommon", + "french": "Roscommon", + "german": "Roscommon", + "italian": "Roscommon", + "spanish": "Roscommon", + "chinese_simple": "罗斯康芒郡", + "korean": "로스코먼 주", + "dutch": "Roscommon", + "portuguese": "Roscommon (condado)", + "russian": "Роскоммон", + "chinese_traditional": "County Roscommon", + "unknown1": "County Roscommon", + "unknown2": "County Roscommon", + "unknown3": "County Roscommon", + "unknown4": "County Roscommon" + }, + "coordinates": { + "latitude": 53.629760132, + "longitude": -8.178854059000003 + } + }, + { + "id": 1377632256, + "name": "County Sligo", + "translations": { + "japanese": "スライゴ州", + "english": "County Sligo", + "french": "Sligo", + "german": "Sligo", + "italian": "Sligo", + "spanish": "Sligo", + "chinese_simple": "斯莱戈郡", + "korean": "슬라이고 주", + "dutch": "Sligo", + "portuguese": "Sligo (condado)", + "russian": "Слайго", + "chinese_traditional": "County Sligo", + "unknown1": "County Sligo", + "unknown2": "County Sligo", + "unknown3": "County Sligo", + "unknown4": "County Sligo" + }, + "coordinates": { + "latitude": 54.261473992, + "longitude": -8.480978904000011 + } + }, + { + "id": 1377697792, + "name": "County Tipperary", + "translations": { + "japanese": "ティペラリー州", + "english": "County Tipperary", + "french": "Tipperary", + "german": "Tipperary", + "italian": "Tipperary", + "spanish": "Tipperary", + "chinese_simple": "蒂珀雷里郡", + "korean": "티퍼레리 주", + "dutch": "Tipperary", + "portuguese": "Tipperary (condado)", + "russian": "Типперэри", + "chinese_traditional": "County Tipperary", + "unknown1": "County Tipperary", + "unknown2": "County Tipperary", + "unknown3": "County Tipperary", + "unknown4": "County Tipperary" + }, + "coordinates": { + "latitude": 52.662963268, + "longitude": -7.832783782000007 + } + }, + { + "id": 1377763328, + "name": "County Waterford", + "translations": { + "japanese": "ウォーターフォード州", + "english": "County Waterford", + "french": "Waterford", + "german": "Waterford", + "italian": "Waterford", + "spanish": "Waterford", + "chinese_simple": "沃特福德郡", + "korean": "워터퍼드 주", + "dutch": "Waterford", + "portuguese": "Waterford (condado)", + "russian": "Уотерфорд", + "chinese_traditional": "County Waterford", + "unknown1": "County Waterford", + "unknown2": "County Waterford", + "unknown3": "County Waterford", + "unknown4": "County Waterford" + }, + "coordinates": { + "latitude": 52.080687884, + "longitude": -7.63502933800001 + } + }, + { + "id": 1377828864, + "name": "County Westmeath", + "translations": { + "japanese": "ウェストミース州", + "english": "County Westmeath", + "french": "Westmeath", + "german": "Westmeath", + "italian": "Westmeath", + "spanish": "Westmeath", + "chinese_simple": "西米斯郡", + "korean": "웨스트미스 주", + "dutch": "Westmeath", + "portuguese": "Westmeath (condado)", + "russian": "Уэстмит", + "chinese_traditional": "County Westmeath", + "unknown1": "County Westmeath", + "unknown2": "County Westmeath", + "unknown3": "County Westmeath", + "unknown4": "County Westmeath" + }, + "coordinates": { + "latitude": 53.519896852, + "longitude": -7.332904493000001 + } + }, + { + "id": 1377894400, + "name": "County Wexford", + "translations": { + "japanese": "ウェックスフォード州", + "english": "County Wexford", + "french": "Wexford", + "german": "Wexford", + "italian": "Wexford", + "spanish": "Wexford", + "chinese_simple": "韦克斯福德郡", + "korean": "웩스퍼드 주", + "dutch": "Wexford", + "portuguese": "Wexford (condado)", + "russian": "Уэксфорд", + "chinese_traditional": "County Wexford", + "unknown1": "County Wexford", + "unknown2": "County Wexford", + "unknown3": "County Wexford", + "unknown4": "County Wexford" + }, + "coordinates": { + "latitude": 52.333373428, + "longitude": -6.453995853000009 + } + }, + { + "id": 1377959936, + "name": "County Wicklow", + "translations": { + "japanese": "ウィックロー州", + "english": "County Wicklow", + "french": "Wicklow", + "german": "Wicklow", + "italian": "Wicklow", + "spanish": "Wicklow", + "chinese_simple": "威克洛郡", + "korean": "위클로 주", + "dutch": "Wicklow", + "portuguese": "Wicklow (condado)", + "russian": "Уиклоу", + "chinese_traditional": "County Wicklow", + "unknown1": "County Wicklow", + "unknown2": "County Wicklow", + "unknown3": "County Wicklow", + "unknown4": "County Wicklow" + }, + "coordinates": { + "latitude": 52.976073616, + "longitude": -6.036514248999993 + } + } + ] + }, + { + "id": 83, + "iso_code": "IT", + "name": "Italy", + "translations": { + "japanese": "イタリア", + "english": "Italy", + "french": "Italie", + "german": "Italien", + "italian": "Italia", + "spanish": "Italia", + "chinese_simple": "意大利", + "korean": "이탈리아", + "dutch": "Italië", + "portuguese": "Itália", + "russian": "Италия", + "chinese_traditional": "Italy", + "unknown1": "Italy", + "unknown2": "Italy", + "unknown3": "Italy", + "unknown4": "Italy" + }, + "regions": [ + { + "id": 1392508928, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": 41.890868664, + "longitude": 12.485995867 + } + }, + { + "id": 1392640000, + "name": "Lazio", + "translations": { + "japanese": "ラツィオ州", + "english": "Lazio", + "french": "Latium", + "german": "Latium", + "italian": "Lazio", + "spanish": "Lacio", + "chinese_simple": "拉齐奥大区", + "korean": "라치오 주", + "dutch": "Lazio", + "portuguese": "Lácio", + "russian": "Лацио", + "chinese_traditional": "Lazio", + "unknown1": "Lazio", + "unknown2": "Lazio", + "unknown3": "Lazio", + "unknown4": "Lazio" + }, + "coordinates": { + "latitude": 41.890868664, + "longitude": 12.485995867 + } + }, + { + "id": 1392705536, + "name": "Aosta Valley", + "translations": { + "japanese": "バッレ・ダオスタ州", + "english": "Aosta Valley", + "french": "Vallée d'Aoste", + "german": "Aostatal", + "italian": "Valle d'Aosta", + "spanish": "Valle de Aosta", + "chinese_simple": "瓦莱-达奥斯塔大区", + "korean": "발레다오스타 주", + "dutch": "Valle d'Aosta", + "portuguese": "Vale de Aosta", + "russian": "Валле-д’Аоста", + "chinese_traditional": "Aosta Valley", + "unknown1": "Aosta Valley", + "unknown2": "Aosta Valley", + "unknown3": "Aosta Valley", + "unknown4": "Aosta Valley" + }, + "coordinates": { + "latitude": 45.7305903, + "longitude": 7.311421249 + } + }, + { + "id": 1392771072, + "name": "Piedmont", + "translations": { + "japanese": "ピエモンテ州", + "english": "Piedmont", + "french": "Piémont", + "german": "Piemont", + "italian": "Piemonte", + "spanish": "Piamonte", + "chinese_simple": "皮埃蒙特大区", + "korean": "피에몬테 주", + "dutch": "Piëmont", + "portuguese": "Piemonte", + "russian": "Пьемонт", + "chinese_traditional": "Piedmont", + "unknown1": "Piedmont", + "unknown2": "Piedmont", + "unknown3": "Piedmont", + "unknown4": "Piedmont" + }, + "coordinates": { + "latitude": 45.07141062, + "longitude": 7.673971063 + } + }, + { + "id": 1392836608, + "name": "Liguria", + "translations": { + "japanese": "リグリア州", + "english": "Liguria", + "french": "Ligurie", + "german": "Ligurien", + "italian": "Liguria", + "spanish": "Liguria", + "chinese_simple": "利古里亚大区", + "korean": "리구리아 주", + "dutch": "Ligurië", + "portuguese": "Ligúria", + "russian": "Лигурия", + "chinese_traditional": "Liguria", + "unknown1": "Liguria", + "unknown2": "Liguria", + "unknown3": "Liguria", + "unknown4": "Liguria" + }, + "coordinates": { + "latitude": 44.401244612, + "longitude": 8.931909054 + } + }, + { + "id": 1392902144, + "name": "Lombardy", + "translations": { + "japanese": "ロンバルディア州", + "english": "Lombardy", + "french": "Lombardie", + "german": "Lombardei", + "italian": "Lombardia", + "spanish": "Lombardía", + "chinese_simple": "伦巴第大区", + "korean": "롬바르디아 주", + "dutch": "Lombardije", + "portuguese": "Lombardia", + "russian": "Ломбардия", + "chinese_traditional": "Lombardy", + "unknown1": "Lombardy", + "unknown2": "Lombardy", + "unknown3": "Lombardy", + "unknown4": "Lombardy" + }, + "coordinates": { + "latitude": 45.466918428, + "longitude": 9.184595288 + } + }, + { + "id": 1392967680, + "name": "Trentino-Alto Adige", + "translations": { + "japanese": "トレンティノ・アルト・アディジェ州", + "english": "Trentino-Alto Adige", + "french": "Trentin-Haut-Adige", + "german": "Trentino-Südtirol", + "italian": "Trentino-Alto Adige", + "spanish": "Trentino-Alto Adigio", + "chinese_simple": "特伦蒂诺-上阿迪杰大区", + "korean": "트렌티노알토아디제 주", + "dutch": "Trentino-Zuid-Tirol", + "portuguese": "Trentino-Alto Ádige", + "russian": "Трентино-Альто-Адидже", + "chinese_traditional": "Trentino-Alto Adige", + "unknown1": "Trentino-Alto Adige", + "unknown2": "Trentino-Alto Adige", + "unknown3": "Trentino-Alto Adige", + "unknown4": "Trentino-Alto Adige" + }, + "coordinates": { + "latitude": 46.065673304, + "longitude": 11.118194296 + } + }, + { + "id": 1393033216, + "name": "Veneto", + "translations": { + "japanese": "ベネト州", + "english": "Veneto", + "french": "Vénétie", + "german": "Venetien", + "italian": "Veneto", + "spanish": "Véneto", + "chinese_simple": "威尼托大区", + "korean": "베네토 주", + "dutch": "Veneto", + "portuguese": "Veneto", + "russian": "Венето", + "chinese_traditional": "Veneto", + "unknown1": "Veneto", + "unknown2": "Veneto", + "unknown3": "Veneto", + "unknown4": "Veneto" + }, + "coordinates": { + "latitude": 45.42846628, + "longitude": 12.332186855 + } + }, + { + "id": 1393098752, + "name": "Friuli Venezia Giulia", + "translations": { + "japanese": "フリウリ・ベネチア・ジュリア州", + "english": "Friuli Venezia Giulia", + "french": "Frioul-Vénétie julienne", + "german": "Friaul-Julisch Venetien", + "italian": "Friuli-Venezia Giulia", + "spanish": "Friuli-Venecia Julia", + "chinese_simple": "弗留利-威尼斯朱利亚大区", + "korean": "프리울리베네치아줄리아 주", + "dutch": "Friuli-Venezia Giulia", + "portuguese": "Friul-Venécia Juliana", + "russian": "Фриули-Венеция-Джулия", + "chinese_traditional": "Friuli Venezia Giulia", + "unknown1": "Friuli Venezia Giulia", + "unknown2": "Friuli Venezia Giulia", + "unknown3": "Friuli Venezia Giulia", + "unknown4": "Friuli Venezia Giulia" + }, + "coordinates": { + "latitude": 45.637206512, + "longitude": 13.765906574 + } + }, + { + "id": 1393164288, + "name": "Emilia-Romagna", + "translations": { + "japanese": "エミリア・ロマーニャ州", + "english": "Emilia-Romagna", + "french": "Émilie-Romagne", + "german": "Emilia-Romagna", + "italian": "Emilia-Romagna", + "spanish": "Emilia-Romaña", + "chinese_simple": "艾米利亚-罗马涅大区", + "korean": "에밀리아로마냐 주", + "dutch": "Emilia-Romagna", + "portuguese": "Emília-Romana", + "russian": "Эмилия-Романья", + "chinese_traditional": "Emilia-Romagna", + "unknown1": "Emilia-Romagna", + "unknown2": "Emilia-Romagna", + "unknown3": "Emilia-Romagna", + "unknown4": "Emilia-Romagna" + }, + "coordinates": { + "latitude": 44.500121564, + "longitude": 11.332428277 + } + }, + { + "id": 1393229824, + "name": "Tuscany", + "translations": { + "japanese": "トスカナ州", + "english": "Tuscany", + "french": "Toscane", + "german": "Toskana", + "italian": "Toscana", + "spanish": "Toscana", + "chinese_simple": "托斯卡纳大区", + "korean": "토스카나 주", + "dutch": "Toscane", + "portuguese": "Toscânia", + "russian": "Тоскана", + "chinese_traditional": "Tuscany", + "unknown1": "Tuscany", + "unknown2": "Tuscany", + "unknown3": "Tuscany", + "unknown4": "Tuscany" + }, + "coordinates": { + "latitude": 43.775023916, + "longitude": 11.250030592 + } + }, + { + "id": 1393295360, + "name": "Umbria", + "translations": { + "japanese": "ウンブリア州", + "english": "Umbria", + "french": "Ombrie", + "german": "Umbrien", + "italian": "Umbria", + "spanish": "Umbría", + "chinese_simple": "翁布里亚大区", + "korean": "움브리아 주", + "dutch": "Umbrië", + "portuguese": "Úmbria", + "russian": "Умбрия", + "chinese_traditional": "Umbria", + "unknown1": "Umbria", + "unknown2": "Umbria", + "unknown3": "Umbria", + "unknown4": "Umbria" + }, + "coordinates": { + "latitude": 43.110351072, + "longitude": 12.381625466 + } + }, + { + "id": 1393360896, + "name": "Marche", + "translations": { + "japanese": "マルケ州", + "english": "Marche", + "french": "Marches", + "german": "Marken", + "italian": "Marche", + "spanish": "Las Marcas", + "chinese_simple": "马尔凯大区", + "korean": "마르케 주", + "dutch": "Marche", + "portuguese": "Marche", + "russian": "Марке", + "chinese_traditional": "Marche", + "unknown1": "Marche", + "unknown2": "Marche", + "unknown3": "Marche", + "unknown4": "Marche" + }, + "coordinates": { + "latitude": 43.61572216, + "longitude": 13.507727161 + } + }, + { + "id": 1393426432, + "name": "Abruzzo", + "translations": { + "japanese": "アブルッツィ州", + "english": "Abruzzo", + "french": "Abruzzes", + "german": "Abruzzen", + "italian": "Abruzzo", + "spanish": "Abruzos", + "chinese_simple": "阿布鲁佐大区", + "korean": "아브루초 주", + "dutch": "Abruzzen", + "portuguese": "Abruzo", + "russian": "Абруццо", + "chinese_traditional": "Abruzzo", + "unknown1": "Abruzzo", + "unknown2": "Abruzzo", + "unknown3": "Abruzzo", + "unknown4": "Abruzzo" + }, + "coordinates": { + "latitude": 42.346801276, + "longitude": 13.386877222999999 + } + }, + { + "id": 1393491968, + "name": "Molise", + "translations": { + "japanese": "モリーゼ州", + "english": "Molise", + "french": "Molise", + "german": "Molise", + "italian": "Molise", + "spanish": "Molise", + "chinese_simple": "莫利塞大区", + "korean": "몰리세 주", + "dutch": "Molise", + "portuguese": "Molise", + "russian": "Молизе", + "chinese_traditional": "Molise", + "unknown1": "Molise", + "unknown2": "Molise", + "unknown3": "Molise", + "unknown4": "Molise" + }, + "coordinates": { + "latitude": 41.55578566, + "longitude": 14.655801572 + } + }, + { + "id": 1393557504, + "name": "Campania", + "translations": { + "japanese": "カンパニア州", + "english": "Campania", + "french": "Campanie", + "german": "Kampanien", + "italian": "Campania", + "spanish": "Campania", + "chinese_simple": "坎帕尼亚大区", + "korean": "캄파니아 주", + "dutch": "Campanië", + "portuguese": "Campânia", + "russian": "Кампания", + "chinese_traditional": "Campania", + "unknown1": "Campania", + "unknown2": "Campania", + "unknown3": "Campania", + "unknown4": "Campania" + }, + "coordinates": { + "latitude": 40.836181176000004, + "longitude": 14.249306326 + } + }, + { + "id": 1393623040, + "name": "Apulia", + "translations": { + "japanese": "プーリア州", + "english": "Apulia", + "french": "Pouilles", + "german": "Apulien", + "italian": "Puglia", + "spanish": "Apulia", + "chinese_simple": "普利亚大区", + "korean": "풀리아 주", + "dutch": "Apulië", + "portuguese": "Apúlia", + "russian": "Апулия", + "chinese_traditional": "Apulia", + "unknown1": "Apulia", + "unknown2": "Apulia", + "unknown3": "Apulia", + "unknown4": "Apulia" + }, + "coordinates": { + "latitude": 41.11633254, + "longitude": 16.858566351 + } + }, + { + "id": 1393688576, + "name": "Basilicata", + "translations": { + "japanese": "バジリカータ州", + "english": "Basilicata", + "french": "Basilicate", + "german": "Basilikata", + "italian": "Basilicata", + "spanish": "Basilicata", + "chinese_simple": "巴西利卡塔大区", + "korean": "바실리카타 주", + "dutch": "Basilicata", + "portuguese": "Basilicata", + "russian": "Базиликата", + "chinese_traditional": "Basilicata", + "unknown1": "Basilicata", + "unknown2": "Basilicata", + "unknown3": "Basilicata", + "unknown4": "Basilicata" + }, + "coordinates": { + "latitude": 40.632934108, + "longitude": 15.787396446 + } + }, + { + "id": 1393754112, + "name": "Calabria", + "translations": { + "japanese": "カラブリア州", + "english": "Calabria", + "french": "Calabre", + "german": "Kalabrien", + "italian": "Calabria", + "spanish": "Calabria", + "chinese_simple": "卡拉布里亚大区", + "korean": "칼라브리아 주", + "dutch": "Calabrië", + "portuguese": "Calábria", + "russian": "Калабрия", + "chinese_traditional": "Calabria", + "unknown1": "Calabria", + "unknown2": "Calabria", + "unknown3": "Calabria", + "unknown4": "Calabria" + }, + "coordinates": { + "latitude": 38.897094284, + "longitude": 16.58940058 + } + }, + { + "id": 1393819648, + "name": "Sicily", + "translations": { + "japanese": "シチリア州", + "english": "Sicily", + "french": "Sicile", + "german": "Sizilien", + "italian": "Sicilia", + "spanish": "Sicilia", + "chinese_simple": "西西里大区", + "korean": "시칠리아 주", + "dutch": "Sicilië", + "portuguese": "Sicília", + "russian": "Сицилия", + "chinese_traditional": "Sicily", + "unknown1": "Sicily", + "unknown2": "Sicily", + "unknown3": "Sicily", + "unknown4": "Sicily" + }, + "coordinates": { + "latitude": 38.117064996, + "longitude": 13.353918149 + } + }, + { + "id": 1393885184, + "name": "Sardinia", + "translations": { + "japanese": "サルデーニャ州", + "english": "Sardinia", + "french": "Sardaigne", + "german": "Sardinien", + "italian": "Sardegna", + "spanish": "Cerdeña", + "chinese_simple": "撒丁大区", + "korean": "사르데냐 주", + "dutch": "Sardinië", + "portuguese": "Sardenha", + "russian": "Сардиния", + "chinese_traditional": "Sardinia", + "unknown1": "Sardinia", + "unknown2": "Sardinia", + "unknown3": "Sardinia", + "unknown4": "Sardinia" + }, + "coordinates": { + "latitude": 39.215697796, + "longitude": 9.102197603 + } + } + ] + }, + { + "id": 84, + "iso_code": "LV", + "name": "Latvia", + "translations": { + "japanese": "ラトビア", + "english": "Latvia", + "french": "Lettonie", + "german": "Lettland", + "italian": "Lettonia", + "spanish": "Letonia", + "chinese_simple": "拉脱维亚", + "korean": "라트비아", + "dutch": "Letland", + "portuguese": "Letónia", + "russian": "Латвия", + "chinese_traditional": "Latvia", + "unknown1": "Latvia", + "unknown2": "Latvia", + "unknown3": "Latvia", + "unknown4": "Latvia" + }, + "regions": [ + { + "id": 1409286144, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": 56.947631188, + "longitude": 24.082096736 + } + }, + { + "id": 1409351680, + "name": "Latvia", + "translations": { + "japanese": "ラトビア", + "english": "Latvia", + "french": "Lettonie", + "german": "Lettland", + "italian": "Lettonia", + "spanish": "Letonia", + "chinese_simple": "拉脱维亚", + "korean": "라트비아", + "dutch": "Letland", + "portuguese": "Letónia", + "russian": "Латвия", + "chinese_traditional": "Latvia", + "unknown1": "Latvia", + "unknown2": "Latvia", + "unknown3": "Latvia", + "unknown4": "Latvia" + }, + "coordinates": { + "latitude": 56.947631188, + "longitude": 24.082096736 + } + } + ] + }, + { + "id": 85, + "iso_code": "LS", + "name": "Lesotho", + "translations": { + "japanese": "レソト", + "english": "Lesotho", + "french": "Lesotho", + "german": "Lesotho", + "italian": "Lesotho", + "spanish": "Lesoto", + "chinese_simple": "莱索托", + "korean": "레소토", + "dutch": "Lesotho", + "portuguese": "Lesoto", + "russian": "Лесото", + "chinese_traditional": "Lesotho", + "unknown1": "Lesotho", + "unknown2": "Lesotho", + "unknown3": "Lesotho", + "unknown4": "Lesotho" + }, + "regions": [ + { + "id": 1426063360, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": -29.317017292000003, + "longitude": 27.465895 + } + }, + { + "id": 1426194432, + "name": "Maseru", + "translations": { + "japanese": "マセル県", + "english": "Maseru", + "french": "Maseru", + "german": "Maseru", + "italian": "Maseru", + "spanish": "Maseru", + "chinese_simple": "马塞卢区", + "korean": "마세루 주", + "dutch": "Maseru", + "portuguese": "Maseru", + "russian": "Масеру", + "chinese_traditional": "Maseru", + "unknown1": "Maseru", + "unknown2": "Maseru", + "unknown3": "Maseru", + "unknown4": "Maseru" + }, + "coordinates": { + "latitude": -29.317017292000003, + "longitude": 27.465895 + } + }, + { + "id": 1426259968, + "name": "Berea", + "translations": { + "japanese": "べレア県", + "english": "Berea", + "french": "Berea", + "german": "Berea", + "italian": "Berea", + "spanish": "Berea", + "chinese_simple": "伯里亚区", + "korean": "베레아 주", + "dutch": "Berea", + "portuguese": "Berea", + "russian": "Береа", + "chinese_traditional": "Berea", + "unknown1": "Berea", + "unknown2": "Berea", + "unknown3": "Berea", + "unknown4": "Berea" + }, + "coordinates": { + "latitude": -29.146729207999996, + "longitude": 27.74055395 + } + }, + { + "id": 1426325504, + "name": "Butha-Buthe", + "translations": { + "japanese": "ブータ・ブーテ県", + "english": "Butha-Buthe", + "french": "Butha-Buthe", + "german": "Butha-Buthe", + "italian": "Butha-Buthe", + "spanish": "Butha-Buthe", + "chinese_simple": "布塔布泰区", + "korean": "부타부테 주", + "dutch": "Butha-Buthe", + "portuguese": "Butha-Buthe", + "russian": "Бута-Буте", + "chinese_traditional": "Butha-Buthe", + "unknown1": "Butha-Buthe", + "unknown2": "Butha-Buthe", + "unknown3": "Butha-Buthe", + "unknown4": "Butha-Buthe" + }, + "coordinates": { + "latitude": -28.767700892, + "longitude": 28.256912776 + } + }, + { + "id": 1426391040, + "name": "Leribe", + "translations": { + "japanese": "レリベ県", + "english": "Leribe", + "french": "Leribe", + "german": "Leribe", + "italian": "Leribe", + "spanish": "Leribe", + "chinese_simple": "莱里贝区", + "korean": "레리베 주", + "dutch": "Leribe", + "portuguese": "Leribe", + "russian": "Лерибе", + "chinese_traditional": "Leribe", + "unknown1": "Leribe", + "unknown2": "Leribe", + "unknown3": "Leribe", + "unknown4": "Leribe" + }, + "coordinates": { + "latitude": -28.877564172, + "longitude": 28.053665153 + } + }, + { + "id": 1426456576, + "name": "Mafeteng", + "translations": { + "japanese": "マフェテング県", + "english": "Mafeteng", + "french": "Mafeteng", + "german": "Mafeteng", + "italian": "Mafeteng", + "spanish": "Mafeteng", + "chinese_simple": "马费滕区", + "korean": "마페텡 주", + "dutch": "Mafeteng", + "portuguese": "Mafeteng", + "russian": "Мафетенг", + "chinese_traditional": "Mafeteng", + "unknown1": "Mafeteng", + "unknown2": "Mafeteng", + "unknown3": "Mafeteng", + "unknown4": "Mafeteng" + }, + "coordinates": { + "latitude": -29.816895216, + "longitude": 27.235181481999998 + } + }, + { + "id": 1426522112, + "name": "Mohale's Hoek", + "translations": { + "japanese": "モハーレスフーク県", + "english": "Mohale's Hoek", + "french": "Mohale's Hoek", + "german": "Mohale's Hoek", + "italian": "Mohale's Hoek", + "spanish": "Mohale's Hoek", + "chinese_simple": "莫哈莱斯胡克区", + "korean": "모할레스후크 주", + "dutch": "Mohale's Hoek", + "portuguese": "Mohale's Hoek", + "russian": "Мохалес-Хук", + "chinese_traditional": "Mohale's Hoek", + "unknown1": "Mohale's Hoek", + "unknown2": "Mohale's Hoek", + "unknown3": "Mohale's Hoek", + "unknown4": "Mohale's Hoek" + }, + "coordinates": { + "latitude": -30.151978219999997, + "longitude": 27.465895 + } + }, + { + "id": 1426587648, + "name": "Mokhotlong", + "translations": { + "japanese": "モコトロング県", + "english": "Mokhotlong", + "french": "Mokhotlong", + "german": "Mokhotlong", + "italian": "Mokhotlong", + "spanish": "Mokhotlong", + "chinese_simple": "莫霍特隆区", + "korean": "모코틀롱 주", + "dutch": "Mokhotlong", + "portuguese": "Mokhotlong", + "russian": "Мокхотлонг", + "chinese_traditional": "Mokhotlong", + "unknown1": "Mokhotlong", + "unknown2": "Mokhotlong", + "unknown3": "Mokhotlong", + "unknown4": "Mokhotlong" + }, + "coordinates": { + "latitude": -29.284058308, + "longitude": 29.064410089 + } + }, + { + "id": 1426653184, + "name": "Qacha's Nek", + "translations": { + "japanese": "クァクハスネック県", + "english": "Qacha's Nek", + "french": "Qacha's Nek", + "german": "Qacha's Nek", + "italian": "Qacha's Nek", + "spanish": "Qacha's Nek", + "chinese_simple": "加查斯内克区", + "korean": "카차스넥 주", + "dutch": "Qacha's Nek", + "portuguese": "Qacha's Nek", + "russian": "Цгачас-Нек", + "chinese_traditional": "Qacha's Nek", + "unknown1": "Qacha's Nek", + "unknown2": "Qacha's Nek", + "unknown3": "Qacha's Nek", + "unknown4": "Qacha's Nek" + }, + "coordinates": { + "latitude": -30.1245124, + "longitude": 28.701860275 + } + }, + { + "id": 1426718720, + "name": "Quthing", + "translations": { + "japanese": "クティング県", + "english": "Quthing", + "french": "Quthing", + "german": "Quthing", + "italian": "Quthing", + "spanish": "Quthing", + "chinese_simple": "古廷区", + "korean": "쿠팅 주", + "dutch": "Quthing", + "portuguese": "Quthing", + "russian": "Цгутинг", + "chinese_traditional": "Quthing", + "unknown1": "Quthing", + "unknown2": "Quthing", + "unknown3": "Quthing", + "unknown4": "Quthing" + }, + "coordinates": { + "latitude": -30.404663764, + "longitude": 27.707594875999998 + } + }, + { + "id": 1426784256, + "name": "Thaba-Tseka", + "translations": { + "japanese": "ターバ・ツェーカ県", + "english": "Thaba-Tseka", + "french": "Thaba-Tseka", + "german": "Thaba-Tseka", + "italian": "Thaba-Tseka", + "spanish": "Thaba-Tseka", + "chinese_simple": "塔巴采卡区", + "korean": "타바체카 주", + "dutch": "Thaba-Tseka", + "portuguese": "Thaba-Tseka", + "russian": "Таба-Цека", + "chinese_traditional": "Thaba-Tseka", + "unknown1": "Thaba-Tseka", + "unknown2": "Thaba-Tseka", + "unknown3": "Thaba-Tseka", + "unknown4": "Thaba-Tseka" + }, + "coordinates": { + "latitude": -29.514771195999998, + "longitude": 28.602983053 + } + } + ] + }, + { + "id": 86, + "iso_code": "LI", + "name": "Liechtenstein", + "translations": { + "japanese": "リヒテンシュタイン", + "english": "Liechtenstein", + "french": "Liechtenstein", + "german": "Liechtenstein", + "italian": "Liechtenstein", + "spanish": "Liechtenstein", + "chinese_simple": "列支敦士登", + "korean": "리히텐슈타인", + "dutch": "Liechtenstein", + "portuguese": "Liechtenstein", + "russian": "Лихтенштейн", + "chinese_traditional": "Liechtenstein", + "unknown1": "Liechtenstein", + "unknown2": "Liechtenstein", + "unknown3": "Liechtenstein", + "unknown4": "Liechtenstein" + }, + "regions": [ + { + "id": 1442840576, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": 47.136840284, + "longitude": 9.519679207 + } + }, + { + "id": 1442906112, + "name": "Liechtenstein", + "translations": { + "japanese": "リヒテンシュタイン", + "english": "Liechtenstein", + "french": "Liechtenstein", + "german": "Liechtenstein", + "italian": "Liechtenstein", + "spanish": "Liechtenstein", + "chinese_simple": "列支敦士登", + "korean": "리히텐슈타인", + "dutch": "Liechtenstein", + "portuguese": "Liechtenstein", + "russian": "Лихтенштейн", + "chinese_traditional": "Liechtenstein", + "unknown1": "Liechtenstein", + "unknown2": "Liechtenstein", + "unknown3": "Liechtenstein", + "unknown4": "Liechtenstein" + }, + "coordinates": { + "latitude": 47.136840284, + "longitude": 9.519679207 + } + } + ] + }, + { + "id": 87, + "iso_code": "LT", + "name": "Lithuania", + "translations": { + "japanese": "リトアニア", + "english": "Lithuania", + "french": "Lituanie", + "german": "Litauen", + "italian": "Lituania", + "spanish": "Lituania", + "chinese_simple": "立陶宛", + "korean": "리투아니아", + "dutch": "Litouwen", + "portuguese": "Lituânia", + "russian": "Литва", + "chinese_traditional": "Lithuania", + "unknown1": "Lithuania", + "unknown2": "Lithuania", + "unknown3": "Lithuania", + "unknown4": "Lithuania" + }, + "regions": [ + { + "id": 1459617792, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": 54.678954456, + "longitude": 25.279609758 + } + }, + { + "id": 1459748864, + "name": "Vilnius", + "translations": { + "japanese": "ヴィリニュス州", + "english": "Vilnius", + "french": "Vilnius", + "german": "Vilnius", + "italian": "Vilnius", + "spanish": "Condado de Vilna", + "chinese_simple": "维尔纽斯县", + "korean": "빌뉴스 주", + "dutch": "Vilnius", + "portuguese": "Vilna", + "russian": "Вильнюс", + "chinese_traditional": "Vilnius", + "unknown1": "Vilnius", + "unknown2": "Vilnius", + "unknown3": "Vilnius", + "unknown4": "Vilnius" + }, + "coordinates": { + "latitude": 54.678954456, + "longitude": 25.279609758 + } + }, + { + "id": 1459814400, + "name": "Alytus", + "translations": { + "japanese": "アリートゥス州", + "english": "Alytus", + "french": "Alytus", + "german": "Alytus", + "italian": "Alytus", + "spanish": "Condado de Alytus", + "chinese_simple": "阿利图斯县", + "korean": "알리투스 주", + "dutch": "Alytus", + "portuguese": "Alytus", + "russian": "Алитус", + "chinese_traditional": "Alytus", + "unknown1": "Alytus", + "unknown2": "Alytus", + "unknown3": "Alytus", + "unknown4": "Alytus" + }, + "coordinates": { + "latitude": 54.3823236, + "longitude": 24.049137662 + } + }, + { + "id": 1459879936, + "name": "Kaunas", + "translations": { + "japanese": "カウナス州", + "english": "Kaunas", + "french": "Kaunas", + "german": "Kaunas", + "italian": "Kaunas", + "spanish": "Condado de Kaunas", + "chinese_simple": "考纳斯县", + "korean": "카우나스 주", + "dutch": "Kaunas", + "portuguese": "Kaunas", + "russian": "Каунас", + "chinese_traditional": "Kaunas", + "unknown1": "Kaunas", + "unknown2": "Kaunas", + "unknown3": "Kaunas", + "unknown4": "Kaunas" + }, + "coordinates": { + "latitude": 54.887694688, + "longitude": 23.917301366 + } + }, + { + "id": 1459945472, + "name": "Klaipėda", + "translations": { + "japanese": "クライペダ州", + "english": "Klaipėda", + "french": "Klaipėda", + "german": "Klaipėda", + "italian": "Klaipėda", + "spanish": "Condado de Klaipėda", + "chinese_simple": "克莱佩达县", + "korean": "클라이페다 주", + "dutch": "Klaipėda", + "portuguese": "Klaipėda", + "russian": "Клайпеда", + "chinese_traditional": "Klaipėda", + "unknown1": "Klaipėda", + "unknown2": "Klaipėda", + "unknown3": "Klaipėda", + "unknown4": "Klaipėda" + }, + "coordinates": { + "latitude": 55.711669288, + "longitude": 21.132259613 + } + }, + { + "id": 1460011008, + "name": "Marijampolė", + "translations": { + "japanese": "マリヤンポレ州", + "english": "Marijampolė", + "french": "Marijampolė", + "german": "Marijampolė", + "italian": "Marijampolė", + "spanish": "Condado de Marijampolė", + "chinese_simple": "马里扬泊列县", + "korean": "마리얌폴레 주", + "dutch": "Marijampolė", + "portuguese": "Marijampolė", + "russian": "Мариямполе", + "chinese_traditional": "Marijampolė", + "unknown1": "Marijampolė", + "unknown2": "Marijampolė", + "unknown3": "Marijampolė", + "unknown4": "Marijampolė" + }, + "coordinates": { + "latitude": 54.54711852, + "longitude": 23.351503929 + } + }, + { + "id": 1460076544, + "name": "Panevėžys", + "translations": { + "japanese": "パネベジス州", + "english": "Panevėžys", + "french": "Panevėžys", + "german": "Panevėžys", + "italian": "Panevėžys", + "spanish": "Condado de Panevėžys", + "chinese_simple": "帕涅韦日斯县", + "korean": "파네베지스 주", + "dutch": "Panevėžys", + "portuguese": "Panevėžys", + "russian": "Паневежис", + "chinese_traditional": "Panevėžys", + "unknown1": "Panevėžys", + "unknown2": "Panevėžys", + "unknown3": "Panevėžys", + "unknown4": "Panevėžys" + }, + "coordinates": { + "latitude": 55.72814878, + "longitude": 24.367742044 + } + }, + { + "id": 1460142080, + "name": "Šiauliai", + "translations": { + "japanese": "シャウレイ州", + "english": "Šiauliai", + "french": "Šiauliai", + "german": "Šiauliai", + "italian": "Šiauliai", + "spanish": "Condado de Šiauliai", + "chinese_simple": "希奥利艾县", + "korean": "샤울랴이 주", + "dutch": "Šiauliai", + "portuguese": "Šiauliai", + "russian": "Шяуляй", + "chinese_traditional": "Šiauliai", + "unknown1": "Šiauliai", + "unknown2": "Šiauliai", + "unknown3": "Šiauliai", + "unknown4": "Šiauliai" + }, + "coordinates": { + "latitude": 55.931395848, + "longitude": 23.307558497 + } + }, + { + "id": 1460207616, + "name": "Taurage", + "translations": { + "japanese": "タウラゲ州", + "english": "Taurage", + "french": "Tauragė", + "german": "Tauragė", + "italian": "Tauragė", + "spanish": "Condado de Tauragė", + "chinese_simple": "陶拉格县", + "korean": "타우라게 주", + "dutch": "Tauragė", + "portuguese": "Tauragė", + "russian": "Таураге", + "chinese_traditional": "Taurage", + "unknown1": "Taurage", + "unknown2": "Taurage", + "unknown3": "Taurage", + "unknown4": "Taurage" + }, + "coordinates": { + "latitude": 55.244750348000004, + "longitude": 22.285827203 + } + }, + { + "id": 1460273152, + "name": "Telšiai", + "translations": { + "japanese": "テルシェイ州", + "english": "Telšiai", + "french": "Telšiai", + "german": "Telšiai", + "italian": "Telšiai", + "spanish": "Condado de Telšiai", + "chinese_simple": "特尔希艾县", + "korean": "텔샤이 주", + "dutch": "Telšiai", + "portuguese": "Telšiai", + "russian": "Тельшяй", + "chinese_traditional": "Telšiai", + "unknown1": "Telšiai", + "unknown2": "Telšiai", + "unknown3": "Telšiai", + "unknown4": "Telšiai" + }, + "coordinates": { + "latitude": 55.980834324, + "longitude": 22.24737495 + } + }, + { + "id": 1460338688, + "name": "Utena", + "translations": { + "japanese": "ウテナ州", + "english": "Utena", + "french": "Utena", + "german": "Utena", + "italian": "Utena", + "spanish": "Condado de Utena", + "chinese_simple": "乌田纳县", + "korean": "우테나 주", + "dutch": "Utena", + "portuguese": "Utena", + "russian": "Утена", + "chinese_traditional": "Utena", + "unknown1": "Utena", + "unknown2": "Utena", + "unknown3": "Utena", + "unknown4": "Utena" + }, + "coordinates": { + "latitude": 55.491942728, + "longitude": 25.603707318999998 + } + } + ] + }, + { + "id": 88, + "iso_code": "LU", + "name": "Luxembourg", + "translations": { + "japanese": "ルクセンブルク", + "english": "Luxembourg", + "french": "Luxembourg", + "german": "Luxemburg", + "italian": "Lussemburgo", + "spanish": "Luxemburgo", + "chinese_simple": "卢森堡", + "korean": "룩셈부르크", + "dutch": "Luxemburg", + "portuguese": "Luxemburgo", + "russian": "Люксембург", + "chinese_traditional": "Luxembourg", + "unknown1": "Luxembourg", + "unknown2": "Luxembourg", + "unknown3": "Luxembourg", + "unknown4": "Luxembourg" + }, + "regions": [ + { + "id": 1476395008, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": 49.608764084, + "longitude": 6.124894585 + } + }, + { + "id": 1476460544, + "name": "Luxembourg", + "translations": { + "japanese": "ルクセンブルク", + "english": "Luxembourg", + "french": "Luxembourg", + "german": "Luxemburg", + "italian": "Lussemburgo", + "spanish": "Luxemburgo", + "chinese_simple": "卢森堡", + "korean": "룩셈부르크", + "dutch": "Luxemburg", + "portuguese": "Luxemburgo", + "russian": "Люксембург", + "chinese_traditional": "Luxembourg", + "unknown1": "Luxembourg", + "unknown2": "Luxembourg", + "unknown3": "Luxembourg", + "unknown4": "Luxembourg" + }, + "coordinates": { + "latitude": 49.608764084, + "longitude": 6.124894585 + } + } + ] + }, + { + "id": 89, + "iso_code": "MK", + "name": "Macedonia (Republic of)", + "translations": { + "japanese": "マケドニア", + "english": "Macedonia (Republic of)", + "french": "Macédoine (République)", + "german": "Mazedonien (Republik)", + "italian": "Macedonia (Repubblica di)", + "spanish": "Macedonia (República)", + "chinese_simple": "马其顿", + "korean": "마케도니아 공화국", + "dutch": "Macedonië", + "portuguese": "Macedónia (República)", + "russian": "Македония (Республика)", + "chinese_traditional": "Macedonia (Republic of)", + "unknown1": "Macedonia (Republic of)", + "unknown2": "Macedonia (Republic of)", + "unknown3": "Macedonia (Republic of)", + "unknown4": "Macedonia (Republic of)" + }, + "regions": [ + { + "id": 1493172224, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": 41.99523878, + "longitude": 21.428891279 + } + }, + { + "id": 1493237760, + "name": "Macedonia (Republic of)", + "translations": { + "japanese": "マケドニア", + "english": "Macedonia (Republic of)", + "french": "Macédoine (République)", + "german": "Mazedonien (Republik)", + "italian": "Macedonia (Repubblica di)", + "spanish": "Macedonia (República)", + "chinese_simple": "马其顿", + "korean": "마케도니아 공화국", + "dutch": "Macedonië", + "portuguese": "Macedónia (República)", + "russian": "Македония (Республика)", + "chinese_traditional": "Macedonia (Republic of)", + "unknown1": "Macedonia (Republic of)", + "unknown2": "Macedonia (Republic of)", + "unknown3": "Macedonia (Republic of)", + "unknown4": "Macedonia (Republic of)" + }, + "coordinates": { + "latitude": 41.99523878, + "longitude": 21.428891279 + } + } + ] + }, + { + "id": 90, + "iso_code": "MT", + "name": "Malta", + "translations": { + "japanese": "マルタ", + "english": "Malta", + "french": "Malte", + "german": "Malta", + "italian": "Malta", + "spanish": "Malta", + "chinese_simple": "马耳他", + "korean": "몰타", + "dutch": "Malta", + "portuguese": "Malta", + "russian": "Мальта", + "chinese_traditional": "Malta", + "unknown1": "Malta", + "unknown2": "Malta", + "unknown3": "Malta", + "unknown4": "Malta" + }, + "regions": [ + { + "id": 1509949440, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": 35.89782674, + "longitude": 14.512978918 + } + }, + { + "id": 1510014976, + "name": "Malta", + "translations": { + "japanese": "マルタ", + "english": "Malta", + "french": "Malte", + "german": "Malta", + "italian": "Malta", + "spanish": "Malta", + "chinese_simple": "马耳他", + "korean": "몰타", + "dutch": "Malta", + "portuguese": "Malta", + "russian": "Мальта", + "chinese_traditional": "Malta", + "unknown1": "Malta", + "unknown2": "Malta", + "unknown3": "Malta", + "unknown4": "Malta" + }, + "coordinates": { + "latitude": 35.89782674, + "longitude": 14.512978918 + } + } + ] + }, + { + "id": 91, + "iso_code": "ME", + "name": "Montenegro", + "translations": { + "japanese": "モンテネグロ", + "english": "Montenegro", + "french": "Monténégro", + "german": "Montenegro", + "italian": "Montenegro", + "spanish": "Montenegro", + "chinese_simple": "黑山", + "korean": "몬테네그로", + "dutch": "Montenegro", + "portuguese": "Montenegro", + "russian": "Черногория", + "chinese_traditional": "Montenegro", + "unknown1": "Montenegro", + "unknown2": "Montenegro", + "unknown3": "Montenegro", + "unknown4": "Montenegro" + }, + "regions": [ + { + "id": 1526726656, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": 42.4346919, + "longitude": 19.264578753 + } + }, + { + "id": 1526792192, + "name": "Montenegro", + "translations": { + "japanese": "モンテネグロ", + "english": "Montenegro", + "french": "Monténégro", + "german": "Montenegro", + "italian": "Montenegro", + "spanish": "Montenegro", + "chinese_simple": "黑山", + "korean": "몬테네그로", + "dutch": "Montenegro", + "portuguese": "Montenegro", + "russian": "Черногория", + "chinese_traditional": "Montenegro", + "unknown1": "Montenegro", + "unknown2": "Montenegro", + "unknown3": "Montenegro", + "unknown4": "Montenegro" + }, + "coordinates": { + "latitude": 42.4346919, + "longitude": 19.264578753 + } + } + ] + }, + { + "id": 92, + "iso_code": "MZ", + "name": "Mozambique", + "translations": { + "japanese": "モザンビーク", + "english": "Mozambique", + "french": "Mozambique", + "german": "Mosambik", + "italian": "Mozambico", + "spanish": "Mozambique", + "chinese_simple": "莫桑比克", + "korean": "모잠비크", + "dutch": "Mozambique", + "portuguese": "Moçambique", + "russian": "Мозамбик", + "chinese_traditional": "Mozambique", + "unknown1": "Mozambique", + "unknown2": "Mozambique", + "unknown3": "Mozambique", + "unknown4": "Mozambique" + }, + "regions": [ + { + "id": 1543503872, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": -25.966187251999997, + "longitude": 32.563565112 + } + }, + { + "id": 1543569408, + "name": "Mozambique", + "translations": { + "japanese": "モザンビーク", + "english": "Mozambique", + "french": "Mozambique", + "german": "Mosambik", + "italian": "Mozambico", + "spanish": "Mozambique", + "chinese_simple": "莫桑比克", + "korean": "모잠비크", + "dutch": "Mozambique", + "portuguese": "Moçambique", + "russian": "Мозамбик", + "chinese_traditional": "Mozambique", + "unknown1": "Mozambique", + "unknown2": "Mozambique", + "unknown3": "Mozambique", + "unknown4": "Mozambique" + }, + "coordinates": { + "latitude": -25.966187251999997, + "longitude": 32.563565112 + } + } + ] + }, + { + "id": 93, + "iso_code": "NA", + "name": "Namibia", + "translations": { + "japanese": "ナミビア", + "english": "Namibia", + "french": "Namibie", + "german": "Namibia", + "italian": "Namibia", + "spanish": "Namibia", + "chinese_simple": "纳米比亚", + "korean": "나미비아", + "dutch": "Namibië", + "portuguese": "Namíbia", + "russian": "Намибия", + "chinese_traditional": "Namibia", + "unknown1": "Namibia", + "unknown2": "Namibia", + "unknown3": "Namibia", + "unknown4": "Namibia" + }, + "regions": [ + { + "id": 1560281088, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": -22.549439244, + "longitude": 17.078293511 + } + }, + { + "id": 1560346624, + "name": "Namibia", + "translations": { + "japanese": "ナミビア", + "english": "Namibia", + "french": "Namibie", + "german": "Namibia", + "italian": "Namibia", + "spanish": "Namibia", + "chinese_simple": "纳米比亚", + "korean": "나미비아", + "dutch": "Namibië", + "portuguese": "Namíbia", + "russian": "Намибия", + "chinese_traditional": "Namibia", + "unknown1": "Namibia", + "unknown2": "Namibia", + "unknown3": "Namibia", + "unknown4": "Namibia" + }, + "coordinates": { + "latitude": -22.549439244, + "longitude": 17.078293511 + } + } + ] + }, + { + "id": 94, + "iso_code": "NL", + "name": "Netherlands", + "translations": { + "japanese": "オランダ", + "english": "Netherlands", + "french": "Pays-Bas", + "german": "Niederlande", + "italian": "Paesi Bassi", + "spanish": "Países Bajos", + "chinese_simple": "荷兰", + "korean": "네덜란드", + "dutch": "Nederland", + "portuguese": "Países Baixos", + "russian": "Нидерланды", + "chinese_traditional": "Netherlands", + "unknown1": "Netherlands", + "unknown2": "Netherlands", + "unknown3": "Netherlands", + "unknown4": "Netherlands" + }, + "regions": [ + { + "id": 1577058304, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": 52.366332412, + "longitude": 4.883436131 + } + }, + { + "id": 1577189376, + "name": "North Holland", + "translations": { + "japanese": "ノールト・ホラント州", + "english": "North Holland", + "french": "Hollande-Septentrionale", + "german": "Nordholland", + "italian": "Olanda Settentrionale", + "spanish": "Holanda Septentrional", + "chinese_simple": "北荷兰省", + "korean": "노르트홀란트 주", + "dutch": "Noord-Holland", + "portuguese": "Holanda Setentrional", + "russian": "Северная Голландия", + "chinese_traditional": "North Holland", + "unknown1": "North Holland", + "unknown2": "North Holland", + "unknown3": "North Holland", + "unknown4": "North Holland" + }, + "coordinates": { + "latitude": 52.366332412, + "longitude": 4.883436131 + } + }, + { + "id": 1577254912, + "name": "Drenthe", + "translations": { + "japanese": "ドレンテ州", + "english": "Drenthe", + "french": "Drenthe", + "german": "Drenthe", + "italian": "Drenthe", + "spanish": "Drente", + "chinese_simple": "德伦特省", + "korean": "드렌터 주", + "dutch": "Drenthe", + "portuguese": "Drente", + "russian": "Дренте", + "chinese_traditional": "Drenthe", + "unknown1": "Drenthe", + "unknown2": "Drenthe", + "unknown3": "Drenthe", + "unknown4": "Drenthe" + }, + "coordinates": { + "latitude": 52.987059944, + "longitude": 6.553362547 + } + }, + { + "id": 1577320448, + "name": "Flevoland", + "translations": { + "japanese": "フレボラント州", + "english": "Flevoland", + "french": "Flevoland", + "german": "Flevoland", + "italian": "Flevoland", + "spanish": "Flevoland", + "chinese_simple": "弗莱福兰省", + "korean": "플레볼란트 주", + "dutch": "Flevoland", + "portuguese": "Flevoland", + "russian": "Флеволанд", + "chinese_traditional": "Flevoland", + "unknown1": "Flevoland", + "unknown2": "Flevoland", + "unknown3": "Flevoland", + "unknown4": "Flevoland" + }, + "coordinates": { + "latitude": 52.503661512, + "longitude": 5.465713105 + } + }, + { + "id": 1577385984, + "name": "Friesland", + "translations": { + "japanese": "フリースラント州", + "english": "Friesland", + "french": "Frise", + "german": "Friesland", + "italian": "Frisia", + "spanish": "Frisia", + "chinese_simple": "弗里斯兰省", + "korean": "프리슬란트 주", + "dutch": "Friesland", + "portuguese": "Frísia", + "russian": "Фрисландия", + "chinese_traditional": "Friesland", + "unknown1": "Friesland", + "unknown2": "Friesland", + "unknown3": "Friesland", + "unknown4": "Friesland" + }, + "coordinates": { + "latitude": 53.190307012, + "longitude": 5.784317487 + } + }, + { + "id": 1577451520, + "name": "Gelderland", + "translations": { + "japanese": "ヘルデンラント州", + "english": "Gelderland", + "french": "Gueldre", + "german": "Gelderland", + "italian": "Gheldria", + "spanish": "Güeldres", + "chinese_simple": "海尔德兰省", + "korean": "헬데를란트 주", + "dutch": "Gelderland", + "portuguese": "Gueldres", + "russian": "Гелдерланд", + "chinese_traditional": "Gelderland", + "unknown1": "Gelderland", + "unknown2": "Gelderland", + "unknown3": "Gelderland", + "unknown4": "Gelderland" + }, + "coordinates": { + "latitude": 51.981810932, + "longitude": 5.905167425 + } + }, + { + "id": 1577517056, + "name": "Groningen", + "translations": { + "japanese": "フローニンゲン州", + "english": "Groningen", + "french": "Groningue", + "german": "Groningen", + "italian": "Groninga", + "spanish": "Groninga", + "chinese_simple": "格罗宁根省", + "korean": "흐로닝언 주", + "dutch": "Groningen", + "portuguese": "Groninga", + "russian": "Гронинген", + "chinese_traditional": "Groningen", + "unknown1": "Groningen", + "unknown2": "Groningen", + "unknown3": "Groningen", + "unknown4": "Groningen" + }, + "coordinates": { + "latitude": 53.217772832, + "longitude": 6.569842084 + } + }, + { + "id": 1577582592, + "name": "Limburg", + "translations": { + "japanese": "リンビュルフ州", + "english": "Limburg", + "french": "Limbourg", + "german": "Limburg", + "italian": "Limburgo", + "spanish": "Limburgo", + "chinese_simple": "林堡省", + "korean": "림뷔르흐 주", + "dutch": "Limburg", + "portuguese": "Limburgo", + "russian": "Лимбург", + "chinese_traditional": "Limburg", + "unknown1": "Limburg", + "unknown2": "Limburg", + "unknown3": "Limburg", + "unknown4": "Limburg" + }, + "coordinates": { + "latitude": 50.850219148, + "longitude": 5.674453907 + } + }, + { + "id": 1577648128, + "name": "North Brabant", + "translations": { + "japanese": "ノールト・ブラバント州", + "english": "North Brabant", + "french": "Brabant-Septentrional", + "german": "Nordbrabant", + "italian": "Brabante Settentrionale", + "spanish": "Brabante Septentrional", + "chinese_simple": "北布拉班特省", + "korean": "노르트브라반트 주", + "dutch": "Noord-Brabant", + "portuguese": "Brabante Setentrional", + "russian": "Северный Брабант", + "chinese_traditional": "North Brabant", + "unknown1": "North Brabant", + "unknown2": "North Brabant", + "unknown3": "North Brabant", + "unknown4": "North Brabant" + }, + "coordinates": { + "latitude": 51.69067324, + "longitude": 5.311904093 + } + }, + { + "id": 1577713664, + "name": "Overijssel", + "translations": { + "japanese": "オーベルアイセル州", + "english": "Overijssel", + "french": "Overijssel", + "german": "Overijssel", + "italian": "Overijssel", + "spanish": "Overijssel", + "chinese_simple": "上艾瑟尔省", + "korean": "오버레이설 주", + "dutch": "Overijssel", + "portuguese": "Overijssel", + "russian": "Оверэйсел", + "chinese_traditional": "Overijssel", + "unknown1": "Overijssel", + "unknown2": "Overijssel", + "unknown3": "Overijssel", + "unknown4": "Overijssel" + }, + "coordinates": { + "latitude": 52.509154676, + "longitude": 6.086442332 + } + }, + { + "id": 1577779200, + "name": "South Holland", + "translations": { + "japanese": "ゾイト・ホラント州", + "english": "South Holland", + "french": "Hollande-Méridionale", + "german": "Südholland", + "italian": "Olanda Meridionale", + "spanish": "Holanda Meridional", + "chinese_simple": "南荷兰省", + "korean": "자위트홀란트 주", + "dutch": "Zuid-Holland", + "portuguese": "Holanda Meridional", + "russian": "Южная Голландия", + "chinese_traditional": "South Holland", + "unknown1": "South Holland", + "unknown2": "South Holland", + "unknown3": "South Holland", + "unknown4": "South Holland" + }, + "coordinates": { + "latitude": 52.080687884, + "longitude": 4.312145515 + } + }, + { + "id": 1577844736, + "name": "Utrecht", + "translations": { + "japanese": "ユトレヒト州", + "english": "Utrecht", + "french": "Utrecht", + "german": "Utrecht", + "italian": "Utrecht", + "spanish": "Utrecht", + "chinese_simple": "乌得勒支省", + "korean": "위트레흐트 주", + "dutch": "Utrecht", + "portuguese": "Utrecht", + "russian": "Утрехт", + "chinese_traditional": "Utrecht", + "unknown1": "Utrecht", + "unknown2": "Utrecht", + "unknown3": "Utrecht", + "unknown4": "Utrecht" + }, + "coordinates": { + "latitude": 52.086181048, + "longitude": 5.10865647 + } + }, + { + "id": 1577910272, + "name": "Zeeland", + "translations": { + "japanese": "ゼーラント州", + "english": "Zeeland", + "french": "Zélande", + "german": "Zeeland", + "italian": "Zelanda", + "spanish": "Zelanda", + "chinese_simple": "泽兰省", + "korean": "제일란트 주", + "dutch": "Zeeland", + "portuguese": "Zelândia", + "russian": "Зеландия", + "chinese_traditional": "Zeeland", + "unknown1": "Zeeland", + "unknown2": "Zeeland", + "unknown3": "Zeeland", + "unknown4": "Zeeland" + }, + "coordinates": { + "latitude": 51.4984125, + "longitude": 3.609018603 + } + } + ] + }, + { + "id": 95, + "iso_code": "NZ", + "name": "New Zealand", + "translations": { + "japanese": "ニュージーランド", + "english": "New Zealand", + "french": "Nouvelle-Zélande", + "german": "Neuseeland", + "italian": "Nuova Zelanda", + "spanish": "Nueva Zelanda", + "chinese_simple": "新西兰", + "korean": "뉴질랜드", + "dutch": "Nieuw-Zeeland", + "portuguese": "Nova Zelândia", + "russian": "Новая Зеландия", + "chinese_traditional": "New Zealand", + "unknown1": "New Zealand", + "unknown2": "New Zealand", + "unknown3": "New Zealand", + "unknown4": "New Zealand" + }, + "regions": [ + { + "id": 1593835520, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": -36.892090448, + "longitude": 174.759996706 + } + }, + { + "id": 1593966592, + "name": "Wellington", + "translations": { + "japanese": "ウェリントン", + "english": "Wellington", + "french": "Wellington", + "german": "Wellington", + "italian": "Wellington", + "spanish": "Región de Wellington", + "chinese_simple": "惠灵顿大区", + "korean": "웰링턴", + "dutch": "Wellington", + "portuguese": "Wellington", + "russian": "Веллингтон", + "chinese_traditional": "Wellington", + "unknown1": "Wellington", + "unknown2": "Wellington", + "unknown3": "Wellington", + "unknown4": "Wellington" + }, + "coordinates": { + "latitude": -41.314087468, + "longitude": 174.781969422 + } + }, + { + "id": 1594032128, + "name": "Auckland", + "translations": { + "japanese": "オークランド", + "english": "Auckland", + "french": "Auckland", + "german": "Auckland", + "italian": "Auckland", + "spanish": "Región de Auckland", + "chinese_simple": "奥克兰大区", + "korean": "오클랜드", + "dutch": "Auckland", + "portuguese": "Auckland", + "russian": "Окленд", + "chinese_traditional": "Auckland", + "unknown1": "Auckland", + "unknown2": "Auckland", + "unknown3": "Auckland", + "unknown4": "Auckland" + }, + "coordinates": { + "latitude": -36.892090448, + "longitude": 174.759996706 + } + }, + { + "id": 1594097664, + "name": "Bay of Plenty", + "translations": { + "japanese": "ベイ・オブ・プレンティ", + "english": "Bay of Plenty", + "french": "Bay of Plenty", + "german": "Bay of Plenty", + "italian": "Bay of Plenty", + "spanish": "Bahía de Plenty", + "chinese_simple": "普伦蒂湾大区", + "korean": "베이오브플렌티", + "dutch": "Bay of Plenty", + "portuguese": "Bay of Plenty", + "russian": "Бей-оф-Пленти", + "chinese_traditional": "Bay of Plenty", + "unknown1": "Bay of Plenty", + "unknown2": "Bay of Plenty", + "unknown3": "Bay of Plenty", + "unknown4": "Bay of Plenty" + }, + "coordinates": { + "latitude": -37.6776129, + "longitude": 176.16625052999999 + } + }, + { + "id": 1594163200, + "name": "Canterbury", + "translations": { + "japanese": "カンタベリー", + "english": "Canterbury", + "french": "Canterbury", + "german": "Canterbury", + "italian": "Canterbury", + "spanish": "Canterbury", + "chinese_simple": "坎特伯雷大区", + "korean": "캔터베리", + "dutch": "Canterbury", + "portuguese": "Canterbury", + "russian": "Кентербери", + "chinese_traditional": "Canterbury", + "unknown1": "Canterbury", + "unknown2": "Canterbury", + "unknown3": "Canterbury", + "unknown4": "Canterbury" + }, + "coordinates": { + "latitude": -43.52783256, + "longitude": 172.639629612 + } + }, + { + "id": 1594228736, + "name": "Otago", + "translations": { + "japanese": "ダニーデン", + "english": "Otago", + "french": "Otago", + "german": "Otago", + "italian": "Otago", + "spanish": "Otago", + "chinese_simple": "奥塔戈大区", + "korean": "오타고", + "dutch": "Otago", + "portuguese": "Otago", + "russian": "Отаго", + "chinese_traditional": "Otago", + "unknown1": "Otago", + "unknown2": "Otago", + "unknown3": "Otago", + "unknown4": "Otago" + }, + "coordinates": { + "latitude": -45.878906752, + "longitude": 170.469823907 + } + }, + { + "id": 1594294272, + "name": "Hawke's Bay", + "translations": { + "japanese": "ホークスベイ", + "english": "Hawke's Bay", + "french": "Hawke's Bay", + "german": "Hawke's Bay", + "italian": "Hawke's Bay", + "spanish": "Bahía de Hawke", + "chinese_simple": "霍克湾大区", + "korean": "호크스베이", + "dutch": "Hawke's Bay", + "portuguese": "Hawke's Bay", + "russian": "Хокс-Бей", + "chinese_traditional": "Hawke's Bay", + "unknown1": "Hawke's Bay", + "unknown2": "Hawke's Bay", + "unknown3": "Hawke's Bay", + "unknown4": "Hawke's Bay" + }, + "coordinates": { + "latitude": -39.495850184, + "longitude": 176.885856979 + } + }, + { + "id": 1594359808, + "name": "Manawatu-Wanganui", + "translations": { + "japanese": "マナワツ・ワンガヌイ", + "english": "Manawatu-Wanganui", + "french": "Manawatu-Wanganui", + "german": "Manawatu-Wanganui", + "italian": "Manawatu-Wanganui", + "spanish": "Manawatu-Wanganui", + "chinese_simple": "玛纳瓦图-旺格努伊大区", + "korean": "마너와투-왕거누이", + "dutch": "Manawatu-Wanganui", + "portuguese": "Manawatu-Wanganui", + "russian": "Манавату-Уангануи", + "chinese_traditional": "Manawatu-Wanganui", + "unknown1": "Manawatu-Wanganui", + "unknown2": "Manawatu-Wanganui", + "unknown3": "Manawatu-Wanganui", + "unknown4": "Manawatu-Wanganui" + }, + "coordinates": { + "latitude": -40.358276932, + "longitude": 175.61693263 + } + }, + { + "id": 1594425344, + "name": "Nelson", + "translations": { + "japanese": "ネルソン・マールボロ", + "english": "Nelson", + "french": "Nelson", + "german": "Nelson", + "italian": "Nelson", + "spanish": "Región de Nelson", + "chinese_simple": "纳尔逊区", + "korean": "넬슨-말버러", + "dutch": "Nelson", + "portuguese": "Nelson", + "russian": "Нельсон", + "chinese_traditional": "Nelson", + "unknown1": "Nelson", + "unknown2": "Nelson", + "unknown3": "Nelson", + "unknown4": "Nelson" + }, + "coordinates": { + "latitude": -41.292114812, + "longitude": 173.243879302 + } + }, + { + "id": 1594490880, + "name": "Northland", + "translations": { + "japanese": "ノースランド", + "english": "Northland", + "french": "Northland", + "german": "Northland", + "italian": "Northland", + "spanish": "Northland", + "chinese_simple": "北地大区", + "korean": "노스랜드", + "dutch": "Northland", + "portuguese": "Northland", + "russian": "Нортленд", + "chinese_traditional": "Northland", + "unknown1": "Northland", + "unknown2": "Northland", + "unknown3": "Northland", + "unknown4": "Northland" + }, + "coordinates": { + "latitude": -35.716553352, + "longitude": 174.315049207 + } + }, + { + "id": 1594621952, + "name": "Southland", + "translations": { + "japanese": "サウスランド", + "english": "Southland", + "french": "Southland", + "german": "Southland", + "italian": "Southland", + "spanish": "Southland", + "chinese_simple": "南地大区", + "korean": "사우스랜드", + "dutch": "Southland", + "portuguese": "Southland", + "russian": "Саутленд", + "chinese_traditional": "Southland", + "unknown1": "Southland", + "unknown2": "Southland", + "unknown3": "Southland", + "unknown4": "Southland" + }, + "coordinates": { + "latitude": -46.41174366, + "longitude": 168.371429529 + } + }, + { + "id": 1594687488, + "name": "Taranaki", + "translations": { + "japanese": "タラナキ", + "english": "Taranaki", + "french": "Taranaki", + "german": "Taranaki", + "italian": "Taranaki", + "spanish": "Taranaki", + "chinese_simple": "塔拉纳基大区", + "korean": "타라나키", + "dutch": "Taranaki", + "portuguese": "Taranaki", + "russian": "Таранаки", + "chinese_traditional": "Taranaki", + "unknown1": "Taranaki", + "unknown2": "Taranaki", + "unknown3": "Taranaki", + "unknown4": "Taranaki" + }, + "coordinates": { + "latitude": -39.067383391999996, + "longitude": 174.073349331 + } + }, + { + "id": 1594753024, + "name": "Waikato", + "translations": { + "japanese": "ワイカト", + "english": "Waikato", + "french": "Waikato", + "german": "Waikato", + "italian": "Waikato", + "spanish": "Waikato", + "chinese_simple": "怀卡托大区", + "korean": "와이카토", + "dutch": "Waikato", + "portuguese": "Waikato", + "russian": "Уаикато", + "chinese_traditional": "Waikato", + "unknown1": "Waikato", + "unknown2": "Waikato", + "unknown3": "Waikato", + "unknown4": "Waikato" + }, + "coordinates": { + "latitude": -37.776489852, + "longitude": 175.265369174 + } + }, + { + "id": 1594818560, + "name": "Gisborne", + "translations": { + "japanese": "ギズボーン", + "english": "Gisborne", + "french": "Gisborne", + "german": "Gisborne", + "italian": "Gisborne", + "spanish": "Gisborne", + "chinese_simple": "吉斯伯恩大区", + "korean": "기즈번", + "dutch": "Gisborne", + "portuguese": "Gisborne", + "russian": "Гисборн", + "chinese_traditional": "Gisborne", + "unknown1": "Gisborne", + "unknown2": "Gisborne", + "unknown3": "Gisborne", + "unknown4": "Gisborne" + }, + "coordinates": { + "latitude": -38.66638242, + "longitude": 178.017451853 + } + }, + { + "id": 1594884096, + "name": "West Coast", + "translations": { + "japanese": "ウェストコースト", + "english": "West Coast", + "french": "West Coast", + "german": "West Coast", + "italian": "West Coast", + "spanish": "Costa Oeste", + "chinese_simple": "西岸大区", + "korean": "웨스트코스트", + "dutch": "West Coast", + "portuguese": "Costa Oeste", + "russian": "Уэст-Кост", + "chinese_traditional": "West Coast", + "unknown1": "West Coast", + "unknown2": "West Coast", + "unknown3": "West Coast", + "unknown4": "West Coast" + }, + "coordinates": { + "latitude": -42.445679252, + "longitude": 171.211403072 + } + }, + { + "id": 1594949632, + "name": "Marlborough", + "translations": { + "japanese": "マールボロ", + "english": "Marlborough", + "french": "Marlborough", + "german": "Marlborough", + "italian": "Marlborough", + "spanish": "Marlborough", + "chinese_simple": "马尔伯勒大区", + "korean": "말버러", + "dutch": "Marlborough", + "portuguese": "Marlborough", + "russian": "Мальборо", + "chinese_traditional": "Marlborough", + "unknown1": "Marlborough", + "unknown2": "Marlborough", + "unknown3": "Marlborough", + "unknown4": "Marlborough" + }, + "coordinates": { + "latitude": -41.511841372, + "longitude": 173.957992572 + } + }, + { + "id": 1595015168, + "name": "Tasman", + "translations": { + "japanese": "タスマン", + "english": "Tasman", + "french": "Tasman", + "german": "Tasman", + "italian": "Tasman", + "spanish": "Tasman", + "chinese_simple": "塔斯曼大区", + "korean": "태즈먼", + "dutch": "Tasman", + "portuguese": "Tasman", + "russian": "Тасман", + "chinese_traditional": "Tasman", + "unknown1": "Tasman", + "unknown2": "Tasman", + "unknown3": "Tasman", + "unknown4": "Tasman" + }, + "coordinates": { + "latitude": -41.341553288, + "longitude": 173.183454333 + } + } + ] + }, + { + "id": 96, + "iso_code": "NO", + "name": "Norway", + "translations": { + "japanese": "ノルウェー", + "english": "Norway", + "french": "Norvège", + "german": "Norwegen", + "italian": "Norvegia", + "spanish": "Noruega", + "chinese_simple": "挪威", + "korean": "노르웨이", + "dutch": "Noorwegen", + "portuguese": "Noruega", + "russian": "Норвегия", + "chinese_traditional": "Norway", + "unknown1": "Norway", + "unknown2": "Norway", + "unknown3": "Norway", + "unknown4": "Norway" + }, + "regions": [ + { + "id": 1610612736, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": 59.935912404, + "longitude": 10.706205871 + } + }, + { + "id": 1611071488, + "name": "Oslo", + "translations": { + "japanese": "オスロ", + "english": "Oslo", + "french": "Oslo", + "german": "Oslo", + "italian": "Oslo", + "spanish": "Oslo", + "chinese_simple": "奥斯陆", + "korean": "오슬로", + "dutch": "Oslo", + "portuguese": "Oslo", + "russian": "Осло", + "chinese_traditional": "Oslo", + "unknown1": "Oslo", + "unknown2": "Oslo", + "unknown3": "Oslo", + "unknown4": "Oslo" + }, + "coordinates": { + "latitude": 59.935912404, + "longitude": 10.706205871 + } + }, + { + "id": 1611137024, + "name": "Akershus", + "translations": { + "japanese": "アーケシュフース県", + "english": "Akershus", + "french": "Akershus", + "german": "Akershus", + "italian": "Akershus", + "spanish": "Akershus", + "chinese_simple": "阿克什胡斯郡", + "korean": "아케르스후스 주", + "dutch": "Akershus", + "portuguese": "Akershus", + "russian": "Акерсхус", + "chinese_traditional": "Akershus", + "unknown1": "Akershus", + "unknown2": "Akershus", + "unknown3": "Akershus", + "unknown4": "Akershus" + }, + "coordinates": { + "latitude": 59.935912404, + "longitude": 10.706205871 + } + }, + { + "id": 1611202560, + "name": "Aust-Agder", + "translations": { + "japanese": "アウスト・アグデル県", + "english": "Aust-Agder", + "french": "Aust-Agder", + "german": "Aust-Agder", + "italian": "Aust-Agder", + "spanish": "Aust-Agder", + "chinese_simple": "东阿格德尔郡", + "korean": "에우스트아그데르 주", + "dutch": "Aust-Agder", + "portuguese": "Aust-Agder", + "russian": "Эуст-Агдер", + "chinese_traditional": "Aust-Agder", + "unknown1": "Aust-Agder", + "unknown2": "Aust-Agder", + "unknown3": "Aust-Agder", + "unknown4": "Aust-Agder" + }, + "coordinates": { + "latitude": 58.480223944, + "longitude": 8.778100042 + } + }, + { + "id": 1611268096, + "name": "Buskerud", + "translations": { + "japanese": "ブスケルー県", + "english": "Buskerud", + "french": "Buskerud", + "german": "Buskerud", + "italian": "Buskerud", + "spanish": "Buskerud", + "chinese_simple": "布斯克吕郡", + "korean": "부스케루 주", + "dutch": "Buskerud", + "portuguese": "Buskerud", + "russian": "Бускеруд", + "chinese_traditional": "Buskerud", + "unknown1": "Buskerud", + "unknown2": "Buskerud", + "unknown3": "Buskerud", + "unknown4": "Buskerud" + }, + "coordinates": { + "latitude": 59.732665336000004, + "longitude": 10.200833403 + } + }, + { + "id": 1611333632, + "name": "Finnmark", + "translations": { + "japanese": "フィンマルク県", + "english": "Finnmark", + "french": "Finnmark", + "german": "Finnmark", + "italian": "Finnmark", + "spanish": "Finnmark", + "chinese_simple": "芬马克郡", + "korean": "핀마르크 주", + "dutch": "Finnmark", + "portuguese": "Finnmark", + "russian": "Финнмарк", + "chinese_traditional": "Finnmark", + "unknown1": "Finnmark", + "unknown2": "Finnmark", + "unknown3": "Finnmark", + "unknown4": "Finnmark" + }, + "coordinates": { + "latitude": 70.076293148, + "longitude": 29.745564285 + } + }, + { + "id": 1611399168, + "name": "Hedmark", + "translations": { + "japanese": "ヘードマルク県", + "english": "Hedmark", + "french": "Hedmark", + "german": "Hedmark", + "italian": "Hedmark", + "spanish": "Hedmark", + "chinese_simple": "海德马克郡", + "korean": "헤드마르크 주", + "dutch": "Hedmark", + "portuguese": "Hedmark", + "russian": "Хедмарк", + "chinese_traditional": "Hedmark", + "unknown1": "Hedmark", + "unknown2": "Hedmark", + "unknown3": "Hedmark", + "unknown4": "Hedmark" + }, + "coordinates": { + "latitude": 60.798339152, + "longitude": 11.052276148 + } + }, + { + "id": 1611464704, + "name": "Hordaland", + "translations": { + "japanese": "ホルダラン県", + "english": "Hordaland", + "french": "Hordaland", + "german": "Hordaland", + "italian": "Hordaland", + "spanish": "Hordaland", + "chinese_simple": "霍达兰郡", + "korean": "호르달란 주", + "dutch": "Hordaland", + "portuguese": "Hordaland", + "russian": "Хордаланн", + "chinese_traditional": "Hordaland", + "unknown1": "Hordaland", + "unknown2": "Hordaland", + "unknown3": "Hordaland", + "unknown4": "Hordaland" + }, + "coordinates": { + "latitude": 60.386351852, + "longitude": 5.32838363 + } + }, + { + "id": 1611530240, + "name": "Møre og Romsdal", + "translations": { + "japanese": "ムーレ・オ・ロムスダール県", + "english": "Møre og Romsdal", + "french": "Møre og Romsdal", + "german": "Møre og Romsdal", + "italian": "Møre og Romsdal", + "spanish": "Møre og Romsdal", + "chinese_simple": "默勒-鲁姆斯达尔郡", + "korean": "뫼레오그롬스달 주", + "dutch": "Møre og Romsdal", + "portuguese": "Møre og Romsdal", + "russian": "Мёре-ог-Румсдал", + "chinese_traditional": "Møre og Romsdal", + "unknown1": "Møre og Romsdal", + "unknown2": "Møre og Romsdal", + "unknown3": "Møre og Romsdal", + "unknown4": "Møre og Romsdal" + }, + "coordinates": { + "latitude": 62.753905536, + "longitude": 7.234516743 + } + }, + { + "id": 1611595776, + "name": "Nordland", + "translations": { + "japanese": "ヌールラン県", + "english": "Nordland", + "french": "Nordland", + "german": "Nordland", + "italian": "Nordland", + "spanish": "Nordland", + "chinese_simple": "诺尔兰郡", + "korean": "노를란 주", + "dutch": "Nordland", + "portuguese": "Nordland", + "russian": "Нурланн", + "chinese_traditional": "Nordland", + "unknown1": "Nordland", + "unknown2": "Nordland", + "unknown3": "Nordland", + "unknown4": "Nordland" + }, + "coordinates": { + "latitude": 67.274779508, + "longitude": 14.403115338 + } + }, + { + "id": 1611661312, + "name": "Nord-Trøndelag", + "translations": { + "japanese": "ヌール・トロンデラーグ県", + "english": "Nord-Trøndelag", + "french": "Nord-Trøndelag", + "german": "Nord-Trøndelag", + "italian": "Nord-Trøndelag", + "spanish": "Nord-Trøndelag", + "chinese_simple": "北特伦德拉格郡", + "korean": "노르트뢰넬라그 주", + "dutch": "Nord-Trøndelag", + "portuguese": "Nord-Trøndelag", + "russian": "Нур-Трёнделаг", + "chinese_traditional": "Nord-Trøndelag", + "unknown1": "Nord-Trøndelag", + "unknown2": "Nord-Trøndelag", + "unknown3": "Nord-Trøndelag", + "unknown4": "Nord-Trøndelag" + }, + "coordinates": { + "latitude": 64.055785404, + "longitude": 11.716950807 + } + }, + { + "id": 1611726848, + "name": "Oppland", + "translations": { + "japanese": "オップラン県", + "english": "Oppland", + "french": "Oppland", + "german": "Oppland", + "italian": "Oppland", + "spanish": "Oppland", + "chinese_simple": "奥普兰郡", + "korean": "오플란 주", + "dutch": "Oppland", + "portuguese": "Oppland", + "russian": "Оппланн", + "chinese_traditional": "Oppland", + "unknown1": "Oppland", + "unknown2": "Oppland", + "unknown3": "Oppland", + "unknown4": "Oppland" + }, + "coordinates": { + "latitude": 61.127928992, + "longitude": 10.431546921 + } + }, + { + "id": 1611792384, + "name": "Rogaland", + "translations": { + "japanese": "ローガラン県", + "english": "Rogaland", + "french": "Rogaland", + "german": "Rogaland", + "italian": "Rogaland", + "spanish": "Rogaland", + "chinese_simple": "罗加兰郡", + "korean": "로갈란 주", + "dutch": "Rogaland", + "portuguese": "Rogaland", + "russian": "Ругаланн", + "chinese_traditional": "Rogaland", + "unknown1": "Rogaland", + "unknown2": "Rogaland", + "unknown3": "Rogaland", + "unknown4": "Rogaland" + }, + "coordinates": { + "latitude": 58.958129212, + "longitude": 5.718399339 + } + }, + { + "id": 1611857920, + "name": "Sogn og Fjordane", + "translations": { + "japanese": "ソグン・オ・フィヨーラネ県", + "english": "Sogn og Fjordane", + "french": "Sogn og Fjordane", + "german": "Sogn og Fjordane", + "italian": "Sogn og Fjordane", + "spanish": "Sogn og Fjordane", + "chinese_simple": "松恩-菲尤拉讷郡", + "korean": "송노피오라네 주", + "dutch": "Sogn og Fjordane", + "portuguese": "Sogn og Fjordane", + "russian": "Согн-ог-Фьюране", + "chinese_traditional": "Sogn og Fjordane", + "unknown1": "Sogn og Fjordane", + "unknown2": "Sogn og Fjordane", + "unknown3": "Sogn og Fjordane", + "unknown4": "Sogn og Fjordane" + }, + "coordinates": { + "latitude": 61.226805944, + "longitude": 6.789569244 + } + }, + { + "id": 1611923456, + "name": "Sør-Trøndelag", + "translations": { + "japanese": "ソール・トロンデラーグ県", + "english": "Sør-Trøndelag", + "french": "Sør-Trøndelag", + "german": "Sør-Trøndelag", + "italian": "Sør-Trøndelag", + "spanish": "Sør-Trøndelag", + "chinese_simple": "南特伦德拉格郡", + "korean": "쇠르트뢰넬라그 주", + "dutch": "Sør-Trøndelag", + "portuguese": "Sør-Trøndelag", + "russian": "Сёр-Трёнделаг", + "chinese_traditional": "Sør-Trøndelag", + "unknown1": "Sør-Trøndelag", + "unknown2": "Sør-Trøndelag", + "unknown3": "Sør-Trøndelag", + "unknown4": "Sør-Trøndelag" + }, + "coordinates": { + "latitude": 63.429564708, + "longitude": 10.393094668 + } + }, + { + "id": 1611988992, + "name": "Telemark", + "translations": { + "japanese": "テレマルク県", + "english": "Telemark", + "french": "Telemark", + "german": "Telemark", + "italian": "Telemark", + "spanish": "Telemark", + "chinese_simple": "泰勒马克郡", + "korean": "텔레마르크 주", + "dutch": "Telemark", + "portuguese": "Telemark", + "russian": "Телемарк", + "chinese_traditional": "Telemark", + "unknown1": "Telemark", + "unknown2": "Telemark", + "unknown3": "Telemark", + "unknown4": "Telemark" + }, + "coordinates": { + "latitude": 59.205321592, + "longitude": 9.552638281 + } + }, + { + "id": 1612054528, + "name": "Troms", + "translations": { + "japanese": "トロムス県", + "english": "Troms", + "french": "Troms", + "german": "Troms", + "italian": "Troms", + "spanish": "Troms", + "chinese_simple": "特罗姆斯郡", + "korean": "트롬스 주", + "dutch": "Troms", + "portuguese": "Troms", + "russian": "Тромс", + "chinese_traditional": "Troms", + "unknown1": "Troms", + "unknown2": "Troms", + "unknown3": "Troms", + "unknown4": "Troms" + }, + "coordinates": { + "latitude": 69.68078534, + "longitude": 18.940481192 + } + }, + { + "id": 1612120064, + "name": "Vest-Agder", + "translations": { + "japanese": "ヴェスト・アグデル県", + "english": "Vest-Agder", + "french": "Vest-Agder", + "german": "Vest-Agder", + "italian": "Vest-Agder", + "spanish": "Vest-Agder", + "chinese_simple": "西阿格德尔郡", + "korean": "베스트아그데르 주", + "dutch": "Vest-Agder", + "portuguese": "Vest-Agder", + "russian": "Вест-Агдер", + "chinese_traditional": "Vest-Agder", + "unknown1": "Vest-Agder", + "unknown2": "Vest-Agder", + "unknown3": "Vest-Agder", + "unknown4": "Vest-Agder" + }, + "coordinates": { + "latitude": 58.167113596, + "longitude": 8.003561803 + } + }, + { + "id": 1612185600, + "name": "Vestfold", + "translations": { + "japanese": "ヴェストフォル県", + "english": "Vestfold", + "french": "Vestfold", + "german": "Vestfold", + "italian": "Vestfold", + "spanish": "Vestfold", + "chinese_simple": "西福尔郡", + "korean": "베스트폴 주", + "dutch": "Vestfold", + "portuguese": "Vestfold", + "russian": "Вестфолл", + "chinese_traditional": "Vestfold", + "unknown1": "Vestfold", + "unknown2": "Vestfold", + "unknown3": "Vestfold", + "unknown4": "Vestfold" + }, + "coordinates": { + "latitude": 59.293212216, + "longitude": 10.420560563 + } + }, + { + "id": 1612251136, + "name": "Østfold", + "translations": { + "japanese": "エストフォル県", + "english": "Østfold", + "french": "Østfold", + "german": "Østfold", + "italian": "Østfold", + "spanish": "Østfold", + "chinese_simple": "东福尔郡", + "korean": "외스트폴 주", + "dutch": "Østfold", + "portuguese": "Østfold", + "russian": "Эстфолл", + "chinese_traditional": "Østfold", + "unknown1": "Østfold", + "unknown2": "Østfold", + "unknown3": "Østfold", + "unknown4": "Østfold" + }, + "coordinates": { + "latitude": 59.287719052, + "longitude": 11.101714759 + } + }, + { + "id": 1612316672, + "name": "Svalbard", + "translations": { + "japanese": "スヴァールバル諸島", + "english": "Svalbard", + "french": "Svalbard", + "german": "Svalbard", + "italian": "Isole Svalbard", + "spanish": "Svalbard", + "chinese_simple": "斯瓦尔巴群岛", + "korean": "스발바르 제도", + "dutch": "Spitsbergen", + "portuguese": "Svalbard", + "russian": "Шпицберген", + "chinese_traditional": "Svalbard", + "unknown1": "Svalbard", + "unknown2": "Svalbard", + "unknown3": "Svalbard", + "unknown4": "Svalbard" + }, + "coordinates": { + "latitude": 78.211669032, + "longitude": 15.54569657 + } + } + ] + }, + { + "id": 97, + "iso_code": "PL", + "name": "Poland", + "translations": { + "japanese": "ポーランド", + "english": "Poland", + "french": "Pologne", + "german": "Polen", + "italian": "Polonia", + "spanish": "Polonia", + "chinese_simple": "波兰", + "korean": "폴란드", + "dutch": "Polen", + "portuguese": "Polónia", + "russian": "Польша", + "chinese_traditional": "Poland", + "unknown1": "Poland", + "unknown2": "Poland", + "unknown3": "Poland", + "unknown4": "Poland" + }, + "regions": [ + { + "id": 1627389952, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": 52.23998964, + "longitude": 21.005916496 + } + }, + { + "id": 1627521024, + "name": "Masovia", + "translations": { + "japanese": "マゾフシェ", + "english": "Masovia", + "french": "Mazovie", + "german": "Masowien", + "italian": "Masovia", + "spanish": "Mazovia", + "chinese_simple": "马佐夫舍省", + "korean": "마조프셰", + "dutch": "Mazovië", + "portuguese": "Masóvia", + "russian": "Мазовецкое воеводство", + "chinese_traditional": "Masovia", + "unknown1": "Masovia", + "unknown2": "Masovia", + "unknown3": "Masovia", + "unknown4": "Masovia" + }, + "coordinates": { + "latitude": 52.23998964, + "longitude": 21.005916496 + } + }, + { + "id": 1627586560, + "name": "Lower Silesia", + "translations": { + "japanese": "ドルヌィ・シロンスク", + "english": "Lower Silesia", + "french": "Basse-Silésie", + "german": "Niederschlesien", + "italian": "Bassa Slesia", + "spanish": "Baja Silesia", + "chinese_simple": "下西里西亚省", + "korean": "하슐레지엔", + "dutch": "Neder-Silezië", + "portuguese": "Baixa Silésia", + "russian": "Нижнесилезское воеводство", + "chinese_traditional": "Lower Silesia", + "unknown1": "Lower Silesia", + "unknown2": "Lower Silesia", + "unknown3": "Lower Silesia", + "unknown4": "Lower Silesia" + }, + "coordinates": { + "latitude": 51.11389102, + "longitude": 17.0288549 + } + }, + { + "id": 1627652096, + "name": "Kuyavian-Pomeranian Voivodeship", + "translations": { + "japanese": "クヤヴィ・ポモージェ", + "english": "Kuyavian-Pomeranian Voivodeship", + "french": "Cujavie-Poméranie", + "german": "Kujawien-Pommern", + "italian": "Cuiavia-Pomerania", + "spanish": "Cuyavia y Pomerania", + "chinese_simple": "库亚瓦滨海省", + "korean": "쿠야비아포메라니아", + "dutch": "Kujavië-Pommeren", + "portuguese": "Cujávia-Pomerânia", + "russian": "Куявско-Поморское воеводство", + "chinese_traditional": "Kuyavian-Pomeranian Voivodeship", + "unknown1": "Kuyavian-Pomeranian Voivodeship", + "unknown2": "Kuyavian-Pomeranian Voivodeship", + "unknown3": "Kuyavian-Pomeranian Voivodeship", + "unknown4": "Kuyavian-Pomeranian Voivodeship" + }, + "coordinates": { + "latitude": 53.113402716, + "longitude": 18.028613478 + } + }, + { + "id": 1627717632, + "name": "Lodz", + "translations": { + "japanese": "ウッジ", + "english": "Lodz", + "french": "Łódź", + "german": "Lodsch", + "italian": "Łódź", + "spanish": "Lodz", + "chinese_simple": "罗兹省", + "korean": "우치", + "dutch": "Łódź", + "portuguese": "Lodz", + "russian": "Лодзинское воеводство", + "chinese_traditional": "Lodz", + "unknown1": "Lodz", + "unknown2": "Lodz", + "unknown3": "Lodz", + "unknown4": "Lodz" + }, + "coordinates": { + "latitude": 51.7730707, + "longitude": 19.467826376 + } + }, + { + "id": 1627783168, + "name": "Lublin", + "translations": { + "japanese": "ルブリン", + "english": "Lublin", + "french": "Lublin", + "german": "Lublin", + "italian": "Lublino", + "spanish": "Lublin", + "chinese_simple": "卢布林省", + "korean": "루블린", + "dutch": "Lublin", + "portuguese": "Lublin", + "russian": "Люблинское воеводство", + "chinese_traditional": "Lublin", + "unknown1": "Lublin", + "unknown2": "Lublin", + "unknown3": "Lublin", + "unknown4": "Lublin" + }, + "coordinates": { + "latitude": 51.234740628, + "longitude": 22.571472511 + } + }, + { + "id": 1627848704, + "name": "Lubusz", + "translations": { + "japanese": "ルブシュ", + "english": "Lubusz", + "french": "Lubusz", + "german": "Lebus", + "italian": "Lebus", + "spanish": "Lubus", + "chinese_simple": "鲁布斯卡省", + "korean": "루부쉬", + "dutch": "Lubusz", + "portuguese": "Lubusz", + "russian": "Любушское воеводство", + "chinese_traditional": "Lubusz", + "unknown1": "Lubusz", + "unknown2": "Lubusz", + "unknown3": "Lubusz", + "unknown4": "Lubusz" + }, + "coordinates": { + "latitude": 52.7343744, + "longitude": 15.232585367 + } + }, + { + "id": 1627914240, + "name": "Lesser Poland", + "translations": { + "japanese": "マウォポルスカ", + "english": "Lesser Poland", + "french": "Petite-Pologne", + "german": "Kleinpolen", + "italian": "Piccola Polonia", + "spanish": "Pequeña Polonia", + "chinese_simple": "小波兰省", + "korean": "소폴란드", + "dutch": "Klein-Polen", + "portuguese": "Pequena Polónia", + "russian": "Малопольское воеводство", + "chinese_traditional": "Lesser Poland", + "unknown1": "Lesser Poland", + "unknown2": "Lesser Poland", + "unknown3": "Lesser Poland", + "unknown4": "Lesser Poland" + }, + "coordinates": { + "latitude": 50.053710368, + "longitude": 19.940239769999998 + } + }, + { + "id": 1627979776, + "name": "Opole", + "translations": { + "japanese": "オポーレ", + "english": "Opole", + "french": "Opole", + "german": "Oppeln", + "italian": "Opole", + "spanish": "Opole", + "chinese_simple": "奥波莱省", + "korean": "오폴레", + "dutch": "Opole", + "portuguese": "Opole", + "russian": "Опольское воеводство", + "chinese_traditional": "Opole", + "unknown1": "Opole", + "unknown2": "Opole", + "unknown3": "Opole", + "unknown4": "Opole" + }, + "coordinates": { + "latitude": 50.663451572, + "longitude": 17.918749898 + } + }, + { + "id": 1628045312, + "name": "Subcarpathia", + "translations": { + "japanese": "ポトカルパチェ", + "english": "Subcarpathia", + "french": "Basses-Carpates", + "german": "Karpatenvorland", + "italian": "Precarpazia", + "spanish": "Subcarpacia", + "chinese_simple": "喀尔巴阡山省", + "korean": "카르파티아", + "dutch": "Subkarpaten", + "portuguese": "Subcarpácia", + "russian": "Подкарпатское воеводство", + "chinese_traditional": "Subcarpathia", + "unknown1": "Subcarpathia", + "unknown2": "Subcarpathia", + "unknown3": "Subcarpathia", + "unknown4": "Subcarpathia" + }, + "coordinates": { + "latitude": 50.037230876, + "longitude": 22.005675074 + } + }, + { + "id": 1628110848, + "name": "Podlachia", + "translations": { + "japanese": "ポドラシェ", + "english": "Podlachia", + "french": "Podlachie", + "german": "Podlachien", + "italian": "Podlachia", + "spanish": "Podlaquia", + "chinese_simple": "波德拉斯省", + "korean": "포들라셰", + "dutch": "Podlachië", + "portuguese": "Podláquia", + "russian": "Подляское воеводство", + "chinese_traditional": "Podlachia", + "unknown1": "Podlachia", + "unknown2": "Podlachia", + "unknown3": "Podlachia", + "unknown4": "Podlachia" + }, + "coordinates": { + "latitude": 53.135375372, + "longitude": 23.153749485 + } + }, + { + "id": 1628176384, + "name": "Pomerania", + "translations": { + "japanese": "ポモージェ", + "english": "Pomerania", + "french": "Poméranie", + "german": "Pommern", + "italian": "Pomerania", + "spanish": "Pomerania", + "chinese_simple": "滨海省", + "korean": "포메라니아", + "dutch": "Pommeren", + "portuguese": "Pomerânia", + "russian": "Поморское воеводство", + "chinese_traditional": "Pomerania", + "unknown1": "Pomerania", + "unknown2": "Pomerania", + "unknown3": "Pomerania", + "unknown4": "Pomerania" + }, + "coordinates": { + "latitude": 54.35485778, + "longitude": 18.632863168 + } + }, + { + "id": 1628241920, + "name": "Silesia", + "translations": { + "japanese": "シュレジエン", + "english": "Silesia", + "french": "Silésie", + "german": "Schlesien", + "italian": "Slesia", + "spanish": "Silesia", + "chinese_simple": "西里西亚省", + "korean": "슐레지엔", + "dutch": "Silezië", + "portuguese": "Silésia", + "russian": "Силезское воеводство", + "chinese_traditional": "Silesia", + "unknown1": "Silesia", + "unknown2": "Silesia", + "unknown3": "Silesia", + "unknown4": "Silesia" + }, + "coordinates": { + "latitude": 50.267943764, + "longitude": 19.011892519 + } + }, + { + "id": 1628307456, + "name": "Świętokrzyskie", + "translations": { + "japanese": "シフィェンティクシシュ", + "english": "Świętokrzyskie", + "french": "Sainte-Croix", + "german": "Heiligkreuz", + "italian": "Santacroce", + "spanish": "Santa Cruz", + "chinese_simple": "圣十字省", + "korean": "시비엥토크시스키에", + "dutch": "Święty Krzyż", + "portuguese": "Santa Cruz", + "russian": "Свентокшиское воеводство", + "chinese_traditional": "Świętokrzyskie", + "unknown1": "Świętokrzyskie", + "unknown2": "Świętokrzyskie", + "unknown3": "Świętokrzyskie", + "unknown4": "Świętokrzyskie" + }, + "coordinates": { + "latitude": 50.86669864, + "longitude": 20.621393966 + } + }, + { + "id": 1628372992, + "name": "Warmian-Masurian Voivodeship", + "translations": { + "japanese": "ヴァルミア・マスールィ", + "english": "Warmian-Masurian Voivodeship", + "french": "Warmie-Mazurie", + "german": "Ermland-Masuren", + "italian": "Varmia-Masuria", + "spanish": "Varmia y Masuria", + "chinese_simple": "瓦尔米亚马祖尔省", + "korean": "바르미아 마수리아", + "dutch": "Ermland-Mazurië", + "portuguese": "Vármia-Masúria", + "russian": "Варминско-Мазурское воеводство", + "chinese_traditional": "Warmian-Masurian Voivodeship", + "unknown1": "Warmian-Masurian Voivodeship", + "unknown2": "Warmian-Masurian Voivodeship", + "unknown3": "Warmian-Masurian Voivodeship", + "unknown4": "Warmian-Masurian Voivodeship" + }, + "coordinates": { + "latitude": 53.772582396, + "longitude": 20.484064490999998 + } + }, + { + "id": 1628438528, + "name": "Greater Poland", + "translations": { + "japanese": "ヴィェルコポルスカ", + "english": "Greater Poland", + "french": "Grande-Pologne", + "german": "Großpolen", + "italian": "Grande Polonia", + "spanish": "Gran Polonia", + "chinese_simple": "大波兰省", + "korean": "대폴란드", + "dutch": "Groot-Polen", + "portuguese": "Grande Polónia", + "russian": "Великопольское воеводство", + "chinese_traditional": "Greater Poland", + "unknown1": "Greater Poland", + "unknown2": "Greater Poland", + "unknown3": "Greater Poland", + "unknown4": "Greater Poland" + }, + "coordinates": { + "latitude": 52.388305068, + "longitude": 16.880539067 + } + }, + { + "id": 1628504064, + "name": "Western Pomerania", + "translations": { + "japanese": "西ポモージェ", + "english": "Western Pomerania", + "french": "Poméranie occidentale", + "german": "Westpommern", + "italian": "Pomerania Occidentale", + "spanish": "Pomerania Occidental", + "chinese_simple": "西滨海省", + "korean": "서포메라니아", + "dutch": "West-Pommeren", + "portuguese": "Pomerânia Ocidental", + "russian": "Западно-Поморское воеводство", + "chinese_traditional": "Western Pomerania", + "unknown1": "Western Pomerania", + "unknown2": "Western Pomerania", + "unknown3": "Western Pomerania", + "unknown4": "Western Pomerania" + }, + "coordinates": { + "latitude": 53.426513064, + "longitude": 14.551431170999999 + } + } + ] + }, + { + "id": 98, + "iso_code": "PT", + "name": "Portugal", + "translations": { + "japanese": "ポルトガル", + "english": "Portugal", + "french": "Portugal", + "german": "Portugal", + "italian": "Portogallo", + "spanish": "Portugal", + "chinese_simple": "葡萄牙", + "korean": "포르투갈", + "dutch": "Portugal", + "portuguese": "Portugal", + "russian": "Португалия", + "chinese_traditional": "Portugal", + "unknown1": "Portugal", + "unknown2": "Portugal", + "unknown3": "Portugal", + "unknown4": "Portugal" + }, + "regions": [ + { + "id": 1644167168, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": 38.721313036, + "longitude": -9.140160384000012 + } + }, + { + "id": 1644298240, + "name": "Lisbon", + "translations": { + "japanese": "リスボン県", + "english": "Lisbon", + "french": "Lisbonne", + "german": "Lissabon", + "italian": "Lisbona", + "spanish": "Distrito de Lisboa", + "chinese_simple": "里斯本区", + "korean": "리스보아 주", + "dutch": "Lissabon", + "portuguese": "Lisboa", + "russian": "Лиссабон", + "chinese_traditional": "Lisbon", + "unknown1": "Lisbon", + "unknown2": "Lisbon", + "unknown3": "Lisbon", + "unknown4": "Lisbon" + }, + "coordinates": { + "latitude": 38.721313036, + "longitude": -9.140160384000012 + } + }, + { + "id": 1644625920, + "name": "Madeira", + "translations": { + "japanese": "マディラ自治州", + "english": "Madeira", + "french": "Madère", + "german": "Madeira", + "italian": "Madera", + "spanish": "Madeira", + "chinese_simple": "马德拉自治区", + "korean": "마데이라 주", + "dutch": "Madeira", + "portuguese": "Madeira", + "russian": "Мадейра", + "chinese_traditional": "Madeira", + "unknown1": "Madeira", + "unknown2": "Madeira", + "unknown3": "Madeira", + "unknown4": "Madeira" + }, + "coordinates": { + "latitude": 32.640380488, + "longitude": -16.90751549000001 + } + }, + { + "id": 1644691456, + "name": "Azores", + "translations": { + "japanese": "アソレス自治州", + "english": "Azores", + "french": "Açores", + "german": "Azoren", + "italian": "Azzorre", + "spanish": "Azores", + "chinese_simple": "亚速尔自治区", + "korean": "아소르스 주", + "dutch": "Azoren", + "portuguese": "Açores", + "russian": "Азорские острова", + "chinese_traditional": "Azores", + "unknown1": "Azores", + "unknown2": "Azores", + "unknown3": "Azores", + "unknown4": "Azores" + }, + "coordinates": { + "latitude": 37.73803668, + "longitude": -25.663642815999992 + } + }, + { + "id": 1644756992, + "name": "Aveiro", + "translations": { + "japanese": "アヴェイロ県", + "english": "Aveiro", + "french": "Aveiro", + "german": "Aveiro", + "italian": "Aveiro", + "spanish": "Distrito de Aveiro", + "chinese_simple": "阿威罗区", + "korean": "아베이루 주", + "dutch": "Aveiro", + "portuguese": "Aveiro", + "russian": "Авейру", + "chinese_traditional": "Aveiro", + "unknown1": "Aveiro", + "unknown2": "Aveiro", + "unknown3": "Aveiro", + "unknown4": "Aveiro" + }, + "coordinates": { + "latitude": 40.638427272, + "longitude": -8.651267453000003 + } + }, + { + "id": 1644822528, + "name": "Beja", + "translations": { + "japanese": "ベージャ県", + "english": "Beja", + "french": "Beja", + "german": "Beja", + "italian": "Beja", + "spanish": "Distrito de Beja", + "chinese_simple": "贝雅区", + "korean": "베자 주", + "dutch": "Beja", + "portuguese": "Beja", + "russian": "Бежа", + "chinese_traditional": "Beja", + "unknown1": "Beja", + "unknown2": "Beja", + "unknown3": "Beja", + "unknown4": "Beja" + }, + "coordinates": { + "latitude": 38.01269488, + "longitude": -7.860249677000013 + } + }, + { + "id": 1644888064, + "name": "Braga", + "translations": { + "japanese": "ブラガ県", + "english": "Braga", + "french": "Braga", + "german": "Braga", + "italian": "Braga", + "spanish": "Distrito de Braga", + "chinese_simple": "布拉加区", + "korean": "브라가 주", + "dutch": "Braga", + "portuguese": "Braga", + "russian": "Брага", + "chinese_traditional": "Braga", + "unknown1": "Braga", + "unknown2": "Braga", + "unknown3": "Braga", + "unknown4": "Braga" + }, + "coordinates": { + "latitude": 41.550292496, + "longitude": -8.420553935000015 + } + }, + { + "id": 1644953600, + "name": "Bragança", + "translations": { + "japanese": "ブラガンサ県", + "english": "Bragança", + "french": "Bragança", + "german": "Bragança", + "italian": "Bragança", + "spanish": "Distrito de Braganza", + "chinese_simple": "布拉甘萨区", + "korean": "브라간사 주", + "dutch": "Bragança", + "portuguese": "Bragança", + "russian": "Браганса", + "chinese_traditional": "Bragança", + "unknown1": "Bragança", + "unknown2": "Bragança", + "unknown3": "Bragança", + "unknown4": "Bragança" + }, + "coordinates": { + "latitude": 41.80297804, + "longitude": -6.7561206979999895 + } + }, + { + "id": 1645019136, + "name": "Castelo Branco", + "translations": { + "japanese": "カステロ・ブランコ県", + "english": "Castelo Branco", + "french": "Castelo Branco", + "german": "Castelo Branco", + "italian": "Castelo Branco", + "spanish": "Distrito de Castelo Branco", + "chinese_simple": "布朗库堡区", + "korean": "카스텔루브랑쿠 주", + "dutch": "Castelo Branco", + "portuguese": "Castelo Branco", + "russian": "Каштелу-Бранку", + "chinese_traditional": "Castelo Branco", + "unknown1": "Castelo Branco", + "unknown2": "Castelo Branco", + "unknown3": "Castelo Branco", + "unknown4": "Castelo Branco" + }, + "coordinates": { + "latitude": 39.819945836, + "longitude": -7.4867135050000115 + } + }, + { + "id": 1645084672, + "name": "Coimbra", + "translations": { + "japanese": "コインブラ県", + "english": "Coimbra", + "french": "Coimbra", + "german": "Coimbra", + "italian": "Coimbra", + "spanish": "Distrito de Coímbra", + "chinese_simple": "科英布拉区", + "korean": "코임브라 주", + "dutch": "Coimbra", + "portuguese": "Coimbra", + "russian": "Коимбра", + "chinese_traditional": "Coimbra", + "unknown1": "Coimbra", + "unknown2": "Coimbra", + "unknown3": "Coimbra", + "unknown4": "Coimbra" + }, + "coordinates": { + "latitude": 40.20996048, + "longitude": -8.426047114 + } + }, + { + "id": 1645150208, + "name": "Évora", + "translations": { + "japanese": "エヴォラ県", + "english": "Évora", + "french": "Évora", + "german": "Évora", + "italian": "Évora", + "spanish": "Distrito de Évora", + "chinese_simple": "埃武拉区", + "korean": "에보라 주", + "dutch": "Évora", + "portuguese": "Évora", + "russian": "Эвора", + "chinese_traditional": "Évora", + "unknown1": "Évora", + "unknown2": "Évora", + "unknown3": "Évora", + "unknown4": "Évora" + }, + "coordinates": { + "latitude": 38.567504444, + "longitude": -7.904195109 + } + }, + { + "id": 1645215744, + "name": "Faro", + "translations": { + "japanese": "ファーロ県", + "english": "Faro", + "french": "Faro", + "german": "Faro", + "italian": "Faro", + "spanish": "Distrito de Faro", + "chinese_simple": "法鲁区", + "korean": "파로 주", + "dutch": "Faro", + "portuguese": "Faro", + "russian": "Фару", + "chinese_traditional": "Faro", + "unknown1": "Faro", + "unknown2": "Faro", + "unknown3": "Faro", + "unknown4": "Faro" + }, + "coordinates": { + "latitude": 37.012939032, + "longitude": -7.931661004000006 + } + }, + { + "id": 1645281280, + "name": "Guarda", + "translations": { + "japanese": "グアルダ県", + "english": "Guarda", + "french": "Guarda", + "german": "Guarda", + "italian": "Guarda", + "spanish": "Distrito de Guarda", + "chinese_simple": "瓜达区", + "korean": "구아르다 주", + "dutch": "Guarda", + "portuguese": "Guarda", + "russian": "Гуарда", + "chinese_traditional": "Guarda", + "unknown1": "Guarda", + "unknown2": "Guarda", + "unknown3": "Guarda", + "unknown4": "Guarda" + }, + "coordinates": { + "latitude": 40.534057156, + "longitude": -7.266986344999992 + } + }, + { + "id": 1645346816, + "name": "Leiria", + "translations": { + "japanese": "レイリア県", + "english": "Leiria", + "french": "Leiria", + "german": "Leiria", + "italian": "Leiria", + "spanish": "Distrito de Leiria", + "chinese_simple": "莱里亚区", + "korean": "레이리아 주", + "dutch": "Leiria", + "portuguese": "Leiria", + "russian": "Лейрия", + "chinese_traditional": "Leiria", + "unknown1": "Leiria", + "unknown2": "Leiria", + "unknown3": "Leiria", + "unknown4": "Leiria" + }, + "coordinates": { + "latitude": 39.74304154, + "longitude": -8.805076465000013 + } + }, + { + "id": 1645412352, + "name": "Portalegre", + "translations": { + "japanese": "ポルタレグレ県", + "english": "Portalegre", + "french": "Portalegre", + "german": "Portalegre", + "italian": "Portalegre", + "spanish": "Distrito de Portalegre", + "chinese_simple": "波塔莱格里区", + "korean": "포르탈레그르 주", + "dutch": "Portalegre", + "portuguese": "Portalegre", + "russian": "Порталегре", + "chinese_traditional": "Portalegre", + "unknown1": "Portalegre", + "unknown2": "Portalegre", + "unknown3": "Portalegre", + "unknown4": "Portalegre" + }, + "coordinates": { + "latitude": 39.287108928, + "longitude": -7.431781715 + } + }, + { + "id": 1645477888, + "name": "Porto", + "translations": { + "japanese": "ポルト県", + "english": "Porto", + "french": "Porto", + "german": "Porto", + "italian": "Porto", + "spanish": "Distrito de Oporto", + "chinese_simple": "波尔图区", + "korean": "포르투 주", + "dutch": "Porto", + "portuguese": "Porto", + "russian": "Порту", + "chinese_traditional": "Porto", + "unknown1": "Porto", + "unknown2": "Porto", + "unknown3": "Porto", + "unknown4": "Porto" + }, + "coordinates": { + "latitude": 41.149291524, + "longitude": -8.607322021000016 + } + }, + { + "id": 1645543424, + "name": "Santarém", + "translations": { + "japanese": "サンタレン県", + "english": "Santarém", + "french": "Santarém", + "german": "Santarém", + "italian": "Santarém", + "spanish": "Distrito de Santarém", + "chinese_simple": "圣塔伦区", + "korean": "산타렝 주", + "dutch": "Santarém", + "portuguese": "Santarém", + "russian": "Сантарен", + "chinese_traditional": "Santarém", + "unknown1": "Santarém", + "unknown2": "Santarém", + "unknown3": "Santarém", + "unknown4": "Santarém" + }, + "coordinates": { + "latitude": 39.232177288, + "longitude": -8.684226526999993 + } + }, + { + "id": 1645608960, + "name": "Setúbal", + "translations": { + "japanese": "セトゥーバル県", + "english": "Setúbal", + "french": "Setúbal", + "german": "Setúbal", + "italian": "Setúbal", + "spanish": "Distrito de Setúbal", + "chinese_simple": "塞图巴尔区", + "korean": "세투발 주", + "dutch": "Setúbal", + "portuguese": "Setúbal", + "russian": "Сетубал", + "chinese_traditional": "Setúbal", + "unknown1": "Setúbal", + "unknown2": "Setúbal", + "unknown3": "Setúbal", + "unknown4": "Setúbal" + }, + "coordinates": { + "latitude": 38.523559132, + "longitude": -8.887474150000003 + } + }, + { + "id": 1645674496, + "name": "Viana do Castelo", + "translations": { + "japanese": "ヴィアナ・ド・カステロ県", + "english": "Viana do Castelo", + "french": "Viana do Castelo", + "german": "Viana do Castelo", + "italian": "Viana do Castelo", + "spanish": "Distrito de Viana do Castelo", + "chinese_simple": "维亚纳堡区", + "korean": "비아나두카스텔루 주", + "dutch": "Viana do Castelo", + "portuguese": "Viana do Castelo", + "russian": "Виана-ду-Каштелу", + "chinese_traditional": "Viana do Castelo", + "unknown1": "Viana do Castelo", + "unknown2": "Viana do Castelo", + "unknown3": "Viana do Castelo", + "unknown4": "Viana do Castelo" + }, + "coordinates": { + "latitude": 41.69311476, + "longitude": -8.827049181000007 + } + }, + { + "id": 1645740032, + "name": "Vila Real", + "translations": { + "japanese": "ヴィラ・レアル県", + "english": "Vila Real", + "french": "Vila Real", + "german": "Vila Real", + "italian": "Vila Real", + "spanish": "Distrito de Vila Real", + "chinese_simple": "雷阿尔城区", + "korean": "빌라헤알 주", + "dutch": "Vila Real", + "portuguese": "Vila Real", + "russian": "Вила-Реал", + "chinese_traditional": "Vila Real", + "unknown1": "Vila Real", + "unknown2": "Vila Real", + "unknown3": "Vila Real", + "unknown4": "Vila Real" + }, + "coordinates": { + "latitude": 41.292113788, + "longitude": -7.744892918000005 + } + }, + { + "id": 1645805568, + "name": "Viseu", + "translations": { + "japanese": "ヴィゼウ県", + "english": "Viseu", + "french": "Viseu", + "german": "Viseu", + "italian": "Viseu", + "spanish": "Distrito de Viseu", + "chinese_simple": "维塞乌区", + "korean": "비제우 주", + "dutch": "Viseu", + "portuguese": "Viseu", + "russian": "Визеу", + "chinese_traditional": "Viseu", + "unknown1": "Viseu", + "unknown2": "Viseu", + "unknown3": "Viseu", + "unknown4": "Viseu" + }, + "coordinates": { + "latitude": 40.654906764, + "longitude": -7.909688288000012 + } + } + ] + }, + { + "id": 99, + "iso_code": "RO", + "name": "Romania", + "translations": { + "japanese": "ルーマニア", + "english": "Romania", + "french": "Roumanie", + "german": "Rumänien", + "italian": "Romania", + "spanish": "Rumanía", + "chinese_simple": "罗马尼亚", + "korean": "루마니아", + "dutch": "Roemenië", + "portuguese": "Roménia", + "russian": "Румыния", + "chinese_traditional": "Romania", + "unknown1": "Romania", + "unknown2": "Romania", + "unknown3": "Romania", + "unknown4": "Romania" + }, + "regions": [ + { + "id": 1660944384, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": 44.428710432, + "longitude": 26.081613892 + } + }, + { + "id": 1661075456, + "name": "Bucharest", + "translations": { + "japanese": "ブカレスト州", + "english": "Bucharest", + "french": "Bucarest", + "german": "Bukarest", + "italian": "Bucarest", + "spanish": "Bucarest", + "chinese_simple": "布加勒斯特市", + "korean": "부쿠레슈티 주", + "dutch": "Boekarest", + "portuguese": "Bucareste", + "russian": "Бухарест", + "chinese_traditional": "Bucharest", + "unknown1": "Bucharest", + "unknown2": "Bucharest", + "unknown3": "Bucharest", + "unknown4": "Bucharest" + }, + "coordinates": { + "latitude": 44.428710432, + "longitude": 26.081613892 + } + }, + { + "id": 1661140992, + "name": "Alba", + "translations": { + "japanese": "アルバ州", + "english": "Alba", + "french": "Alba", + "german": "Alba", + "italian": "Alba", + "spanish": "Alba", + "chinese_simple": "阿尔巴县", + "korean": "알바 주", + "dutch": "Alba", + "portuguese": "Alba", + "russian": "Алба", + "chinese_traditional": "Alba", + "unknown1": "Alba", + "unknown2": "Alba", + "unknown3": "Alba", + "unknown4": "Alba" + }, + "coordinates": { + "latitude": 46.065673304, + "longitude": 23.56573791 + } + }, + { + "id": 1661206528, + "name": "Arad", + "translations": { + "japanese": "アラド州", + "english": "Arad", + "french": "Arad", + "german": "Arad", + "italian": "Arad", + "spanish": "Arad", + "chinese_simple": "阿拉德县", + "korean": "아라드 주", + "dutch": "Arad", + "portuguese": "Arad", + "russian": "Арад", + "chinese_traditional": "Arad", + "unknown1": "Arad", + "unknown2": "Arad", + "unknown3": "Arad", + "unknown4": "Arad" + }, + "coordinates": { + "latitude": 46.181029748, + "longitude": 21.31353452 + } + }, + { + "id": 1661272064, + "name": "Arges", + "translations": { + "japanese": "アルジェシュ州", + "english": "Arges", + "french": "Argeş", + "german": "Argeş", + "italian": "Argeş", + "spanish": "Argeş", + "chinese_simple": "阿尔杰什县", + "korean": "아르제슈 주", + "dutch": "Argeş", + "portuguese": "Arges", + "russian": "Арджеш", + "chinese_traditional": "Arges", + "unknown1": "Arges", + "unknown2": "Arges", + "unknown3": "Arges", + "unknown4": "Arges" + }, + "coordinates": { + "latitude": 44.846190896, + "longitude": 24.878607691 + } + }, + { + "id": 1661337600, + "name": "Bacau", + "translations": { + "japanese": "バカウ州", + "english": "Bacau", + "french": "Bacău", + "german": "Bacău", + "italian": "Bacău", + "spanish": "Bacău", + "chinese_simple": "巴克乌县", + "korean": "바커우 주", + "dutch": "Bacău", + "portuguese": "Bacău", + "russian": "Бакэу", + "chinese_traditional": "Bacau", + "unknown1": "Bacau", + "unknown2": "Bacau", + "unknown3": "Bacau", + "unknown4": "Bacau" + }, + "coordinates": { + "latitude": 46.565551228, + "longitude": 26.9165771 + } + }, + { + "id": 1661403136, + "name": "Bihor", + "translations": { + "japanese": "ビホル州", + "english": "Bihor", + "french": "Bihor", + "german": "Bihor", + "italian": "Bihor", + "spanish": "Bihor", + "chinese_simple": "比霍尔县", + "korean": "비호르 주", + "dutch": "Bihor", + "portuguese": "Bihor", + "russian": "Бихор", + "chinese_traditional": "Bihor", + "unknown1": "Bihor", + "unknown2": "Bihor", + "unknown3": "Bihor", + "unknown4": "Bihor" + }, + "coordinates": { + "latitude": 47.065429152, + "longitude": 21.912291031 + } + }, + { + "id": 1661468672, + "name": "Bistrita-Nasaud", + "translations": { + "japanese": "ビストリツァ・ナサウド州", + "english": "Bistrita-Nasaud", + "french": "Bistrita-Năsăud", + "german": "Bistrita-Năsăud", + "italian": "Bistrita-Năsăud", + "spanish": "Bistrita-Năsăud", + "chinese_simple": "比斯特里察-讷瑟乌德县", + "korean": "비스트리차너서우드 주", + "dutch": "Bistrita-Năsăud", + "portuguese": "Bistrita-Năsăud", + "russian": "Бистрица-Нэсэуд", + "chinese_traditional": "Bistrita-Nasaud", + "unknown1": "Bistrita-Nasaud", + "unknown2": "Bistrita-Nasaud", + "unknown3": "Bistrita-Nasaud", + "unknown4": "Bistrita-Nasaud" + }, + "coordinates": { + "latitude": 47.13134712, + "longitude": 24.49957834 + } + }, + { + "id": 1661534208, + "name": "Botosani", + "translations": { + "japanese": "ボトシャニ州", + "english": "Botosani", + "french": "Botoşani", + "german": "Botoşani", + "italian": "Botoşani", + "spanish": "Botoşani", + "chinese_simple": "博托沙尼县", + "korean": "보토샤니 주", + "dutch": "Botoşani", + "portuguese": "Botoşani", + "russian": "Ботошани", + "chinese_traditional": "Botosani", + "unknown1": "Botosani", + "unknown2": "Botosani", + "unknown3": "Botosani", + "unknown4": "Botosani" + }, + "coordinates": { + "latitude": 47.746581488, + "longitude": 26.663890866 + } + }, + { + "id": 1661599744, + "name": "Braila", + "translations": { + "japanese": "ブライラ州", + "english": "Braila", + "french": "Brăila", + "german": "Brăila", + "italian": "Brăila", + "spanish": "Brăila", + "chinese_simple": "布勒伊拉县", + "korean": "브러일라 주", + "dutch": "Brăila", + "portuguese": "Brăila", + "russian": "Брэила", + "chinese_traditional": "Braila", + "unknown1": "Braila", + "unknown2": "Braila", + "unknown3": "Braila", + "unknown4": "Braila" + }, + "coordinates": { + "latitude": 45.280150852, + "longitude": 27.965774289 + } + }, + { + "id": 1661665280, + "name": "Brasov", + "translations": { + "japanese": "ブラショヴ州", + "english": "Brasov", + "french": "Braşov", + "german": "Braşov", + "italian": "Braşov", + "spanish": "Braşov", + "chinese_simple": "布拉索夫县", + "korean": "브라쇼브 주", + "dutch": "Braşov", + "portuguese": "Braşov", + "russian": "Брашов", + "chinese_traditional": "Brasov", + "unknown1": "Brasov", + "unknown2": "Brasov", + "unknown3": "Brasov", + "unknown4": "Brasov" + }, + "coordinates": { + "latitude": 45.64819284, + "longitude": 25.614693676999998 + } + }, + { + "id": 1661730816, + "name": "Buzau", + "translations": { + "japanese": "ブザウ州", + "english": "Buzau", + "french": "Buzău", + "german": "Buzău", + "italian": "Buzău", + "spanish": "Buzău", + "chinese_simple": "布泽乌县", + "korean": "부저우 주", + "dutch": "Buzău", + "portuguese": "Buzău", + "russian": "Бузэу", + "chinese_traditional": "Buzau", + "unknown1": "Buzau", + "unknown2": "Buzau", + "unknown3": "Buzau", + "unknown4": "Buzau" + }, + "coordinates": { + "latitude": 45.148314916, + "longitude": 26.812206699 + } + }, + { + "id": 1661796352, + "name": "Calarasi", + "translations": { + "japanese": "カララシ州", + "english": "Calarasi", + "french": "Călăraşi", + "german": "Călăraşi", + "italian": "Călăraşi", + "spanish": "Călăraşi", + "chinese_simple": "克勒拉希县", + "korean": "컬러라시 주", + "dutch": "Călăraşi", + "portuguese": "Călăraşi", + "russian": "Кэлэраши", + "chinese_traditional": "Calarasi", + "unknown1": "Calarasi", + "unknown2": "Calarasi", + "unknown3": "Calarasi", + "unknown4": "Calarasi" + }, + "coordinates": { + "latitude": 44.197997544, + "longitude": 27.328565525 + } + }, + { + "id": 1661861888, + "name": "Caras-Severin", + "translations": { + "japanese": "カラシュ・セヴェリン州", + "english": "Caras-Severin", + "french": "Caraş-Severin", + "german": "Caraş-Severin", + "italian": "Caraş-Severin", + "spanish": "Caraş-Severin", + "chinese_simple": "卡拉什-塞维林县", + "korean": "카라슈세베린 주", + "dutch": "Caraş-Severin", + "portuguese": "Caraş-Severin", + "russian": "Караш-Северин", + "chinese_traditional": "Caras-Severin", + "unknown1": "Caras-Severin", + "unknown2": "Caras-Severin", + "unknown3": "Caras-Severin", + "unknown4": "Caras-Severin" + }, + "coordinates": { + "latitude": 45.296630344, + "longitude": 21.879331957 + } + }, + { + "id": 1661927424, + "name": "Cluj", + "translations": { + "japanese": "クルージュ州", + "english": "Cluj", + "french": "Cluj", + "german": "Cluj", + "italian": "Cluj", + "spanish": "Cluj", + "chinese_simple": "克卢日县", + "korean": "클루지 주", + "dutch": "Cluj", + "portuguese": "Cluj", + "russian": "Клуж", + "chinese_traditional": "Cluj", + "unknown1": "Cluj", + "unknown2": "Cluj", + "unknown3": "Cluj", + "unknown4": "Cluj" + }, + "coordinates": { + "latitude": 46.779784624, + "longitude": 23.582217447 + } + }, + { + "id": 1661992960, + "name": "Constanta", + "translations": { + "japanese": "コンスタンツァ州", + "english": "Constanta", + "french": "Constanta", + "german": "Constanta", + "italian": "Costanza", + "spanish": "Constanza", + "chinese_simple": "康斯坦察县", + "korean": "콘스탄차 주", + "dutch": "Constanta", + "portuguese": "Constanta", + "russian": "Констанца", + "chinese_traditional": "Constanta", + "unknown1": "Constanta", + "unknown2": "Constanta", + "unknown3": "Constanta", + "unknown4": "Constanta" + }, + "coordinates": { + "latitude": 44.181518052, + "longitude": 28.630448947999998 + } + }, + { + "id": 1662058496, + "name": "Covasna", + "translations": { + "japanese": "コヴァスナ州", + "english": "Covasna", + "french": "Covasna", + "german": "Covasna", + "italian": "Covasna", + "spanish": "Covasna", + "chinese_simple": "科瓦斯纳县", + "korean": "코바스나 주", + "dutch": "Covasna", + "portuguese": "Covasna", + "russian": "Ковасна", + "chinese_traditional": "Covasna", + "unknown1": "Covasna", + "unknown2": "Covasna", + "unknown3": "Covasna", + "unknown4": "Covasna" + }, + "coordinates": { + "latitude": 45.862426236, + "longitude": 25.795968584 + } + }, + { + "id": 1662124032, + "name": "Dâmbovita", + "translations": { + "japanese": "ドゥンボビツァ州", + "english": "Dâmbovita", + "french": "Dâmbovita", + "german": "Dâmbovita", + "italian": "Dâmbovita", + "spanish": "Dâmbovita", + "chinese_simple": "登博维察县", + "korean": "듬보비차 주", + "dutch": "Dâmbovita", + "portuguese": "Dâmbovita", + "russian": "Дымбовица", + "chinese_traditional": "Dâmbovita", + "unknown1": "Dâmbovita", + "unknown2": "Dâmbovita", + "unknown3": "Dâmbovita", + "unknown4": "Dâmbovita" + }, + "coordinates": { + "latitude": 44.93408152, + "longitude": 25.455391486 + } + }, + { + "id": 1662189568, + "name": "Dolj", + "translations": { + "japanese": "ドルジュ州", + "english": "Dolj", + "french": "Dolj", + "german": "Dolj", + "italian": "Dolj", + "spanish": "Dolj", + "chinese_simple": "多尔日县", + "korean": "돌지 주", + "dutch": "Dolj", + "portuguese": "Dolj", + "russian": "Долж", + "chinese_traditional": "Dolj", + "unknown1": "Dolj", + "unknown2": "Dolj", + "unknown3": "Dolj", + "unknown4": "Dolj" + }, + "coordinates": { + "latitude": 44.313353988, + "longitude": 23.812930965 + } + }, + { + "id": 1662255104, + "name": "Galati", + "translations": { + "japanese": "ガラツィ州", + "english": "Galati", + "french": "Galati", + "german": "Galati", + "italian": "Galati", + "spanish": "Galati", + "chinese_simple": "加拉茨县", + "korean": "갈라치 주", + "dutch": "Galati", + "portuguese": "Galati", + "russian": "Галац", + "chinese_traditional": "Galati", + "unknown1": "Galati", + "unknown2": "Galati", + "unknown3": "Galati", + "unknown4": "Galati" + }, + "coordinates": { + "latitude": 45.42846628, + "longitude": 28.031692437 + } + }, + { + "id": 1662320640, + "name": "Giurgiu", + "translations": { + "japanese": "ジュルジュ州", + "english": "Giurgiu", + "french": "Giurgiu", + "german": "Giurgiu", + "italian": "Giurgiu", + "spanish": "Giurgiu", + "chinese_simple": "久尔久县", + "korean": "지우르지우 주", + "dutch": "Giurgiu", + "portuguese": "Giurgiu", + "russian": "Джурджу", + "chinese_traditional": "Giurgiu", + "unknown1": "Giurgiu", + "unknown2": "Giurgiu", + "unknown3": "Giurgiu", + "unknown4": "Giurgiu" + }, + "coordinates": { + "latitude": 43.895873524, + "longitude": 25.966257133 + } + }, + { + "id": 1662386176, + "name": "Gorj", + "translations": { + "japanese": "ゴルジュ州", + "english": "Gorj", + "french": "Gorj", + "german": "Gorj", + "italian": "Gorj", + "spanish": "Gorj", + "chinese_simple": "戈尔日县", + "korean": "고르지 주", + "dutch": "Gorj", + "portuguese": "Gorj", + "russian": "Горж", + "chinese_traditional": "Gorj", + "unknown1": "Gorj", + "unknown2": "Gorj", + "unknown3": "Gorj", + "unknown4": "Gorj" + }, + "coordinates": { + "latitude": 45.049437964, + "longitude": 23.263613065 + } + }, + { + "id": 1662451712, + "name": "Harghita", + "translations": { + "japanese": "ハルギタ州", + "english": "Harghita", + "french": "Harghita", + "german": "Harghita", + "italian": "Harghita", + "spanish": "Harghita", + "chinese_simple": "哈尔吉塔县", + "korean": "하르기타 주", + "dutch": "Harghita", + "portuguese": "Harghita", + "russian": "Харгита", + "chinese_traditional": "Harghita", + "unknown1": "Harghita", + "unknown2": "Harghita", + "unknown3": "Harghita", + "unknown4": "Harghita" + }, + "coordinates": { + "latitude": 46.36230416, + "longitude": 25.795968584 + } + }, + { + "id": 1662517248, + "name": "Hunedoara", + "translations": { + "japanese": "フネドアラ州", + "english": "Hunedoara", + "french": "Hunedoara", + "german": "Hunedoara", + "italian": "Hunedoara", + "spanish": "Hunedoara", + "chinese_simple": "胡内多阿拉县", + "korean": "후네도아라 주", + "dutch": "Hunedoara", + "portuguese": "Hunedoara", + "russian": "Хунедоара", + "chinese_traditional": "Hunedoara", + "unknown1": "Hunedoara", + "unknown2": "Hunedoara", + "unknown3": "Hunedoara", + "unknown4": "Hunedoara" + }, + "coordinates": { + "latitude": 45.878905728, + "longitude": 22.901063251 + } + }, + { + "id": 1662582784, + "name": "Ialomita", + "translations": { + "japanese": "ヤロミツァ州", + "english": "Ialomita", + "french": "Ialomita", + "german": "Ialomita", + "italian": "Ialomita", + "spanish": "Ialomita", + "chinese_simple": "雅洛米察县", + "korean": "이알로미차 주", + "dutch": "Ialomita", + "portuguese": "Ialomita", + "russian": "Яломица", + "chinese_traditional": "Ialomita", + "unknown1": "Ialomita", + "unknown2": "Ialomita", + "unknown3": "Ialomita", + "unknown4": "Ialomita" + }, + "coordinates": { + "latitude": 44.566039532, + "longitude": 27.378004136 + } + }, + { + "id": 1662648320, + "name": "Iasi", + "translations": { + "japanese": "ヤシ州", + "english": "Iasi", + "french": "Iaşi", + "german": "Iaşi", + "italian": "Iaşi", + "spanish": "Iaşi", + "chinese_simple": "雅西县", + "korean": "이아시 주", + "dutch": "Iaşi", + "portuguese": "Iasi", + "russian": "Яссы", + "chinese_traditional": "Iasi", + "unknown1": "Iasi", + "unknown2": "Iasi", + "unknown3": "Iasi", + "unknown4": "Iasi" + }, + "coordinates": { + "latitude": 47.164306104, + "longitude": 27.564772222 + } + }, + { + "id": 1662713856, + "name": "Ilfov", + "translations": { + "japanese": "イルホヴ州", + "english": "Ilfov", + "french": "Ilfov", + "german": "Ilfov", + "italian": "Ilfov", + "spanish": "Ilfov", + "chinese_simple": "伊尔福夫县", + "korean": "일포브 주", + "dutch": "Ilfov", + "portuguese": "Ilfov", + "russian": "Илфов", + "chinese_traditional": "Ilfov", + "unknown1": "Ilfov", + "unknown2": "Ilfov", + "unknown3": "Ilfov", + "unknown4": "Ilfov" + }, + "coordinates": { + "latitude": 44.566039532, + "longitude": 25.949777596 + } + }, + { + "id": 1662779392, + "name": "Maramures", + "translations": { + "japanese": "マラムレシュ州", + "english": "Maramures", + "french": "Maramureş", + "german": "Maramureş", + "italian": "Maramureş", + "spanish": "Maramureş", + "chinese_simple": "马拉穆列什县", + "korean": "마라무레슈 주", + "dutch": "Maramureş", + "portuguese": "Maramures", + "russian": "Марамуреш", + "chinese_traditional": "Maramures", + "unknown1": "Maramures", + "unknown2": "Maramures", + "unknown3": "Maramures", + "unknown4": "Maramures" + }, + "coordinates": { + "latitude": 47.647704536, + "longitude": 23.582217447 + } + }, + { + "id": 1662844928, + "name": "Mehedinti", + "translations": { + "japanese": "メヘディンツィ州", + "english": "Mehedinti", + "french": "Mehedinti", + "german": "Mehedinti", + "italian": "Mehedinti", + "spanish": "Mehedinti", + "chinese_simple": "梅赫丁茨县", + "korean": "메헤딘치 주", + "dutch": "Mehedinti", + "portuguese": "Mehedinti", + "russian": "Мехединци", + "chinese_traditional": "Mehedinti", + "unknown1": "Mehedinti", + "unknown2": "Mehedinti", + "unknown3": "Mehedinti", + "unknown4": "Mehedinti" + }, + "coordinates": { + "latitude": 44.6319575, + "longitude": 22.648377017 + } + }, + { + "id": 1662910464, + "name": "Mures", + "translations": { + "japanese": "ムレシュ州", + "english": "Mures", + "french": "Mureş", + "german": "Mureş", + "italian": "Mureş", + "spanish": "Mureş", + "chinese_simple": "穆列什县", + "korean": "무레슈 주", + "dutch": "Mureş", + "portuguese": "Mures", + "russian": "Муреш", + "chinese_traditional": "Mures", + "unknown1": "Mures", + "unknown2": "Mures", + "unknown3": "Mures", + "unknown4": "Mures" + }, + "coordinates": { + "latitude": 46.538085408, + "longitude": 24.549016951 + } + }, + { + "id": 1662976000, + "name": "Neamt", + "translations": { + "japanese": "ネアムツ州", + "english": "Neamt", + "french": "Neamt", + "german": "Neamt", + "italian": "Neamt", + "spanish": "Neamt", + "chinese_simple": "尼亚姆茨县", + "korean": "네암츠 주", + "dutch": "Neamt", + "portuguese": "Neamt", + "russian": "Нямц", + "chinese_traditional": "Neamt", + "unknown1": "Neamt", + "unknown2": "Neamt", + "unknown3": "Neamt", + "unknown4": "Neamt" + }, + "coordinates": { + "latitude": 46.928100052, + "longitude": 26.361766021 + } + }, + { + "id": 1663041536, + "name": "Olt", + "translations": { + "japanese": "オルト州", + "english": "Olt", + "french": "Olt", + "german": "Olt", + "italian": "Olt", + "spanish": "Olt", + "chinese_simple": "奥尔特县", + "korean": "올트 주", + "dutch": "Olt", + "portuguese": "Olt", + "russian": "Олт", + "chinese_traditional": "Olt", + "unknown1": "Olt", + "unknown2": "Olt", + "unknown3": "Olt", + "unknown4": "Olt" + }, + "coordinates": { + "latitude": 44.428710432, + "longitude": 24.345769328 + } + }, + { + "id": 1663107072, + "name": "Prahova", + "translations": { + "japanese": "プラホヴァ州", + "english": "Prahova", + "french": "Prahova", + "german": "Prahova", + "italian": "Prahova", + "spanish": "Prahova", + "chinese_simple": "普拉霍瓦县", + "korean": "프라호바 주", + "dutch": "Prahova", + "portuguese": "Prahova", + "russian": "Прахова", + "chinese_traditional": "Prahova", + "unknown1": "Prahova", + "unknown2": "Prahova", + "unknown3": "Prahova", + "unknown4": "Prahova" + }, + "coordinates": { + "latitude": 44.928588356, + "longitude": 26.015695744 + } + }, + { + "id": 1663172608, + "name": "Salaj", + "translations": { + "japanese": "サラージュ州", + "english": "Salaj", + "french": "Sălaj", + "german": "Sălaj", + "italian": "Sălaj", + "spanish": "Sălaj", + "chinese_simple": "瑟拉日县", + "korean": "설라지 주", + "dutch": "Sălaj", + "portuguese": "Salaj", + "russian": "Сэлаж", + "chinese_traditional": "Salaj", + "unknown1": "Salaj", + "unknown2": "Salaj", + "unknown3": "Salaj", + "unknown4": "Salaj" + }, + "coordinates": { + "latitude": 47.180785596, + "longitude": 23.054872263 + } + }, + { + "id": 1663238144, + "name": "Satu Mare", + "translations": { + "japanese": "サトゥ・マーレ州", + "english": "Satu Mare", + "french": "Satu Mare", + "german": "Satu Mare", + "italian": "Satu Mare", + "spanish": "Satu Mare", + "chinese_simple": "萨图-马雷县", + "korean": "사투마레 주", + "dutch": "Satu Mare", + "portuguese": "Satu Mare", + "russian": "Сату-Маре", + "chinese_traditional": "Satu Mare", + "unknown1": "Satu Mare", + "unknown2": "Satu Mare", + "unknown3": "Satu Mare", + "unknown4": "Satu Mare" + }, + "coordinates": { + "latitude": 47.785033636, + "longitude": 22.879090535 + } + }, + { + "id": 1663303680, + "name": "Sibiu", + "translations": { + "japanese": "シビウ州", + "english": "Sibiu", + "french": "Sibiu", + "german": "Sibiu", + "italian": "Sibiu", + "spanish": "Sibiu", + "chinese_simple": "锡比乌县", + "korean": "시비우 주", + "dutch": "Sibiu", + "portuguese": "Sibiu", + "russian": "Сибиу", + "chinese_traditional": "Sibiu", + "unknown1": "Sibiu", + "unknown2": "Sibiu", + "unknown3": "Sibiu", + "unknown4": "Sibiu" + }, + "coordinates": { + "latitude": 45.780028776, + "longitude": 24.148014884 + } + }, + { + "id": 1663369216, + "name": "Suceava", + "translations": { + "japanese": "スチャヴァ州", + "english": "Suceava", + "french": "Suceava", + "german": "Suceava", + "italian": "Suceava", + "spanish": "Suceava", + "chinese_simple": "苏恰瓦县", + "korean": "수체아바 주", + "dutch": "Suceava", + "portuguese": "Suceava", + "russian": "Сучава", + "chinese_traditional": "Suceava", + "unknown1": "Suceava", + "unknown2": "Suceava", + "unknown3": "Suceava", + "unknown4": "Suceava" + }, + "coordinates": { + "latitude": 47.647704536, + "longitude": 26.246409262 + } + }, + { + "id": 1663434752, + "name": "Teleorman", + "translations": { + "japanese": "テレオルマン州", + "english": "Teleorman", + "french": "Teleorman", + "german": "Teleorman", + "italian": "Teleorman", + "spanish": "Teleorman", + "chinese_simple": "特列奥尔曼县", + "korean": "텔레오르만 주", + "dutch": "Teleorman", + "portuguese": "Teleorman", + "russian": "Телеорман", + "chinese_traditional": "Teleorman", + "unknown1": "Teleorman", + "unknown2": "Teleorman", + "unknown3": "Teleorman", + "unknown4": "Teleorman" + }, + "coordinates": { + "latitude": 43.961791492, + "longitude": 25.329048369 + } + }, + { + "id": 1663500288, + "name": "Timis", + "translations": { + "japanese": "ティミシュ州", + "english": "Timis", + "french": "Timiş", + "german": "Timiş", + "italian": "Timiş", + "spanish": "Timiş", + "chinese_simple": "蒂米什县", + "korean": "티미슈 주", + "dutch": "Timiş", + "portuguese": "Timis", + "russian": "Тимиш", + "chinese_traditional": "Timis", + "unknown1": "Timis", + "unknown2": "Timis", + "unknown3": "Timis", + "unknown4": "Timis" + }, + "coordinates": { + "latitude": 45.752562956, + "longitude": 21.220150477 + } + }, + { + "id": 1663565824, + "name": "Tulcea", + "translations": { + "japanese": "トゥルチャ州", + "english": "Tulcea", + "french": "Tulcea", + "german": "Tulcea", + "italian": "Tulcea", + "spanish": "Tulcea", + "chinese_simple": "图尔恰县", + "korean": "툴체아 주", + "dutch": "Tulcea", + "portuguese": "Tulcea", + "russian": "Тулча", + "chinese_traditional": "Tulcea", + "unknown1": "Tulcea", + "unknown2": "Tulcea", + "unknown3": "Tulcea", + "unknown4": "Tulcea" + }, + "coordinates": { + "latitude": 45.164794408, + "longitude": 28.795244317999998 + } + }, + { + "id": 1663631360, + "name": "Vâlcea", + "translations": { + "japanese": "ヴルチャ州", + "english": "Vâlcea", + "french": "Vâlcea", + "german": "Vâlcea", + "italian": "Vâlcea", + "spanish": "Vâlcea", + "chinese_simple": "沃尔恰县", + "korean": "블체아 주", + "dutch": "Vâlcea", + "portuguese": "Vâlcea", + "russian": "Вылча", + "chinese_traditional": "Vâlcea", + "unknown1": "Vâlcea", + "unknown2": "Vâlcea", + "unknown3": "Vâlcea", + "unknown4": "Vâlcea" + }, + "coordinates": { + "latitude": 45.104369604, + "longitude": 24.373235223 + } + }, + { + "id": 1663696896, + "name": "Vaslui", + "translations": { + "japanese": "ヴァスルイ州", + "english": "Vaslui", + "french": "Vaslui", + "german": "Vaslui", + "italian": "Vaslui", + "spanish": "Vaslui", + "chinese_simple": "瓦斯卢伊县", + "korean": "바슬루이 주", + "dutch": "Vaslui", + "portuguese": "Vaslui", + "russian": "Васлуй", + "chinese_traditional": "Vaslui", + "unknown1": "Vaslui", + "unknown2": "Vaslui", + "unknown3": "Vaslui", + "unknown4": "Vaslui" + }, + "coordinates": { + "latitude": 46.63696236, + "longitude": 27.718581234 + } + }, + { + "id": 1663762432, + "name": "Vrancea", + "translations": { + "japanese": "フランチェア州", + "english": "Vrancea", + "french": "Vrancea", + "german": "Vrancea", + "italian": "Vrancea", + "spanish": "Vrancea", + "chinese_simple": "弗朗恰县", + "korean": "브란체아 주", + "dutch": "Vrancea", + "portuguese": "Vrancea", + "russian": "Вранча", + "chinese_traditional": "Vrancea", + "unknown1": "Vrancea", + "unknown2": "Vrancea", + "unknown3": "Vrancea", + "unknown4": "Vrancea" + }, + "coordinates": { + "latitude": 45.697631316, + "longitude": 27.180249692 + } + } + ] + }, + { + "id": 100, + "iso_code": "RU", + "name": "Russia", + "translations": { + "japanese": "ロシア", + "english": "Russia", + "french": "Russie", + "german": "Russland", + "italian": "Russia", + "spanish": "Rusia", + "chinese_simple": "俄罗斯", + "korean": "러시아", + "dutch": "Rusland", + "portuguese": "Rússia", + "russian": "Россия", + "chinese_traditional": "Russia", + "unknown1": "Russia", + "unknown2": "Russia", + "unknown3": "Russia", + "unknown4": "Russia" + }, + "regions": [ + { + "id": 1677721600, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": 55.766600928, + "longitude": 37.611796613 + } + }, + { + "id": 1678311424, + "name": "Moscow City", + "translations": { + "japanese": "モスクワ市", + "english": "Moscow City", + "french": "Moscou (ville)", + "german": "Moskau (Stadt)", + "italian": "Mosca", + "spanish": "Ciudad de Moscú", + "chinese_simple": "莫斯科市", + "korean": "모스크바", + "dutch": "Moskou (stad)", + "portuguese": "Moscovo (cidade)", + "russian": "Москва", + "chinese_traditional": "Moscow City", + "unknown1": "Moscow City", + "unknown2": "Moscow City", + "unknown3": "Moscow City", + "unknown4": "Moscow City" + }, + "coordinates": { + "latitude": 55.766600928, + "longitude": 37.611796613 + } + }, + { + "id": 1678376960, + "name": "Adygey", + "translations": { + "japanese": "アディゲ共和国", + "english": "Adygey", + "french": "Adyguée", + "german": "Republik Adygeja", + "italian": "Repubblica di Adighezia", + "spanish": "República de Adigueya", + "chinese_simple": "阿迪格共和国", + "korean": "아디게야 공화국", + "dutch": "Adygië", + "portuguese": "Adiguésia (república)", + "russian": "Республика Адыгея", + "chinese_traditional": "Adygey", + "unknown1": "Adygey", + "unknown2": "Adygey", + "unknown3": "Adygey", + "unknown4": "Adygey" + }, + "coordinates": { + "latitude": 44.598998516, + "longitude": 40.078233984 + } + }, + { + "id": 1678442496, + "name": "Gorno-Altay", + "translations": { + "japanese": "アルタイ共和国", + "english": "Gorno-Altay", + "french": "Altaï (république)", + "german": "Republik Altai", + "italian": "Repubblica dell'Altaj", + "spanish": "República de Altái", + "chinese_simple": "阿尔泰共和国", + "korean": "고르노알타이 공화국", + "dutch": "Altaj (republiek)", + "portuguese": "Altai (república)", + "russian": "Республика Алтай", + "chinese_traditional": "Gorno-Altay", + "unknown1": "Gorno-Altay", + "unknown2": "Gorno-Altay", + "unknown3": "Gorno-Altay", + "unknown4": "Gorno-Altay" + }, + "coordinates": { + "latitude": 51.948851948, + "longitude": 85.962758171 + } + }, + { + "id": 1678508032, + "name": "Altay", + "translations": { + "japanese": "アルタイ地方", + "english": "Altay", + "french": "Altaï (kraï)", + "german": "Region Altai", + "italian": "Territorio dell'Altaj", + "spanish": "Territorio de Altái", + "chinese_simple": "阿尔泰边疆区", + "korean": "알타이 지방", + "dutch": "Altaj (kraj)", + "portuguese": "Altai (território)", + "russian": "Алтайский край", + "chinese_traditional": "Altay", + "unknown1": "Altay", + "unknown2": "Altay", + "unknown3": "Altay", + "unknown4": "Altay" + }, + "coordinates": { + "latitude": 53.349608768, + "longitude": 83.749007034 + } + }, + { + "id": 1678573568, + "name": "Amur", + "translations": { + "japanese": "アムール州", + "english": "Amur", + "french": "Amour", + "german": "Oblast Amur", + "italian": "Regione dell'Amur", + "spanish": "Región de Amur", + "chinese_simple": "阿穆尔州", + "korean": "아무르 주", + "dutch": "Amoer", + "portuguese": "Amur (região)", + "russian": "Амурская область", + "chinese_traditional": "Amur", + "unknown1": "Amur", + "unknown2": "Amur", + "unknown3": "Amur", + "unknown4": "Amur" + }, + "coordinates": { + "latitude": 50.361327552, + "longitude": 127.513164127 + } + }, + { + "id": 1678639104, + "name": "Arkhangel'sk", + "translations": { + "japanese": "アルハンゲリスク州", + "english": "Arkhangel'sk", + "french": "Arkhangelsk", + "german": "Oblast Archangelsk", + "italian": "Regione di Arcangelo", + "spanish": "Región de Arjánguelsk", + "chinese_simple": "阿尔汉格尔斯克州", + "korean": "아르한겔스크 주", + "dutch": "Archangelsk", + "portuguese": "Arkhangelsk (região)", + "russian": "Архангельская область", + "chinese_traditional": "Arkhangel'sk", + "unknown1": "Arkhangel'sk", + "unknown2": "Arkhangel'sk", + "unknown3": "Arkhangel'sk", + "unknown4": "Arkhangel'sk" + }, + "coordinates": { + "latitude": 64.528197508, + "longitude": 40.528674662 + } + }, + { + "id": 1678704640, + "name": "Astrakhan'", + "translations": { + "japanese": "アストラハン州", + "english": "Astrakhan'", + "french": "Astrakhan", + "german": "Oblast Astrachan", + "italian": "Regione di Astrachan'", + "spanish": "Región de Astracán", + "chinese_simple": "阿斯特拉罕州", + "korean": "아스트라한 주", + "dutch": "Astrachan", + "portuguese": "Astracã (região)", + "russian": "Астраханская область", + "chinese_traditional": "Astrakhan'", + "unknown1": "Astrakhan'", + "unknown2": "Astrakhan'", + "unknown3": "Astrakhan'", + "unknown4": "Astrakhan'" + }, + "coordinates": { + "latitude": 46.345824668, + "longitude": 48.048836713 + } + }, + { + "id": 1678770176, + "name": "Bashkortostan", + "translations": { + "japanese": "バシコルトスタン共和国", + "english": "Bashkortostan", + "french": "Bachkirie", + "german": "Republik Baschkortostan", + "italian": "Repubblica del Baškortostan", + "spanish": "República de Baskortostán", + "chinese_simple": "巴什科尔托斯坦共和国", + "korean": "바시키르 공화국", + "dutch": "Basjkirostan", + "portuguese": "Basquíria (república)", + "russian": "Республика Башкортостан", + "chinese_traditional": "Bashkortostan", + "unknown1": "Bashkortostan", + "unknown2": "Bashkortostan", + "unknown3": "Bashkortostan", + "unknown4": "Bashkortostan" + }, + "coordinates": { + "latitude": 54.744872424, + "longitude": 55.964507652 + } + }, + { + "id": 1678835712, + "name": "Belgorod", + "translations": { + "japanese": "ベルゴロド州", + "english": "Belgorod", + "french": "Belgorod", + "german": "Oblast Belgorod", + "italian": "Regione di Belgorod", + "spanish": "Región de Bélgorod", + "chinese_simple": "别尔哥罗德州", + "korean": "벨고로트 주", + "dutch": "Belgorod", + "portuguese": "Belgorod (região)", + "russian": "Белгородская область", + "chinese_traditional": "Belgorod", + "unknown1": "Belgorod", + "unknown2": "Belgorod", + "unknown3": "Belgorod", + "unknown4": "Belgorod" + }, + "coordinates": { + "latitude": 50.597533604, + "longitude": 36.595558498 + } + }, + { + "id": 1678901248, + "name": "Bryansk", + "translations": { + "japanese": "ブリャンスク州", + "english": "Bryansk", + "french": "Briansk", + "german": "Oblast Brjansk", + "italian": "Regione di Brjansk", + "spanish": "Región de Briansk", + "chinese_simple": "布良斯克州", + "korean": "브랸스크 주", + "dutch": "Brjansk", + "portuguese": "Briansk (região)", + "russian": "Брянская область", + "chinese_traditional": "Bryansk", + "unknown1": "Bryansk", + "unknown2": "Bryansk", + "unknown3": "Bryansk", + "unknown4": "Bryansk" + }, + "coordinates": { + "latitude": 53.245238652, + "longitude": 34.365327824 + } + }, + { + "id": 1678966784, + "name": "Buryat", + "translations": { + "japanese": "ブリヤート共和国", + "english": "Buryat", + "french": "Bouriatie", + "german": "Republik Burjatien", + "italian": "Repubblica di Buriazia", + "spanish": "República de Buriatia", + "chinese_simple": "布里亚特共和国", + "korean": "부랴트 공화국", + "dutch": "Boerjatië", + "portuguese": "Buriácia (república)", + "russian": "Республика Бурятия", + "chinese_traditional": "Buryat", + "unknown1": "Buryat", + "unknown2": "Buryat", + "unknown3": "Buryat", + "unknown4": "Buryat" + }, + "coordinates": { + "latitude": 51.82800234, + "longitude": 107.594897073 + } + }, + { + "id": 1679032320, + "name": "Chechnya", + "translations": { + "japanese": "チェチェン共和国", + "english": "Chechnya", + "french": "Tchétchénie", + "german": "Republik Tschetschenien", + "italian": "Repubblica Cecena", + "spanish": "República de Chechenia", + "chinese_simple": "车臣共和国", + "korean": "체첸 공화국", + "dutch": "Tsjetsjenië", + "portuguese": "Chechénia (república)", + "russian": "Чеченская республика", + "chinese_traditional": "Chechnya", + "unknown1": "Chechnya", + "unknown2": "Chechnya", + "unknown3": "Chechnya", + "unknown4": "Chechnya" + }, + "coordinates": { + "latitude": 43.31359814, + "longitude": 45.681276564 + } + }, + { + "id": 1679097856, + "name": "Chelyabinsk", + "translations": { + "japanese": "チェリャビンスク州", + "english": "Chelyabinsk", + "french": "Tcheliabinsk", + "german": "Oblast Tscheljabinsk", + "italian": "Regione di Čeljabinsk", + "spanish": "Región de Cheliábinsk", + "chinese_simple": "车里雅宾斯克州", + "korean": "첼랴빈스크 주", + "dutch": "Tsjeljabinsk", + "portuguese": "Chelyabinsk (região)", + "russian": "Челябинская область", + "chinese_traditional": "Chelyabinsk", + "unknown1": "Chelyabinsk", + "unknown2": "Chelyabinsk", + "unknown3": "Chelyabinsk", + "unknown4": "Chelyabinsk" + }, + "coordinates": { + "latitude": 55.162352888, + "longitude": 61.397261682999996 + } + }, + { + "id": 1679163392, + "name": "Chukot", + "translations": { + "japanese": "チュクチ自治管区", + "english": "Chukot", + "french": "Tchoukotka", + "german": "Autonomer Kreis der Tschuktschen", + "italian": "Circondario Autonomo di Čukotka", + "spanish": "Distrito autónomo de Chukotka", + "chinese_simple": "楚科奇自治区", + "korean": "축치 자치구", + "dutch": "Tsjoekotka", + "portuguese": "Chukotka (distrito)", + "russian": "Чукотский автономный округ", + "chinese_traditional": "Chukot", + "unknown1": "Chukot", + "unknown2": "Chukot", + "unknown3": "Chukot", + "unknown4": "Chukot" + }, + "coordinates": { + "latitude": 64.731444576, + "longitude": 177.512079385 + } + }, + { + "id": 1679228928, + "name": "Chuvash", + "translations": { + "japanese": "チュヴァシ共和国", + "english": "Chuvash", + "french": "Tchouvachie", + "german": "Republik Tschuwaschien", + "italian": "Repubblica Ciuvascia", + "spanish": "República de Chuvasia", + "chinese_simple": "楚瓦什共和国", + "korean": "추바시 공화국", + "dutch": "Tsjoevasjië", + "portuguese": "Chuváchia (república)", + "russian": "Чувашская республика", + "chinese_traditional": "Chuvash", + "unknown1": "Chuvash", + "unknown2": "Chuvash", + "unknown3": "Chuvash", + "unknown4": "Chuvash" + }, + "coordinates": { + "latitude": 56.129149752000004, + "longitude": 47.230353042 + } + }, + { + "id": 1679294464, + "name": "Dagestan", + "translations": { + "japanese": "ダゲスタン共和国", + "english": "Dagestan", + "french": "Daguestan", + "german": "Republik Dagestan", + "italian": "Repubblica del Dagestan", + "spanish": "República de Daguestán", + "chinese_simple": "达吉斯坦共和国", + "korean": "다게스탄 공화국", + "dutch": "Dagestan", + "portuguese": "Daguestão (república)", + "russian": "Республика Дагестан", + "chinese_traditional": "Dagestan", + "unknown1": "Dagestan", + "unknown2": "Dagestan", + "unknown3": "Dagestan", + "unknown4": "Dagestan" + }, + "coordinates": { + "latitude": 42.962035644000004, + "longitude": 47.499518813 + } + }, + { + "id": 1679360000, + "name": "Ingushetia", + "translations": { + "japanese": "イングーシ共和国", + "english": "Ingushetia", + "french": "Ingouchie", + "german": "Republik Inguschetien", + "italian": "Repubblica di Inguscezia", + "spanish": "República de Ingusetia", + "chinese_simple": "印古什共和国", + "korean": "인구시 공화국", + "dutch": "Ingoesjetië", + "portuguese": "Ingúchia (república)", + "russian": "Республика Ингушетия", + "chinese_traditional": "Ingushetia", + "unknown1": "Ingushetia", + "unknown2": "Ingushetia", + "unknown3": "Ingushetia", + "unknown4": "Ingushetia" + }, + "coordinates": { + "latitude": 43.165282712, + "longitude": 44.813354282 + } + }, + { + "id": 1679425536, + "name": "Irkutsk", + "translations": { + "japanese": "イルクーツク州", + "english": "Irkutsk", + "french": "Irkoutsk", + "german": "Oblast Irkutsk", + "italian": "Regione di Irkutsk", + "spanish": "Región de Irkutsk", + "chinese_simple": "伊尔库茨克州", + "korean": "이르쿠츠크 주", + "dutch": "Irkoetsk", + "portuguese": "Irkutsk (região)", + "russian": "Иркутская область", + "chinese_traditional": "Irkutsk", + "unknown1": "Irkutsk", + "unknown2": "Irkutsk", + "unknown3": "Irkutsk", + "unknown4": "Irkutsk" + }, + "coordinates": { + "latitude": 52.278441788, + "longitude": 104.298989673 + } + }, + { + "id": 1679491072, + "name": "Ivanovo", + "translations": { + "japanese": "イヴァノヴォ州", + "english": "Ivanovo", + "french": "Ivanovo", + "german": "Oblast Iwanowo", + "italian": "Regione di Ivanovo", + "spanish": "Región de Ivánovo", + "chinese_simple": "伊万诺沃州", + "korean": "이바노보 주", + "dutch": "Ivanovo", + "portuguese": "Ivanovo (região)", + "russian": "Ивановская область", + "chinese_traditional": "Ivanovo", + "unknown1": "Ivanovo", + "unknown2": "Ivanovo", + "unknown3": "Ivanovo", + "unknown4": "Ivanovo" + }, + "coordinates": { + "latitude": 56.9915765, + "longitude": 40.97911534 + } + }, + { + "id": 1679556608, + "name": "Kabardin-Balkar", + "translations": { + "japanese": "カバルダ・バルカル共和国", + "english": "Kabardin-Balkar", + "french": "Kabardino-Balkarie", + "german": "Republik Kabardino-Balkarien", + "italian": "Repubblica di Cabardino-Balcaria", + "spanish": "República de Kabardino-Balkaria", + "chinese_simple": "卡巴尔达-巴尔卡尔共和国", + "korean": "카바르디노발카르 공화국", + "dutch": "Kabardië-Balkarië", + "portuguese": "Cabárdia-Balcária (república)", + "russian": "Кабардино-Балкарская республика", + "chinese_traditional": "Kabardin-Balkar", + "unknown1": "Kabardin-Balkar", + "unknown2": "Kabardin-Balkar", + "unknown3": "Kabardin-Balkar", + "unknown4": "Kabardin-Balkar" + }, + "coordinates": { + "latitude": 43.47839306, + "longitude": 43.615841259999996 + } + }, + { + "id": 1679622144, + "name": "Kaliningrad", + "translations": { + "japanese": "カリーニングラード州", + "english": "Kaliningrad", + "french": "Kaliningrad", + "german": "Oblast Kaliningrad", + "italian": "Regione di Kaliningrad", + "spanish": "Región de Kaliningrado", + "chinese_simple": "加里宁格勒州", + "korean": "칼리닌그라드 주", + "dutch": "Kaliningrad", + "portuguese": "Kaliningrado (região)", + "russian": "Калининградская область", + "chinese_traditional": "Kaliningrad", + "unknown1": "Kaliningrad", + "unknown2": "Kaliningrad", + "unknown3": "Kaliningrad", + "unknown4": "Kaliningrad" + }, + "coordinates": { + "latitude": 54.711913440000004, + "longitude": 20.511530386 + } + }, + { + "id": 1679687680, + "name": "Kalmyk", + "translations": { + "japanese": "カルムイク共和国", + "english": "Kalmyk", + "french": "Kalmoukie", + "german": "Republik Kalmückien", + "italian": "Repubblica di Calmucchia", + "spanish": "República de Kalmukia", + "chinese_simple": "卡尔梅克共和国", + "korean": "칼미크 공화국", + "dutch": "Kalmukkië", + "portuguese": "Calmúquia (república)", + "russian": "Республика Калмыкия", + "chinese_traditional": "Kalmyk", + "unknown1": "Kalmyk", + "unknown2": "Kalmyk", + "unknown3": "Kalmyk", + "unknown4": "Kalmyk" + }, + "coordinates": { + "latitude": 46.312865684, + "longitude": 44.264036382 + } + }, + { + "id": 1679753216, + "name": "Kaluga", + "translations": { + "japanese": "カルーガ州", + "english": "Kaluga", + "french": "Kalouga", + "german": "Oblast Kaluga", + "italian": "Regione di Kaluga", + "spanish": "Región de Kaluga", + "chinese_simple": "卡卢加州", + "korean": "칼루가 주", + "dutch": "Kaloega", + "portuguese": "Kaluga (região)", + "russian": "Калужская область", + "chinese_traditional": "Kaluga", + "unknown1": "Kaluga", + "unknown2": "Kaluga", + "unknown3": "Kaluga", + "unknown4": "Kaluga" + }, + "coordinates": { + "latitude": 54.54711852, + "longitude": 36.282447295 + } + }, + { + "id": 1679818752, + "name": "Kamchatka", + "translations": { + "japanese": "カムチャツカ地方", + "english": "Kamchatka", + "french": "Kamtchatka", + "german": "Region Kamtschatka", + "italian": "Territorio della Kamčatka", + "spanish": "Territorio de Kamchatka", + "chinese_simple": "勘察加边疆区", + "korean": "캄차카 지방", + "dutch": "Kamtsjatka", + "portuguese": "Kamchatka (território)", + "russian": "Камчатский край", + "chinese_traditional": "Kamchatka", + "unknown1": "Kamchatka", + "unknown2": "Kamchatka", + "unknown3": "Kamchatka", + "unknown4": "Kamchatka" + }, + "coordinates": { + "latitude": 53.014525764, + "longitude": 158.648502699 + } + }, + { + "id": 1679884288, + "name": "Karachay-Cherkess", + "translations": { + "japanese": "カラチャイ・チェルケス共和国", + "english": "Karachay-Cherkess", + "french": "Karatchaïévo-Tcherkessie", + "german": "Republik Karatschai-Tscherkessien", + "italian": "Repubblica Karačajevo-Čerkessia", + "spanish": "República de Karacháyevo-Cherkesia", + "chinese_simple": "卡拉恰耶夫-切尔克斯共和国", + "korean": "카라차예보체르케스카야 공화국", + "dutch": "Karatsjaj-Tsjerkessië", + "portuguese": "Karachay-Cherkess (república)", + "russian": "Карачаево-Черкесская республика", + "chinese_traditional": "Karachay-Cherkess", + "unknown1": "Karachay-Cherkess", + "unknown2": "Karachay-Cherkess", + "unknown3": "Karachay-Cherkess", + "unknown4": "Karachay-Cherkess" + }, + "coordinates": { + "latitude": 44.214477036, + "longitude": 42.044792066 + } + }, + { + "id": 1679949824, + "name": "Karelia", + "translations": { + "japanese": "カレリア共和国", + "english": "Karelia", + "french": "Carélie", + "german": "Republik Karelien", + "italian": "씒⭤ꥠ軷bblica di Carelia", + "spanish": "República de Carelia", + "chinese_simple": "卡累利阿共和国", + "korean": "카렐리야 공화국", + "dutch": "Karelië", + "portuguese": "Carélia (república)", + "russian": "Республика Карелия", + "chinese_traditional": "Karelia", + "unknown1": "Karelia", + "unknown2": "Karelia", + "unknown3": "Karelia", + "unknown4": "Karelia" + }, + "coordinates": { + "latitude": 61.781615508, + "longitude": 34.33236875 + } + }, + { + "id": 1680015360, + "name": "Kemerovo", + "translations": { + "japanese": "ケメロヴォ州", + "english": "Kemerovo", + "french": "Kemerovo", + "german": "Oblast Kemerowo", + "italian": "Regione di Kemerovo", + "spanish": "Región de Kémerovo", + "chinese_simple": "克麦罗沃州", + "korean": "케메로보 주", + "dutch": "Kemerovo", + "portuguese": "Kemerovo (região)", + "russian": "Кемеровская область", + "chinese_traditional": "Kemerovo", + "unknown1": "Kemerovo", + "unknown2": "Kemerovo", + "unknown3": "Kemerovo", + "unknown4": "Kemerovo" + }, + "coordinates": { + "latitude": 55.360106792, + "longitude": 86.089101288 + } + }, + { + "id": 1680080896, + "name": "Khabarovsk", + "translations": { + "japanese": "ハバロフスク地方", + "english": "Khabarovsk", + "french": "Khabarovsk", + "german": "Region Chabarowsk", + "italian": "Territorio di Chabarovsk", + "spanish": "Territorio de Jabárovsk", + "chinese_simple": "哈巴罗夫斯克边疆区", + "korean": "하바롭스크 지방", + "dutch": "Chabarovsk", + "portuguese": "Khabarovsk (território)", + "russian": "Хабаровский край", + "chinese_traditional": "Khabarovsk", + "unknown1": "Khabarovsk", + "unknown2": "Khabarovsk", + "unknown3": "Khabarovsk", + "unknown4": "Khabarovsk" + }, + "coordinates": { + "latitude": 48.482665464, + "longitude": 135.066285252 + } + }, + { + "id": 1680146432, + "name": "Khakassia", + "translations": { + "japanese": "ハカス共和国", + "english": "Khakassia", + "french": "Khakassie", + "german": "Republik Chakassien", + "italian": "Repubblica di Khakassia", + "spanish": "República de Jakasia", + "chinese_simple": "哈卡斯共和国", + "korean": "하카스 공화국", + "dutch": "Chakassië", + "portuguese": "Cacássia (república)", + "russian": "Республика Хакасия", + "chinese_traditional": "Khakassia", + "unknown1": "Khakassia", + "unknown2": "Khakassia", + "unknown3": "Khakassia", + "unknown4": "Khakassia" + }, + "coordinates": { + "latitude": 53.712157592, + "longitude": 91.411991739 + } + }, + { + "id": 1680211968, + "name": "Khanty-Mansiy", + "translations": { + "japanese": "ハンティ・マンシ自治管区", + "english": "Khanty-Mansiy", + "french": "Khantys-Mansis", + "german": "Autonomer Kreis der Chanten und Mansen", + "italian": "Circondario Autonomo di Chantia-Mansia", + "spanish": "Distrito autónomo de Janti-Mansi", + "chinese_simple": "汉特-曼西自治区", + "korean": "한티만시 자치구", + "dutch": "Chanto-Mansië", + "portuguese": "Khanty-Mansiy", + "russian": "Ханты-Мансийский автономный округ", + "chinese_traditional": "Khanty-Mansiy", + "unknown1": "Khanty-Mansiy", + "unknown2": "Khanty-Mansiy", + "unknown3": "Khanty-Mansiy", + "unknown4": "Khanty-Mansiy" + }, + "coordinates": { + "latitude": 60.996093056, + "longitude": 68.999821419 + } + }, + { + "id": 1680277504, + "name": "Kirov", + "translations": { + "japanese": "キーロフ州", + "english": "Kirov", + "french": "Kirov", + "german": "Oblast Kirow", + "italian": "Regione di Kirov", + "spanish": "Región de Kírov", + "chinese_simple": "基洛夫州", + "korean": "키로프 주", + "dutch": "Kírov", + "portuguese": "Kirov (região)", + "russian": "Кировская область", + "chinese_traditional": "Kirov", + "unknown1": "Kirov", + "unknown2": "Kirov", + "unknown3": "Kirov", + "unknown4": "Kirov" + }, + "coordinates": { + "latitude": 58.595580388, + "longitude": 49.647351802 + } + }, + { + "id": 1680343040, + "name": "Komi", + "translations": { + "japanese": "コミ共和国", + "english": "Komi", + "french": "Komis", + "german": "Republik Komi", + "italian": "Repubblica di Komi", + "spanish": "República de Komi", + "chinese_simple": "科米共和国", + "korean": "코미 공화국", + "dutch": "Komi", + "portuguese": "Komi (república)", + "russian": "Республика Коми", + "chinese_traditional": "Komi", + "unknown1": "Komi", + "unknown2": "Komi", + "unknown3": "Komi", + "unknown4": "Komi" + }, + "coordinates": { + "latitude": 61.666259064, + "longitude": 50.81190575 + } + }, + { + "id": 1680408576, + "name": "Kostroma", + "translations": { + "japanese": "コストロマ州", + "english": "Kostroma", + "french": "Kostroma", + "german": "Oblast Kostroma", + "italian": "Regione di Kostroma", + "spanish": "Región de Kostromá", + "chinese_simple": "科斯特罗马州", + "korean": "코스트로마 주", + "dutch": "Kostroma", + "portuguese": "Kostroma (região)", + "russian": "Костромская область", + "chinese_traditional": "Kostroma", + "unknown1": "Kostroma", + "unknown2": "Kostroma", + "unknown3": "Kostroma", + "unknown4": "Kostroma" + }, + "coordinates": { + "latitude": 57.766112624, + "longitude": 40.929676729 + } + }, + { + "id": 1680474112, + "name": "Krasnodar", + "translations": { + "japanese": "クラスノダール地方", + "english": "Krasnodar", + "french": "Krasnodar", + "german": "Region Krasnodar", + "italian": "Territorio di Krasnodar", + "spanish": "Territorio de Krasnodar", + "chinese_simple": "克拉斯诺达尔边疆区", + "korean": "크라스노다르 지방", + "dutch": "Krasnodar", + "portuguese": "Krasnodar (território)", + "russian": "Краснодарский край", + "chinese_traditional": "Krasnodar", + "unknown1": "Krasnodar", + "unknown2": "Krasnodar", + "unknown3": "Krasnodar", + "unknown4": "Krasnodar" + }, + "coordinates": { + "latitude": 45.032958472, + "longitude": 38.963118647 + } + }, + { + "id": 1680539648, + "name": "Krasnoyarsk", + "translations": { + "japanese": "クラスノヤルスク地方", + "english": "Krasnoyarsk", + "french": "Krasnoïarsk", + "german": "Region Krasnojarsk", + "italian": "Territorio di Krasnojarsk", + "spanish": "Territorio de Krasnoyarsk", + "chinese_simple": "克拉斯诺亚尔斯克边疆区", + "korean": "크라스노야르스크 지방", + "dutch": "Krasnojarsk", + "portuguese": "Krasnoyarsk (território)", + "russian": "Красноярский край", + "chinese_traditional": "Krasnoyarsk", + "unknown1": "Krasnoyarsk", + "unknown2": "Krasnoyarsk", + "unknown3": "Krasnoyarsk", + "unknown4": "Krasnoyarsk" + }, + "coordinates": { + "latitude": 55.997313816, + "longitude": 92.884163711 + } + }, + { + "id": 1680605184, + "name": "Kurgan", + "translations": { + "japanese": "クルガン州", + "english": "Kurgan", + "french": "Kourgan", + "german": "Oblast Kurgan", + "italian": "Regione di Kurgan", + "spanish": "Región de Kurgán", + "chinese_simple": "库尔干州", + "korean": "쿠르간 주", + "dutch": "Koergan", + "portuguese": "Kurgan (região)", + "russian": "Курганская область", + "chinese_traditional": "Kurgan", + "unknown1": "Kurgan", + "unknown2": "Kurgan", + "unknown3": "Kurgan", + "unknown4": "Kurgan" + }, + "coordinates": { + "latitude": 55.464476908, + "longitude": 65.346857384 + } + }, + { + "id": 1680670720, + "name": "Kursk", + "translations": { + "japanese": "クルスク州", + "english": "Kursk", + "french": "Koursk", + "german": "Oblast Kursk", + "italian": "Regione di Kursk", + "spanish": "Región de Kursk", + "chinese_simple": "库尔斯克州", + "korean": "쿠르스크 주", + "dutch": "Koersk", + "portuguese": "Kursk (região)", + "russian": "Курская область", + "chinese_traditional": "Kursk", + "unknown1": "Kursk", + "unknown2": "Kursk", + "unknown3": "Kursk", + "unknown4": "Kursk" + }, + "coordinates": { + "latitude": 51.712645896, + "longitude": 36.178076894 + } + }, + { + "id": 1680736256, + "name": "Leningrad", + "translations": { + "japanese": "レニングラード州", + "english": "Leningrad", + "french": "Léningrad", + "german": "Oblast Leningrad", + "italian": "Regione di Leningrado", + "spanish": "Región de Leningrado", + "chinese_simple": "列宁格勒州", + "korean": "레닌그라드 주", + "dutch": "Leningrad", + "portuguese": "Leninegrado (região)", + "russian": "Ленинградская область", + "chinese_traditional": "Leningrad", + "unknown1": "Leningrad", + "unknown2": "Leningrad", + "unknown3": "Leningrad", + "unknown4": "Leningrad" + }, + "coordinates": { + "latitude": 59.946898732, + "longitude": 30.311361722 + } + }, + { + "id": 1680801792, + "name": "Lipetsk", + "translations": { + "japanese": "リペツク州", + "english": "Lipetsk", + "french": "Lipetsk", + "german": "Oblast Lipezk", + "italian": "Regione di Lipeck", + "spanish": "Región de Lípetsk", + "chinese_simple": "利佩茨克州", + "korean": "리페츠크 주", + "dutch": "Lipetsk", + "portuguese": "Lipetsk (região)", + "russian": "Липецкая область", + "chinese_traditional": "Lipetsk", + "unknown1": "Lipetsk", + "unknown2": "Lipetsk", + "unknown3": "Lipetsk", + "unknown4": "Lipetsk" + }, + "coordinates": { + "latitude": 52.613524792, + "longitude": 39.594834232 + } + }, + { + "id": 1680867328, + "name": "Magadan", + "translations": { + "japanese": "マガダン州", + "english": "Magadan", + "french": "Magadan", + "german": "Oblast Magadan", + "italian": "Regione di Magadan", + "spanish": "Región de Magadán", + "chinese_simple": "马加丹州", + "korean": "마가단 주", + "dutch": "Magadan", + "portuguese": "Magadão (região)", + "russian": "Магаданская область", + "chinese_traditional": "Magadan", + "unknown1": "Magadan", + "unknown2": "Magadan", + "unknown3": "Magadan", + "unknown4": "Magadan" + }, + "coordinates": { + "latitude": 59.562377252, + "longitude": 150.798749908 + } + }, + { + "id": 1680932864, + "name": "Mariy-El", + "translations": { + "japanese": "マリ・エル共和国", + "english": "Mariy-El", + "french": "Maris", + "german": "Republik Mari El", + "italian": "Repubblica di Mari-El", + "spanish": "República de Mari-El", + "chinese_simple": "马里-埃尔共和国", + "korean": "마리옐 공화국", + "dutch": "Mari El", + "portuguese": "Mari El (república)", + "russian": "Республика Марий Эл", + "chinese_traditional": "Mariy-El", + "unknown1": "Mariy-El", + "unknown2": "Mariy-El", + "unknown3": "Mariy-El", + "unknown4": "Mariy-El" + }, + "coordinates": { + "latitude": 56.629027676, + "longitude": 47.862068627 + } + }, + { + "id": 1680998400, + "name": "Mordovia", + "translations": { + "japanese": "モルドヴィア共和国", + "english": "Mordovia", + "french": "Mordovie", + "german": "Republik Mordwinien", + "italian": "Repubblica di Mordovia", + "spanish": "República de Mordovia", + "chinese_simple": "莫尔多瓦社会主义共和国", + "korean": "모르도바 공화국", + "dutch": "Mordovië", + "portuguese": "Mordóvia (república)", + "russian": "Республика Мордовия", + "chinese_traditional": "Mordovia", + "unknown1": "Mordovia", + "unknown2": "Mordovia", + "unknown3": "Mordovia", + "unknown4": "Mordovia" + }, + "coordinates": { + "latitude": 54.179076532, + "longitude": 45.181397275 + } + }, + { + "id": 1681063936, + "name": "Moscow", + "translations": { + "japanese": "モスクワ州", + "english": "Moscow", + "french": "Moscou (oblast)", + "german": "Oblast Moskau", + "italian": "Regione di Mosca", + "spanish": "Región de Moscú", + "chinese_simple": "莫斯科州", + "korean": "모스크바 주", + "dutch": "Moskou (oblast)", + "portuguese": "Moscovo (região)", + "russian": "Московская область", + "chinese_traditional": "Moscow", + "unknown1": "Moscow", + "unknown2": "Moscow", + "unknown3": "Moscow", + "unknown4": "Moscow" + }, + "coordinates": { + "latitude": 55.766600928, + "longitude": 37.611796613 + } + }, + { + "id": 1681129472, + "name": "Murmansk", + "translations": { + "japanese": "ムルマンスク州", + "english": "Murmansk", + "french": "Mourmansk", + "german": "Oblast Murmansk", + "italian": "Regione di Murmansk", + "spanish": "Región de Múrmansk", + "chinese_simple": "摩尔曼斯克州", + "korean": "무르만스크 주", + "dutch": "Moermansk", + "portuguese": "Murmansk (região)", + "russian": "Мурманская область", + "chinese_traditional": "Murmansk", + "unknown1": "Murmansk", + "unknown2": "Murmansk", + "unknown3": "Murmansk", + "unknown4": "Murmansk" + }, + "coordinates": { + "latitude": 68.961180856, + "longitude": 33.079923938 + } + }, + { + "id": 1681195008, + "name": "Nenets", + "translations": { + "japanese": "ネネツ自治管区", + "english": "Nenets", + "french": "Nénétsie", + "german": "Autonomer Kreis der Nenzen", + "italian": "Circondario Autonomo di Nenec", + "spanish": "Distrito autónomo de Nenetsia", + "chinese_simple": "涅涅茨自治区", + "korean": "네네츠 자치구", + "dutch": "Nenetsië", + "portuguese": "Nenetsia (distrito)", + "russian": "Ненецкий автономный округ", + "chinese_traditional": "Nenets", + "unknown1": "Nenets", + "unknown2": "Nenets", + "unknown3": "Nenets", + "unknown4": "Nenets" + }, + "coordinates": { + "latitude": 67.631835168, + "longitude": 53.047629603 + } + }, + { + "id": 1681260544, + "name": "Nizhegorod", + "translations": { + "japanese": "ニジニ・ノヴゴロド州", + "english": "Nizhegorod", + "french": "Nijni Novgorod", + "german": "Oblast Nischni Nowgorod", + "italian": "Regione di Nižnij Novgorod", + "spanish": "Región de Nizhni Nóvgorod", + "chinese_simple": "下诺夫哥罗德州", + "korean": "니제고로드 주", + "dutch": "Nizjni Novgorod", + "portuguese": "Nizhegorod (região)", + "russian": "Нижегородская область", + "chinese_traditional": "Nizhegorod", + "unknown1": "Nizhegorod", + "unknown2": "Nizhegorod", + "unknown3": "Nizhegorod", + "unknown4": "Nizhegorod" + }, + "coordinates": { + "latitude": 56.33239682, + "longitude": 43.994870610999996 + } + }, + { + "id": 1681326080, + "name": "Novgorod", + "translations": { + "japanese": "ノヴゴロド州", + "english": "Novgorod", + "french": "Novgorod", + "german": "Oblast Nowgorod", + "italian": "Regione di Novgorod", + "spanish": "Región de Nóvgorod", + "chinese_simple": "诺夫哥罗德州", + "korean": "노브고로드 주", + "dutch": "Novgorod", + "portuguese": "Novgorod (região)", + "russian": "Новгородская область", + "chinese_traditional": "Novgorod", + "unknown1": "Novgorod", + "unknown2": "Novgorod", + "unknown3": "Novgorod", + "unknown4": "Novgorod" + }, + "coordinates": { + "latitude": 58.546141912, + "longitude": 31.278161225999998 + } + }, + { + "id": 1681391616, + "name": "Novosibirsk", + "translations": { + "japanese": "ノヴォシビルスク州", + "english": "Novosibirsk", + "french": "Novossibirsk", + "german": "Oblast Nowosibirsk", + "italian": "Regione di Novosibirsk", + "spanish": "Región de Novosibirsk", + "chinese_simple": "新西伯利亚州", + "korean": "노보시비르스크 주", + "dutch": "Novosibirsk", + "portuguese": "Novosibirsk (região)", + "russian": "Новосибирская область", + "chinese_traditional": "Novosibirsk", + "unknown1": "Novosibirsk", + "unknown2": "Novosibirsk", + "unknown3": "Novosibirsk", + "unknown4": "Novosibirsk" + }, + "coordinates": { + "latitude": 55.01403746, + "longitude": 82.930523363 + } + }, + { + "id": 1681457152, + "name": "Omsk", + "translations": { + "japanese": "オムスク州", + "english": "Omsk", + "french": "Omsk", + "german": "Oblast Omsk", + "italian": "Regione di Omsk", + "spanish": "Región de Omsk", + "chinese_simple": "鄂木斯克州", + "korean": "옴스크 주", + "dutch": "Omsk", + "portuguese": "Omsk (região)", + "russian": "Омская область", + "chinese_traditional": "Omsk", + "unknown1": "Omsk", + "unknown2": "Omsk", + "unknown3": "Omsk", + "unknown4": "Omsk" + }, + "coordinates": { + "latitude": 54.981078476, + "longitude": 73.361405545 + } + }, + { + "id": 1681522688, + "name": "Orenburg", + "translations": { + "japanese": "オレンブルク州", + "english": "Orenburg", + "french": "Orenbourg", + "german": "Oblast Orenburg", + "italian": "Regione di Orenburg", + "spanish": "Región de Oremburgo", + "chinese_simple": "奥伦堡州", + "korean": "오렌부르크 주", + "dutch": "Orenburg", + "portuguese": "Oremburgo (região)", + "russian": "Оренбургская область", + "chinese_traditional": "Orenburg", + "unknown1": "Orenburg", + "unknown2": "Orenburg", + "unknown3": "Orenburg", + "unknown4": "Orenburg" + }, + "coordinates": { + "latitude": 51.778563864, + "longitude": 55.09658537 + } + }, + { + "id": 1681588224, + "name": "Orel", + "translations": { + "japanese": "オリョール州", + "english": "Orel", + "french": "Orel", + "german": "Oblast Orjol", + "italian": "Regione di Orël", + "spanish": "Región de Oriol", + "chinese_simple": "奥廖尔州", + "korean": "오룔 주", + "dutch": "Orjol", + "portuguese": "Orel (região)", + "russian": "Орловская область", + "chinese_traditional": "Orel", + "unknown1": "Orel", + "unknown2": "Orel", + "unknown3": "Orel", + "unknown4": "Orel" + }, + "coordinates": { + "latitude": 52.965087288, + "longitude": 36.062720135 + } + }, + { + "id": 1681653760, + "name": "Penza", + "translations": { + "japanese": "ペンザ州", + "english": "Penza", + "french": "Penza", + "german": "Oblast Pensa", + "italian": "Regione di Penza", + "spanish": "Región de Penza", + "chinese_simple": "奔萨州", + "korean": "펜자 주", + "dutch": "Penza", + "portuguese": "Penza (região)", + "russian": "Пензенская область", + "chinese_traditional": "Penza", + "unknown1": "Penza", + "unknown2": "Penza", + "unknown3": "Penza", + "unknown4": "Penza" + }, + "coordinates": { + "latitude": 53.195800176, + "longitude": 45.016601905 + } + }, + { + "id": 1681719296, + "name": "Perm'", + "translations": { + "japanese": "ペルミ地方", + "english": "Perm'", + "french": "Perm", + "german": "Region Perm", + "italian": "Territorio di Perm'", + "spanish": "Territorio de Perm", + "chinese_simple": "彼尔姆边疆区", + "korean": "페름 지방", + "dutch": "Perm", + "portuguese": "Perm (território)", + "russian": "Пермский край", + "chinese_traditional": "Perm'", + "unknown1": "Perm'", + "unknown2": "Perm'", + "unknown3": "Perm'", + "unknown4": "Perm'" + }, + "coordinates": { + "latitude": 57.996825512, + "longitude": 56.316071108 + } + }, + { + "id": 1681784832, + "name": "Primor'ye", + "translations": { + "japanese": "沿海地方", + "english": "Primor'ye", + "french": "Primorie", + "german": "Region Primorje", + "italian": "Territorio del Litorale", + "spanish": "Territorio de Primorie", + "chinese_simple": "滨海边疆区", + "korean": "프리모르스키 지방", + "dutch": "Primorski", + "portuguese": "Primorie (território)", + "russian": "Приморский край", + "chinese_traditional": "Primor'ye", + "unknown1": "Primor'ye", + "unknown2": "Primor'ye", + "unknown3": "Primor'ye", + "unknown4": "Primor'ye" + }, + "coordinates": { + "latitude": 43.132323728, + "longitude": 131.896720969 + } + }, + { + "id": 1681850368, + "name": "Pskov", + "translations": { + "japanese": "プスコフ州", + "english": "Pskov", + "french": "Pskov", + "german": "Oblast Pskow", + "italian": "Regione di Pskov", + "spanish": "Región de Pskov", + "chinese_simple": "普斯科夫州", + "korean": "프스코프 부", + "dutch": "Pskov", + "portuguese": "Pskov (região)", + "russian": "Псковская область", + "chinese_traditional": "Pskov", + "unknown1": "Pskov", + "unknown2": "Pskov", + "unknown3": "Pskov", + "unknown4": "Pskov" + }, + "coordinates": { + "latitude": 57.8155511, + "longitude": 28.328324103 + } + }, + { + "id": 1681915904, + "name": "Rostov", + "translations": { + "japanese": "ロストフ州", + "english": "Rostov", + "french": "Rostov", + "german": "Oblast Rostow", + "italian": "Regione di Rostov", + "spanish": "Región de Rostov", + "chinese_simple": "罗斯托夫州", + "korean": "로스토프 주", + "dutch": "Rostov", + "portuguese": "Rostov (região)", + "russian": "Ростовская область", + "chinese_traditional": "Rostov", + "unknown1": "Rostov", + "unknown2": "Rostov", + "unknown3": "Rostov", + "unknown4": "Rostov" + }, + "coordinates": { + "latitude": 47.230224072, + "longitude": 39.699204633 + } + }, + { + "id": 1681981440, + "name": "Ryazan'", + "translations": { + "japanese": "リャザン州", + "english": "Ryazan'", + "french": "Riazan", + "german": "Oblast Rjasan", + "italian": "Regione di Rjazan'", + "spanish": "Región de Riazán", + "chinese_simple": "梁赞州", + "korean": "랴잔 주", + "dutch": "Rjazan", + "portuguese": "Riazan (região)", + "russian": "Рязанская область", + "chinese_traditional": "Ryazan'", + "unknown1": "Ryazan'", + "unknown2": "Ryazan'", + "unknown3": "Ryazan'", + "unknown4": "Ryazan'" + }, + "coordinates": { + "latitude": 54.596556996, + "longitude": 39.699204633 + } + }, + { + "id": 1682046976, + "name": "Sakha", + "translations": { + "japanese": "サハ共和国", + "english": "Sakha", + "french": "Sakha (Iakoutie)", + "german": "Republik Sacha (Jakutien)", + "italian": "Repubblica di Sacha (Jacuzia)", + "spanish": "República de Sajá (Yakutia)", + "chinese_simple": "萨哈共和国", + "korean": "사하 공화국", + "dutch": "Jakoetië", + "portuguese": "Iacútia (república)", + "russian": "Республика Саха (Якутия)", + "chinese_traditional": "Sakha", + "unknown1": "Sakha", + "unknown2": "Sakha", + "unknown3": "Sakha", + "unknown4": "Sakha" + }, + "coordinates": { + "latitude": 62.028807888, + "longitude": 129.732408443 + } + }, + { + "id": 1682112512, + "name": "Sakhalin", + "translations": { + "japanese": "サハリン州", + "english": "Sakhalin", + "french": "Sakhaline", + "german": "Oblast Sachalin", + "italian": "Regione di Sachalin", + "spanish": "Región de Sajalín", + "chinese_simple": "萨哈林州", + "korean": "사할린 주", + "dutch": "Sachalin", + "portuguese": "Sacalina (região)", + "russian": "Сахалинская область", + "chinese_traditional": "Sakhalin", + "unknown1": "Sakhalin", + "unknown2": "Sakhalin", + "unknown3": "Sakhalin", + "unknown4": "Sakhalin" + }, + "coordinates": { + "latitude": 50.218505288, + "longitude": 142.998435728 + } + }, + { + "id": 1682178048, + "name": "Samara", + "translations": { + "japanese": "サマラ州", + "english": "Samara", + "french": "Samara", + "german": "Oblast Samara", + "italian": "Regione di Samara", + "spanish": "Región de Samara", + "chinese_simple": "萨马拉州", + "korean": "사마라 주", + "dutch": "Samara", + "portuguese": "Samara (região)", + "russian": "Самарская область", + "chinese_traditional": "Samara", + "unknown1": "Samara", + "unknown2": "Samara", + "unknown3": "Samara", + "unknown4": "Samara" + }, + "coordinates": { + "latitude": 53.22875916, + "longitude": 50.163710628 + } + }, + { + "id": 1682243584, + "name": "St. Petersburg", + "translations": { + "japanese": "サンクトペテルブルク市", + "english": "St. Petersburg", + "french": "Saint-Pétersbourg", + "german": "Sankt Petersburg", + "italian": "San Pietroburgo", + "spanish": "San Petersburgo", + "chinese_simple": "圣彼得堡市", + "korean": "상트페테르부르크 주", + "dutch": "Sint-Petersburg", + "portuguese": "São Petersburgo", + "russian": "Санкт-Петербург", + "chinese_traditional": "St. Petersburg", + "unknown1": "St. Petersburg", + "unknown2": "St. Petersburg", + "unknown3": "St. Petersburg", + "unknown4": "St. Petersburg" + }, + "coordinates": { + "latitude": 59.946898732, + "longitude": 30.311361722 + } + }, + { + "id": 1682309120, + "name": "Saratov", + "translations": { + "japanese": "サラトフ州", + "english": "Saratov", + "french": "Saratov", + "german": "Oblast Saratow", + "italian": "Regione di Saratov", + "spanish": "Región de Sarátov", + "chinese_simple": "萨拉托夫州", + "korean": "사라토프 주", + "dutch": "Saratov", + "portuguese": "Saratov (região)", + "russian": "Саратовская область", + "chinese_traditional": "Saratov", + "unknown1": "Saratov", + "unknown2": "Saratov", + "unknown3": "Saratov", + "unknown4": "Saratov" + }, + "coordinates": { + "latitude": 51.531371484, + "longitude": 46.016360483 + } + }, + { + "id": 1682374656, + "name": "North Ossetia", + "translations": { + "japanese": "北オセチア共和国", + "english": "North Ossetia", + "french": "Ossétie-du-Nord-Alanie", + "german": "Republik Nordossetien-Alanien", + "italian": "Repubblica dell'Ossezia Settentrionale-Alania", + "spanish": "República de Osetia del Norte-Alania", + "chinese_simple": "北奥塞梯-阿兰社会主义共和国", + "korean": "북오세티야 공화국", + "dutch": "Noord-Ossetië", + "portuguese": "Ossétia-Alânia do Norte (república)", + "russian": "Республика Северная Осетия-Алания", + "chinese_traditional": "North Ossetia", + "unknown1": "North Ossetia", + "unknown2": "North Ossetia", + "unknown3": "North Ossetia", + "unknown4": "North Ossetia" + }, + "coordinates": { + "latitude": 43.01147412, + "longitude": 44.648558912 + } + }, + { + "id": 1682440192, + "name": "Smolensk", + "translations": { + "japanese": "スモレンスク州", + "english": "Smolensk", + "french": "Smolensk", + "german": "Oblast Smolensk", + "italian": "Regione di Smolensk", + "spanish": "Región de Smolensk", + "chinese_simple": "斯摩棱斯克州", + "korean": "스몰렌스크 주", + "dutch": "Smolensk", + "portuguese": "Smolensk (região)", + "russian": "Смоленская область", + "chinese_traditional": "Smolensk", + "unknown1": "Smolensk", + "unknown2": "Smolensk", + "unknown3": "Smolensk", + "unknown4": "Smolensk" + }, + "coordinates": { + "latitude": 54.783324572, + "longitude": 32.047206286 + } + }, + { + "id": 1682505728, + "name": "Stavropol'", + "translations": { + "japanese": "スタヴロポリ地方", + "english": "Stavropol'", + "french": "Stavropol", + "german": "Region Stawropol", + "italian": "Territorio di Stavropol'", + "spanish": "Territorio de Stávropol", + "chinese_simple": "斯塔夫罗波尔边疆区", + "korean": "스타브로폴 지방", + "dutch": "Stavropol", + "portuguese": "Stavropol (território)", + "russian": "Ставропольский край", + "chinese_traditional": "Stavropol'", + "unknown1": "Stavropol'", + "unknown2": "Stavropol'", + "unknown3": "Stavropol'", + "unknown4": "Stavropol'" + }, + "coordinates": { + "latitude": 45.049437964, + "longitude": 41.978873918 + } + }, + { + "id": 1682571264, + "name": "Sverdlovsk", + "translations": { + "japanese": "スヴェルドロフスク州", + "english": "Sverdlovsk", + "french": "Sverdlovsk", + "german": "Oblast Swerdlowsk", + "italian": "Regione di Sverdlovsk", + "spanish": "Región de Sverdlovsk", + "chinese_simple": "斯维尔德洛夫斯克州", + "korean": "스베르들롭스크 주", + "dutch": "Sverdlovsk", + "portuguese": "Sverdlovsk (região)", + "russian": "Свердловская область", + "chinese_traditional": "Sverdlovsk", + "unknown1": "Sverdlovsk", + "unknown2": "Sverdlovsk", + "unknown3": "Sverdlovsk", + "unknown4": "Sverdlovsk" + }, + "coordinates": { + "latitude": 56.832274744, + "longitude": 60.578778012 + } + }, + { + "id": 1682636800, + "name": "Tambov", + "translations": { + "japanese": "タンボフ州", + "english": "Tambov", + "french": "Tambov", + "german": "Oblast Tambow", + "italian": "Regione di Tambov", + "spanish": "Región de Tambov", + "chinese_simple": "坦波夫州", + "korean": "탐보프 주", + "dutch": "Tambov", + "portuguese": "Tambov (região)", + "russian": "Тамбовская область", + "chinese_traditional": "Tambov", + "unknown1": "Tambov", + "unknown2": "Tambov", + "unknown3": "Tambov", + "unknown4": "Tambov" + }, + "coordinates": { + "latitude": 52.712401744, + "longitude": 41.429556018 + } + }, + { + "id": 1682702336, + "name": "Tatarstan", + "translations": { + "japanese": "タタールスタン共和国", + "english": "Tatarstan", + "french": "Tatarstan", + "german": "Republik Tatarstan", + "italian": "Repubblica del Tatarstan", + "spanish": "República de Tartaristán", + "chinese_simple": "鞑靼斯坦共和国", + "korean": "타타르 공화국", + "dutch": "Tatarije", + "portuguese": "Tartária (república)", + "russian": "Республика Татарстан", + "chinese_traditional": "Tatarstan", + "unknown1": "Tatarstan", + "unknown2": "Tatarstan", + "unknown3": "Tatarstan", + "unknown4": "Tatarstan" + }, + "coordinates": { + "latitude": 55.78308042, + "longitude": 49.16395205 + } + }, + { + "id": 1682767872, + "name": "Tomsk", + "translations": { + "japanese": "トムスク州", + "english": "Tomsk", + "french": "Tomsk", + "german": "Oblast Tomsk", + "italian": "Regione di Tomsk", + "spanish": "Región de Tomsk", + "chinese_simple": "托木斯克州", + "korean": "톰스크 주", + "dutch": "Tomsk", + "portuguese": "Tomsk (região)", + "russian": "Томская область", + "chinese_traditional": "Tomsk", + "unknown1": "Tomsk", + "unknown2": "Tomsk", + "unknown3": "Tomsk", + "unknown4": "Tomsk" + }, + "coordinates": { + "latitude": 56.49719174, + "longitude": 84.96299959299999 + } + }, + { + "id": 1682833408, + "name": "Tula", + "translations": { + "japanese": "トゥーラ州", + "english": "Tula", + "french": "Toula", + "german": "Oblast Tula", + "italian": "Regione di Tula", + "spanish": "Región de Tula", + "chinese_simple": "图拉州", + "korean": "툴라 주", + "dutch": "Toela", + "portuguese": "Tula (região)", + "russian": "Тульская область", + "chinese_traditional": "Tula", + "unknown1": "Tula", + "unknown2": "Tula", + "unknown3": "Tula", + "unknown4": "Tula" + }, + "coordinates": { + "latitude": 54.195556024, + "longitude": 37.611796613 + } + }, + { + "id": 1682898944, + "name": "Tver'", + "translations": { + "japanese": "トヴェリ州", + "english": "Tver'", + "french": "Tver", + "german": "Oblast Twer", + "italian": "Regione di Tver", + "spanish": "Región de Tver", + "chinese_simple": "特维尔州", + "korean": "트베리 주", + "dutch": "Tver", + "portuguese": "Tver (região)", + "russian": "Тверская область", + "chinese_traditional": "Tver'", + "unknown1": "Tver'", + "unknown2": "Tver'", + "unknown3": "Tver'", + "unknown4": "Tver'" + }, + "coordinates": { + "latitude": 56.8542474, + "longitude": 35.914404302 + } + }, + { + "id": 1682964480, + "name": "Tyumen'", + "translations": { + "japanese": "チュメニ州", + "english": "Tyumen'", + "french": "Tioumen", + "german": "Oblast Tjumen", + "italian": "Regione di Tjumen'", + "spanish": "Región de Tiumén", + "chinese_simple": "秋明州", + "korean": "튜멘 주", + "dutch": "Tjoemen", + "portuguese": "Tiumen (região)", + "russian": "Тюменская область", + "chinese_traditional": "Tyumen'", + "unknown1": "Tyumen'", + "unknown2": "Tyumen'", + "unknown3": "Tyumen'", + "unknown4": "Tyumen'" + }, + "coordinates": { + "latitude": 57.145385092, + "longitude": 65.528132291 + } + }, + { + "id": 1683030016, + "name": "Tuva", + "translations": { + "japanese": "トゥヴァ共和国", + "english": "Tuva", + "french": "Touva", + "german": "Republik Tuwa", + "italian": "Repubblica di Tuva", + "spanish": "República de Tuvá", + "chinese_simple": "特瓦共和国", + "korean": "투바 공화국", + "dutch": "Toeva", + "portuguese": "Tuva (república)", + "russian": "Республика Тыва", + "chinese_traditional": "Tuva", + "unknown1": "Tuva", + "unknown2": "Tuva", + "unknown3": "Tuva", + "unknown4": "Tuva" + }, + "coordinates": { + "latitude": 51.712645896, + "longitude": 94.449719726 + } + }, + { + "id": 1683095552, + "name": "Udmurt", + "translations": { + "japanese": "ウドムルト共和国", + "english": "Udmurt", + "french": "Oudmourtie", + "german": "Republik Udmurtien", + "italian": "Repubblica Udmurta", + "spanish": "República de Udmurtia", + "chinese_simple": "乌德穆尔特共和国", + "korean": "우드무르트 공화국", + "dutch": "Oedmoertië", + "portuguese": "Udmúrtia (república)", + "russian": "Удмуртская республика", + "chinese_traditional": "Udmurt", + "unknown1": "Udmurt", + "unknown2": "Udmurt", + "unknown3": "Udmurt", + "unknown4": "Udmurt" + }, + "coordinates": { + "latitude": 56.832274744, + "longitude": 53.179465899 + } + }, + { + "id": 1683161088, + "name": "Ul'yanovsk", + "translations": { + "japanese": "ウリヤノフスク州", + "english": "Ul'yanovsk", + "french": "Oulianovsk", + "german": "Oblast Uljanowsk", + "italian": "Regione di Ul'janovsk", + "spanish": "Región de Uliánovsk", + "chinese_simple": "乌里扬诺夫斯克州", + "korean": "울리야놉스크 주", + "dutch": "Oeljanovsk", + "portuguese": "Ulianovsk (região)", + "russian": "Ульяновская область", + "chinese_traditional": "Ul'yanovsk", + "unknown1": "Ul'yanovsk", + "unknown2": "Ul'yanovsk", + "unknown3": "Ul'yanovsk", + "unknown4": "Ul'yanovsk" + }, + "coordinates": { + "latitude": 54.316405632, + "longitude": 48.361947916 + } + }, + { + "id": 1683226624, + "name": "Vladimir", + "translations": { + "japanese": "ヴラジーミル州", + "english": "Vladimir", + "french": "Vladimir", + "german": "Oblast Wladimir", + "italian": "Regione di Vladimir", + "spanish": "Región de Vladímir", + "chinese_simple": "弗拉基米尔州", + "korean": "블라디미르 주", + "dutch": "Vladimir", + "portuguese": "Vladimir (região)", + "russian": "Владимирская область", + "chinese_traditional": "Vladimir", + "unknown1": "Vladimir", + "unknown2": "Vladimir", + "unknown3": "Vladimir", + "unknown4": "Vladimir" + }, + "coordinates": { + "latitude": 56.145629244, + "longitude": 40.413317903 + } + }, + { + "id": 1683292160, + "name": "Volgograd", + "translations": { + "japanese": "ヴォルゴグラード州", + "english": "Volgograd", + "french": "Volgograd", + "german": "Oblast Wolgograd", + "italian": "Regione di Volgograd", + "spanish": "Región de Volgogrado", + "chinese_simple": "伏尔加格勒州", + "korean": "볼고그라드 주", + "dutch": "Wolgograd", + "portuguese": "Volgogrado (região)", + "russian": "Волгоградская область", + "chinese_traditional": "Volgograd", + "unknown1": "Volgograd", + "unknown2": "Volgograd", + "unknown3": "Volgograd", + "unknown4": "Volgograd" + }, + "coordinates": { + "latitude": 48.69689886, + "longitude": 44.516722616 + } + }, + { + "id": 1683357696, + "name": "Vologda", + "translations": { + "japanese": "ヴォログダ州", + "english": "Vologda", + "french": "Vologda", + "german": "Oblast Wologda", + "italian": "Regione di Vologda", + "spanish": "Región de Vólogda", + "chinese_simple": "沃洛格达州", + "korean": "볼로그다 주", + "dutch": "Vologda", + "portuguese": "Vologda (região)", + "russian": "Вологодская область", + "chinese_traditional": "Vologda", + "unknown1": "Vologda", + "unknown2": "Vologda", + "unknown3": "Vologda", + "unknown4": "Vologda" + }, + "coordinates": { + "latitude": 59.21630792, + "longitude": 39.896959077 + } + }, + { + "id": 1683423232, + "name": "Voronezh", + "translations": { + "japanese": "ヴォロネジ州", + "english": "Voronezh", + "french": "Voronej", + "german": "Oblast Woronesch", + "italian": "Regione di Voronež", + "spanish": "Región de Vorónezh", + "chinese_simple": "沃罗涅什州", + "korean": "보로네시 주", + "dutch": "Voronezj", + "portuguese": "Voronezh (região)", + "russian": "Воронежская область", + "chinese_traditional": "Voronezh", + "unknown1": "Voronezh", + "unknown2": "Voronezh", + "unknown3": "Voronezh", + "unknown4": "Voronezh" + }, + "coordinates": { + "latitude": 51.668700584, + "longitude": 39.210311702 + } + }, + { + "id": 1683488768, + "name": "Yamal-Nenets", + "translations": { + "japanese": "ヤマロ・ネネツ自治管区", + "english": "Yamal-Nenets", + "french": "Iamalie", + "german": "Autonomer Kreis der Jamal-Nenzen", + "italian": "Circondario Autonomo Jamalo-Nenec", + "spanish": "Distrito autónomo de Yamalo-Nénets", + "chinese_simple": "亚马尔-涅涅茨自治区", + "korean": "야말로네네츠 자치구", + "dutch": "Jamalië", + "portuguese": "Yamal-Nenets", + "russian": "Ямало-Ненецкий автономный округ", + "chinese_traditional": "Yamal-Nenets", + "unknown1": "Yamal-Nenets", + "unknown2": "Yamal-Nenets", + "unknown3": "Yamal-Nenets", + "unknown4": "Yamal-Nenets" + }, + "coordinates": { + "latitude": 66.533202368, + "longitude": 66.599302196 + } + }, + { + "id": 1683554304, + "name": "Yaroslavl'", + "translations": { + "japanese": "ヤロスラヴリ州", + "english": "Yaroslavl'", + "french": "Iaroslavl", + "german": "Oblast Jaroslawl", + "italian": "Regione di Jaroslavl'", + "spanish": "Región de Yaroslavl", + "chinese_simple": "雅罗斯拉夫尔州", + "korean": "야로슬라블 주", + "dutch": "Jaroslavl", + "portuguese": "Yaroslavl (região)", + "russian": "Ярославская область", + "chinese_traditional": "Yaroslavl'", + "unknown1": "Yaroslavl'", + "unknown2": "Yaroslavl'", + "unknown3": "Yaroslavl'", + "unknown4": "Yaroslavl'" + }, + "coordinates": { + "latitude": 57.612304032, + "longitude": 39.847520466 + } + }, + { + "id": 1683619840, + "name": "Yevrey", + "translations": { + "japanese": "ユダヤ自治州", + "english": "Yevrey", + "french": "Oblast autonome juif", + "german": "Jüdisches Autonomes Gebiet", + "italian": "Regione Autonoma Ebraica", + "spanish": "Región Autónoma Hebrea", + "chinese_simple": "犹太自治州", + "korean": "유태인 자치주", + "dutch": "Joodse Autonome Oblast", + "portuguese": "Região Autónoma Judaica", + "russian": "Еврейская автономная область", + "chinese_traditional": "Yevrey", + "unknown1": "Yevrey", + "unknown2": "Yevrey", + "unknown3": "Yevrey", + "unknown4": "Yevrey" + }, + "coordinates": { + "latitude": 48.77929632, + "longitude": 132.929438621 + } + }, + { + "id": 1683685376, + "name": "Zabaykal'ye", + "translations": { + "japanese": "ザバイカリエ地方", + "english": "Zabaykal'ye", + "french": "Transbaïkalie", + "german": "Region Transbaikalien", + "italian": "Territorio della Transbajkalia", + "spanish": "Territorio de Zabaikalie", + "chinese_simple": "外贝加尔边疆区", + "korean": "자바이칼 지방", + "dutch": "Transbaikal", + "portuguese": "Transbaikalia (território)", + "russian": "Забайкальский край", + "chinese_traditional": "Zabaykal'ye", + "unknown1": "Zabaykal'ye", + "unknown2": "Zabaykal'ye", + "unknown3": "Zabaykal'ye", + "unknown4": "Zabaykal'ye" + }, + "coordinates": { + "latitude": 52.0477289, + "longitude": 113.500064498 + } + } + ] + }, + { + "id": 101, + "iso_code": "RS", + "name": "Serbia and Kosovo", + "translations": { + "japanese": "セルビア・コソヴォ", + "english": "Serbia and Kosovo", + "french": "Serbie et Kosovo", + "german": "Serbien und Kosovo", + "italian": "Serbia e Kosovo", + "spanish": "Serbia y Kosovo", + "chinese_simple": "塞尔维亚及科索沃", + "korean": "세르비아 코소보", + "dutch": "Servië en Kosovo", + "portuguese": "Sérvia e Kosovo", + "russian": "Сербия и Косово", + "chinese_traditional": "Serbia and Kosovo", + "unknown1": "Serbia and Kosovo", + "unknown2": "Serbia and Kosovo", + "unknown3": "Serbia and Kosovo", + "unknown4": "Serbia and Kosovo" + }, + "regions": [ + { + "id": 1694498816, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": 44.829711404, + "longitude": 20.484064490999998 + } + }, + { + "id": 1694564352, + "name": "Serbia and Kosovo", + "translations": { + "japanese": "セルビア・コソヴォ", + "english": "Serbia and Kosovo", + "french": "Serbie et Kosovo", + "german": "Serbien und Kosovo", + "italian": "Serbia e Kosovo", + "spanish": "Serbia y Kosovo", + "chinese_simple": "塞尔维亚及科索沃", + "korean": "세르비아 코소보", + "dutch": "Servië en Kosovo", + "portuguese": "Sérvia e Kosovo", + "russian": "Сербия и Косово", + "chinese_traditional": "Serbia and Kosovo", + "unknown1": "Serbia and Kosovo", + "unknown2": "Serbia and Kosovo", + "unknown3": "Serbia and Kosovo", + "unknown4": "Serbia and Kosovo" + }, + "coordinates": { + "latitude": 44.829711404, + "longitude": 20.484064490999998 + } + } + ] + }, + { + "id": 102, + "iso_code": "SK", + "name": "Slovakia", + "translations": { + "japanese": "スロバキア", + "english": "Slovakia", + "french": "Slovaquie", + "german": "Slowakei", + "italian": "Slovacchia", + "spanish": "Eslovaquia", + "chinese_simple": "斯洛伐克", + "korean": "슬로바키아", + "dutch": "Slowakije", + "portuguese": "Eslováquia", + "russian": "Словакия", + "chinese_traditional": "Slovakia", + "unknown1": "Slovakia", + "unknown2": "Slovakia", + "unknown3": "Slovakia", + "unknown4": "Slovakia" + }, + "regions": [ + { + "id": 1711276032, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": 48.14758246, + "longitude": 17.122238943 + } + }, + { + "id": 1711407104, + "name": "Bratislava", + "translations": { + "japanese": "ブラティスラバ", + "english": "Bratislava", + "french": "Bratislava", + "german": "Bratislava", + "italian": "Bratislava", + "spanish": "Bratislava", + "chinese_simple": "布拉迪斯拉发州", + "korean": "브라티슬라바", + "dutch": "Bratislava", + "portuguese": "Bratislava", + "russian": "Братиславский край", + "chinese_traditional": "Bratislava", + "unknown1": "Bratislava", + "unknown2": "Bratislava", + "unknown3": "Bratislava", + "unknown4": "Bratislava" + }, + "coordinates": { + "latitude": 48.14758246, + "longitude": 17.122238943 + } + }, + { + "id": 1711472640, + "name": "Banská Bystrica", + "translations": { + "japanese": "バンスカ・ビストリツァ", + "english": "Banská Bystrica", + "french": "Banská Bystrica", + "german": "Banská Bystrica", + "italian": "Banská Bystrica", + "spanish": "Banská Bystrica", + "chinese_simple": "班斯卡-比斯特里察州", + "korean": "반스카비스트리차", + "dutch": "Banská Bystrica", + "portuguese": "Banská Bystrica", + "russian": "Банскобистрицкий край", + "chinese_traditional": "Banská Bystrica", + "unknown1": "Banská Bystrica", + "unknown2": "Banská Bystrica", + "unknown3": "Banská Bystrica", + "unknown4": "Banská Bystrica" + }, + "coordinates": { + "latitude": 48.72436468, + "longitude": 19.143728815 + } + }, + { + "id": 1711538176, + "name": "Košice", + "translations": { + "japanese": "コシツェ", + "english": "Košice", + "french": "Košice", + "german": "Košice", + "italian": "Košice", + "spanish": "Košice", + "chinese_simple": "科希策州", + "korean": "코시체", + "dutch": "Košice", + "portuguese": "Košice", + "russian": "Кошицкий край", + "chinese_traditional": "Košice", + "unknown1": "Košice", + "unknown2": "Košice", + "unknown3": "Košice", + "unknown4": "Košice" + }, + "coordinates": { + "latitude": 48.718871516, + "longitude": 21.253109551 + } + }, + { + "id": 1711603712, + "name": "Nitra", + "translations": { + "japanese": "二トラ", + "english": "Nitra", + "french": "Nitra", + "german": "Nitra", + "italian": "Nitra", + "spanish": "Nitra", + "chinese_simple": "尼特拉州", + "korean": "니트라", + "dutch": "Nitra", + "portuguese": "Nitra", + "russian": "Нитранский край", + "chinese_traditional": "Nitra", + "unknown1": "Nitra", + "unknown2": "Nitra", + "unknown3": "Nitra", + "unknown4": "Nitra" + }, + "coordinates": { + "latitude": 48.301391052, + "longitude": 18.07255891 + } + }, + { + "id": 1711669248, + "name": "Prešov", + "translations": { + "japanese": "プレショフ", + "english": "Prešov", + "french": "Prešov", + "german": "Prešov", + "italian": "Prešov", + "spanish": "Prešov", + "chinese_simple": "普雷绍夫州", + "korean": "프레쇼프", + "dutch": "Prešov", + "portuguese": "Prešov", + "russian": "Прешовский край", + "chinese_traditional": "Prešov", + "unknown1": "Prešov", + "unknown2": "Prešov", + "unknown3": "Prešov", + "unknown4": "Prešov" + }, + "coordinates": { + "latitude": 48.966063896, + "longitude": 21.253109551 + } + }, + { + "id": 1711734784, + "name": "Trencín", + "translations": { + "japanese": "トレンチーン", + "english": "Trencín", + "french": "Trenčín", + "german": "Trenčín", + "italian": "Trenčín", + "spanish": "Trenčín", + "chinese_simple": "特伦钦州", + "korean": "트렌친", + "dutch": "Trenčín", + "portuguese": "Trencin", + "russian": "Тренчинский край", + "chinese_traditional": "Trencín", + "unknown1": "Trencín", + "unknown2": "Trencín", + "unknown3": "Trencín", + "unknown4": "Trencín" + }, + "coordinates": { + "latitude": 48.8891596, + "longitude": 18.050586194 + } + }, + { + "id": 1711800320, + "name": "Trnava", + "translations": { + "japanese": "トルナバ", + "english": "Trnava", + "french": "Trnava", + "german": "Trnava", + "italian": "Trnava", + "spanish": "Trnava", + "chinese_simple": "特尔纳瓦州", + "korean": "트르나바", + "dutch": "Trnava", + "portuguese": "Trnava", + "russian": "Трнавский край", + "chinese_traditional": "Trnava", + "unknown1": "Trnava", + "unknown2": "Trnava", + "unknown3": "Trnava", + "unknown4": "Trnava" + }, + "coordinates": { + "latitude": 48.372802184, + "longitude": 17.572679621 + } + }, + { + "id": 1711865856, + "name": "Žilina", + "translations": { + "japanese": "ジリナ", + "english": "Žilina", + "french": "Žilina", + "german": "Žilina", + "italian": "Žilina", + "spanish": "Žilina", + "chinese_simple": "日利纳州", + "korean": "질리나", + "dutch": "Žilina", + "portuguese": "Zilina", + "russian": "Жилинский край", + "chinese_traditional": "Žilina", + "unknown1": "Žilina", + "unknown2": "Žilina", + "unknown3": "Žilina", + "unknown4": "Žilina" + }, + "coordinates": { + "latitude": 49.21874944, + "longitude": 18.726247211 + } + } + ] + }, + { + "id": 103, + "iso_code": "SI", + "name": "Slovenia", + "translations": { + "japanese": "スロベニア", + "english": "Slovenia", + "french": "Slovénie", + "german": "Slowenien", + "italian": "Slovenia", + "spanish": "Eslovenia", + "chinese_simple": "斯洛文尼亚", + "korean": "슬로베니아", + "dutch": "Slovenië", + "portuguese": "Eslovénia", + "russian": "Словения", + "chinese_traditional": "Slovenia", + "unknown1": "Slovenia", + "unknown2": "Slovenia", + "unknown3": "Slovenia", + "unknown4": "Slovenia" + }, + "regions": [ + { + "id": 1728053248, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": 46.049193812, + "longitude": 14.50199256 + } + }, + { + "id": 1728118784, + "name": "Slovenia", + "translations": { + "japanese": "スロベニア", + "english": "Slovenia", + "french": "Slovénie", + "german": "Slowenien", + "italian": "Slovenia", + "spanish": "Eslovenia", + "chinese_simple": "斯洛文尼亚", + "korean": "슬로베니아", + "dutch": "Slovenië", + "portuguese": "Eslovénia", + "russian": "Словения", + "chinese_traditional": "Slovenia", + "unknown1": "Slovenia", + "unknown2": "Slovenia", + "unknown3": "Slovenia", + "unknown4": "Slovenia" + }, + "coordinates": { + "latitude": 46.049193812, + "longitude": 14.50199256 + } + } + ] + }, + { + "id": 104, + "iso_code": "ZA", + "name": "South Africa", + "translations": { + "japanese": "南アフリカ", + "english": "South Africa", + "french": "Afrique du Sud", + "german": "Südafrika", + "italian": "Sudafrica", + "spanish": "Sudáfrica", + "chinese_simple": "南非", + "korean": "남아프리카 공화국", + "dutch": "Zuid-Afrika", + "portuguese": "África do Sul", + "russian": "ЮАР", + "chinese_traditional": "South Africa", + "unknown1": "South Africa", + "unknown2": "South Africa", + "unknown3": "South Africa", + "unknown4": "South Africa" + }, + "regions": [ + { + "id": 1744830464, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": -26.141968499999997, + "longitude": 28.048171974 + } + }, + { + "id": 1744961536, + "name": "Gauteng", + "translations": { + "japanese": "ハウテン州", + "english": "Gauteng", + "french": "Gauteng", + "german": "Gauteng", + "italian": "Gauteng", + "spanish": "Gauteng", + "chinese_simple": "豪登省", + "korean": "하우텡 주", + "dutch": "Gauteng", + "portuguese": "Gauteng", + "russian": "Гаутенг ", + "chinese_traditional": "Gauteng", + "unknown1": "Gauteng", + "unknown2": "Gauteng", + "unknown3": "Gauteng", + "unknown4": "Gauteng" + }, + "coordinates": { + "latitude": -26.141968499999997, + "longitude": 28.048171974 + } + }, + { + "id": 1745027072, + "name": "Western Cape", + "translations": { + "japanese": "ウェスタン・ケープ州", + "english": "Western Cape", + "french": "Cap-Occidental", + "german": "Westkap", + "italian": "Capo Occidentale", + "spanish": "Cabo Occidental", + "chinese_simple": "西开普省", + "korean": "웨스턴케이프 주", + "dutch": "West-Kaap", + "portuguese": "Cabo Ocidental", + "russian": "Западно-Капская провинция", + "chinese_traditional": "Western Cape", + "unknown1": "Western Cape", + "unknown2": "Western Cape", + "unknown3": "Western Cape", + "unknown4": "Western Cape" + }, + "coordinates": { + "latitude": -33.920288724, + "longitude": 18.424122366 + } + }, + { + "id": 1745092608, + "name": "Northern Cape", + "translations": { + "japanese": "ノーザン・ケープ州", + "english": "Northern Cape", + "french": "Cap-du-Nord", + "german": "Nordkap", + "italian": "Capo Settentrionale", + "spanish": "Cabo Septentrional", + "chinese_simple": "北开普省", + "korean": "노던케이프 주", + "dutch": "Noord-Kaap", + "portuguese": "Cabo do Norte", + "russian": "Северо-Капская провинция", + "chinese_traditional": "Northern Cape", + "unknown1": "Northern Cape", + "unknown2": "Northern Cape", + "unknown3": "Northern Cape", + "unknown4": "Northern Cape" + }, + "coordinates": { + "latitude": -28.740235071999997, + "longitude": 24.77423729 + } + }, + { + "id": 1745158144, + "name": "Eastern Cape", + "translations": { + "japanese": "イースタン・ケープ州", + "english": "Eastern Cape", + "french": "Cap-Oriental", + "german": "Ostkap", + "italian": "Capo Orientale", + "spanish": "Cabo Oriental", + "chinese_simple": "东开普省", + "korean": "이스턴케이프 주", + "dutch": "Oost-Kaap", + "portuguese": "Cabo Oriental", + "russian": "Восточно-Капская провинция", + "chinese_traditional": "Eastern Cape", + "unknown1": "Eastern Cape", + "unknown2": "Eastern Cape", + "unknown3": "Eastern Cape", + "unknown4": "Eastern Cape" + }, + "coordinates": { + "latitude": -32.8710944, + "longitude": 27.383497315 + } + }, + { + "id": 1745223680, + "name": "KwaZulu-Natal", + "translations": { + "japanese": "クワズールー・ナタール州", + "english": "KwaZulu-Natal", + "french": "KwaZulu-Natal", + "german": "KwaZulu-Natal", + "italian": "KwaZulu-Natal", + "spanish": "KwaZulu-Natal", + "chinese_simple": "夸祖鲁-纳塔尔省", + "korean": "콰줄루나탈 주", + "dutch": "KwaZoeloe-Natal", + "portuguese": "KwaZulu-Natal", + "russian": "Квазулу-Натал", + "chinese_traditional": "KwaZulu-Natal", + "unknown1": "KwaZulu-Natal", + "unknown2": "KwaZulu-Natal", + "unknown3": "KwaZulu-Natal", + "unknown4": "KwaZulu-Natal" + }, + "coordinates": { + "latitude": -29.597168656, + "longitude": 30.382773049 + } + }, + { + "id": 1745289216, + "name": "Free State", + "translations": { + "japanese": "フリー・ステート州", + "english": "Free State", + "french": "État-Libre", + "german": "Freistaat", + "italian": "Stato Libero", + "spanish": "Estado Libre", + "chinese_simple": "自由邦省", + "korean": "프리스테이트 주", + "dutch": "Vrijstaat", + "portuguese": "Estado Livre", + "russian": "Фри-Стейт", + "chinese_traditional": "Free State", + "unknown1": "Free State", + "unknown2": "Free State", + "unknown3": "Free State", + "unknown4": "Free State" + }, + "coordinates": { + "latitude": -29.113770224, + "longitude": 26.218943367 + } + }, + { + "id": 1745354752, + "name": "North West", + "translations": { + "japanese": "ノース・ウェスト州", + "english": "North West", + "french": "Nord-Ouest", + "german": "Nordwest", + "italian": "Provincia del Nordovest", + "spanish": "Noroeste", + "chinese_simple": "西北省", + "korean": "노스웨스트 주", + "dutch": "Noordwest", + "portuguese": "Noroeste", + "russian": "Северо-Западная провинция", + "chinese_traditional": "North West", + "unknown1": "North West", + "unknown2": "North West", + "unknown3": "North West", + "unknown4": "North West" + }, + "coordinates": { + "latitude": -25.850830807999998, + "longitude": 25.647652751 + } + }, + { + "id": 1745420288, + "name": "Mpumalanga", + "translations": { + "japanese": "ムプマランガ州", + "english": "Mpumalanga", + "french": "Mpumalanga", + "german": "Mpumalanga", + "italian": "Mpumalanga", + "spanish": "Mpumalanga", + "chinese_simple": "普马兰加省", + "korean": "음푸말랑가 주", + "dutch": "Mpumalanga", + "portuguese": "Mpumalanga", + "russian": "Мпумаланга", + "chinese_traditional": "Mpumalanga", + "unknown1": "Mpumalanga", + "unknown2": "Mpumalanga", + "unknown3": "Mpumalanga", + "unknown4": "Mpumalanga" + }, + "coordinates": { + "latitude": -25.460816163999993, + "longitude": 30.98152956 + } + }, + { + "id": 1745485824, + "name": "Limpopo", + "translations": { + "japanese": "リンポポ州", + "english": "Limpopo", + "french": "Limpopo", + "german": "Limpopo", + "italian": "Limpopo", + "spanish": "Limpopo", + "chinese_simple": "林波波省", + "korean": "림포푸 주", + "dutch": "Limpopo", + "portuguese": "Limpopo", + "russian": "Лимпопо", + "chinese_traditional": "Limpopo", + "unknown1": "Limpopo", + "unknown2": "Limpopo", + "unknown3": "Limpopo", + "unknown4": "Limpopo" + }, + "coordinates": { + "latitude": -23.889771260000003, + "longitude": 29.437946261 + } + } + ] + }, + { + "id": 105, + "iso_code": "ES", + "name": "Spain", + "translations": { + "japanese": "スペイン", + "english": "Spain", + "french": "Espagne", + "german": "Spanien", + "italian": "Spagna", + "spanish": "España", + "chinese_simple": "西班牙", + "korean": "스페인", + "dutch": "Spanje", + "portuguese": "Espanha", + "russian": "Испания", + "chinese_traditional": "Spain", + "unknown1": "Spain", + "unknown2": "Spain", + "unknown3": "Spain", + "unknown4": "Spain" + }, + "regions": [ + { + "id": 1761607680, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": 40.413207548, + "longitude": -3.7019131739999978 + } + }, + { + "id": 1761738752, + "name": "Madrid", + "translations": { + "japanese": "マドリード州", + "english": "Madrid", + "french": "Madrid", + "german": "Madrid", + "italian": "Madrid", + "spanish": "Madrid", + "chinese_simple": "马德里自治区", + "korean": "마드리드 주", + "dutch": "Madrid", + "portuguese": "Madrid", + "russian": "Мадрид", + "chinese_traditional": "Madrid", + "unknown1": "Madrid", + "unknown2": "Madrid", + "unknown3": "Madrid", + "unknown4": "Madrid" + }, + "coordinates": { + "latitude": 40.413207548, + "longitude": -3.7019131739999978 + } + }, + { + "id": 1761804288, + "name": "Andalusia", + "translations": { + "japanese": "アンダルシーア州", + "english": "Andalusia", + "french": "Andalousie", + "german": "Andalusien", + "italian": "Andalusia", + "spanish": "Andalucía", + "chinese_simple": "安达卢西亚自治区", + "korean": "안달루시아 주", + "dutch": "Andalusië", + "portuguese": "Andaluzia", + "russian": "Андалусия", + "chinese_traditional": "Andalusia", + "unknown1": "Andalusia", + "unknown2": "Andalusia", + "unknown3": "Andalusia", + "unknown4": "Andalusia" + }, + "coordinates": { + "latitude": 37.38098102, + "longitude": -5.99806199599999 + } + }, + { + "id": 1761869824, + "name": "Aragon", + "translations": { + "japanese": "アラゴン州", + "english": "Aragon", + "french": "Aragon", + "german": "Aragonien", + "italian": "Aragona", + "spanish": "Aragón", + "chinese_simple": "阿拉贡自治区", + "korean": "아라곤 주", + "dutch": "Aragón", + "portuguese": "Aragão", + "russian": "Арагон", + "chinese_traditional": "Aragon", + "unknown1": "Aragon", + "unknown2": "Aragon", + "unknown3": "Aragon", + "unknown4": "Aragon" + }, + "coordinates": { + "latitude": 41.649169448, + "longitude": -0.8674328099999968 + } + }, + { + "id": 1761935360, + "name": "Principality of Asturias", + "translations": { + "japanese": "アストゥーリアス州", + "english": "Principality of Asturias", + "french": "Asturies", + "german": "Asturien", + "italian": "Principato delle Asturie", + "spanish": "Asturias", + "chinese_simple": "阿斯图利亚斯自治区", + "korean": "아스투리아스 주", + "dutch": "Asturië", + "portuguese": "Astúrias", + "russian": "Астурия", + "chinese_traditional": "Principality of Asturias", + "unknown1": "Principality of Asturias", + "unknown2": "Principality of Asturias", + "unknown3": "Principality of Asturias", + "unknown4": "Principality of Asturias" + }, + "coordinates": { + "latitude": 43.363036616, + "longitude": -5.833266626000011 + } + }, + { + "id": 1762000896, + "name": "Balearic Islands", + "translations": { + "japanese": "バレアーレス諸島", + "english": "Balearic Islands", + "french": "Îles Baléares", + "german": "Balearische Inseln", + "italian": "Baleari", + "spanish": "Illes Balears", + "chinese_simple": "巴利阿里自治区", + "korean": "발레아레스 제도", + "dutch": "Balearen", + "portuguese": "Ilhas Baleares", + "russian": "Балеарские острова", + "chinese_traditional": "Balearic Islands", + "unknown1": "Balearic Islands", + "unknown2": "Balearic Islands", + "unknown3": "Balearic Islands", + "unknown4": "Balearic Islands" + }, + "coordinates": { + "latitude": 39.567260292, + "longitude": 2.63672592 + } + }, + { + "id": 1762066432, + "name": "Canary Islands", + "translations": { + "japanese": "カナリア諸島", + "english": "Canary Islands", + "french": "Îles Canaries", + "german": "Kanarische Inseln", + "italian": "Canarie", + "spanish": "Canarias", + "chinese_simple": "加那利自治区", + "korean": "카나리아 제도", + "dutch": "Canarische Eilanden", + "portuguese": "Canárias", + "russian": "Канарские острова", + "chinese_traditional": "Canary Islands", + "unknown1": "Canary Islands", + "unknown2": "Canary Islands", + "unknown3": "Canary Islands", + "unknown4": "Canary Islands" + }, + "coordinates": { + "latitude": 28.119506516, + "longitude": -15.429850339000012 + } + }, + { + "id": 1762131968, + "name": "Cantabria", + "translations": { + "japanese": "カンタブリア州", + "english": "Cantabria", + "french": "Cantabrie", + "german": "Kantabrien", + "italian": "Cantabria", + "spanish": "Cantabria", + "chinese_simple": "坎塔布利亚自治区", + "korean": "칸타브리아 주", + "dutch": "Cantabrië", + "portuguese": "Cantábria", + "russian": "Кантабрия", + "chinese_traditional": "Cantabria", + "unknown1": "Cantabria", + "unknown2": "Cantabria", + "unknown3": "Cantabria", + "unknown4": "Cantabria" + }, + "coordinates": { + "latitude": 43.461913568, + "longitude": -3.8007903959999965 + } + }, + { + "id": 1762197504, + "name": "Castile-La Mancha", + "translations": { + "japanese": "カスティーリャ・ラ・マンチャ", + "english": "Castile-La Mancha", + "french": "Castille-La Manche", + "german": "Kastilien-La Mancha", + "italian": "Castiglia-La Mancha", + "spanish": "Castilla-La Mancha", + "chinese_simple": "卡斯蒂利亚-拉曼恰自治区", + "korean": "카스티야라만차", + "dutch": "Castilië-La Mancha", + "portuguese": "Castela-La Mancha", + "russian": "Кастилия-Ла-Манча", + "chinese_traditional": "Castile-La Mancha", + "unknown1": "Castile-La Mancha", + "unknown2": "Castile-La Mancha", + "unknown3": "Castile-La Mancha", + "unknown4": "Castile-La Mancha" + }, + "coordinates": { + "latitude": 39.85290482, + "longitude": -4.020517556000016 + } + }, + { + "id": 1762263040, + "name": "Castilla y León", + "translations": { + "japanese": "カスティーリャ・レオン", + "english": "Castilla y León", + "french": "Castille-et-León", + "german": "Kastilien-León", + "italian": "Castiglia e León", + "spanish": "Castilla y León", + "chinese_simple": "卡斯蒂利亚-莱昂自治区", + "korean": "카스티야이레온", + "dutch": "Castilië en León", + "portuguese": "Castela e Leão", + "russian": "Кастилия и Леон", + "chinese_traditional": "Castilla y León", + "unknown1": "Castilla y León", + "unknown2": "Castilla y León", + "unknown3": "Castilla y León", + "unknown4": "Castilla y León" + }, + "coordinates": { + "latitude": 41.63818312, + "longitude": -4.718151288999991 + } + }, + { + "id": 1762328576, + "name": "Catalonia", + "translations": { + "japanese": "カタルーニャ", + "english": "Catalonia", + "french": "Catalogne", + "german": "Katalonien", + "italian": "Catalogna", + "spanish": "Cataluña", + "chinese_simple": "加泰罗尼亚自治区", + "korean": "카탈루냐", + "dutch": "Catalonië", + "portuguese": "Catalunha", + "russian": "Каталония", + "chinese_traditional": "Catalonia", + "unknown1": "Catalonia", + "unknown2": "Catalonia", + "unknown3": "Catalonia", + "unknown4": "Catalonia" + }, + "coordinates": { + "latitude": 41.380004412, + "longitude": 2.169805705 + } + }, + { + "id": 1762394112, + "name": "Valencia", + "translations": { + "japanese": "バレンシア州", + "english": "Valencia", + "french": "Valence", + "german": "Valencia", + "italian": "Comunità Valenciana", + "spanish": "Comunitat Valenciana", + "chinese_simple": "巴伦西亚自治区", + "korean": "발렌시아 주", + "dutch": "Valencia", + "portuguese": "Valência", + "russian": "Валенсия", + "chinese_traditional": "Valencia", + "unknown1": "Valencia", + "unknown2": "Valencia", + "unknown3": "Valencia", + "unknown4": "Valencia" + }, + "coordinates": { + "latitude": 39.46838334, + "longitude": -0.3620603420000066 + } + }, + { + "id": 1762459648, + "name": "Extremadura", + "translations": { + "japanese": "エストレマドゥーラ", + "english": "Extremadura", + "french": "Estrémadure", + "german": "Extremadura", + "italian": "Estremadura", + "spanish": "Extremadura", + "chinese_simple": "埃斯特雷马杜拉自治区", + "korean": "에스트레마두라", + "dutch": "Extremadura", + "portuguese": "Estremadura", + "russian": "Эстремадура", + "chinese_traditional": "Extremadura", + "unknown1": "Extremadura", + "unknown2": "Extremadura", + "unknown3": "Extremadura", + "unknown4": "Extremadura" + }, + "coordinates": { + "latitude": 38.913573776, + "longitude": -6.338639094000001 + } + }, + { + "id": 1762525184, + "name": "Galicia", + "translations": { + "japanese": "ガリーシア", + "english": "Galicia", + "french": "Galice", + "german": "Galicien", + "italian": "Galizia", + "spanish": "Galicia", + "chinese_simple": "加利西亚自治区", + "korean": "갈리시아", + "dutch": "Galicië", + "portuguese": "Galiza", + "russian": "Галисия", + "chinese_traditional": "Galicia", + "unknown1": "Galicia", + "unknown2": "Galicia", + "unknown3": "Galicia", + "unknown4": "Galicia" + }, + "coordinates": { + "latitude": 42.87414502, + "longitude": -8.541403873000007 + } + }, + { + "id": 1762590720, + "name": "Murcia", + "translations": { + "japanese": "ムルシア州", + "english": "Murcia", + "french": "Murcie", + "german": "Murcia", + "italian": "Murcia", + "spanish": "Murcia", + "chinese_simple": "穆尔西亚自治区", + "korean": "무르시아 주", + "dutch": "Murcia", + "portuguese": "Múrcia", + "russian": "Мурсия", + "chinese_traditional": "Murcia", + "unknown1": "Murcia", + "unknown2": "Murcia", + "unknown3": "Murcia", + "unknown4": "Murcia" + }, + "coordinates": { + "latitude": 37.979735896, + "longitude": -1.1201190440000062 + } + }, + { + "id": 1762656256, + "name": "Navarre", + "translations": { + "japanese": "ナバーラ州", + "english": "Navarre", + "french": "Navarre", + "german": "Navarra", + "italian": "Navarra", + "spanish": "Navarra", + "chinese_simple": "纳瓦拉自治区", + "korean": "나바라 주", + "dutch": "Navarra", + "portuguese": "Navarra", + "russian": "Наварра", + "chinese_traditional": "Navarre", + "unknown1": "Navarre", + "unknown2": "Navarre", + "unknown3": "Navarre", + "unknown4": "Navarre" + }, + "coordinates": { + "latitude": 42.813720216, + "longitude": -1.6474642279999898 + } + }, + { + "id": 1762721792, + "name": "Basque Country", + "translations": { + "japanese": "バスク", + "english": "Basque Country", + "french": "Pays basque", + "german": "Baskenland", + "italian": "Paesi Baschi", + "spanish": "País Vasco", + "chinese_simple": "巴斯克自治区", + "korean": "바스크", + "dutch": "Baskenland", + "portuguese": "País Basco", + "russian": "Страна Басков", + "chinese_traditional": "Basque Country", + "unknown1": "Basque Country", + "unknown2": "Basque Country", + "unknown3": "Basque Country", + "unknown4": "Basque Country" + }, + "coordinates": { + "latitude": 42.8466792, + "longitude": -2.6691955219999954 + } + }, + { + "id": 1762787328, + "name": "La Rioja", + "translations": { + "japanese": "ラ・リオハ州", + "english": "La Rioja", + "french": "La Rioja", + "german": "La Rioja", + "italian": "La Rioja", + "spanish": "La Rioja", + "chinese_simple": "拉里奥哈自治区", + "korean": "라리오하", + "dutch": "La Rioja", + "portuguese": "La Rioja", + "russian": "Риоха", + "chinese_traditional": "La Rioja", + "unknown1": "La Rioja", + "unknown2": "La Rioja", + "unknown3": "La Rioja", + "unknown4": "La Rioja" + }, + "coordinates": { + "latitude": 42.46215772, + "longitude": -2.4494683620000046 + } + }, + { + "id": 1762852864, + "name": "Ceuta", + "translations": { + "japanese": "セウタ", + "english": "Ceuta", + "french": "Ceuta", + "german": "Ceuta", + "italian": "Ceuta", + "spanish": "Ceuta", + "chinese_simple": "休达", + "korean": "세우타", + "dutch": "Ceuta", + "portuguese": "Ceuta", + "russian": "Сеута", + "chinese_traditional": "Ceuta", + "unknown1": "Ceuta", + "unknown2": "Ceuta", + "unknown3": "Ceuta", + "unknown4": "Ceuta" + }, + "coordinates": { + "latitude": 35.881347248, + "longitude": -5.3114146210000115 + } + }, + { + "id": 1762918400, + "name": "Melilla", + "translations": { + "japanese": "メリラ", + "english": "Melilla", + "french": "Melilla", + "german": "Melilla", + "italian": "Melilla", + "spanish": "Melilla", + "chinese_simple": "梅利利亚", + "korean": "멜리야", + "dutch": "Melilla", + "portuguese": "Melilla", + "russian": "Мелилья", + "chinese_traditional": "Melilla", + "unknown1": "Melilla", + "unknown2": "Melilla", + "unknown3": "Melilla", + "unknown4": "Melilla" + }, + "coordinates": { + "latitude": 35.299071864, + "longitude": -2.9493476510000107 + } + } + ] + }, + { + "id": 106, + "iso_code": "SZ", + "name": "Swaziland", + "translations": { + "japanese": "スワジランド", + "english": "Swaziland", + "french": "Swaziland", + "german": "Swasiland", + "italian": "Swaziland", + "spanish": "Suazilandia", + "chinese_simple": "斯威士兰", + "korean": "스와질란드", + "dutch": "Swaziland", + "portuguese": "Suazilândia", + "russian": "Свазиленд", + "chinese_traditional": "Swaziland", + "unknown1": "Swaziland", + "unknown2": "Swaziland", + "unknown3": "Swaziland", + "unknown4": "Swaziland" + }, + "regions": [ + { + "id": 1778384896, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": -26.317749747999997, + "longitude": 31.135338572 + } + }, + { + "id": 1778515968, + "name": "Hhohho", + "translations": { + "japanese": "ホホ", + "english": "Hhohho", + "french": "Hhohho", + "german": "Hhohho", + "italian": "Hhohho", + "spanish": "Hhohho", + "chinese_simple": "霍霍区", + "korean": "호호", + "dutch": "Hhohho", + "portuguese": "Hhohho", + "russian": "Хохо", + "chinese_traditional": "Hhohho", + "unknown1": "Hhohho", + "unknown2": "Hhohho", + "unknown3": "Hhohho", + "unknown4": "Hhohho" + }, + "coordinates": { + "latitude": -26.317749747999997, + "longitude": 31.135338572 + } + }, + { + "id": 1778581504, + "name": "Lubombo", + "translations": { + "japanese": "ルボンボ", + "english": "Lubombo", + "french": "Lubombo", + "german": "Lubombo", + "italian": "Lubombo", + "spanish": "Lubombo", + "chinese_simple": "卢邦博区", + "korean": "로밤바", + "dutch": "Lubombo", + "portuguese": "Lubombo", + "russian": "Лубомбо", + "chinese_traditional": "Lubombo", + "unknown1": "Lubombo", + "unknown2": "Lubombo", + "unknown3": "Lubombo", + "unknown4": "Lubombo" + }, + "coordinates": { + "latitude": -26.449585684, + "longitude": 31.948329064 + } + }, + { + "id": 1778647040, + "name": "Manzini", + "translations": { + "japanese": "マンジニ", + "english": "Manzini", + "french": "Manzini", + "german": "Manzini", + "italian": "Manzini", + "spanish": "Manzini", + "chinese_simple": "曼齐尼区", + "korean": "만지니", + "dutch": "Manzini", + "portuguese": "Manzini", + "russian": "Манзини", + "chinese_traditional": "Manzini", + "unknown1": "Manzini", + "unknown2": "Manzini", + "unknown3": "Manzini", + "unknown4": "Manzini" + }, + "coordinates": { + "latitude": -26.488037831999996, + "longitude": 31.382531627 + } + }, + { + "id": 1778712576, + "name": "Shiselweni", + "translations": { + "japanese": "シセルウェニ", + "english": "Shiselweni", + "french": "Shiselweni", + "german": "Shiselweni", + "italian": "Shiselweni", + "spanish": "Shiselweni", + "chinese_simple": "希塞卢韦尼区", + "korean": "시셀웨니", + "dutch": "Shiselweni", + "portuguese": "Shiselweni", + "russian": "Шиселвени", + "chinese_traditional": "Shiselweni", + "unknown1": "Shiselweni", + "unknown2": "Shiselweni", + "unknown3": "Shiselweni", + "unknown4": "Shiselweni" + }, + "coordinates": { + "latitude": -27.1032722, + "longitude": 31.20125672 + } + } + ] + }, + { + "id": 107, + "iso_code": "SE", + "name": "Sweden", + "translations": { + "japanese": "スウェーデン", + "english": "Sweden", + "french": "Suède", + "german": "Schweden", + "italian": "Svezia", + "spanish": "Suecia", + "chinese_simple": "瑞典", + "korean": "스웨덴", + "dutch": "Zweden", + "portuguese": "Suécia", + "russian": "Швеция", + "chinese_traditional": "Sweden", + "unknown1": "Sweden", + "unknown2": "Sweden", + "unknown3": "Sweden", + "unknown4": "Sweden" + }, + "regions": [ + { + "id": 1795162112, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": 59.282225888, + "longitude": 18.07255891 + } + }, + { + "id": 1795293184, + "name": "Stockholm County", + "translations": { + "japanese": "ストックホルム州", + "english": "Stockholm County", + "french": "Stockholm", + "german": "Stockholms län", + "italian": "Stoccolma", + "spanish": "Estocolmo", + "chinese_simple": "斯德哥尔摩省", + "korean": "스톡홀름 주", + "dutch": "Stockholms län", + "portuguese": "Estocolmo", + "russian": "Лен Стокгольм", + "chinese_traditional": "Stockholm County", + "unknown1": "Stockholm County", + "unknown2": "Stockholm County", + "unknown3": "Stockholm County", + "unknown4": "Stockholm County" + }, + "coordinates": { + "latitude": 59.282225888, + "longitude": 18.07255891 + } + }, + { + "id": 1795358720, + "name": "Skåne County", + "translations": { + "japanese": "スコーネ州", + "english": "Skåne County", + "french": "Skåne", + "german": "Skåne län", + "italian": "Scania", + "spanish": "Escania", + "chinese_simple": "斯科耐省", + "korean": "스코네 주", + "dutch": "Skåne län", + "portuguese": "Escânia", + "russian": "Лен Сконе", + "chinese_traditional": "Skåne County", + "unknown1": "Skåne County", + "unknown2": "Skåne County", + "unknown3": "Skåne County", + "unknown4": "Skåne County" + }, + "coordinates": { + "latitude": 55.596312844, + "longitude": 12.996861513999999 + } + }, + { + "id": 1795424256, + "name": "Västra Götaland County", + "translations": { + "japanese": "ヴェストラ・イェータランド州", + "english": "Västra Götaland County", + "french": "Västra Götaland", + "german": "Västra Götalands län", + "italian": "Västra Götaland", + "spanish": "Västra Götaland", + "chinese_simple": "西约特兰省", + "korean": "베스트라예탈란드 주", + "dutch": "Västra Götalands län", + "portuguese": "Västra Götaland", + "russian": "Лен Вестра-Гёталанд", + "chinese_traditional": "Västra Götaland County", + "unknown1": "Västra Götaland County", + "unknown2": "Västra Götaland County", + "unknown3": "Västra Götaland County", + "unknown4": "Västra Götaland County" + }, + "coordinates": { + "latitude": 57.70568782, + "longitude": 11.969637041 + } + }, + { + "id": 1795489792, + "name": "Östergötland County", + "translations": { + "japanese": "エステルイェトランド州", + "english": "Östergötland County", + "french": "Östergötland", + "german": "Östergötlands län", + "italian": "Östergötland", + "spanish": "Östergötland", + "chinese_simple": "东约特兰省", + "korean": "외스테르예틀란드 주", + "dutch": "Östergötlands län", + "portuguese": "Östergötland", + "russian": "Лен Эстергётланд", + "chinese_traditional": "Östergötland County", + "unknown1": "Östergötland County", + "unknown2": "Östergötland County", + "unknown3": "Östergötland County", + "unknown4": "Östergötland County" + }, + "coordinates": { + "latitude": 58.397826484, + "longitude": 15.633587434 + } + }, + { + "id": 1795555328, + "name": "Södermanland County", + "translations": { + "japanese": "セーデルマンランド州", + "english": "Södermanland County", + "french": "Södermanland", + "german": "Södermanlands län", + "italian": "Södermanland", + "spanish": "Södermanland", + "chinese_simple": "南曼兰省", + "korean": "쇠데르만란드 주", + "dutch": "Södermanlands län", + "portuguese": "Södermanland", + "russian": "Лен Сёдерманланд", + "chinese_traditional": "Södermanland County", + "unknown1": "Södermanland County", + "unknown2": "Södermanland County", + "unknown3": "Södermanland County", + "unknown4": "Södermanland County" + }, + "coordinates": { + "latitude": 58.754882144, + "longitude": 17.012375363 + } + }, + { + "id": 1795620864, + "name": "Värmland County", + "translations": { + "japanese": "ベルムランド州", + "english": "Värmland County", + "french": "Värmland", + "german": "Värmlands län", + "italian": "Värmland", + "spanish": "Värmland", + "chinese_simple": "韦姆兰省", + "korean": "베름란드 주", + "dutch": "Värmlands län", + "portuguese": "Värmland", + "russian": "Лен Вермланд", + "chinese_traditional": "Värmland County", + "unknown1": "Värmland County", + "unknown2": "Värmland County", + "unknown3": "Värmland County", + "unknown4": "Värmland County" + }, + "coordinates": { + "latitude": 59.386596004, + "longitude": 13.502233982 + } + }, + { + "id": 1795686400, + "name": "Uppsala County", + "translations": { + "japanese": "ウプサラ州", + "english": "Uppsala County", + "french": "Uppsala", + "german": "Uppsala län", + "italian": "Uppsala", + "spanish": "Uppsala", + "chinese_simple": "乌普萨拉省", + "korean": "웁살라 주", + "dutch": "Uppsala län", + "portuguese": "Uppsala", + "russian": "Лен Уппсала", + "chinese_traditional": "Uppsala County", + "unknown1": "Uppsala County", + "unknown2": "Uppsala County", + "unknown3": "Uppsala County", + "unknown4": "Uppsala County" + }, + "coordinates": { + "latitude": 59.853514944, + "longitude": 17.63310459 + } + }, + { + "id": 1795751936, + "name": "Gävleborg County", + "translations": { + "japanese": "イェーブレボリ州", + "english": "Gävleborg County", + "french": "Gävleborg", + "german": "Gävleborgs län", + "italian": "Gävleborg", + "spanish": "Gävleborg", + "chinese_simple": "耶夫勒堡省", + "korean": "예블레보리 주", + "dutch": "Gävleborgs län", + "portuguese": "Gävleborg", + "russian": "Лен Евлеборг", + "chinese_traditional": "Gävleborg County", + "unknown1": "Gävleborg County", + "unknown2": "Gävleborg County", + "unknown3": "Gävleborg County", + "unknown4": "Gävleborg County" + }, + "coordinates": { + "latitude": 60.67199638, + "longitude": 17.182663912 + } + }, + { + "id": 1795817472, + "name": "Västerbotten County", + "translations": { + "japanese": "ベステルボッテン州", + "english": "Västerbotten County", + "french": "Västerbotten", + "german": "Västerbottens län", + "italian": "Västerbotten", + "spanish": "Västerbotten", + "chinese_simple": "西博滕省", + "korean": "베스테르보텐 주", + "dutch": "Västerbottens län", + "portuguese": "Västerbotten", + "russian": "Лен Вестерботтен", + "chinese_traditional": "Västerbotten County", + "unknown1": "Västerbotten County", + "unknown2": "Västerbotten County", + "unknown3": "Västerbotten County", + "unknown4": "Västerbotten County" + }, + "coordinates": { + "latitude": 63.819579352, + "longitude": 20.247857794 + } + }, + { + "id": 1795883008, + "name": "Norrbotten County", + "translations": { + "japanese": "ノルボッテン州", + "english": "Norrbotten County", + "french": "Norrbotten", + "german": "Norrbottens län", + "italian": "Norrbotten", + "spanish": "Norrbotten", + "chinese_simple": "北博滕省", + "korean": "노르보텐 주", + "dutch": "Norrbottens län", + "portuguese": "Norrbotten", + "russian": "Лен Норрботтен", + "chinese_traditional": "Norrbotten County", + "unknown1": "Norrbotten County", + "unknown2": "Norrbotten County", + "unknown3": "Norrbotten County", + "unknown4": "Norrbotten County" + }, + "coordinates": { + "latitude": 65.577391832, + "longitude": 22.214415876 + } + }, + { + "id": 1795948544, + "name": "Gotland Island", + "translations": { + "japanese": "ゴトランド州", + "english": "Gotland Island", + "french": "Gotland", + "german": "Gotlands Iän", + "italian": "Gotland", + "spanish": "Gotland", + "chinese_simple": "哥得兰省", + "korean": "고틀란드 주", + "dutch": "Gotlands län", + "portuguese": "Gotland", + "russian": "Готланд", + "chinese_traditional": "Gotland Island", + "unknown1": "Gotland Island", + "unknown2": "Gotland Island", + "unknown3": "Gotland Island", + "unknown4": "Gotland Island" + }, + "coordinates": { + "latitude": 57.634276688, + "longitude": 18.286792891 + } + }, + { + "id": 1796014080, + "name": "Jämtland County", + "translations": { + "japanese": "イェムトランド州", + "english": "Jämtland County", + "french": "Jämtland", + "german": "Jämtlands län", + "italian": "Jämtland", + "spanish": "Jämtland", + "chinese_simple": "耶姆特兰省", + "korean": "옘틀란드 주", + "dutch": "Jämtlands län", + "portuguese": "Jämtland", + "russian": "Лен Емтланд", + "chinese_traditional": "Jämtland County", + "unknown1": "Jämtland County", + "unknown2": "Jämtland County", + "unknown3": "Jämtland County", + "unknown4": "Jämtland County" + }, + "coordinates": { + "latitude": 63.165892836, + "longitude": 14.650308393 + } + }, + { + "id": 1796079616, + "name": "Dalarna County", + "translations": { + "japanese": "ダーラナ州", + "english": "Dalarna County", + "french": "Dalarna", + "german": "Dalarnas län", + "italian": "Dalarna", + "spanish": "Dalarna", + "chinese_simple": "达拉纳省", + "korean": "달라르나 주", + "dutch": "Dalarnas län", + "portuguese": "Dalecarlia", + "russian": "Лен Даларна", + "chinese_traditional": "Dalarna County", + "unknown1": "Dalarna County", + "unknown2": "Dalarna County", + "unknown3": "Dalarna County", + "unknown4": "Dalarna County" + }, + "coordinates": { + "latitude": 60.611571576, + "longitude": 15.644573792 + } + }, + { + "id": 1796145152, + "name": "Blekinge County", + "translations": { + "japanese": "ブレーキンゲ州", + "english": "Blekinge County", + "french": "Blekinge", + "german": "Blekinge län", + "italian": "Blekinge", + "spanish": "Blekinge", + "chinese_simple": "布莱金厄省", + "korean": "블레킹에 주", + "dutch": "Blekinge län", + "portuguese": "Blekinge", + "russian": "Лен Блекинге", + "chinese_traditional": "Blekinge County", + "unknown1": "Blekinge County", + "unknown2": "Blekinge County", + "unknown3": "Blekinge County", + "unknown4": "Blekinge County" + }, + "coordinates": { + "latitude": 56.1676019, + "longitude": 15.584148823 + } + }, + { + "id": 1796210688, + "name": "Örebro County", + "translations": { + "japanese": "エレブルー州", + "english": "Örebro County", + "french": "Örebro", + "german": "Örebro län", + "italian": "Örebro", + "spanish": "Örebro", + "chinese_simple": "厄勒布鲁省", + "korean": "외레브로 주", + "dutch": "Örebro län", + "portuguese": "Örebro", + "russian": "Лен Эребру", + "chinese_traditional": "Örebro County", + "unknown1": "Örebro County", + "unknown2": "Örebro County", + "unknown3": "Örebro County", + "unknown4": "Örebro County" + }, + "coordinates": { + "latitude": 59.27123956, + "longitude": 15.221599009 + } + }, + { + "id": 1796276224, + "name": "Västernorrland County", + "translations": { + "japanese": "ベステルノルランド州", + "english": "Västernorrland County", + "french": "Västernorrland", + "german": "Västernorrlands län", + "italian": "Västernorrland", + "spanish": "Västernorrland", + "chinese_simple": "西诺尔兰省", + "korean": "베스테르노를란드 주", + "dutch": "Västernorrlands län", + "portuguese": "Västernorrland", + "russian": "Лен Вестерноррланд", + "chinese_traditional": "Västernorrland County", + "unknown1": "Västernorrland County", + "unknown2": "Västernorrland County", + "unknown3": "Västernorrland County", + "unknown4": "Västernorrland County" + }, + "coordinates": { + "latitude": 62.627562764000004, + "longitude": 17.929736255999998 + } + }, + { + "id": 1796341760, + "name": "Jönköping County", + "translations": { + "japanese": "イェンチェピング州", + "english": "Jönköping County", + "french": "Jönköping", + "german": "Jönköpings län", + "italian": "Jönköping", + "spanish": "Jönköping", + "chinese_simple": "延雪平省", + "korean": "옌셰핑 주", + "dutch": "Jönköpings län", + "portuguese": "Jönköping", + "russian": "Лен Йёнчёпинг", + "chinese_traditional": "Jönköping County", + "unknown1": "Jönköping County", + "unknown2": "Jönköping County", + "unknown3": "Jönköping County", + "unknown4": "Jönköping County" + }, + "coordinates": { + "latitude": 57.771605788, + "longitude": 14.166908641 + } + }, + { + "id": 1796407296, + "name": "Kronoberg County", + "translations": { + "japanese": "クロノベリ州", + "english": "Kronoberg County", + "french": "Kronoberg", + "german": "Kronobergs län", + "italian": "Kronoberg", + "spanish": "Kronoberg", + "chinese_simple": "克鲁努贝里省", + "korean": "크로노베리 주", + "dutch": "Kronobergs län", + "portuguese": "Kronoberg", + "russian": "Лен Крунуберг", + "chinese_traditional": "Kronoberg County", + "unknown1": "Kronoberg County", + "unknown2": "Kronoberg County", + "unknown3": "Kronoberg County", + "unknown4": "Kronoberg County" + }, + "coordinates": { + "latitude": 56.88171322, + "longitude": 14.815103763 + } + }, + { + "id": 1796472832, + "name": "Kalmar County", + "translations": { + "japanese": "カルマル州", + "english": "Kalmar County", + "french": "Kalmar", + "german": "Kalmar län", + "italian": "Kalmar", + "spanish": "Kalmar", + "chinese_simple": "卡尔马省", + "korean": "칼마르 주", + "dutch": "Kalmar län", + "portuguese": "Kalmar", + "russian": "Лен Кальмар", + "chinese_traditional": "Kalmar County", + "unknown1": "Kalmar County", + "unknown2": "Kalmar County", + "unknown3": "Kalmar County", + "unknown4": "Kalmar County" + }, + "coordinates": { + "latitude": 56.661986660000004, + "longitude": 16.364180241 + } + }, + { + "id": 1796538368, + "name": "Västmanland County", + "translations": { + "japanese": "ベストマンランド州", + "english": "Västmanland County", + "french": "Västmanland", + "german": "Västmanlands län", + "italian": "Västmanland", + "spanish": "Västmanland", + "chinese_simple": "西曼兰省", + "korean": "베스트만란드 주", + "dutch": "Västmanlands län", + "portuguese": "Västmanland", + "russian": "Лен Вестманланд", + "chinese_traditional": "Västmanland County", + "unknown1": "Västmanland County", + "unknown2": "Västmanland County", + "unknown3": "Västmanland County", + "unknown4": "Västmanland County" + }, + "coordinates": { + "latitude": 59.611815728, + "longitude": 16.550948327 + } + }, + { + "id": 1796603904, + "name": "Halland County", + "translations": { + "japanese": "ハランド州", + "english": "Halland County", + "french": "Halland", + "german": "Hallands län", + "italian": "Halland", + "spanish": "Halland", + "chinese_simple": "哈兰省", + "korean": "할란드 주", + "dutch": "Hallands län", + "portuguese": "Halland", + "russian": "Лен Халланд", + "chinese_traditional": "Halland County", + "unknown1": "Halland County", + "unknown2": "Halland County", + "unknown3": "Halland County", + "unknown4": "Halland County" + }, + "coordinates": { + "latitude": 56.656493496, + "longitude": 12.859532039 + } + } + ] + }, + { + "id": 108, + "iso_code": "CH", + "name": "Switzerland", + "translations": { + "japanese": "スイス", + "english": "Switzerland", + "french": "Suisse", + "german": "Schweiz", + "italian": "Svizzera", + "spanish": "Suiza", + "chinese_simple": "瑞士", + "korean": "스위스", + "dutch": "Zwitserland", + "portuguese": "Suíça", + "russian": "Швейцария", + "chinese_traditional": "Switzerland", + "unknown1": "Switzerland", + "unknown2": "Switzerland", + "unknown3": "Switzerland", + "unknown4": "Switzerland" + }, + "regions": [ + { + "id": 1811939328, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": 46.950072708, + "longitude": 7.41579165 + } + }, + { + "id": 1812070400, + "name": "Bern", + "translations": { + "japanese": "ベルン州", + "english": "Bern", + "french": "Berne", + "german": "Bern", + "italian": "Berna", + "spanish": "Berna", + "chinese_simple": "伯尔尼州", + "korean": "베른 주", + "dutch": "Bern", + "portuguese": "Berna", + "russian": "Берн", + "chinese_traditional": "Bern", + "unknown1": "Bern", + "unknown2": "Bern", + "unknown3": "Bern", + "unknown4": "Bern" + }, + "coordinates": { + "latitude": 46.950072708, + "longitude": 7.41579165 + } + }, + { + "id": 1812201472, + "name": "Aargau", + "translations": { + "japanese": "アールガウ州", + "english": "Aargau", + "french": "Argovie", + "german": "Aargau", + "italian": "Argovia", + "spanish": "Argovia", + "chinese_simple": "阿尔高州", + "korean": "아르가우 주", + "dutch": "Aargau", + "portuguese": "Argóvia", + "russian": "Ааргау", + "chinese_traditional": "Aargau", + "unknown1": "Aargau", + "unknown2": "Aargau", + "unknown3": "Aargau", + "unknown4": "Aargau" + }, + "coordinates": { + "latitude": 47.389525828000004, + "longitude": 8.042014056 + } + }, + { + "id": 1812267008, + "name": "Basel-City", + "translations": { + "japanese": "バーゼル=シュタット準州", + "english": "Basel-City", + "french": "Bâle-Ville", + "german": "Basel-Stadt", + "italian": "Basilea Città", + "spanish": "Ciudad de Basilea", + "chinese_simple": "巴塞尔州", + "korean": "바젤", + "dutch": "Bazel-Stad", + "portuguese": "Basileia", + "russian": "Базель", + "chinese_traditional": "Basel-City", + "unknown1": "Basel-City", + "unknown2": "Basel-City", + "unknown3": "Basel-City", + "unknown4": "Basel-City" + }, + "coordinates": { + "latitude": 47.537841256, + "longitude": 7.5860801989999995 + } + }, + { + "id": 1812332544, + "name": "Fribourg", + "translations": { + "japanese": "フリブール州", + "english": "Fribourg", + "french": "Fribourg", + "german": "Freiburg", + "italian": "Friburgo", + "spanish": "Friburgo", + "chinese_simple": "弗里堡州", + "korean": "프리부르 주", + "dutch": "Fribourg", + "portuguese": "Friburgo", + "russian": "Фрибур", + "chinese_traditional": "Fribourg", + "unknown1": "Fribourg", + "unknown2": "Fribourg", + "unknown3": "Fribourg", + "unknown4": "Fribourg" + }, + "coordinates": { + "latitude": 46.80175728, + "longitude": 7.1576122369999995 + } + }, + { + "id": 1812398080, + "name": "Geneva", + "translations": { + "japanese": "ジュネーヴ州", + "english": "Geneva", + "french": "Genève", + "german": "Genf", + "italian": "Ginevra", + "spanish": "Ginebra", + "chinese_simple": "日内瓦州", + "korean": "제네바 주", + "dutch": "Genève", + "portuguese": "Genebra", + "russian": "Женева", + "chinese_traditional": "Geneva", + "unknown1": "Geneva", + "unknown2": "Geneva", + "unknown3": "Geneva", + "unknown4": "Geneva" + }, + "coordinates": { + "latitude": 46.19750924, + "longitude": 6.168840017 + } + }, + { + "id": 1812463616, + "name": "Glarus", + "translations": { + "japanese": "グラールス州", + "english": "Glarus", + "french": "Glaris", + "german": "Glarus", + "italian": "Glarona", + "spanish": "Glaris", + "chinese_simple": "格拉鲁斯州", + "korean": "글라루스 주", + "dutch": "Glarus", + "portuguese": "Glaris", + "russian": "Гларус", + "chinese_traditional": "Glarus", + "unknown1": "Glarus", + "unknown2": "Glarus", + "unknown3": "Glarus", + "unknown4": "Glarus" + }, + "coordinates": { + "latitude": 47.032470168, + "longitude": 9.069238529 + } + }, + { + "id": 1812529152, + "name": "Graubünden", + "translations": { + "japanese": "グラウビュンデン州", + "english": "Graubünden", + "french": "Grisons", + "german": "Graubünden", + "italian": "Grigioni", + "spanish": "Grisones", + "chinese_simple": "格劳宾登州", + "korean": "그라우뷘덴 주", + "dutch": "Graubünden", + "portuguese": "Grisons", + "russian": "Граубюнден", + "chinese_traditional": "Graubünden", + "unknown1": "Graubünden", + "unknown2": "Graubünden", + "unknown3": "Graubünden", + "unknown4": "Graubünden" + }, + "coordinates": { + "latitude": 46.85668892, + "longitude": 9.536158744 + } + }, + { + "id": 1812594688, + "name": "Jura", + "translations": { + "japanese": "ジュラ州", + "english": "Jura", + "french": "Jura", + "german": "Jura", + "italian": "Giura", + "spanish": "Jura", + "chinese_simple": "汝拉州", + "korean": "쥐라 주", + "dutch": "Jura", + "portuguese": "Jura", + "russian": "Юра", + "chinese_traditional": "Jura", + "unknown1": "Jura", + "unknown2": "Jura", + "unknown3": "Jura", + "unknown4": "Jura" + }, + "coordinates": { + "latitude": 47.362060008, + "longitude": 7.344380323 + } + }, + { + "id": 1812660224, + "name": "Luzern", + "translations": { + "japanese": "ルツェルン州", + "english": "Luzern", + "french": "Lucerne", + "german": "Luzern", + "italian": "Lucerna", + "spanish": "Lucerna", + "chinese_simple": "卢塞恩州", + "korean": "루체른 주", + "dutch": "Luzern", + "portuguese": "Lucerna", + "russian": "Люцерн", + "chinese_traditional": "Luzern", + "unknown1": "Luzern", + "unknown2": "Luzern", + "unknown3": "Luzern", + "unknown4": "Luzern" + }, + "coordinates": { + "latitude": 47.04894966, + "longitude": 8.305686648 + } + }, + { + "id": 1812725760, + "name": "Neuchâtel", + "translations": { + "japanese": "ヌシャテル州", + "english": "Neuchâtel", + "french": "Neuchâtel", + "german": "Neuenburg", + "italian": "Neuchâtel", + "spanish": "Neuchâtel", + "chinese_simple": "纳沙泰尔州", + "korean": "뇌샤텔 주", + "dutch": "Neuchâtel", + "portuguese": "Neuchâtel", + "russian": "Нёвшатель", + "chinese_traditional": "Neuchâtel", + "unknown1": "Neuchâtel", + "unknown2": "Neuchâtel", + "unknown3": "Neuchâtel", + "unknown4": "Neuchâtel" + }, + "coordinates": { + "latitude": 46.950072708, + "longitude": 6.932391898 + } + }, + { + "id": 1812791296, + "name": "Obwalden", + "translations": { + "japanese": "オプバルデン準州", + "english": "Obwalden", + "french": "Obwald", + "german": "Obwalden", + "italian": "Obvaldo", + "spanish": "Obwalden", + "chinese_simple": "上瓦尔登半州", + "korean": "옵발덴 주", + "dutch": "Obwalden", + "portuguese": "Obwald", + "russian": "Обвальден", + "chinese_traditional": "Obwalden", + "unknown1": "Obwalden", + "unknown2": "Obwalden", + "unknown3": "Obwalden", + "unknown4": "Obwalden" + }, + "coordinates": { + "latitude": 46.88415474, + "longitude": 8.234275321 + } + }, + { + "id": 1812856832, + "name": "St. Gallen", + "translations": { + "japanese": "ザンクト・ガレン州", + "english": "St. Gallen", + "french": "Saint-Gall", + "german": "Sankt Gallen", + "italian": "San Gallo", + "spanish": "Sankt Gallen", + "chinese_simple": "圣加仑州", + "korean": "장크트갈렌 주", + "dutch": "Sankt Gallen", + "portuguese": "Saint-Gall", + "russian": "Санкт-Галлен", + "chinese_traditional": "St. Gallen", + "unknown1": "St. Gallen", + "unknown2": "St. Gallen", + "unknown3": "St. Gallen", + "unknown4": "St. Gallen" + }, + "coordinates": { + "latitude": 47.422484812, + "longitude": 9.371363374 + } + }, + { + "id": 1812922368, + "name": "Schaffhausen", + "translations": { + "japanese": "シャフハウゼン州", + "english": "Schaffhausen", + "french": "Schaffhouse", + "german": "Schaffhausen", + "italian": "Sciaffusa", + "spanish": "Schaffhausen", + "chinese_simple": "沙夫豪森州", + "korean": "샤프하우젠 주", + "dutch": "Schaffhausen", + "portuguese": "Schaffhausen", + "russian": "Шаффхаузен", + "chinese_traditional": "Schaffhausen", + "unknown1": "Schaffhausen", + "unknown2": "Schaffhausen", + "unknown3": "Schaffhausen", + "unknown4": "Schaffhausen" + }, + "coordinates": { + "latitude": 47.697143012, + "longitude": 8.629784209 + } + }, + { + "id": 1812987904, + "name": "Schwyz", + "translations": { + "japanese": "シュビーツ州", + "english": "Schwyz", + "french": "Schwytz", + "german": "Schwyz", + "italian": "Svitto", + "spanish": "Schwyz", + "chinese_simple": "施维茨州", + "korean": "슈비츠 주", + "dutch": "Schwyz", + "portuguese": "Schwyz", + "russian": "Швиц", + "chinese_traditional": "Schwyz", + "unknown1": "Schwyz", + "unknown2": "Schwyz", + "unknown3": "Schwyz", + "unknown4": "Schwyz" + }, + "coordinates": { + "latitude": 47.04894966, + "longitude": 8.613304672 + } + }, + { + "id": 1813053440, + "name": "Solothurn", + "translations": { + "japanese": "ゾーロトゥルン州", + "english": "Solothurn", + "french": "Soleure", + "german": "Solothurn", + "italian": "Soletta", + "spanish": "Soleura", + "chinese_simple": "索洛图恩州", + "korean": "졸로투른 주", + "dutch": "Solothurn", + "portuguese": "Soleura", + "russian": "Золотурн", + "chinese_traditional": "Solothurn", + "unknown1": "Solothurn", + "unknown2": "Solothurn", + "unknown3": "Solothurn", + "unknown4": "Solothurn" + }, + "coordinates": { + "latitude": 47.208251416, + "longitude": 7.536641588 + } + }, + { + "id": 1813118976, + "name": "Thurgau", + "translations": { + "japanese": "トゥールガウ州", + "english": "Thurgau", + "french": "Thurgovie", + "german": "Thurgau", + "italian": "Turgovia", + "spanish": "Turgovia", + "chinese_simple": "图尔高州", + "korean": "투르가우 주", + "dutch": "Thurgau", + "portuguese": "Turgóvia", + "russian": "Тургау", + "chinese_traditional": "Thurgau", + "unknown1": "Thurgau", + "unknown2": "Thurgau", + "unknown3": "Thurgau", + "unknown4": "Thurgau" + }, + "coordinates": { + "latitude": 47.554320748, + "longitude": 8.887963622 + } + }, + { + "id": 1813184512, + "name": "Ticino", + "translations": { + "japanese": "ティチーノ州", + "english": "Ticino", + "french": "Tessin", + "german": "Tessin", + "italian": "Ticino", + "spanish": "Tesino", + "chinese_simple": "提契诺州", + "korean": "티치노 주", + "dutch": "Ticino", + "portuguese": "Tessino", + "russian": "Тичино", + "chinese_traditional": "Ticino", + "unknown1": "Ticino", + "unknown2": "Ticino", + "unknown3": "Ticino", + "unknown4": "Ticino" + }, + "coordinates": { + "latitude": 46.192016076, + "longitude": 9.019799918 + } + }, + { + "id": 1813250048, + "name": "Uri", + "translations": { + "japanese": "ウーリ州", + "english": "Uri", + "french": "Uri", + "german": "Uri", + "italian": "Uri", + "spanish": "Uri", + "chinese_simple": "乌里州", + "korean": "우리 주", + "dutch": "Uri", + "portuguese": "Uri", + "russian": "Ури", + "chinese_traditional": "Uri", + "unknown1": "Uri", + "unknown2": "Uri", + "unknown3": "Uri", + "unknown4": "Uri" + }, + "coordinates": { + "latitude": 46.862182084, + "longitude": 8.635277388 + } + }, + { + "id": 1813315584, + "name": "Valais", + "translations": { + "japanese": "バレー州", + "english": "Valais", + "french": "Valais", + "german": "Wallis", + "italian": "Vallese", + "spanish": "Valais", + "chinese_simple": "瓦莱州", + "korean": "발레 주", + "dutch": "Wallis", + "portuguese": "Valais", + "russian": "Вале", + "chinese_traditional": "Valais", + "unknown1": "Valais", + "unknown2": "Valais", + "unknown3": "Valais", + "unknown4": "Valais" + }, + "coordinates": { + "latitude": 46.235961388, + "longitude": 7.355366681 + } + }, + { + "id": 1813381120, + "name": "Vaud", + "translations": { + "japanese": "ボー州", + "english": "Vaud", + "french": "Vaud", + "german": "Waadt", + "italian": "Vaud", + "spanish": "Vaud", + "chinese_simple": "沃州", + "korean": "보 주", + "dutch": "Vaud", + "portuguese": "Vaud", + "russian": "Во", + "chinese_traditional": "Vaud", + "unknown1": "Vaud", + "unknown2": "Vaud", + "unknown3": "Vaud", + "unknown4": "Vaud" + }, + "coordinates": { + "latitude": 46.521605916, + "longitude": 6.624773874 + } + }, + { + "id": 1813446656, + "name": "Zug", + "translations": { + "japanese": "ツーク州", + "english": "Zug", + "french": "Zoug", + "german": "Zug", + "italian": "Zugo", + "spanish": "Zug", + "chinese_simple": "楚格州", + "korean": "추크 주", + "dutch": "Zug", + "portuguese": "Zug", + "russian": "Цуг", + "chinese_traditional": "Zug", + "unknown1": "Zug", + "unknown2": "Zug", + "unknown3": "Zug", + "unknown4": "Zug" + }, + "coordinates": { + "latitude": 47.175292432, + "longitude": 8.503441092 + } + }, + { + "id": 1813512192, + "name": "Zurich", + "translations": { + "japanese": "チューリヒ州", + "english": "Zurich", + "french": "Zurich", + "german": "Zürich", + "italian": "Zurigo", + "spanish": "Zúrich", + "chinese_simple": "苏黎世州", + "korean": "취리히 주", + "dutch": "Zürich", + "portuguese": "Zurique", + "russian": "Цюрих", + "chinese_traditional": "Zurich", + "unknown1": "Zurich", + "unknown2": "Zurich", + "unknown3": "Zurich", + "unknown4": "Zurich" + }, + "coordinates": { + "latitude": 47.389525828000004, + "longitude": 8.536400166 + } + }, + { + "id": 1813577728, + "name": "Appenzell Outer Rhodes", + "translations": { + "japanese": "アッペンツェル・アウサーローデン準州", + "english": "Appenzell Outer Rhodes", + "french": "Appenzell Rhodes-Extérieures", + "german": "Appenzell Ausserrhoden", + "italian": "Appenzello Esterno", + "spanish": "Appenzell Rodas Exteriores", + "chinese_simple": "外阿彭策尔半州", + "korean": "아펜첼아우서로덴 주", + "dutch": "Appenzell Ausserrhoden", + "portuguese": "Appenzell Rodas Exteriores", + "russian": "Аппенцелль-Ауссерроден", + "chinese_traditional": "Appenzell Outer Rhodes", + "unknown1": "Appenzell Outer Rhodes", + "unknown2": "Appenzell Outer Rhodes", + "unknown3": "Appenzell Outer Rhodes", + "unknown4": "Appenzell Outer Rhodes" + }, + "coordinates": { + "latitude": 47.3785395, + "longitude": 9.261499794 + } + }, + { + "id": 1813643264, + "name": "Appenzell Inner Rhodes", + "translations": { + "japanese": "アッペンツェル・インナーローデン準州", + "english": "Appenzell Inner Rhodes", + "french": "Appenzell Rhodes-Intérieures", + "german": "Appenzell Innerrhoden", + "italian": "Appenzello Interno", + "spanish": "Appenzell Rodas Interiores", + "chinese_simple": "内阿彭策尔半州", + "korean": "아펜첼이너로덴 주", + "dutch": "Appenzell Innerrhoden", + "portuguese": "Appenzell Rodas Interiores", + "russian": "Аппенцелль-Иннерроден", + "chinese_traditional": "Appenzell Inner Rhodes", + "unknown1": "Appenzell Inner Rhodes", + "unknown2": "Appenzell Inner Rhodes", + "unknown3": "Appenzell Inner Rhodes", + "unknown4": "Appenzell Inner Rhodes" + }, + "coordinates": { + "latitude": 47.329101024, + "longitude": 9.415308806 + } + }, + { + "id": 1813708800, + "name": "Basel-Landschaft", + "translations": { + "japanese": "バーゼル=ラント準州", + "english": "Basel-Landschaft", + "french": "Bâle-Campagne", + "german": "Basel-Landschaft", + "italian": "Basilea Campagna", + "spanish": "Basilea-Campiña", + "chinese_simple": "巴塞尔乡村半州", + "korean": "바젤란트 주", + "dutch": "Basel-Landschaft", + "portuguese": "Basileia (cidade)", + "russian": "Базель-Ланд", + "chinese_traditional": "Basel-Landschaft", + "unknown1": "Basel-Landschaft", + "unknown2": "Basel-Landschaft", + "unknown3": "Basel-Landschaft", + "unknown4": "Basel-Landschaft" + }, + "coordinates": { + "latitude": 47.466430124, + "longitude": 7.728902853 + } + }, + { + "id": 1813774336, + "name": "Nidwalden", + "translations": { + "japanese": "ニトバルデン準州", + "english": "Nidwalden", + "french": "Nidwald", + "german": "Nidwalden", + "italian": "Nidvaldo", + "spanish": "Nidwalden", + "chinese_simple": "下瓦尔登半州", + "korean": "니트발덴 주", + "dutch": "Nidwalden", + "portuguese": "Nidwald", + "russian": "Нидвальден", + "chinese_traditional": "Nidwalden", + "unknown1": "Nidwalden", + "unknown2": "Nidwalden", + "unknown3": "Nidwalden", + "unknown4": "Nidwalden" + }, + "coordinates": { + "latitude": 46.944579544, + "longitude": 8.34963208 + } + } + ] + }, + { + "id": 109, + "iso_code": "TR", + "name": "Turkey", + "translations": { + "japanese": "トルコ", + "english": "Turkey", + "french": "Turquie", + "german": "Türkei", + "italian": "Turchia", + "spanish": "Turquía", + "chinese_simple": "土耳其", + "korean": "터키", + "dutch": "Turkije", + "portuguese": "Turquia", + "russian": "Турция", + "chinese_traditional": "Turkey", + "unknown1": "Turkey", + "unknown2": "Turkey", + "unknown3": "Turkey", + "unknown4": "Turkey" + }, + "regions": [ + { + "id": 1828716544, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": 39.924315952, + "longitude": 32.84921042 + } + }, + { + "id": 1828847616, + "name": "Ankara", + "translations": { + "japanese": "アンカラ県", + "english": "Ankara", + "french": "Ankara", + "german": "Ankara", + "italian": "Ankara", + "spanish": "Ankara", + "chinese_simple": "安卡拉省", + "korean": "앙카라 주", + "dutch": "Ankara", + "portuguese": "Ancara", + "russian": "Анкара", + "chinese_traditional": "Ankara", + "unknown1": "Ankara", + "unknown2": "Ankara", + "unknown3": "Ankara", + "unknown4": "Ankara" + }, + "coordinates": { + "latitude": 39.924315952, + "longitude": 32.84921042 + } + }, + { + "id": 1828913152, + "name": "İstanbul", + "translations": { + "japanese": "イスタンブル県", + "english": "İstanbul", + "french": "İstanbul", + "german": "İstanbul", + "italian": "Istanbul", + "spanish": "Estambul", + "chinese_simple": "伊斯坦布尔省", + "korean": "이스탄불 주", + "dutch": "Istanboel", + "portuguese": "Istambul", + "russian": "Стамбул", + "chinese_traditional": "İstanbul", + "unknown1": "İstanbul", + "unknown2": "İstanbul", + "unknown3": "İstanbul", + "unknown4": "İstanbul" + }, + "coordinates": { + "latitude": 41.011962424000004, + "longitude": 28.971026046 + } + }, + { + "id": 1828978688, + "name": "İzmir", + "translations": { + "japanese": "イズミル県", + "english": "İzmir", + "french": "İzmir", + "german": "İzmir", + "italian": "Smirne", + "spanish": "İzmir", + "chinese_simple": "伊兹密尔省", + "korean": "이즈미르 주", + "dutch": "İzmir", + "portuguese": "İzmir", + "russian": "Измир", + "chinese_traditional": "İzmir", + "unknown1": "İzmir", + "unknown2": "İzmir", + "unknown3": "İzmir", + "unknown4": "İzmir" + }, + "coordinates": { + "latitude": 38.419189016, + "longitude": 27.125317902 + } + }, + { + "id": 1829044224, + "name": "Bursa", + "translations": { + "japanese": "ブルサ県", + "english": "Bursa", + "french": "Bursa", + "german": "Bursa", + "italian": "Bursa", + "spanish": "Bursa", + "chinese_simple": "布尔萨省", + "korean": "부르사 주", + "dutch": "Bursa", + "portuguese": "Bursa", + "russian": "Бурса", + "chinese_traditional": "Bursa", + "unknown1": "Bursa", + "unknown2": "Bursa", + "unknown3": "Bursa", + "unknown4": "Bursa" + }, + "coordinates": { + "latitude": 40.18249466, + "longitude": 29.064410089 + } + }, + { + "id": 1829109760, + "name": "Adana", + "translations": { + "japanese": "アダナ県", + "english": "Adana", + "french": "Adana", + "german": "Adana", + "italian": "Adana", + "spanish": "Adana", + "chinese_simple": "阿达纳省", + "korean": "아다나 주", + "dutch": "Adana", + "portuguese": "Adana", + "russian": "Адана", + "chinese_traditional": "Adana", + "unknown1": "Adana", + "unknown2": "Adana", + "unknown3": "Adana", + "unknown4": "Adana" + }, + "coordinates": { + "latitude": 36.996459540000004, + "longitude": 35.32114097 + } + }, + { + "id": 1829175296, + "name": "Gaziantep", + "translations": { + "japanese": "ガジアンテプ県", + "english": "Gaziantep", + "french": "Gaziantep", + "german": "Gaziantep", + "italian": "Gaziantep", + "spanish": "Gaziantep", + "chinese_simple": "加济安泰普省", + "korean": "가지안테프 주", + "dutch": "Gaziantep", + "portuguese": "Gaziantep", + "russian": "Газиантеп", + "chinese_traditional": "Gaziantep", + "unknown1": "Gaziantep", + "unknown2": "Gaziantep", + "unknown3": "Gaziantep", + "unknown4": "Gaziantep" + }, + "coordinates": { + "latitude": 37.062377508, + "longitude": 37.381083095 + } + }, + { + "id": 1829240832, + "name": "Konya", + "translations": { + "japanese": "コニヤ県", + "english": "Konya", + "french": "Konya", + "german": "Konya", + "italian": "Konya", + "spanish": "Konya", + "chinese_simple": "科尼亚省", + "korean": "코니아 주", + "dutch": "Konya", + "portuguese": "Konya", + "russian": "Конья", + "chinese_traditional": "Konya", + "unknown1": "Konya", + "unknown2": "Konya", + "unknown3": "Konya", + "unknown4": "Konya" + }, + "coordinates": { + "latitude": 37.864379452, + "longitude": 32.481167427 + } + }, + { + "id": 1829306368, + "name": "Antalya", + "translations": { + "japanese": "アンタリヤ県", + "english": "Antalya", + "french": "Antalya", + "german": "Antalya", + "italian": "Adalia", + "spanish": "Antalya", + "chinese_simple": "安塔利亚省", + "korean": "안탈리아 주", + "dutch": "Antalya", + "portuguese": "Antalya", + "russian": "Анталья", + "chinese_traditional": "Antalya", + "unknown1": "Antalya", + "unknown2": "Antalya", + "unknown3": "Antalya", + "unknown4": "Antalya" + }, + "coordinates": { + "latitude": 36.897582588, + "longitude": 30.679404715 + } + }, + { + "id": 1829371904, + "name": "Diyarbakır", + "translations": { + "japanese": "ディヤルバクル県", + "english": "Diyarbakır", + "french": "Diyarbakır", + "german": "Diyarbakır", + "italian": "Diyarbakır", + "spanish": "Diyarbakır", + "chinese_simple": "迪亚巴克尔省", + "korean": "디야르바키르 주", + "dutch": "Diyarbakır", + "portuguese": "Diyarbakır", + "russian": "Диярбакыр", + "chinese_traditional": "Diyarbakır", + "unknown1": "Diyarbakır", + "unknown2": "Diyarbakır", + "unknown3": "Diyarbakır", + "unknown4": "Diyarbakır" + }, + "coordinates": { + "latitude": 37.979735896, + "longitude": 40.21007028 + } + }, + { + "id": 1829437440, + "name": "Mersin", + "translations": { + "japanese": "メルシン県", + "english": "Mersin", + "french": "Mersin", + "german": "Mersin", + "italian": "Mersin", + "spanish": "Mersin", + "chinese_simple": "梅尔辛省", + "korean": "메르신 주", + "dutch": "Mersin", + "portuguese": "Mersin", + "russian": "Мерсин", + "chinese_traditional": "Mersin", + "unknown1": "Mersin", + "unknown2": "Mersin", + "unknown3": "Mersin", + "unknown4": "Mersin" + }, + "coordinates": { + "latitude": 36.798705636, + "longitude": 34.629000416 + } + }, + { + "id": 1829502976, + "name": "Kayseri", + "translations": { + "japanese": "カイセリ県", + "english": "Kayseri", + "french": "Kayseri", + "german": "Kayseri", + "italian": "Kayseri", + "spanish": "Kayseri", + "chinese_simple": "开塞利省", + "korean": "카이세리 주", + "dutch": "Kayseri", + "portuguese": "Kayseri", + "russian": "Кайсери", + "chinese_traditional": "Kayseri", + "unknown1": "Kayseri", + "unknown2": "Kayseri", + "unknown3": "Kayseri", + "unknown4": "Kayseri" + }, + "coordinates": { + "latitude": 38.732299364, + "longitude": 35.480443161 + } + }, + { + "id": 1829634048, + "name": "Şanlıurfa", + "translations": { + "japanese": "シャンルウルファ県", + "english": "Şanlıurfa", + "french": "Şanlıurfa", + "german": "Şanlıurfa", + "italian": "Şanlıurfa", + "spanish": "Şanlıurfa", + "chinese_simple": "尚利乌尔法省", + "korean": "샨리우르파 주", + "dutch": "Şanlıurfa", + "portuguese": "Şanlıurfa", + "russian": "Шанлыурфа", + "chinese_traditional": "Şanlıurfa", + "unknown1": "Şanlıurfa", + "unknown2": "Şanlıurfa", + "unknown3": "Şanlıurfa", + "unknown4": "Şanlıurfa" + }, + "coordinates": { + "latitude": 37.144774968, + "longitude": 38.798323277 + } + }, + { + "id": 1829699584, + "name": "Malatya", + "translations": { + "japanese": "マラティヤ県", + "english": "Malatya", + "french": "Malatya", + "german": "Malatya", + "italian": "Malatya", + "spanish": "Malatya", + "chinese_simple": "马拉蒂亚省", + "korean": "말라티아 주", + "dutch": "Malatya", + "portuguese": "Malatya", + "russian": "Малатья", + "chinese_traditional": "Malatya", + "unknown1": "Malatya", + "unknown2": "Malatya", + "unknown3": "Malatya", + "unknown4": "Malatya" + }, + "coordinates": { + "latitude": 38.347777884, + "longitude": 38.309430346 + } + }, + { + "id": 1829765120, + "name": "Erzurum", + "translations": { + "japanese": "エルズルム県", + "english": "Erzurum", + "french": "Erzurum", + "german": "Erzurum", + "italian": "Erzurum", + "spanish": "Erzurum", + "chinese_simple": "埃尔祖鲁姆省", + "korean": "에르주룸 주", + "dutch": "Erzurum", + "portuguese": "Erzurum", + "russian": "Эрзурум", + "chinese_traditional": "Erzurum", + "unknown1": "Erzurum", + "unknown2": "Erzurum", + "unknown3": "Erzurum", + "unknown4": "Erzurum" + }, + "coordinates": { + "latitude": 39.896850132, + "longitude": 41.264760648 + } + }, + { + "id": 1829830656, + "name": "Samsun", + "translations": { + "japanese": "サムスン県", + "english": "Samsun", + "french": "Samsun", + "german": "Samsun", + "italian": "Samsun", + "spanish": "Samsun", + "chinese_simple": "萨姆松省", + "korean": "삼순 주", + "dutch": "Samsun", + "portuguese": "Samsun", + "russian": "Самсун", + "chinese_traditional": "Samsun", + "unknown1": "Samsun", + "unknown2": "Samsun", + "unknown3": "Samsun", + "unknown4": "Samsun" + }, + "coordinates": { + "latitude": 41.286620624, + "longitude": 36.331885906 + } + }, + { + "id": 1829896192, + "name": "Van", + "translations": { + "japanese": "ワン県", + "english": "Van", + "french": "Van", + "german": "Van", + "italian": "Van", + "spanish": "Van", + "chinese_simple": "凡省", + "korean": "반 주", + "dutch": "Van", + "portuguese": "Van", + "russian": "Ван", + "chinese_traditional": "Van", + "unknown1": "Van", + "unknown2": "Van", + "unknown3": "Van", + "unknown4": "Van" + }, + "coordinates": { + "latitude": 38.490600148, + "longitude": 43.379634562999996 + } + }, + { + "id": 1829961728, + "name": "Kahramanmaraş", + "translations": { + "japanese": "カフラマンマラシュ県", + "english": "Kahramanmaraş", + "french": "Kahramanmaraş", + "german": "Kahramanmaraş", + "italian": "Kahramanmaraş", + "spanish": "Kahramanmaraş", + "chinese_simple": "卡赫拉曼马拉什省", + "korean": "카라만마라슈 주", + "dutch": "Kahramanmaraş", + "portuguese": "Kahramanmaraş", + "russian": "Кахраманмараш", + "chinese_traditional": "Kahramanmaraş", + "unknown1": "Kahramanmaraş", + "unknown2": "Kahramanmaraş", + "unknown3": "Kahramanmaraş", + "unknown4": "Kahramanmaraş" + }, + "coordinates": { + "latitude": 37.578734924, + "longitude": 36.930642417 + } + }, + { + "id": 1830027264, + "name": "Denizli", + "translations": { + "japanese": "デニズリ県", + "english": "Denizli", + "french": "Denizli", + "german": "Denizli", + "italian": "Denizli", + "spanish": "Denizli", + "chinese_simple": "代尼兹利省", + "korean": "데니즐리 주", + "dutch": "Denizli", + "portuguese": "Denizli", + "russian": "Денизли", + "chinese_traditional": "Denizli", + "unknown1": "Denizli", + "unknown2": "Denizli", + "unknown3": "Denizli", + "unknown4": "Denizli" + }, + "coordinates": { + "latitude": 37.765502500000004, + "longitude": 29.080889626 + } + }, + { + "id": 1830092800, + "name": "Batman", + "translations": { + "japanese": "バトマン県", + "english": "Batman", + "french": "Batman", + "german": "Batman", + "italian": "Batman", + "spanish": "Batman", + "chinese_simple": "巴特曼省", + "korean": "바트만 주", + "dutch": "Batman", + "portuguese": "Batman", + "russian": "Батман", + "chinese_traditional": "Batman", + "unknown1": "Batman", + "unknown2": "Batman", + "unknown3": "Batman", + "unknown4": "Batman" + }, + "coordinates": { + "latitude": 37.880858944, + "longitude": 41.116444815 + } + }, + { + "id": 1830158336, + "name": "Elazığ", + "translations": { + "japanese": "エラズー県", + "english": "Elazığ", + "french": "Elazığ", + "german": "Elazığ", + "italian": "Elâzığ", + "spanish": "Elazığ", + "chinese_simple": "埃拉泽省", + "korean": "엘라지 주", + "dutch": "Elazığ", + "portuguese": "Elazığ", + "russian": "Элязыг", + "chinese_traditional": "Elazığ", + "unknown1": "Elazığ", + "unknown2": "Elazığ", + "unknown3": "Elazığ", + "unknown4": "Elazığ" + }, + "coordinates": { + "latitude": 38.682860888, + "longitude": 39.215804881 + } + }, + { + "id": 1830223872, + "name": "Sakarya", + "translations": { + "japanese": "サカリヤ県", + "english": "Sakarya", + "french": "Sakarya", + "german": "Sakarya", + "italian": "Sakarya", + "spanish": "Sakarya", + "chinese_simple": "萨卡里亚省", + "korean": "사카리아 주", + "dutch": "Sakarya", + "portuguese": "Sakarya", + "russian": "Сакарья", + "chinese_traditional": "Sakarya", + "unknown1": "Sakarya", + "unknown2": "Sakarya", + "unknown3": "Sakarya", + "unknown4": "Sakarya" + }, + "coordinates": { + "latitude": 40.781249536, + "longitude": 30.399252586 + } + }, + { + "id": 1830289408, + "name": "Kocaeli", + "translations": { + "japanese": "コジャエリ県", + "english": "Kocaeli", + "french": "Kocaeli", + "german": "Kocaeli", + "italian": "Kocaeli", + "spanish": "Kocaeli", + "chinese_simple": "科贾埃利省", + "korean": "코자엘리 주", + "dutch": "Kocaeli", + "portuguese": "Kocaeli", + "russian": "Коджаэли", + "chinese_traditional": "Kocaeli", + "unknown1": "Kocaeli", + "unknown2": "Kocaeli", + "unknown3": "Kocaeli", + "unknown4": "Kocaeli" + }, + "coordinates": { + "latitude": 40.764770044, + "longitude": 29.915852834 + } + }, + { + "id": 1830354944, + "name": "Sivas", + "translations": { + "japanese": "シワス県", + "english": "Sivas", + "french": "Sivas", + "german": "Sivas", + "italian": "Sivas", + "spanish": "Sivas", + "chinese_simple": "锡瓦斯省", + "korean": "시바스 주", + "dutch": "Sivas", + "portuguese": "Sivas", + "russian": "Сивас", + "chinese_traditional": "Sivas", + "unknown1": "Sivas", + "unknown2": "Sivas", + "unknown3": "Sivas", + "unknown4": "Sivas" + }, + "coordinates": { + "latitude": 39.748534704, + "longitude": 37.013040102 + } + }, + { + "id": 1830420480, + "name": "Manisa", + "translations": { + "japanese": "マニサ県", + "english": "Manisa", + "french": "Manisa", + "german": "Manisa", + "italian": "Manisa", + "spanish": "Manisa", + "chinese_simple": "马尼萨省", + "korean": "마니사 주", + "dutch": "Manisa", + "portuguese": "Manisa", + "russian": "Маниса", + "chinese_traditional": "Manisa", + "unknown1": "Manisa", + "unknown2": "Manisa", + "unknown3": "Manisa", + "unknown4": "Manisa" + }, + "coordinates": { + "latitude": 38.611449756, + "longitude": 27.421949568 + } + }, + { + "id": 1830486016, + "name": "Trabzon", + "translations": { + "japanese": "トラブゾン県", + "english": "Trabzon", + "french": "Trabzon", + "german": "Trabzon", + "italian": "Trebisonda", + "spanish": "Trabzon", + "chinese_simple": "特拉布宗省", + "korean": "트라브존 주", + "dutch": "Trabzon", + "portuguese": "Trabzon", + "russian": "Трабзон", + "chinese_traditional": "Trabzon", + "unknown1": "Trabzon", + "unknown2": "Trabzon", + "unknown3": "Trabzon", + "unknown4": "Trabzon" + }, + "coordinates": { + "latitude": 40.995482932, + "longitude": 39.732163707 + } + }, + { + "id": 1830551552, + "name": "Balıkesir", + "translations": { + "japanese": "バルケシル県", + "english": "Balıkesir", + "french": "Balıkesir", + "german": "Balıkesir", + "italian": "Balıkesir", + "spanish": "Balıkesir", + "chinese_simple": "巴勒克埃西尔省", + "korean": "발리케시르 주", + "dutch": "Balıkesir", + "portuguese": "Balıkesir", + "russian": "Балыкесир", + "chinese_traditional": "Balıkesir", + "unknown1": "Balıkesir", + "unknown2": "Balıkesir", + "unknown3": "Balıkesir", + "unknown4": "Balıkesir" + }, + "coordinates": { + "latitude": 39.644164588, + "longitude": 27.877883425 + } + }, + { + "id": 1830617088, + "name": "Adıyaman", + "translations": { + "japanese": "アディヤマン県", + "english": "Adıyaman", + "french": "Adıyaman", + "german": "Adıyaman", + "italian": "Adıyaman", + "spanish": "Adıyaman", + "chinese_simple": "阿德亚曼省", + "korean": "아디야만 주", + "dutch": "Adıyaman", + "portuguese": "Adıyaman", + "russian": "Адыяман", + "chinese_traditional": "Adıyaman", + "unknown1": "Adıyaman", + "unknown2": "Adıyaman", + "unknown3": "Adıyaman", + "unknown4": "Adıyaman" + }, + "coordinates": { + "latitude": 37.760009336, + "longitude": 38.270978093 + } + }, + { + "id": 1830682624, + "name": "Tekirdağ", + "translations": { + "japanese": "テキルダー県", + "english": "Tekirdağ", + "french": "Tekirdağ", + "german": "Tekirdağ", + "italian": "Tekirdağ", + "spanish": "Tekirdağ", + "chinese_simple": "泰基尔达省", + "korean": "테키르다 주", + "dutch": "Tekirdağ", + "portuguese": "Tekirdağ", + "russian": "Текирдаг", + "chinese_traditional": "Tekirdağ", + "unknown1": "Tekirdağ", + "unknown2": "Tekirdağ", + "unknown3": "Tekirdağ", + "unknown4": "Tekirdağ" + }, + "coordinates": { + "latitude": 40.97900344, + "longitude": 27.515333611 + } + }, + { + "id": 1830748160, + "name": "Kırıkkale", + "translations": { + "japanese": "クルッカレ県", + "english": "Kırıkkale", + "french": "Kırıkkale", + "german": "Kırıkkale", + "italian": "Kırıkkale", + "spanish": "Kırıkkale", + "chinese_simple": "克勒克卡莱省", + "korean": "키리칼레 주", + "dutch": "Kırıkkale", + "portuguese": "Kırıkkale", + "russian": "Кырыккале", + "chinese_traditional": "Kırıkkale", + "unknown1": "Kırıkkale", + "unknown2": "Kırıkkale", + "unknown3": "Kırıkkale", + "unknown4": "Kırıkkale" + }, + "coordinates": { + "latitude": 39.847411656, + "longitude": 33.497405542 + } + }, + { + "id": 1830813696, + "name": "Osmaniye", + "translations": { + "japanese": "オスマニエ県", + "english": "Osmaniye", + "french": "Osmaniye", + "german": "Osmaniye", + "italian": "Osmaniye", + "spanish": "Osmaniye", + "chinese_simple": "奥斯曼尼菲省", + "korean": "오스마니예 주", + "dutch": "Osmaniye", + "portuguese": "Osmaniye", + "russian": "Османие", + "chinese_traditional": "Osmaniye", + "unknown1": "Osmaniye", + "unknown2": "Osmaniye", + "unknown3": "Osmaniye", + "unknown4": "Osmaniye" + }, + "coordinates": { + "latitude": 37.078857, + "longitude": 36.249488221 + } + }, + { + "id": 1830879232, + "name": "Kütahya", + "translations": { + "japanese": "キュターヤ県", + "english": "Kütahya", + "french": "Kütahya", + "german": "Kütahya", + "italian": "Kütahya", + "spanish": "Kütahya", + "chinese_simple": "屈塔希亚省", + "korean": "퀴타히아 주", + "dutch": "Kütahya", + "portuguese": "Kütahya", + "russian": "Кютахья", + "chinese_traditional": "Kütahya", + "unknown1": "Kütahya", + "unknown2": "Kütahya", + "unknown3": "Kütahya", + "unknown4": "Kütahya" + }, + "coordinates": { + "latitude": 39.4134517, + "longitude": 29.981770982 + } + }, + { + "id": 1830944768, + "name": "Çorum", + "translations": { + "japanese": "チョルム県", + "english": "Çorum", + "french": "Çorum", + "german": "Çorum", + "italian": "Çorum", + "spanish": "Çorum", + "chinese_simple": "乔鲁姆省", + "korean": "초룸 주", + "dutch": "Çorum", + "portuguese": "Çorum", + "russian": "Чорум", + "chinese_traditional": "Çorum", + "unknown1": "Çorum", + "unknown2": "Çorum", + "unknown3": "Çorum", + "unknown4": "Çorum" + }, + "coordinates": { + "latitude": 40.550536648, + "longitude": 34.953097977 + } + }, + { + "id": 1831010304, + "name": "Isparta", + "translations": { + "japanese": "イスパルタ県", + "english": "Isparta", + "french": "Isparta", + "german": "Isparta", + "italian": "Isparta", + "spanish": "Isparta", + "chinese_simple": "伊斯帕尔塔省", + "korean": "이스파르타 주", + "dutch": "Isparta", + "portuguese": "Isparta", + "russian": "Ыспарта", + "chinese_traditional": "Isparta", + "unknown1": "Isparta", + "unknown2": "Isparta", + "unknown3": "Isparta", + "unknown4": "Isparta" + }, + "coordinates": { + "latitude": 37.765502500000004, + "longitude": 30.547568419 + } + }, + { + "id": 1831075840, + "name": "Aydın", + "translations": { + "japanese": "アイドゥン県", + "english": "Aydın", + "french": "Aydın", + "german": "Aydın", + "italian": "Aydın", + "spanish": "Aydın", + "chinese_simple": "艾登省", + "korean": "아이딘 주", + "dutch": "Aydın", + "portuguese": "Aydın", + "russian": "Айдын", + "chinese_traditional": "Aydın", + "unknown1": "Aydın", + "unknown2": "Aydın", + "unknown3": "Aydın", + "unknown4": "Aydın" + }, + "coordinates": { + "latitude": 37.842406796, + "longitude": 27.844924351 + } + }, + { + "id": 1831141376, + "name": "Hatay", + "translations": { + "japanese": "ハタイ県", + "english": "Hatay", + "french": "Hatay", + "german": "Hatay", + "italian": "Hatay", + "spanish": "Hatay", + "chinese_simple": "哈塔伊省", + "korean": "하타이 주", + "dutch": "Hatay", + "portuguese": "Hatay", + "russian": "Хатай", + "chinese_traditional": "Hatay", + "unknown1": "Hatay", + "unknown2": "Hatay", + "unknown3": "Hatay", + "unknown4": "Hatay" + }, + "coordinates": { + "latitude": 36.19995076, + "longitude": 36.14511782 + } + }, + { + "id": 1831206912, + "name": "Mardin", + "translations": { + "japanese": "マルディン県", + "english": "Mardin", + "french": "Mardin", + "german": "Mardin", + "italian": "Mardin", + "spanish": "Mardin", + "chinese_simple": "马尔丁省", + "korean": "마르딘 주", + "dutch": "Mardin", + "portuguese": "Mardin", + "russian": "Мардин", + "chinese_traditional": "Mardin", + "unknown1": "Mardin", + "unknown2": "Mardin", + "unknown3": "Mardin", + "unknown4": "Mardin" + }, + "coordinates": { + "latitude": 37.315063052, + "longitude": 40.737415464 + } + }, + { + "id": 1831272448, + "name": "Aksaray", + "translations": { + "japanese": "アクサライ県", + "english": "Aksaray", + "french": "Aksaray", + "german": "Aksaray", + "italian": "Aksaray", + "spanish": "Aksaray", + "chinese_simple": "阿克萨赖省", + "korean": "악사라이 주", + "dutch": "Aksaray", + "portuguese": "Aksaray", + "russian": "Аксарай", + "chinese_traditional": "Aksaray", + "unknown1": "Aksaray", + "unknown2": "Aksaray", + "unknown3": "Aksaray", + "unknown4": "Aksaray" + }, + "coordinates": { + "latitude": 38.36975054, + "longitude": 34.024750726 + } + }, + { + "id": 1831337984, + "name": "Afyonkarahisar", + "translations": { + "japanese": "アフィヨンカラヒサール県", + "english": "Afyonkarahisar", + "french": "Afyonkarahisar", + "german": "Afyonkarahisar", + "italian": "Afyonkarahisar", + "spanish": "Afyonkarahisar", + "chinese_simple": "阿菲永卡拉希萨尔省", + "korean": "아피온카라히사르 주", + "dutch": "Afyonkarahisar", + "portuguese": "Afyonkarahisar", + "russian": "Афьонкарахисар", + "chinese_traditional": "Afyonkarahisar", + "unknown1": "Afyonkarahisar", + "unknown2": "Afyonkarahisar", + "unknown3": "Afyonkarahisar", + "unknown4": "Afyonkarahisar" + }, + "coordinates": { + "latitude": 38.75427202, + "longitude": 30.536582061 + } + }, + { + "id": 1831403520, + "name": "Tokat", + "translations": { + "japanese": "トカト県", + "english": "Tokat", + "french": "Tokat", + "german": "Tokat", + "italian": "Tokat", + "spanish": "Tokat", + "chinese_simple": "托卡特省", + "korean": "토카트 주", + "dutch": "Tokat", + "portuguese": "Tokat", + "russian": "Токат", + "chinese_traditional": "Tokat", + "unknown1": "Tokat", + "unknown2": "Tokat", + "unknown3": "Tokat", + "unknown4": "Tokat" + }, + "coordinates": { + "latitude": 40.314330596, + "longitude": 36.546119886999996 + } + }, + { + "id": 1831469056, + "name": "Edirne", + "translations": { + "japanese": "エディルネ県", + "english": "Edirne", + "french": "Edirne", + "german": "Edirne", + "italian": "Edirne", + "spanish": "Edirne", + "chinese_simple": "埃迪尔内省", + "korean": "에디르네 주", + "dutch": "Edirne", + "portuguese": "Edirne", + "russian": "Эдирне", + "chinese_traditional": "Edirne", + "unknown1": "Edirne", + "unknown2": "Edirne", + "unknown3": "Edirne", + "unknown4": "Edirne" + }, + "coordinates": { + "latitude": 41.66564894, + "longitude": 26.565013644 + } + }, + { + "id": 1831534592, + "name": "Karaman", + "translations": { + "japanese": "カラマン県", + "english": "Karaman", + "french": "Karaman", + "german": "Karaman", + "italian": "Karaman", + "spanish": "Karaman", + "chinese_simple": "卡拉曼省", + "korean": "카라만 주", + "dutch": "Karaman", + "portuguese": "Karaman", + "russian": "Караман", + "chinese_traditional": "Karaman", + "unknown1": "Karaman", + "unknown2": "Karaman", + "unknown3": "Karaman", + "unknown4": "Karaman" + }, + "coordinates": { + "latitude": 37.177733952, + "longitude": 33.211760233999996 + } + }, + { + "id": 1831600128, + "name": "Ordu", + "translations": { + "japanese": "オルドゥ県", + "english": "Ordu", + "french": "Ordu", + "german": "Ordu", + "italian": "Ordu", + "spanish": "Ordu", + "chinese_simple": "奥尔杜省", + "korean": "오르두 주", + "dutch": "Ordu", + "portuguese": "Ordu", + "russian": "Орду", + "chinese_traditional": "Ordu", + "unknown1": "Ordu", + "unknown2": "Ordu", + "unknown3": "Ordu", + "unknown4": "Ordu" + }, + "coordinates": { + "latitude": 40.97900344, + "longitude": 37.880962384 + } + }, + { + "id": 1831665664, + "name": "Siirt", + "translations": { + "japanese": "シイルト県", + "english": "Siirt", + "french": "Siirt", + "german": "Siirt", + "italian": "Siirt", + "spanish": "Siirt", + "chinese_simple": "锡尔特省", + "korean": "시이르트 주", + "dutch": "Siirt", + "portuguese": "Siirt", + "russian": "Сиирт", + "chinese_traditional": "Siirt", + "unknown1": "Siirt", + "unknown2": "Siirt", + "unknown3": "Siirt", + "unknown4": "Siirt" + }, + "coordinates": { + "latitude": 37.93029742, + "longitude": 41.945914844 + } + }, + { + "id": 1831731200, + "name": "Erzincan", + "translations": { + "japanese": "エルジンジャン県", + "english": "Erzincan", + "french": "Erzincan", + "german": "Erzincan", + "italian": "Erzincan", + "spanish": "Erzincan", + "chinese_simple": "埃尔津詹省", + "korean": "에르진잔 주", + "dutch": "Erzincan", + "portuguese": "Erzincan", + "russian": "Эрзинджан", + "chinese_traditional": "Erzincan", + "unknown1": "Erzincan", + "unknown2": "Erzincan", + "unknown3": "Erzincan", + "unknown4": "Erzincan" + }, + "coordinates": { + "latitude": 39.748534704, + "longitude": 39.49595701 + } + }, + { + "id": 1831796736, + "name": "Çankırı", + "translations": { + "japanese": "チャンクル県", + "english": "Çankırı", + "french": "Çankırı", + "german": "Çankırı", + "italian": "Çankırı", + "spanish": "Çankırı", + "chinese_simple": "昌克勒省", + "korean": "창키리 주", + "dutch": "Çankırı", + "portuguese": "Çankırı", + "russian": "Чанкыры", + "chinese_traditional": "Çankırı", + "unknown1": "Çankırı", + "unknown2": "Çankırı", + "unknown3": "Çankırı", + "unknown4": "Çankırı" + }, + "coordinates": { + "latitude": 40.599975124000004, + "longitude": 33.612762301 + } + }, + { + "id": 1831862272, + "name": "Zonguldak", + "translations": { + "japanese": "ゾングルダク県", + "english": "Zonguldak", + "french": "Zonguldak", + "german": "Zonguldak", + "italian": "Zonguldak", + "spanish": "Zonguldak", + "chinese_simple": "宗古尔达克省", + "korean": "종굴다크 주", + "dutch": "Zonguldak", + "portuguese": "Zonguldak", + "russian": "Зонгулдак", + "chinese_traditional": "Zonguldak", + "unknown1": "Zonguldak", + "unknown2": "Zonguldak", + "unknown3": "Zonguldak", + "unknown4": "Zonguldak" + }, + "coordinates": { + "latitude": 41.44592238, + "longitude": 31.778040515 + } + }, + { + "id": 1831927808, + "name": "Yozgat", + "translations": { + "japanese": "ヨズガト県", + "english": "Yozgat", + "french": "Yozgat", + "german": "Yozgat", + "italian": "Yozgat", + "spanish": "Yozgat", + "chinese_simple": "约兹加特省", + "korean": "요즈가트 주", + "dutch": "Yozgat", + "portuguese": "Yozgat", + "russian": "Йозгат", + "chinese_traditional": "Yozgat", + "unknown1": "Yozgat", + "unknown2": "Yozgat", + "unknown3": "Yozgat", + "unknown4": "Yozgat" + }, + "coordinates": { + "latitude": 39.819945836, + "longitude": 34.799288965 + } + }, + { + "id": 1831993344, + "name": "Uşak", + "translations": { + "japanese": "ウシャク県", + "english": "Uşak", + "french": "Uşak", + "german": "Uşak", + "italian": "Uşak", + "spanish": "Uşak", + "chinese_simple": "乌萨克省", + "korean": "우샤크 주", + "dutch": "Uşak", + "portuguese": "Uşak", + "russian": "Ушак", + "chinese_traditional": "Uşak", + "unknown1": "Uşak", + "unknown2": "Uşak", + "unknown3": "Uşak", + "unknown4": "Uşak" + }, + "coordinates": { + "latitude": 38.682860888, + "longitude": 29.399494008 + } + }, + { + "id": 1832058880, + "name": "Ağrı", + "translations": { + "japanese": "アール県", + "english": "Ağrı", + "french": "Ağrı", + "german": "Ağrı", + "italian": "Ağrı", + "spanish": "Ağrı", + "chinese_simple": "阿勒省", + "korean": "아리 주", + "dutch": "Ağrı", + "portuguese": "Ağrı", + "russian": "Агры", + "chinese_traditional": "Ağrı", + "unknown1": "Ağrı", + "unknown2": "Ağrı", + "unknown3": "Ağrı", + "unknown4": "Ağrı" + }, + "coordinates": { + "latitude": 39.721068884, + "longitude": 43.055537002 + } + }, + { + "id": 1832124416, + "name": "Amasya", + "translations": { + "japanese": "アマシヤ県", + "english": "Amasya", + "french": "Amasya", + "german": "Amasya", + "italian": "Amasya", + "spanish": "Amasya", + "chinese_simple": "阿马西亚省", + "korean": "아마시아 주", + "dutch": "Amasya", + "portuguese": "Amasya", + "russian": "Амасья", + "chinese_traditional": "Amasya", + "unknown1": "Amasya", + "unknown2": "Amasya", + "unknown3": "Amasya", + "unknown4": "Amasya" + }, + "coordinates": { + "latitude": 40.6494136, + "longitude": 35.832006617 + } + }, + { + "id": 1832189952, + "name": "Ardahan", + "translations": { + "japanese": "アルダハン県", + "english": "Ardahan", + "french": "Ardahan", + "german": "Ardahan", + "italian": "Ardahan", + "spanish": "Ardahan", + "chinese_simple": "阿尔达罕省", + "korean": "아르다한 주", + "dutch": "Ardahan", + "portuguese": "Ardahan", + "russian": "Ардахан", + "chinese_traditional": "Ardahan", + "unknown1": "Ardahan", + "unknown2": "Ardahan", + "unknown3": "Ardahan", + "unknown4": "Ardahan" + }, + "coordinates": { + "latitude": 41.11633254, + "longitude": 42.698480367 + } + }, + { + "id": 1832255488, + "name": "Artvin", + "translations": { + "japanese": "アルトウィン県", + "english": "Artvin", + "french": "Artvin", + "german": "Artvin", + "italian": "Artvin", + "spanish": "Artvin", + "chinese_simple": "阿尔特温省", + "korean": "아르트빈 주", + "dutch": "Artvin", + "portuguese": "Artvin", + "russian": "Артвин", + "chinese_traditional": "Artvin", + "unknown1": "Artvin", + "unknown2": "Artvin", + "unknown3": "Artvin", + "unknown4": "Artvin" + }, + "coordinates": { + "latitude": 41.182250508, + "longitude": 41.814078548 + } + }, + { + "id": 1832321024, + "name": "Bartın", + "translations": { + "japanese": "バルトゥン県", + "english": "Bartın", + "french": "Bartın", + "german": "Bartın", + "italian": "Bartın", + "spanish": "Bartın", + "chinese_simple": "巴尔腾省", + "korean": "바르틴 주", + "dutch": "Bartın", + "portuguese": "Bartın", + "russian": "Бартын", + "chinese_traditional": "Bartın", + "unknown1": "Bartın", + "unknown2": "Bartın", + "unknown3": "Bartın", + "unknown4": "Bartın" + }, + "coordinates": { + "latitude": 41.632689956, + "longitude": 32.332851594 + } + }, + { + "id": 1832386560, + "name": "Bayburt", + "translations": { + "japanese": "バイブルト県", + "english": "Bayburt", + "french": "Bayburt", + "german": "Bayburt", + "italian": "Bayburt", + "spanish": "Bayburt", + "chinese_simple": "巴伊布尔特省", + "korean": "바이부르트 주", + "dutch": "Bayburt", + "portuguese": "Bayburt", + "russian": "Байбурт", + "chinese_traditional": "Bayburt", + "unknown1": "Bayburt", + "unknown2": "Bayburt", + "unknown3": "Bayburt", + "unknown4": "Bayburt" + }, + "coordinates": { + "latitude": 40.248412628, + "longitude": 40.215563459 + } + }, + { + "id": 1832452096, + "name": "Bilecik", + "translations": { + "japanese": "ビレジク県", + "english": "Bilecik", + "french": "Bilecik", + "german": "Bilecik", + "italian": "Bilecik", + "spanish": "Bilecik", + "chinese_simple": "比莱吉克省", + "korean": "빌레지크 주", + "dutch": "Bilecik", + "portuguese": "Bilecik", + "russian": "Биледжик", + "chinese_traditional": "Bilecik", + "unknown1": "Bilecik", + "unknown2": "Bilecik", + "unknown3": "Bilecik", + "unknown4": "Bilecik" + }, + "coordinates": { + "latitude": 40.138549348, + "longitude": 29.976277803 + } + }, + { + "id": 1832517632, + "name": "Bingöl", + "translations": { + "japanese": "ビンギョル県", + "english": "Bingöl", + "french": "Bingöl", + "german": "Bingöl", + "italian": "Bingöl", + "spanish": "Bingöl", + "chinese_simple": "宾格尔省", + "korean": "빙괼 주", + "dutch": "Bingöl", + "portuguese": "Bingöl", + "russian": "Бингёль", + "chinese_traditional": "Bingöl", + "unknown1": "Bingöl", + "unknown2": "Bingöl", + "unknown3": "Bingöl", + "unknown4": "Bingöl" + }, + "coordinates": { + "latitude": 38.880614792, + "longitude": 40.495715588 + } + }, + { + "id": 1832583168, + "name": "Bitlis", + "translations": { + "japanese": "ビトリス県", + "english": "Bitlis", + "french": "Bitlis", + "german": "Bitlis", + "italian": "Bitlis", + "spanish": "Bitlis", + "chinese_simple": "比特利斯省", + "korean": "비틀리스 주", + "dutch": "Bitlis", + "portuguese": "Bitlis", + "russian": "Битлис", + "chinese_traditional": "Bitlis", + "unknown1": "Bitlis", + "unknown2": "Bitlis", + "unknown3": "Bitlis", + "unknown4": "Bitlis" + }, + "coordinates": { + "latitude": 38.39721636, + "longitude": 42.116203393 + } + }, + { + "id": 1832648704, + "name": "Bolu", + "translations": { + "japanese": "ボル県", + "english": "Bolu", + "french": "Bolu", + "german": "Bolu", + "italian": "Bolu", + "spanish": "Bolu", + "chinese_simple": "博卢省", + "korean": "볼루 주", + "dutch": "Bolu", + "portuguese": "Bolu", + "russian": "Болу", + "chinese_traditional": "Bolu", + "unknown1": "Bolu", + "unknown2": "Bolu", + "unknown3": "Bolu", + "unknown4": "Bolu" + }, + "coordinates": { + "latitude": 40.73181106, + "longitude": 31.596765608 + } + }, + { + "id": 1832714240, + "name": "Burdur", + "translations": { + "japanese": "ブルドゥル県", + "english": "Burdur", + "french": "Burdur", + "german": "Burdur", + "italian": "Burdur", + "spanish": "Burdur", + "chinese_simple": "布尔杜尔省", + "korean": "부르두르 주", + "dutch": "Burdur", + "portuguese": "Burdur", + "russian": "Бурдур", + "chinese_traditional": "Burdur", + "unknown1": "Burdur", + "unknown2": "Burdur", + "unknown3": "Burdur", + "unknown4": "Burdur" + }, + "coordinates": { + "latitude": 37.716064024, + "longitude": 30.289389006 + } + }, + { + "id": 1832779776, + "name": "Çanakkale", + "translations": { + "japanese": "チャナッカレ県", + "english": "Çanakkale", + "french": "Çanakkale", + "german": "Çanakkale", + "italian": "Çanakkale", + "spanish": "Çanakkale", + "chinese_simple": "恰纳卡莱省", + "korean": "차나칼레 주", + "dutch": "Çanakkale", + "portuguese": "Çanakkale", + "russian": "Чанаккале", + "chinese_traditional": "Çanakkale", + "unknown1": "Çanakkale", + "unknown2": "Çanakkale", + "unknown3": "Çanakkale", + "unknown4": "Çanakkale" + }, + "coordinates": { + "latitude": 40.149535676, + "longitude": 26.394725095 + } + }, + { + "id": 1832845312, + "name": "Düzce", + "translations": { + "japanese": "デュズジェ県", + "english": "Düzce", + "french": "Düzce", + "german": "Düzce", + "italian": "Düzce", + "spanish": "Düzce", + "chinese_simple": "迪兹杰省", + "korean": "뒤즈제 주", + "dutch": "Düzce", + "portuguese": "Düzce", + "russian": "Дюздже", + "chinese_traditional": "Düzce", + "unknown1": "Düzce", + "unknown2": "Düzce", + "unknown3": "Düzce", + "unknown4": "Düzce" + }, + "coordinates": { + "latitude": 40.830688012, + "longitude": 31.14632493 + } + }, + { + "id": 1832910848, + "name": "Eskişehir", + "translations": { + "japanese": "エスキシェヒル県", + "english": "Eskişehir", + "french": "Eskişehir", + "german": "Eskişehir", + "italian": "Eskişehir", + "spanish": "Eskişehir", + "chinese_simple": "埃斯基谢希尔省", + "korean": "에스키셰히르 주", + "dutch": "Eskişehir", + "portuguese": "Eskişehir", + "russian": "Эскишехир", + "chinese_traditional": "Eskişehir", + "unknown1": "Eskişehir", + "unknown2": "Eskişehir", + "unknown3": "Eskişehir", + "unknown4": "Eskişehir" + }, + "coordinates": { + "latitude": 39.776000524, + "longitude": 30.520102524 + } + }, + { + "id": 1832976384, + "name": "Giresun", + "translations": { + "japanese": "ギレスン県", + "english": "Giresun", + "french": "Giresun", + "german": "Giresun", + "italian": "Giresun", + "spanish": "Giresun", + "chinese_simple": "吉雷松省", + "korean": "기레순 주", + "dutch": "Giresun", + "portuguese": "Giresun", + "russian": "Гиресун", + "chinese_traditional": "Giresun", + "unknown1": "Giresun", + "unknown2": "Giresun", + "unknown3": "Giresun", + "unknown4": "Giresun" + }, + "coordinates": { + "latitude": 40.89660598, + "longitude": 38.413800746999996 + } + }, + { + "id": 1833041920, + "name": "Gümüşhane", + "translations": { + "japanese": "ギュミュシュハーネ県", + "english": "Gümüşhane", + "french": "Gümüşhane", + "german": "Gümüşhane", + "italian": "Gümüşhane", + "spanish": "Gümüşhane", + "chinese_simple": "居米什哈内省", + "korean": "귀뮈샤네 주", + "dutch": "Gümüşhane", + "portuguese": "Gümüşhane", + "russian": "Гюмюшхане", + "chinese_traditional": "Gümüşhane", + "unknown1": "Gümüşhane", + "unknown2": "Gümüşhane", + "unknown3": "Gümüşhane", + "unknown4": "Gümüşhane" + }, + "coordinates": { + "latitude": 40.45715286, + "longitude": 39.479477473 + } + }, + { + "id": 1833107456, + "name": "Hakkari", + "translations": { + "japanese": "ハッキャリ県", + "english": "Hakkari", + "french": "Hakkari", + "german": "Hakkâri", + "italian": "Hakkâri", + "spanish": "Hakkari", + "chinese_simple": "哈卡里省", + "korean": "하카리 주", + "dutch": "Hakkâri", + "portuguese": "Hakkari", + "russian": "Хаккяри", + "chinese_traditional": "Hakkari", + "unknown1": "Hakkari", + "unknown2": "Hakkari", + "unknown3": "Hakkari", + "unknown4": "Hakkari" + }, + "coordinates": { + "latitude": 37.578734924, + "longitude": 43.731198019 + } + }, + { + "id": 1833172992, + "name": "Iğdır", + "translations": { + "japanese": "ウードゥル県", + "english": "Iğdır", + "french": "Iğdır", + "german": "Iğdır", + "italian": "Iğdır", + "spanish": "Iğdır", + "chinese_simple": "伊迪尔省", + "korean": "이디르 주", + "dutch": "Iğdır", + "portuguese": "Iğdır", + "russian": "Ыгдыр", + "chinese_traditional": "Iğdır", + "unknown1": "Iğdır", + "unknown2": "Iğdır", + "unknown3": "Iğdır", + "unknown4": "Iğdır" + }, + "coordinates": { + "latitude": 39.913329624, + "longitude": 44.033322864 + } + }, + { + "id": 1833238528, + "name": "Karabük", + "translations": { + "japanese": "カラビュック県", + "english": "Karabük", + "french": "Karabük", + "german": "Karabük", + "italian": "Karabük", + "spanish": "Karabük", + "chinese_simple": "卡拉比克省", + "korean": "카라뷔크 주", + "dutch": "Karabük", + "portuguese": "Karabük", + "russian": "Карабюк", + "chinese_traditional": "Karabük", + "unknown1": "Karabük", + "unknown2": "Karabük", + "unknown3": "Karabük", + "unknown4": "Karabük" + }, + "coordinates": { + "latitude": 41.19873, + "longitude": 32.62948326 + } + }, + { + "id": 1833304064, + "name": "Kars", + "translations": { + "japanese": "カルス県", + "english": "Kars", + "french": "Kars", + "german": "Kars", + "italian": "Kars", + "spanish": "Kars", + "chinese_simple": "卡尔斯省", + "korean": "카르스 주", + "dutch": "Kars", + "portuguese": "Kars", + "russian": "Карс", + "chinese_traditional": "Kars", + "unknown1": "Kars", + "unknown2": "Kars", + "unknown3": "Kars", + "unknown4": "Kars" + }, + "coordinates": { + "latitude": 40.616454616, + "longitude": 43.099482434 + } + }, + { + "id": 1833369600, + "name": "Kastamonu", + "translations": { + "japanese": "カスタモヌ県", + "english": "Kastamonu", + "french": "Kastamonu", + "german": "Kastamonu", + "italian": "Kastamonu", + "spanish": "Kastamonu", + "chinese_simple": "卡斯塔莫努省", + "korean": "카스타모누 주", + "dutch": "Kastamonu", + "portuguese": "Kastamonu", + "russian": "Кастамону", + "chinese_traditional": "Kastamonu", + "unknown1": "Kastamonu", + "unknown2": "Kastamonu", + "unknown3": "Kastamonu", + "unknown4": "Kastamonu" + }, + "coordinates": { + "latitude": 41.369018084, + "longitude": 33.766571313 + } + }, + { + "id": 1833435136, + "name": "Kilis", + "translations": { + "japanese": "キリス県", + "english": "Kilis", + "french": "Kilis", + "german": "Kilis", + "italian": "Kilis", + "spanish": "Kilis", + "chinese_simple": "基利斯省", + "korean": "킬리스 주", + "dutch": "Kilis", + "portuguese": "Kilis", + "russian": "Килис", + "chinese_traditional": "Kilis", + "unknown1": "Kilis", + "unknown2": "Kilis", + "unknown3": "Kilis", + "unknown4": "Kilis" + }, + "coordinates": { + "latitude": 36.710815012, + "longitude": 37.111917324 + } + }, + { + "id": 1833500672, + "name": "Kırklareli", + "translations": { + "japanese": "クルクラーレリ県", + "english": "Kırklareli", + "french": "Kırklareli", + "german": "Kırklareli", + "italian": "Kırklareli", + "spanish": "Kırklareli", + "chinese_simple": "柯克拉雷利省", + "korean": "키르클라렐리 주", + "dutch": "Kırklareli", + "portuguese": "Kırklareli", + "russian": "Кыркларели", + "chinese_traditional": "Kırklareli", + "unknown1": "Kırklareli", + "unknown2": "Kırklareli", + "unknown3": "Kırklareli", + "unknown4": "Kırklareli" + }, + "coordinates": { + "latitude": 41.731566908, + "longitude": 27.213208766 + } + }, + { + "id": 1833566208, + "name": "Kırşehir", + "translations": { + "japanese": "クルシェヒル県", + "english": "Kırşehir", + "french": "Kırşehir", + "german": "Kırşehir", + "italian": "Kırşehir", + "spanish": "Kırşehir", + "chinese_simple": "克尔谢希尔省", + "korean": "키르셰히르 주", + "dutch": "Kırşehir", + "portuguese": "Kırşehir", + "russian": "Кыршехир", + "chinese_traditional": "Kırşehir", + "unknown1": "Kırşehir", + "unknown2": "Kırşehir", + "unknown3": "Kırşehir", + "unknown4": "Kırşehir" + }, + "coordinates": { + "latitude": 39.149779828, + "longitude": 34.162080201 + } + }, + { + "id": 1833631744, + "name": "Muğla", + "translations": { + "japanese": "ムーラ県", + "english": "Muğla", + "french": "Muğla", + "german": "Muğla", + "italian": "Muğla", + "spanish": "Muğla", + "chinese_simple": "穆拉省", + "korean": "물라 주", + "dutch": "Muğla", + "portuguese": "Muğla", + "russian": "Мугла", + "chinese_traditional": "Muğla", + "unknown1": "Muğla", + "unknown2": "Muğla", + "unknown3": "Muğla", + "unknown4": "Muğla" + }, + "coordinates": { + "latitude": 37.210692936, + "longitude": 28.361283177 + } + }, + { + "id": 1833697280, + "name": "Muş", + "translations": { + "japanese": "ムシュ県", + "english": "Muş", + "french": "Muş", + "german": "Muş", + "italian": "Muş", + "spanish": "Muş", + "chinese_simple": "穆什省", + "korean": "무슈 주", + "dutch": "Muş", + "portuguese": "Muş", + "russian": "Муш", + "chinese_traditional": "Muş", + "unknown1": "Muş", + "unknown2": "Muş", + "unknown3": "Muş", + "unknown4": "Muş" + }, + "coordinates": { + "latitude": 38.748778856, + "longitude": 41.495474166 + } + }, + { + "id": 1833762816, + "name": "Nevşehir", + "translations": { + "japanese": "ネヴシェヒル県", + "english": "Nevşehir", + "french": "Nevşehir", + "german": "Nevşehir", + "italian": "Nevşehir", + "spanish": "Nevşehir", + "chinese_simple": "内夫谢希尔省", + "korean": "네브셰히르 주", + "dutch": "Nevşehir", + "portuguese": "Nevşehir", + "russian": "Невшехир", + "chinese_traditional": "Nevşehir", + "unknown1": "Nevşehir", + "unknown2": "Nevşehir", + "unknown3": "Nevşehir", + "unknown4": "Nevşehir" + }, + "coordinates": { + "latitude": 38.622436084, + "longitude": 34.711398101 + } + }, + { + "id": 1833828352, + "name": "Niğde", + "translations": { + "japanese": "ニーデ県", + "english": "Niğde", + "french": "Niğde", + "german": "Niğde", + "italian": "Niğde", + "spanish": "Niğde", + "chinese_simple": "尼代省", + "korean": "니데 주", + "dutch": "Niğde", + "portuguese": "Niğde", + "russian": "Нигде", + "chinese_traditional": "Niğde", + "unknown1": "Niğde", + "unknown2": "Niğde", + "unknown3": "Niğde", + "unknown4": "Niğde" + }, + "coordinates": { + "latitude": 37.963256404, + "longitude": 34.678439027 + } + }, + { + "id": 1833893888, + "name": "Rize", + "translations": { + "japanese": "リゼ県", + "english": "Rize", + "french": "Rize", + "german": "Rize", + "italian": "Rize", + "spanish": "Rize", + "chinese_simple": "里泽省", + "korean": "리제 주", + "dutch": "Rize", + "portuguese": "Rize", + "russian": "Ризе", + "chinese_traditional": "Rize", + "unknown1": "Rize", + "unknown2": "Rize", + "unknown3": "Rize", + "unknown4": "Rize" + }, + "coordinates": { + "latitude": 41.011962424000004, + "longitude": 40.512195125 + } + }, + { + "id": 1833959424, + "name": "Sinop", + "translations": { + "japanese": "シノプ県", + "english": "Sinop", + "french": "Sinop", + "german": "Sinop", + "italian": "Sinope", + "spanish": "Sinop", + "chinese_simple": "锡诺普省", + "korean": "시노프 주", + "dutch": "Sinop", + "portuguese": "Sinop", + "russian": "Синоп", + "chinese_traditional": "Sinop", + "unknown1": "Sinop", + "unknown2": "Sinop", + "unknown3": "Sinop", + "unknown4": "Sinop" + }, + "coordinates": { + "latitude": 42.028197764, + "longitude": 35.145359242 + } + }, + { + "id": 1834024960, + "name": "Şırnak", + "translations": { + "japanese": "シュルナク県", + "english": "Şırnak", + "french": "Şırnak", + "german": "Şırnak", + "italian": "Şırnak", + "spanish": "Şırnak", + "chinese_simple": "锡尔纳克省", + "korean": "시르나크 주", + "dutch": "Şırnak", + "portuguese": "Şırnak", + "russian": "Ширнак", + "chinese_traditional": "Şırnak", + "unknown1": "Şırnak", + "unknown2": "Şırnak", + "unknown3": "Şırnak", + "unknown4": "Şırnak" + }, + "coordinates": { + "latitude": 37.512816956, + "longitude": 42.456780490999996 + } + }, + { + "id": 1834090496, + "name": "Tunceli", + "translations": { + "japanese": "トゥンジェリ県", + "english": "Tunceli", + "french": "Tunceli", + "german": "Tunceli", + "italian": "Tunceli", + "spanish": "Tunceli", + "chinese_simple": "通杰利省", + "korean": "툰젤리 주", + "dutch": "Tunceli", + "portuguese": "Tunceli", + "russian": "Тунджели", + "chinese_traditional": "Tunceli", + "unknown1": "Tunceli", + "unknown2": "Tunceli", + "unknown3": "Tunceli", + "unknown4": "Tunceli" + }, + "coordinates": { + "latitude": 39.11132768, + "longitude": 39.545395621 + } + }, + { + "id": 1834156032, + "name": "Yalova", + "translations": { + "japanese": "ヤロワ県", + "english": "Yalova", + "french": "Yalova", + "german": "Yalova", + "italian": "Yalova", + "spanish": "Yalova", + "chinese_simple": "亚罗法省", + "korean": "얄로바 주", + "dutch": "Yalova", + "portuguese": "Yalova", + "russian": "Ялова", + "chinese_traditional": "Yalova", + "unknown1": "Yalova", + "unknown2": "Yalova", + "unknown3": "Yalova", + "unknown4": "Yalova" + }, + "coordinates": { + "latitude": 40.6494136, + "longitude": 29.262164533 + } + } + ] + }, + { + "id": 110, + "iso_code": "GB", + "name": "United Kingdom", + "translations": { + "japanese": "イギリス", + "english": "United Kingdom", + "french": "Royaume-Uni", + "german": "Vereinigtes Königreich", + "italian": "Regno Unito", + "spanish": "Reino Unido", + "chinese_simple": "英国", + "korean": "영국", + "dutch": "Verenigd Koninkrijk", + "portuguese": "Reino Unido", + "russian": "Великобритания", + "chinese_traditional": "United Kingdom", + "unknown1": "United Kingdom", + "unknown2": "United Kingdom", + "unknown3": "United Kingdom", + "unknown4": "United Kingdom" + }, + "regions": [ + { + "id": 1845493760, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": 51.503905664, + "longitude": -0.11486728700000981 + } + }, + { + "id": 1845690368, + "name": "London", + "translations": { + "japanese": "ロンドン", + "english": "London", + "french": "Londres", + "german": "London", + "italian": "Londra", + "spanish": "Londres", + "chinese_simple": "伦敦", + "korean": "런던", + "dutch": "Londen", + "portuguese": "Londres", + "russian": "Лондон", + "chinese_traditional": "London", + "unknown1": "London", + "unknown2": "London", + "unknown3": "London", + "unknown4": "London" + }, + "coordinates": { + "latitude": 51.503905664, + "longitude": -0.11486728700000981 + } + }, + { + "id": 1845821440, + "name": "Scotland", + "translations": { + "japanese": "スコットランド", + "english": "Scotland", + "french": "Écosse", + "german": "Schottland", + "italian": "Scozia", + "spanish": "Escocia", + "chinese_simple": "苏格兰", + "korean": "스코틀랜드", + "dutch": "Schotland", + "portuguese": "Escócia", + "russian": "Шотландия", + "chinese_traditional": "Scotland", + "unknown1": "Scotland", + "unknown2": "Scotland", + "unknown3": "Scotland", + "unknown4": "Scotland" + }, + "coordinates": { + "latitude": 55.953368504000004, + "longitude": -3.2020338849999916 + } + }, + { + "id": 1845886976, + "name": "Wales", + "translations": { + "japanese": "ウェールズ", + "english": "Wales", + "french": "Pays de Galles", + "german": "Wales", + "italian": "Galles", + "spanish": "Gales", + "chinese_simple": "威尔士", + "korean": "웨일즈", + "dutch": "Wales", + "portuguese": "País de Gales", + "russian": "Уэльс", + "chinese_traditional": "Wales", + "unknown1": "Wales", + "unknown2": "Wales", + "unknown3": "Wales", + "unknown4": "Wales" + }, + "coordinates": { + "latitude": 51.476439844, + "longitude": -3.174567990000014 + } + }, + { + "id": 1845952512, + "name": "Northern Ireland", + "translations": { + "japanese": "北アイルランド", + "english": "Northern Ireland", + "french": "Irlande du Nord", + "german": "Nordirland", + "italian": "Irlanda del Nord", + "spanish": "Irlanda del Norte", + "chinese_simple": "北爱尔兰", + "korean": "북아일랜드", + "dutch": "Noord-Ierland", + "portuguese": "Irlanda do Norte", + "russian": "Северная Ирландия", + "chinese_traditional": "Northern Ireland", + "unknown1": "Northern Ireland", + "unknown2": "Northern Ireland", + "unknown3": "Northern Ireland", + "unknown4": "Northern Ireland" + }, + "coordinates": { + "latitude": 54.585570668, + "longitude": -5.904677953000004 + } + }, + { + "id": 1846018048, + "name": "South West", + "translations": { + "japanese": "南西部地方", + "english": "South West", + "french": "Sud-Ouest", + "german": "South West", + "italian": "Sud Ovest", + "spanish": "Suroeste", + "chinese_simple": "西南英格兰", + "korean": "사우스웨스트", + "dutch": "South West", + "portuguese": "Sudoeste", + "russian": "Юго-Западный регион", + "chinese_traditional": "South West", + "unknown1": "South West", + "unknown2": "South West", + "unknown3": "South West", + "unknown4": "South West" + }, + "coordinates": { + "latitude": 51.448974024, + "longitude": -2.5813046579999934 + } + }, + { + "id": 1846083584, + "name": "West Midlands", + "translations": { + "japanese": "ウェスト・ミッドランド地方", + "english": "West Midlands", + "french": "Midlands de l'Ouest", + "german": "West Midlands", + "italian": "Midlands Occidentali", + "spanish": "Midlands Occidentales", + "chinese_simple": "西米德兰兹", + "korean": "웨스트미들랜즈", + "dutch": "West Midlands", + "portuguese": "Midlands Ocidentais", + "russian": "Западный Мидленд", + "chinese_traditional": "West Midlands", + "unknown1": "West Midlands", + "unknown2": "West Midlands", + "unknown3": "West Midlands", + "unknown4": "West Midlands" + }, + "coordinates": { + "latitude": 52.481688856, + "longitude": -1.8891641040000025 + } + }, + { + "id": 1846149120, + "name": "North West", + "translations": { + "japanese": "北西部地方", + "english": "North West", + "french": "Nord-Ouest", + "german": "North West", + "italian": "Nord Ovest", + "spanish": "Noroeste", + "chinese_simple": "西北英格兰", + "korean": "노스웨스트", + "dutch": "North West", + "portuguese": "Noroeste", + "russian": "Северо-Западный регион", + "chinese_traditional": "North West", + "unknown1": "North West", + "unknown2": "North West", + "unknown3": "North West", + "unknown4": "North West" + }, + "coordinates": { + "latitude": 53.464965212, + "longitude": -2.229741202000014 + } + }, + { + "id": 1846214656, + "name": "North East", + "translations": { + "japanese": "北東部地方", + "english": "North East", + "french": "Nord-Est", + "german": "North East", + "italian": "Nord Est", + "spanish": "Noreste", + "chinese_simple": "东北英格兰", + "korean": "노스이스트", + "dutch": "North East", + "portuguese": "Nordeste", + "russian": "Северо-Восточный регион", + "chinese_traditional": "North East", + "unknown1": "North East", + "unknown2": "North East", + "unknown3": "North East", + "unknown4": "North East" + }, + "coordinates": { + "latitude": 54.970092148, + "longitude": -1.6090119750000156 + } + }, + { + "id": 1846280192, + "name": "Yorkshire and the Humber", + "translations": { + "japanese": "ヨークシャー・アンド・ザ・ハンバー地方", + "english": "Yorkshire and the Humber", + "french": "Yorkshire et Humber", + "german": "Yorkshire and the Humber", + "italian": "Yorkshire e Humber", + "spanish": "Yorkshire y Humber", + "chinese_simple": "约克郡-亨伯", + "korean": "요크셔-험버", + "dutch": "Yorkshire and the Humber", + "portuguese": "Yorkshire e Humber", + "russian": "Йоркшир и Хамбер", + "chinese_traditional": "Yorkshire and the Humber", + "unknown1": "Yorkshire and the Humber", + "unknown2": "Yorkshire and the Humber", + "unknown3": "Yorkshire and the Humber", + "unknown4": "Yorkshire and the Humber" + }, + "coordinates": { + "latitude": 53.794555052, + "longitude": -1.548587005999991 + } + }, + { + "id": 1846345728, + "name": "East Midlands", + "translations": { + "japanese": "イースト・ミッドランド地方", + "english": "East Midlands", + "french": "Midlands de l'Est", + "german": "East Midlands", + "italian": "Midlands Orientali", + "spanish": "Midlands Orientales", + "chinese_simple": "东米德兰兹", + "korean": "이스트미들랜즈", + "dutch": "East Midlands", + "portuguese": "Midlands Orientais", + "russian": "Восточный Мидленд", + "chinese_traditional": "East Midlands", + "unknown1": "East Midlands", + "unknown2": "East Midlands", + "unknown3": "East Midlands", + "unknown4": "East Midlands" + }, + "coordinates": { + "latitude": 52.630004284, + "longitude": -1.1365985810000154 + } + }, + { + "id": 1846411264, + "name": "East of England", + "translations": { + "japanese": "イングランド東部地方", + "english": "East of England", + "french": "Est", + "german": "East of England", + "italian": "Est dell'Inghilterra", + "spanish": "Este de Inglaterra", + "chinese_simple": "东英格兰", + "korean": "이스트오브잉글랜드", + "dutch": "East of England", + "portuguese": "Este de Inglaterra", + "russian": "Восточный регион", + "chinese_traditional": "East of England", + "unknown1": "East of England", + "unknown2": "East of England", + "unknown3": "East of England", + "unknown4": "East of England" + }, + "coordinates": { + "latitude": 52.62451112, + "longitude": 1.296390244 + } + }, + { + "id": 1846476800, + "name": "South East", + "translations": { + "japanese": "南東部地方", + "english": "South East", + "french": "Sud-Est", + "german": "South East", + "italian": "Sud Est", + "spanish": "씒⭤ꥠ軷됎矙ᑻ۱埣ἴឦ", + "chinese_simple": "东南英格兰", + "korean": "사우스이스트", + "dutch": "South East", + "portuguese": "Sudeste", + "russian": "Юго-Восточный регион", + "chinese_traditional": "South East", + "unknown1": "South East", + "unknown2": "South East", + "unknown3": "South East", + "unknown4": "South East" + }, + "coordinates": { + "latitude": 50.83923282, + "longitude": -0.1258536450000065 + } + } + ] + }, + { + "id": 111, + "iso_code": "ZM", + "name": "Zambia", + "translations": { + "japanese": "ザンビア", + "english": "Zambia", + "french": "Zambie", + "german": "Sambia", + "italian": "Zambia", + "spanish": "Zambia", + "chinese_simple": "赞比亚", + "korean": "잠비아", + "dutch": "Zambia", + "portuguese": "Zâmbia", + "russian": "Замбия", + "chinese_traditional": "Zambia", + "unknown1": "Zambia", + "unknown2": "Zambia", + "unknown3": "Zambia", + "unknown4": "Zambia" + }, + "regions": [ + { + "id": 1862270976, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": -15.413819207999992, + "longitude": 28.278885492 + } + }, + { + "id": 1862336512, + "name": "Zambia", + "translations": { + "japanese": "ザンビア", + "english": "Zambia", + "french": "Zambie", + "german": "Sambia", + "italian": "Zambia", + "spanish": "Zambia", + "chinese_simple": "赞比亚", + "korean": "잠비아", + "dutch": "Zambia", + "portuguese": "Zâmbia", + "russian": "Замбия", + "chinese_traditional": "Zambia", + "unknown1": "Zambia", + "unknown2": "Zambia", + "unknown3": "Zambia", + "unknown4": "Zambia" + }, + "coordinates": { + "latitude": -15.413819207999992, + "longitude": 28.278885492 + } + } + ] + }, + { + "id": 112, + "iso_code": "ZW", + "name": "Zimbabwe", + "translations": { + "japanese": "ジンバブエ", + "english": "Zimbabwe", + "french": "Zimbabwe", + "german": "Simbabwe", + "italian": "Zimbabwe", + "spanish": "Zimbabue", + "chinese_simple": "津巴布韦", + "korean": "짐바브웨", + "dutch": "Zimbabwe", + "portuguese": "Zimbabué", + "russian": "Зимбабве", + "chinese_traditional": "Zimbabwe", + "unknown1": "Zimbabwe", + "unknown2": "Zimbabwe", + "unknown3": "Zimbabwe", + "unknown4": "Zimbabwe" + }, + "regions": [ + { + "id": 1879048192, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": -17.819825039999998, + "longitude": 31.047447708 + } + }, + { + "id": 1879113728, + "name": "Zimbabwe", + "translations": { + "japanese": "ジンバブエ", + "english": "Zimbabwe", + "french": "Zimbabwe", + "german": "Simbabwe", + "italian": "Zimbabwe", + "spanish": "Zimbabue", + "chinese_simple": "津巴布韦", + "korean": "짐바브웨", + "dutch": "Zimbabwe", + "portuguese": "Zimbabué", + "russian": "Зимбабве", + "chinese_traditional": "Zimbabwe", + "unknown1": "Zimbabwe", + "unknown2": "Zimbabwe", + "unknown3": "Zimbabwe", + "unknown4": "Zimbabwe" + }, + "coordinates": { + "latitude": -17.819825039999998, + "longitude": 31.047447708 + } + } + ] + }, + { + "id": 113, + "iso_code": "AZ", + "name": "Azerbaijan", + "translations": { + "japanese": "アゼルバイジャン", + "english": "Azerbaijan", + "french": "Azerbaïdjan", + "german": "Aserbaidschan", + "italian": "Azerbaigian", + "spanish": "Azerbaiyán", + "chinese_simple": "阿塞拜疆", + "korean": "아제르바이잔", + "dutch": "Azerbeidzjan", + "portuguese": "Azerbaijão", + "russian": "Азербайджан", + "chinese_traditional": "Azerbaijan", + "unknown1": "Azerbaijan", + "unknown2": "Azerbaijan", + "unknown3": "Azerbaijan", + "unknown4": "Azerbaijan" + }, + "regions": [ + { + "id": 1895825408, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": 40.3747554, + "longitude": 49.889051678 + } + }, + { + "id": 1895890944, + "name": "Azerbaijan", + "translations": { + "japanese": "アゼルバイジャン", + "english": "Azerbaijan", + "french": "Azerbaïdjan", + "german": "Aserbaidschan", + "italian": "Azerbaigian", + "spanish": "Azerbaiyán", + "chinese_simple": "阿塞拜疆", + "korean": "아제르바이잔", + "dutch": "Azerbeidzjan", + "portuguese": "Azerbaijão", + "russian": "Азербайджан", + "chinese_traditional": "Azerbaijan", + "unknown1": "Azerbaijan", + "unknown2": "Azerbaijan", + "unknown3": "Azerbaijan", + "unknown4": "Azerbaijan" + }, + "coordinates": { + "latitude": 40.3747554, + "longitude": 49.889051678 + } + } + ] + }, + { + "id": 114, + "iso_code": "MR", + "name": "Mauritania", + "translations": { + "japanese": "モーリタニア", + "english": "Mauritania", + "french": "Mauritanie", + "german": "Mauretanien", + "italian": "Mauritania", + "spanish": "Mauritania", + "chinese_simple": "毛里塔尼亚", + "korean": "모리타니", + "dutch": "Mauritanië", + "portuguese": "Mauritânia", + "russian": "Мавритания", + "chinese_traditional": "Mauritania", + "unknown1": "Mauritania", + "unknown2": "Mauritania", + "unknown3": "Mauritania", + "unknown4": "Mauritania" + }, + "regions": [ + { + "id": 1912602624, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": 18.09997538, + "longitude": -15.946209164999999 + } + }, + { + "id": 1912668160, + "name": "Mauritania", + "translations": { + "japanese": "モーリタニア", + "english": "Mauritania", + "french": "Mauritanie", + "german": "Mauretanien", + "italian": "Mauritania", + "spanish": "Mauritania", + "chinese_simple": "毛里塔尼亚", + "korean": "모리타니", + "dutch": "Mauritanië", + "portuguese": "Mauritânia", + "russian": "Мавритания", + "chinese_traditional": "Mauritania", + "unknown1": "Mauritania", + "unknown2": "Mauritania", + "unknown3": "Mauritania", + "unknown4": "Mauritania" + }, + "coordinates": { + "latitude": 18.09997538, + "longitude": -15.946209164999999 + } + } + ] + }, + { + "id": 115, + "iso_code": "ML", + "name": "Mali", + "translations": { + "japanese": "マリ", + "english": "Mali", + "french": "Mali", + "german": "Mali", + "italian": "Mali", + "spanish": "Malí", + "chinese_simple": "马里", + "korean": "말리", + "dutch": "Mali", + "portuguese": "Mali", + "russian": "Мали", + "chinese_traditional": "Mali", + "unknown1": "Mali", + "unknown2": "Mali", + "unknown3": "Mali", + "unknown4": "Mali" + }, + "regions": [ + { + "id": 1929379840, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": 12.645263528, + "longitude": -7.997579152000014 + } + }, + { + "id": 1929445376, + "name": "Mali", + "translations": { + "japanese": "マリ", + "english": "Mali", + "french": "Mali", + "german": "Mali", + "italian": "Mali", + "spanish": "Malí", + "chinese_simple": "马里", + "korean": "말리", + "dutch": "Mali", + "portuguese": "Mali", + "russian": "Мали", + "chinese_traditional": "Mali", + "unknown1": "Mali", + "unknown2": "Mali", + "unknown3": "Mali", + "unknown4": "Mali" + }, + "coordinates": { + "latitude": 12.645263528, + "longitude": -7.997579152000014 + } + } + ] + }, + { + "id": 116, + "iso_code": "NE", + "name": "Niger", + "translations": { + "japanese": "ニジェール", + "english": "Niger", + "french": "Niger", + "german": "Niger", + "italian": "Niger", + "spanish": "Níger", + "chinese_simple": "尼日尔", + "korean": "니제르", + "dutch": "Niger", + "portuguese": "Níger", + "russian": "Нигер", + "chinese_traditional": "Niger", + "unknown1": "Niger", + "unknown2": "Niger", + "unknown3": "Niger", + "unknown4": "Niger" + }, + "regions": [ + { + "id": 1946157056, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": 13.518676604, + "longitude": 2.103887557 + } + }, + { + "id": 1946222592, + "name": "Niger", + "translations": { + "japanese": "ニジェール", + "english": "Niger", + "french": "Niger", + "german": "Niger", + "italian": "Niger", + "spanish": "Níger", + "chinese_simple": "尼日尔", + "korean": "니제르", + "dutch": "Niger", + "portuguese": "Níger", + "russian": "Нигер", + "chinese_traditional": "Niger", + "unknown1": "Niger", + "unknown2": "Niger", + "unknown3": "Niger", + "unknown4": "Niger" + }, + "coordinates": { + "latitude": 13.518676604, + "longitude": 2.103887557 + } + } + ] + }, + { + "id": 117, + "iso_code": "TD", + "name": "Chad", + "translations": { + "japanese": "チャド", + "english": "Chad", + "french": "Tchad", + "german": "Tschad", + "italian": "Ciad", + "spanish": "Chad", + "chinese_simple": "乍得", + "korean": "차드", + "dutch": "Tsjaad", + "portuguese": "Chade", + "russian": "Чад", + "chinese_traditional": "Chad", + "unknown1": "Chad", + "unknown2": "Chad", + "unknown3": "Chad", + "unknown4": "Chad" + }, + "regions": [ + { + "id": 1962934272, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": 12.106933456, + "longitude": 15.034830923 + } + }, + { + "id": 1962999808, + "name": "Chad", + "translations": { + "japanese": "チャド", + "english": "Chad", + "french": "Tchad", + "german": "Tschad", + "italian": "Ciad", + "spanish": "Chad", + "chinese_simple": "乍得", + "korean": "차드", + "dutch": "Tsjaad", + "portuguese": "Chade", + "russian": "Чад", + "chinese_traditional": "Chad", + "unknown1": "Chad", + "unknown2": "Chad", + "unknown3": "Chad", + "unknown4": "Chad" + }, + "coordinates": { + "latitude": 12.106933456, + "longitude": 15.034830923 + } + } + ] + }, + { + "id": 118, + "iso_code": "SD", + "name": "Sudan", + "translations": { + "japanese": "スーダン", + "english": "Sudan", + "french": "Soudan", + "german": "Sudan", + "italian": "Sudan", + "spanish": "Sudán", + "chinese_simple": "苏丹", + "korean": "수단", + "dutch": "Soedan", + "portuguese": "Sudão", + "russian": "Судан", + "chinese_traditional": "Sudan", + "unknown1": "Sudan", + "unknown2": "Sudan", + "unknown3": "Sudan", + "unknown4": "Sudan" + }, + "regions": [ + { + "id": 1979711488, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": 15.62805158, + "longitude": 32.530606038 + } + }, + { + "id": 1979777024, + "name": "Sudan", + "translations": { + "japanese": "スーダン", + "english": "Sudan", + "french": "Soudan", + "german": "Sudan", + "italian": "Sudan", + "spanish": "Sudán", + "chinese_simple": "苏丹", + "korean": "수단", + "dutch": "Soedan", + "portuguese": "Sudão", + "russian": "Судан", + "chinese_traditional": "Sudan", + "unknown1": "Sudan", + "unknown2": "Sudan", + "unknown3": "Sudan", + "unknown4": "Sudan" + }, + "coordinates": { + "latitude": 15.62805158, + "longitude": 32.530606038 + } + } + ] + }, + { + "id": 119, + "iso_code": "ER", + "name": "Eritrea", + "translations": { + "japanese": "エリトリア", + "english": "Eritrea", + "french": "Érythrée", + "german": "Eritrea", + "italian": "Eritrea", + "spanish": "Eritrea", + "chinese_simple": "厄立特里亚", + "korean": "에리트레아", + "dutch": "Eritrea", + "portuguese": "Eritreia", + "russian": "Эритрея", + "chinese_traditional": "Eritrea", + "unknown1": "Eritrea", + "unknown2": "Eritrea", + "unknown3": "Eritrea", + "unknown4": "Eritrea" + }, + "regions": [ + { + "id": 1996488704, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": 15.331420724, + "longitude": 38.930159573 + } + }, + { + "id": 1996554240, + "name": "Eritrea", + "translations": { + "japanese": "エリトリア", + "english": "Eritrea", + "french": "Érythrée", + "german": "Eritrea", + "italian": "Eritrea", + "spanish": "Eritrea", + "chinese_simple": "厄立特里亚", + "korean": "에리트레아", + "dutch": "Eritrea", + "portuguese": "Eritreia", + "russian": "Эритрея", + "chinese_traditional": "Eritrea", + "unknown1": "Eritrea", + "unknown2": "Eritrea", + "unknown3": "Eritrea", + "unknown4": "Eritrea" + }, + "coordinates": { + "latitude": 15.331420724, + "longitude": 38.930159573 + } + } + ] + }, + { + "id": 120, + "iso_code": "DJ", + "name": "Djibouti", + "translations": { + "japanese": "ジブチ", + "english": "Djibouti", + "french": "Djibouti", + "german": "Dschibuti", + "italian": "Gibuti", + "spanish": "Yibuti", + "chinese_simple": "吉布提", + "korean": "지부티", + "dutch": "Djibouti", + "portuguese": "Djibouti", + "russian": "Джибути", + "chinese_traditional": "Djibouti", + "unknown1": "Djibouti", + "unknown2": "Djibouti", + "unknown3": "Djibouti", + "unknown4": "Djibouti" + }, + "regions": [ + { + "id": 2013265920, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": 11.585082876, + "longitude": 43.143427865999996 + } + }, + { + "id": 2013331456, + "name": "Djibouti", + "translations": { + "japanese": "ジブチ", + "english": "Djibouti", + "french": "Djibouti", + "german": "Dschibuti", + "italian": "Gibuti", + "spanish": "Yibuti", + "chinese_simple": "吉布提", + "korean": "지부티", + "dutch": "Djibouti", + "portuguese": "Djibouti", + "russian": "Джибути", + "chinese_traditional": "Djibouti", + "unknown1": "Djibouti", + "unknown2": "Djibouti", + "unknown3": "Djibouti", + "unknown4": "Djibouti" + }, + "coordinates": { + "latitude": 11.585082876, + "longitude": 43.143427865999996 + } + } + ] + }, + { + "id": 121, + "iso_code": "SO", + "name": "Somalia", + "translations": { + "japanese": "ソマリア", + "english": "Somalia", + "french": "Somalie", + "german": "Somalia", + "italian": "Somalia", + "spanish": "Somalia", + "chinese_simple": "索马里", + "korean": "소말리아", + "dutch": "Somalië", + "portuguese": "Somália", + "russian": "Сомали", + "chinese_traditional": "Somalia", + "unknown1": "Somalia", + "unknown2": "Somalia", + "unknown3": "Somalia", + "unknown4": "Somalia" + }, + "regions": [ + { + "id": 2030043136, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": 2.03247068, + "longitude": 45.346192645 + } + }, + { + "id": 2030108672, + "name": "Somalia", + "translations": { + "japanese": "ソマリア", + "english": "Somalia", + "french": "Somalie", + "german": "Somalia", + "italian": "Somalia", + "spanish": "Somalia", + "chinese_simple": "索马里", + "korean": "소말리아", + "dutch": "Somalië", + "portuguese": "Somália", + "russian": "Сомали", + "chinese_traditional": "Somalia", + "unknown1": "Somalia", + "unknown2": "Somalia", + "unknown3": "Somalia", + "unknown4": "Somalia" + }, + "coordinates": { + "latitude": 2.03247068, + "longitude": 45.346192645 + } + } + ] + }, + { + "id": 122, + "iso_code": "AD", + "name": "Andorra", + "translations": { + "japanese": "アンドラ", + "english": "Andorra", + "french": "Andorre", + "german": "Andorra", + "italian": "Andorra", + "spanish": "Andorra", + "chinese_simple": "安道尔", + "korean": "안도라", + "dutch": "Andorra", + "portuguese": "Andorra", + "russian": "Андорра", + "chinese_traditional": "Andorra", + "unknown1": "Andorra", + "unknown2": "Andorra", + "unknown3": "Andorra", + "unknown4": "Andorra" + }, + "regions": [ + { + "id": 2046820352, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": 42.495116704, + "longitude": 1.499637867 + } + }, + { + "id": 2046885888, + "name": "Andorra", + "translations": { + "japanese": "アンドラ", + "english": "Andorra", + "french": "Andorre", + "german": "Andorra", + "italian": "Andorra", + "spanish": "Andorra", + "chinese_simple": "安道尔", + "korean": "안도라", + "dutch": "Andorra", + "portuguese": "Andorra", + "russian": "Андорра", + "chinese_traditional": "Andorra", + "unknown1": "Andorra", + "unknown2": "Andorra", + "unknown3": "Andorra", + "unknown4": "Andorra" + }, + "coordinates": { + "latitude": 42.495116704, + "longitude": 1.499637867 + } + } + ] + }, + { + "id": 123, + "iso_code": "GI", + "name": "Gibraltar", + "translations": { + "japanese": "ジブラルタル", + "english": "Gibraltar", + "french": "Gibraltar", + "german": "Gibraltar", + "italian": "Gibilterra", + "spanish": "Gibraltar", + "chinese_simple": "直布罗陀", + "korean": "지브롤터", + "dutch": "Gibraltar", + "portuguese": "Gibraltar", + "russian": "Гибралтар", + "chinese_traditional": "Gibraltar", + "unknown1": "Gibraltar", + "unknown2": "Gibraltar", + "unknown3": "Gibraltar", + "unknown4": "Gibraltar" + }, + "regions": [ + { + "id": 2063597568, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": 36.128539628, + "longitude": -5.344373695000002 + } + }, + { + "id": 2063663104, + "name": "Gibraltar", + "translations": { + "japanese": "ジブラルタル", + "english": "Gibraltar", + "french": "Gibraltar", + "german": "Gibraltar", + "italian": "Gibilterra", + "spanish": "Gibraltar", + "chinese_simple": "直布罗陀", + "korean": "지브롤터", + "dutch": "Gibraltar", + "portuguese": "Gibraltar", + "russian": "Гибралтар", + "chinese_traditional": "Gibraltar", + "unknown1": "Gibraltar", + "unknown2": "Gibraltar", + "unknown3": "Gibraltar", + "unknown4": "Gibraltar" + }, + "coordinates": { + "latitude": 36.128539628, + "longitude": -5.344373695000002 + } + } + ] + }, + { + "id": 124, + "iso_code": "GG", + "name": "Guernsey", + "translations": { + "japanese": "ガーンジー島", + "english": "Guernsey", + "french": "Guernesey", + "german": "Guernsey", + "italian": "Guernsey", + "spanish": "Guernsey", + "chinese_simple": "根西", + "korean": "건지 섬", + "dutch": "Guernsey", + "portuguese": "Guernsey", + "russian": "Гернси", + "chinese_traditional": "Guernsey", + "unknown1": "Guernsey", + "unknown2": "Guernsey", + "unknown3": "Guernsey", + "unknown4": "Guernsey" + }, + "regions": [ + { + "id": 2080374784, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": 49.449462328, + "longitude": -2.5483455840000033 + } + }, + { + "id": 2080440320, + "name": "Guernsey", + "translations": { + "japanese": "ガーンジー島", + "english": "Guernsey", + "french": "Guernesey", + "german": "Guernsey", + "italian": "Guernsey", + "spanish": "Guernsey", + "chinese_simple": "根西", + "korean": "건지 섬", + "dutch": "Guernsey", + "portuguese": "Guernsey", + "russian": "Гернси", + "chinese_traditional": "Guernsey", + "unknown1": "Guernsey", + "unknown2": "Guernsey", + "unknown3": "Guernsey", + "unknown4": "Guernsey" + }, + "coordinates": { + "latitude": 49.449462328, + "longitude": -2.5483455840000033 + } + } + ] + }, + { + "id": 125, + "iso_code": "IM", + "name": "Isle of Man", + "translations": { + "japanese": "マン島", + "english": "Isle of Man", + "french": "Île de Man", + "german": "Isle of Man", + "italian": "Isola di Man", + "spanish": "Isla de Man", + "chinese_simple": "马恩岛", + "korean": "맨 섬", + "dutch": "Man", + "portuguese": "Ilha de Man", + "russian": "Мэн (остров)", + "chinese_traditional": "Isle of Man", + "unknown1": "Isle of Man", + "unknown2": "Isle of Man", + "unknown3": "Isle of Man", + "unknown4": "Isle of Man" + }, + "regions": [ + { + "id": 2097152000, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": 54.16259704, + "longitude": -4.481944591999991 + } + }, + { + "id": 2097217536, + "name": "Isle of Man", + "translations": { + "japanese": "マン島", + "english": "Isle of Man", + "french": "Île de Man", + "german": "Isle of Man", + "italian": "Isola di Man", + "spanish": "Isla de Man", + "chinese_simple": "马恩岛", + "korean": "맨 섬", + "dutch": "Man", + "portuguese": "Ilha de Man", + "russian": "Мэн (остров)", + "chinese_traditional": "Isle of Man", + "unknown1": "Isle of Man", + "unknown2": "Isle of Man", + "unknown3": "Isle of Man", + "unknown4": "Isle of Man" + }, + "coordinates": { + "latitude": 54.16259704, + "longitude": -4.481944591999991 + } + } + ] + }, + { + "id": 126, + "iso_code": "JE", + "name": "Jersey", + "translations": { + "japanese": "ジャージー島", + "english": "Jersey", + "french": "Jersey", + "german": "Jersey", + "italian": "Jersey", + "spanish": "Jersey", + "chinese_simple": "泽西", + "korean": "저지 섬", + "dutch": "Jersey", + "portuguese": "Jérsia", + "russian": "Джерси", + "chinese_traditional": "Jersey", + "unknown1": "Jersey", + "unknown2": "Jersey", + "unknown3": "Jersey", + "unknown4": "Jersey" + }, + "regions": [ + { + "id": 2113929216, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": 49.185790456, + "longitude": -2.1088912639999933 + } + }, + { + "id": 2113994752, + "name": "Jersey", + "translations": { + "japanese": "ジャージー島", + "english": "Jersey", + "french": "Jersey", + "german": "Jersey", + "italian": "Jersey", + "spanish": "Jersey", + "chinese_simple": "泽西", + "korean": "저지 섬", + "dutch": "Jersey", + "portuguese": "Jérsia", + "russian": "Джерси", + "chinese_traditional": "Jersey", + "unknown1": "Jersey", + "unknown2": "Jersey", + "unknown3": "Jersey", + "unknown4": "Jersey" + }, + "coordinates": { + "latitude": 49.185790456, + "longitude": -2.1088912639999933 + } + } + ] + }, + { + "id": 127, + "iso_code": "MC", + "name": "Monaco", + "translations": { + "japanese": "モナコ", + "english": "Monaco", + "french": "Monaco", + "german": "Monaco", + "italian": "Monaco (Principato di)", + "spanish": "Mónaco", + "chinese_simple": "摩纳哥", + "korean": "모나코", + "dutch": "Monaco", + "portuguese": "Mónaco", + "russian": "Монако", + "chinese_traditional": "Monaco", + "unknown1": "Monaco", + "unknown2": "Monaco", + "unknown3": "Monaco", + "unknown4": "Monaco" + }, + "regions": [ + { + "id": 2130706432, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": 43.714599112, + "longitude": 7.41579165 + } + }, + { + "id": 2130771968, + "name": "Monaco", + "translations": { + "japanese": "モナコ", + "english": "Monaco", + "french": "Monaco", + "german": "Monaco", + "italian": "Monaco (Principato di)", + "spanish": "Mónaco", + "chinese_simple": "摩纳哥", + "korean": "모나코", + "dutch": "Monaco", + "portuguese": "Mónaco", + "russian": "Монако", + "chinese_traditional": "Monaco", + "unknown1": "Monaco", + "unknown2": "Monaco", + "unknown3": "Monaco", + "unknown4": "Monaco" + }, + "coordinates": { + "latitude": 43.714599112, + "longitude": 7.41579165 + } + } + ] + }, + { + "id": 128, + "iso_code": "TW", + "name": "Taiwan", + "translations": { + "japanese": "台湾", + "english": "Taiwan", + "french": "Taïwan", + "german": "Taiwan", + "italian": "Taiwan", + "spanish": "Taiwán", + "chinese_simple": "中国 台湾", + "korean": "타이완", + "dutch": "Taiwan", + "portuguese": "Taiwan", + "russian": "Тайвань", + "chinese_traditional": "臺灣", + "unknown1": "Taiwan", + "unknown2": "Taiwan", + "unknown3": "Taiwan", + "unknown4": "Taiwan" + }, + "regions": [ + { + "id": 2147483648, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": 25.032348348, + "longitude": 121.503626301 + } + }, + { + "id": 2147614720, + "name": "Taipei City", + "translations": { + "japanese": "台北市", + "english": "Taipei City", + "french": "Taipei", + "german": "Taipeh", + "italian": "Taipei", + "spanish": "Taipéi", + "chinese_simple": "-", + "korean": "타이베이", + "dutch": "Taipei", + "portuguese": "Taipei", + "russian": "Тайбэй", + "chinese_traditional": "臺北市", + "unknown1": "Taipei City", + "unknown2": "Taipei City", + "unknown3": "Taipei City", + "unknown4": "Taipei City" + }, + "coordinates": { + "latitude": 25.032348348, + "longitude": 121.503626301 + } + }, + { + "id": 2147680256, + "name": "Kaohsiung City", + "translations": { + "japanese": "高雄市", + "english": "Kaohsiung City", + "french": "Kaohsiung", + "german": "Kaohsiung", + "italian": "Kaohsiung", + "spanish": "Condado de Kaohsiung", + "chinese_simple": "-", + "korean": "가오슝", + "dutch": "Kaohsiung", + "portuguese": "Condado de Kaohsiung", + "russian": "Гаосюн", + "chinese_traditional": "高雄市", + "unknown1": "Kaohsiung City", + "unknown2": "Kaohsiung City", + "unknown3": "Kaohsiung City", + "unknown4": "Kaohsiung City" + }, + "coordinates": { + "latitude": 22.598876696, + "longitude": 120.278647384 + } + }, + { + "id": 2147745792, + "name": "Keelung City", + "translations": { + "japanese": "基隆市", + "english": "Keelung City", + "french": "Keelung", + "german": "Keelung", + "italian": "Keelung", + "spanish": "Keelung", + "chinese_simple": "-", + "korean": "지룽", + "dutch": "Chilung", + "portuguese": "Keelung", + "russian": "Килунг", + "chinese_traditional": "基隆市", + "unknown1": "Keelung City", + "unknown2": "Keelung City", + "unknown3": "Keelung City", + "unknown4": "Keelung City" + }, + "coordinates": { + "latitude": 25.120238972, + "longitude": 121.734339819 + } + }, + { + "id": 2147811328, + "name": "Hsinchu City", + "translations": { + "japanese": "新竹市", + "english": "Hsinchu City", + "french": "Hsinchu", + "german": "Hsinchu", + "italian": "Hsinchu", + "spanish": "Hsinchu", + "chinese_simple": "-", + "korean": "신주", + "dutch": "Hsinchu", + "portuguese": "Hsinchu", + "russian": "Синьчжу", + "chinese_traditional": "新竹市", + "unknown1": "Hsinchu City", + "unknown2": "Hsinchu City", + "unknown3": "Hsinchu City", + "unknown4": "Hsinchu City" + }, + "coordinates": { + "latitude": 24.724731164, + "longitude": 120.954308401 + } + }, + { + "id": 2147876864, + "name": "Taichung City", + "translations": { + "japanese": "台中市", + "english": "Taichung City", + "french": "Taichung", + "german": "Taichung", + "italian": "Taichung", + "spanish": "Taichung", + "chinese_simple": "-", + "korean": "타이중", + "dutch": "Taichung", + "portuguese": "Taichung", + "russian": "Тайчжун", + "chinese_traditional": "臺中市", + "unknown1": "Taichung City", + "unknown2": "Taichung City", + "unknown3": "Taichung City", + "unknown4": "Taichung City" + }, + "coordinates": { + "latitude": 24.131469452, + "longitude": 120.646690377 + } + }, + { + "id": 2147942400, + "name": "Chiayi City", + "translations": { + "japanese": "嘉義市", + "english": "Chiayi City", + "french": "Chiayi", + "german": "Chiayi", + "italian": "Chiayi", + "spanish": "Chiayi", + "chinese_simple": "-", + "korean": "자이", + "dutch": "Chiayi", + "portuguese": "Chiayi", + "russian": "Цзяи", + "chinese_traditional": "嘉義市", + "unknown1": "Chiayi City", + "unknown2": "Chiayi City", + "unknown3": "Chiayi City", + "unknown4": "Chiayi City" + }, + "coordinates": { + "latitude": 23.461303444, + "longitude": 120.448935933 + } + }, + { + "id": 2148007936, + "name": "Tainan City", + "translations": { + "japanese": "台南市", + "english": "Tainan City", + "french": "Tainan", + "german": "Tainan", + "italian": "Tainan", + "spanish": "Tainan", + "chinese_simple": "-", + "korean": "타이난", + "dutch": "Tainan", + "portuguese": "Tainan", + "russian": "Тайнань", + "chinese_traditional": "臺南市", + "unknown1": "Tainan City", + "unknown2": "Tainan City", + "unknown3": "Tainan City", + "unknown4": "Tainan City" + }, + "coordinates": { + "latitude": 22.98889134, + "longitude": 120.19075652 + } + }, + { + "id": 2148073472, + "name": "New Taipei City", + "translations": { + "japanese": "新北市", + "english": "New Taipei City", + "french": "Nouveau Taipei", + "german": "Xinbei", + "italian": "New Taipei", + "spanish": "Nuevo Taipéi", + "chinese_simple": "-", + "korean": "신베이", + "dutch": "Nieuw Taipei", + "portuguese": "Nova Taipei", + "russian": "Новый Тайбэй", + "chinese_traditional": "新北市", + "unknown1": "New Taipei City", + "unknown2": "New Taipei City", + "unknown3": "New Taipei City", + "unknown4": "New Taipei City" + }, + "coordinates": { + "latitude": 24.977416708, + "longitude": 121.448694511 + } + }, + { + "id": 2148139008, + "name": "Taoyuan City", + "translations": { + "japanese": "桃園市", + "english": "Taoyuan City", + "french": "Taoyuan", + "german": "Taoyuan", + "italian": "Taoyuan", + "spanish": "Ciudad de Taoyuan", + "chinese_simple": "-", + "korean": "타오위안", + "dutch": "Taoyuan", + "portuguese": "Cidade de Taoyuan", + "russian": "Таоюань", + "chinese_traditional": "桃園市", + "unknown1": "Taoyuan City", + "unknown2": "Taoyuan City", + "unknown3": "Taoyuan City", + "unknown4": "Taoyuan City" + }, + "coordinates": { + "latitude": 24.971923544, + "longitude": 121.28939232 + } + }, + { + "id": 2148204544, + "name": "HsinChu County", + "translations": { + "japanese": "新竹県", + "english": "HsinChu County", + "french": "Hsinchu (comté de)", + "german": "Kreis Hsinchu", + "italian": "Contea di HsinChu", + "spanish": "Condado de Hsinchu", + "chinese_simple": "-", + "korean": "신주 현", + "dutch": "HsinChu", + "portuguese": "Condado de Hsinchu", + "russian": "Синьчжу (уезд)", + "chinese_traditional": "新竹縣", + "unknown1": "HsinChu County", + "unknown2": "HsinChu County", + "unknown3": "HsinChu County", + "unknown4": "HsinChu County" + }, + "coordinates": { + "latitude": 24.82910128, + "longitude": 120.99825383299999 + } + }, + { + "id": 2148270080, + "name": "Miaoli County", + "translations": { + "japanese": "苗栗県", + "english": "Miaoli County", + "french": "Miaoli (comté de)", + "german": "Kreis Miaoli", + "italian": "Contea di Miaoli", + "spanish": "Distrito de Miaoli", + "chinese_simple": "-", + "korean": "먀오리 현", + "dutch": "Miaoli", + "portuguese": "Distrito de Miaoli", + "russian": "Мяоли (уезд)", + "chinese_traditional": "苗栗縣", + "unknown1": "Miaoli County", + "unknown2": "Miaoli County", + "unknown3": "Miaoli County", + "unknown4": "Miaoli County" + }, + "coordinates": { + "latitude": 24.532470424, + "longitude": 120.79500621 + } + }, + { + "id": 2148335616, + "name": "Taichung County", + "translations": { + "japanese": "台中県", + "english": "Taichung County", + "french": "Taichung (comté de)", + "german": "Kreis Taichung", + "italian": "Contea di Taichung", + "spanish": "Condado de Taichung", + "chinese_simple": "-", + "korean": "타이중 현", + "dutch": "Taichung", + "portuguese": "Condado de Taichung", + "russian": "Тайчжун (уезд)", + "chinese_traditional": "Taichung County", + "unknown1": "Taichung County", + "unknown2": "Taichung County", + "unknown3": "Taichung County", + "unknown4": "Taichung County" + }, + "coordinates": { + "latitude": 24.219360076, + "longitude": 120.701622167 + } + }, + { + "id": 2148401152, + "name": "Changhua County", + "translations": { + "japanese": "彰化県", + "english": "Changhua County", + "french": "Changhua (comté de)", + "german": "Kreis Changhua", + "italian": "Contea di Changhua", + "spanish": "Condado de Changhua", + "chinese_simple": "-", + "korean": "장화 현", + "dutch": "Changhua", + "portuguese": "Condado de Changhua", + "russian": "Чжанхуа (уезд)", + "chinese_traditional": "彰化縣", + "unknown1": "Changhua County", + "unknown2": "Changhua County", + "unknown3": "Changhua County", + "unknown4": "Changhua County" + }, + "coordinates": { + "latitude": 24.065551484, + "longitude": 120.531333618 + } + }, + { + "id": 2148466688, + "name": "Nantou County", + "translations": { + "japanese": "南投県", + "english": "Nantou County", + "french": "Nantou (comté de)", + "german": "Kreis Nantou", + "italian": "Contea di Nantou", + "spanish": "Condado de Nantou", + "chinese_simple": "-", + "korean": "난터우 현", + "dutch": "Nantou", + "portuguese": "Condado de Nantou", + "russian": "Наньтоу (уезд)", + "chinese_traditional": "南投縣", + "unknown1": "Nantou County", + "unknown2": "Nantou County", + "unknown3": "Nantou County", + "unknown4": "Nantou County" + }, + "coordinates": { + "latitude": 23.8952634, + "longitude": 120.696128988 + } + }, + { + "id": 2148532224, + "name": "Yunlin County", + "translations": { + "japanese": "雲林県", + "english": "Yunlin County", + "french": "Yunlin (comté de)", + "german": "Kreis Yunlin", + "italian": "Contea di Yunlin", + "spanish": "Condado de Yunlin", + "chinese_simple": "-", + "korean": "윈린 현", + "dutch": "Yunlin", + "portuguese": "Condado de Yunlin", + "russian": "Юньлинь (уезд)", + "chinese_traditional": "雲林縣", + "unknown1": "Yunlin County", + "unknown2": "Yunlin County", + "unknown3": "Yunlin County", + "unknown4": "Yunlin County" + }, + "coordinates": { + "latitude": 23.686523168, + "longitude": 120.52584043899999 + } + }, + { + "id": 2148597760, + "name": "Chiayi County", + "translations": { + "japanese": "嘉義県", + "english": "Chiayi County", + "french": "Chiayi (comté de)", + "german": "Kreis Chiayi", + "italian": "Contea di Chiayi", + "spanish": "Condado de Chiayi", + "chinese_simple": "-", + "korean": "자이 현", + "dutch": "Chiayi", + "portuguese": "Condado de Chiayi", + "russian": "Цзяи (уезд)", + "chinese_traditional": "嘉義縣", + "unknown1": "Chiayi County", + "unknown2": "Chiayi County", + "unknown3": "Chiayi County", + "unknown4": "Chiayi County" + }, + "coordinates": { + "latitude": 23.461303444, + "longitude": 120.448935933 + } + }, + { + "id": 2148663296, + "name": "Tainan County", + "translations": { + "japanese": "台南県", + "english": "Tainan County", + "french": "Tainan (comté de)", + "german": "Kreis Tainan", + "italian": "Contea di Tainan", + "spanish": "Condado de Tainan", + "chinese_simple": "-", + "korean": "타이난 현", + "dutch": "Tainan", + "portuguese": "Condado de Tainan", + "russian": "Тайнань (уезд)", + "chinese_traditional": "Tainan County", + "unknown1": "Tainan County", + "unknown2": "Tainan County", + "unknown3": "Tainan County", + "unknown4": "Tainan County" + }, + "coordinates": { + "latitude": 23.049316144, + "longitude": 120.333579174 + } + }, + { + "id": 2148728832, + "name": "Kaohsiung County", + "translations": { + "japanese": "高雄県", + "english": "Kaohsiung County", + "french": "Kaohsiung (comté de)", + "german": "Kreis Kaohsiung", + "italian": "Contea di Kaohsiung", + "spanish": "Distrito de Kaohsiung", + "chinese_simple": "-", + "korean": "가오슝 현", + "dutch": "Kaohsiung", + "portuguese": "Distrito de Kaohsiung", + "russian": "Гаосюн (уезд)", + "chinese_traditional": "Kaohsiung County", + "unknown1": "Kaohsiung County", + "unknown2": "Kaohsiung County", + "unknown3": "Kaohsiung County", + "unknown4": "Kaohsiung County" + }, + "coordinates": { + "latitude": 22.620849352, + "longitude": 120.350058711 + } + }, + { + "id": 2148794368, + "name": "Pingtung County", + "translations": { + "japanese": "屏東県", + "english": "Pingtung County", + "french": "Pingtung (comté de)", + "german": "Kreis Pingtung", + "italian": "Contea di Pingtung", + "spanish": "Condado de Pingtung", + "chinese_simple": "-", + "korean": "핑둥 현", + "dutch": "Pingtung", + "portuguese": "Condado de Pingtung", + "russian": "Пиндун (уезд)", + "chinese_traditional": "屏東縣", + "unknown1": "Pingtung County", + "unknown2": "Pingtung County", + "unknown3": "Pingtung County", + "unknown4": "Pingtung County" + }, + "coordinates": { + "latitude": 22.664794664, + "longitude": 120.498374544 + } + }, + { + "id": 2148859904, + "name": "Yilan County", + "translations": { + "japanese": "宜蘭県", + "english": "Yilan County", + "french": "Ilan (comté de)", + "german": "Kreis Yilan", + "italian": "Contea di Yilan", + "spanish": "Condado de Yilan", + "chinese_simple": "-", + "korean": "이란 현", + "dutch": "Yilan", + "portuguese": "Condado de Yilan", + "russian": "Илань (уезд)", + "chinese_traditional": "宜蘭縣", + "unknown1": "Yilan County", + "unknown2": "Yilan County", + "unknown3": "Yilan County", + "unknown4": "Yilan County" + }, + "coordinates": { + "latitude": 24.763183312, + "longitude": 121.745326177 + } + }, + { + "id": 2148925440, + "name": "Hualien County", + "translations": { + "japanese": "花蓮県", + "english": "Hualien County", + "french": "Hualien (comté de)", + "german": "Kreis Hualien", + "italian": "Contea di Hualien", + "spanish": "Condado de Hualien", + "chinese_simple": "-", + "korean": "화롄 현", + "dutch": "Hualien", + "portuguese": "Condado de Hualien", + "russian": "Хуалянь (уезд)", + "chinese_traditional": "花蓮縣", + "unknown1": "Hualien County", + "unknown2": "Hualien County", + "unknown3": "Hualien County", + "unknown4": "Hualien County" + }, + "coordinates": { + "latitude": 23.983154024, + "longitude": 121.629969418 + } + }, + { + "id": 2148990976, + "name": "Taitung County", + "translations": { + "japanese": "台東県", + "english": "Taitung County", + "french": "Taitung (comté de)", + "german": "Kreis Taitung", + "italian": "Contea di Taitung", + "spanish": "Condado de Taitung", + "chinese_simple": "-", + "korean": "타이둥 현", + "dutch": "Taitung", + "portuguese": "Condado de Taitung", + "russian": "Тайдун (уезд)", + "chinese_traditional": "臺東縣", + "unknown1": "Taitung County", + "unknown2": "Taitung County", + "unknown3": "Taitung County", + "unknown4": "Taitung County" + }, + "coordinates": { + "latitude": 22.752685288, + "longitude": 121.14107648699999 + } + }, + { + "id": 2149056512, + "name": "Penghu County", + "translations": { + "japanese": "澎湖県", + "english": "Penghu County", + "french": "Penghu (comté de)", + "german": "Kreis Penghu", + "italian": "Contea di Penghu", + "spanish": "Islas Pescadores", + "chinese_simple": "-", + "korean": "펑후 현", + "dutch": "Penghu", + "portuguese": "Ilhas Pescadores", + "russian": "Пэнху (уезд)", + "chinese_traditional": "澎湖縣", + "unknown1": "Penghu County", + "unknown2": "Penghu County", + "unknown3": "Penghu County", + "unknown4": "Penghu County" + }, + "coordinates": { + "latitude": 23.56567356, + "longitude": 119.58101365099999 + } + }, + { + "id": 2149122048, + "name": "Kinmen County", + "translations": { + "japanese": "金門県", + "english": "Kinmen County", + "french": "Kinmen (comté de)", + "german": "Kreis Kinmen", + "italian": "Contea di Kinmen", + "spanish": "Condado de Kinmen", + "chinese_simple": "-", + "korean": "진먼 현", + "dutch": "Kinmen", + "portuguese": "Condado de Kinmen", + "russian": "Цзиньмэн (уезд)", + "chinese_traditional": "金門縣", + "unknown1": "Kinmen County", + "unknown2": "Kinmen County", + "unknown3": "Kinmen County", + "unknown4": "Kinmen County" + }, + "coordinates": { + "latitude": 24.428100308, + "longitude": 118.328568839 + } + }, + { + "id": 2149187584, + "name": "Lienchiang County", + "translations": { + "japanese": "連江県", + "english": "Lienchiang County", + "french": "Lienchiang (comté de)", + "german": "Kreis Lienchiang", + "italian": "Contea di Lienchiang", + "spanish": "Condado de Lienchiang", + "chinese_simple": "-", + "korean": "롄장 현", + "dutch": "Lienchiang", + "portuguese": "Condado de Lienchiang", + "russian": "Ляньцзян (уезд)", + "chinese_traditional": "連江縣", + "unknown1": "Lienchiang County", + "unknown2": "Lienchiang County", + "unknown3": "Lienchiang County", + "unknown4": "Lienchiang County" + }, + "coordinates": { + "latitude": 26.14746064, + "longitude": 119.927083928 + } + } + ] + }, + { + "id": 136, + "iso_code": "KR", + "name": "South Korea", + "translations": { + "japanese": "韓国", + "english": "South Korea", + "french": "Corée du Sud", + "german": "Südkorea", + "italian": "Corea del Sud", + "spanish": "Corea del Sur", + "chinese_simple": "韩国", + "korean": "대한민국", + "dutch": "Zuid-Korea", + "portuguese": "Coreia do Sul", + "russian": "Южная Корея", + "chinese_traditional": "韓國", + "unknown1": "South Korea", + "unknown2": "South Korea", + "unknown3": "South Korea", + "unknown4": "South Korea" + }, + "regions": [ + { + "id": 2281701376, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": 37.496337464, + "longitude": 126.996805301 + } + }, + { + "id": 2281832448, + "name": "Seoul-teukbyeolsi", + "translations": { + "japanese": "ソウル特別市", + "english": "Seoul-teukbyeolsi", + "french": "Séoul", + "german": "Seoul", + "italian": "Seoul", + "spanish": "Seúl", + "chinese_simple": "首尔特别市", + "korean": "서울특별시", + "dutch": "Seoul", + "portuguese": "Seul", + "russian": "Сеул", + "chinese_traditional": "首爾特別市", + "unknown1": "Seoul-teukbyeolsi", + "unknown2": "Seoul-teukbyeolsi", + "unknown3": "Seoul-teukbyeolsi", + "unknown4": "Seoul-teukbyeolsi" + }, + "coordinates": { + "latitude": 37.496337464, + "longitude": 126.996805301 + } + }, + { + "id": 2281897984, + "name": "Busan-gwangyeoksi", + "translations": { + "japanese": "プサン広域市", + "english": "Busan-gwangyeoksi", + "french": "Pusan", + "german": "Busan", + "italian": "Busan", + "spanish": "Busán", + "chinese_simple": "釜山广域市", + "korean": "부산광역시", + "dutch": "Busan", + "portuguese": "Busan", + "russian": "Пусан", + "chinese_traditional": "釜山廣域市", + "unknown1": "Busan-gwangyeoksi", + "unknown2": "Busan-gwangyeoksi", + "unknown3": "Busan-gwangyeoksi", + "unknown4": "Busan-gwangyeoksi" + }, + "coordinates": { + "latitude": 35.079345304, + "longitude": 129.029281531 + } + }, + { + "id": 2281963520, + "name": "Daegu-gwangyeoksi", + "translations": { + "japanese": "テグ広域市", + "english": "Daegu-gwangyeoksi", + "french": "Daegu", + "german": "Daegu", + "italian": "Daegu", + "spanish": "Daegu", + "chinese_simple": "大邱广域市", + "korean": "대구광역시", + "dutch": "Daegu", + "portuguese": "Daegu", + "russian": "Тэгу", + "chinese_traditional": "大邱廣域市", + "unknown1": "Daegu-gwangyeoksi", + "unknown2": "Daegu-gwangyeoksi", + "unknown3": "Daegu-gwangyeoksi", + "unknown4": "Daegu-gwangyeoksi" + }, + "coordinates": { + "latitude": 35.848388264, + "longitude": 128.578840853 + } + }, + { + "id": 2282029056, + "name": "Incheon-gwangyeoksi", + "translations": { + "japanese": "インチョン広域市", + "english": "Incheon-gwangyeoksi", + "french": "Incheon", + "german": "Incheon", + "italian": "Incheon", + "spanish": "Incheon", + "chinese_simple": "仁川广域市", + "korean": "인천광역시", + "dutch": "Incheon", + "portuguese": "Incheon", + "russian": "Инчхон", + "chinese_traditional": "仁川廣域市", + "unknown1": "Incheon-gwangyeoksi", + "unknown2": "Incheon-gwangyeoksi", + "unknown3": "Incheon-gwangyeoksi", + "unknown4": "Incheon-gwangyeoksi" + }, + "coordinates": { + "latitude": 37.496337464, + "longitude": 126.62876230799999 + } + }, + { + "id": 2282094592, + "name": "Gwangju-gwangyeoksi", + "translations": { + "japanese": "クァンジュ広域市", + "english": "Gwangju-gwangyeoksi", + "french": "Gwangju", + "german": "Gwangju", + "italian": "Gwangju", + "spanish": "Gwangju", + "chinese_simple": "光州广域市", + "korean": "광주광역시", + "dutch": "Gwangju", + "portuguese": "Gwangju", + "russian": "Кванджу", + "chinese_traditional": "光州廣域市", + "unknown1": "Gwangju-gwangyeoksi", + "unknown2": "Gwangju-gwangyeoksi", + "unknown3": "Gwangju-gwangyeoksi", + "unknown4": "Gwangju-gwangyeoksi" + }, + "coordinates": { + "latitude": 35.112304288, + "longitude": 126.86496900499999 + } + }, + { + "id": 2282160128, + "name": "Daejeon-gwangyeoksi", + "translations": { + "japanese": "テジョン広域市", + "english": "Daejeon-gwangyeoksi", + "french": "Daejeon", + "german": "Daejeon", + "italian": "Daejeon", + "spanish": "Daejeon", + "chinese_simple": "大田广域市", + "korean": "대전광역시", + "dutch": "Daejeon", + "portuguese": "Daejeon", + "russian": "Тэджон", + "chinese_traditional": "大田廣域市", + "unknown1": "Daejeon-gwangyeoksi", + "unknown2": "Daejeon-gwangyeoksi", + "unknown3": "Daejeon-gwangyeoksi", + "unknown4": "Daejeon-gwangyeoksi" + }, + "coordinates": { + "latitude": 36.348266188000004, + "longitude": 127.43076644199999 + } + }, + { + "id": 2282225664, + "name": "Ulsan-gwangyeoksi", + "translations": { + "japanese": "ウルサン広域市", + "english": "Ulsan-gwangyeoksi", + "french": "Ulsan", + "german": "Ulsan", + "italian": "Ulsan", + "spanish": "Ulsan", + "chinese_simple": "蔚山广域市", + "korean": "울산광역시", + "dutch": "Ulsan", + "portuguese": "Ulsan", + "russian": "Ульсан", + "chinese_traditional": "蔚山廣域市", + "unknown1": "Ulsan-gwangyeoksi", + "unknown2": "Ulsan-gwangyeoksi", + "unknown3": "Ulsan-gwangyeoksi", + "unknown4": "Ulsan-gwangyeoksi" + }, + "coordinates": { + "latitude": 35.51330526, + "longitude": 129.36436545 + } + }, + { + "id": 2282291200, + "name": "Gyeonggi-do", + "translations": { + "japanese": "キョンギ道", + "english": "Gyeonggi-do", + "french": "Gyeonggi", + "german": "Gyeonggi-do", + "italian": "Gyeonggi", + "spanish": "Gyeonggi", + "chinese_simple": "京畿道", + "korean": "경기도", + "dutch": "Gyeonggi-do", + "portuguese": "Gyeonggi", + "russian": "Кёнгидо", + "chinese_traditional": "京畿道", + "unknown1": "Gyeonggi-do", + "unknown2": "Gyeonggi-do", + "unknown3": "Gyeonggi-do", + "unknown4": "Gyeonggi-do" + }, + "coordinates": { + "latitude": 37.496337464, + "longitude": 126.996805301 + } + }, + { + "id": 2282356736, + "name": "Gangwon-do", + "translations": { + "japanese": "カンウォン道", + "english": "Gangwon-do", + "french": "Gangwon", + "german": "Gangwon-do", + "italian": "Gangwon", + "spanish": "Gangwon", + "chinese_simple": "江原道", + "korean": "강원도", + "dutch": "Gangwon-do", + "portuguese": "Gangwon", + "russian": "Канвондо", + "chinese_traditional": "江原道", + "unknown1": "Gangwon-do", + "unknown2": "Gangwon-do", + "unknown3": "Gangwon-do", + "unknown4": "Gangwon-do" + }, + "coordinates": { + "latitude": 37.93029742, + "longitude": 127.765850361 + } + }, + { + "id": 2282422272, + "name": "Chungcheongbuk-do", + "translations": { + "japanese": "チュンチョンブク道", + "english": "Chungcheongbuk-do", + "french": "Chungcheong du Nord", + "german": "Chungcheongbuk-do", + "italian": "Chungcheong Settentrionale", + "spanish": "Chungcheong del Norte", + "chinese_simple": "忠清北道", + "korean": "충청북도", + "dutch": "Noord-Chungcheong", + "portuguese": "Chungcheong do Norte", + "russian": "Чхунчхон-Пукто", + "chinese_traditional": "忠清北道", + "unknown1": "Chungcheongbuk-do", + "unknown2": "Chungcheongbuk-do", + "unknown3": "Chungcheongbuk-do", + "unknown4": "Chungcheongbuk-do" + }, + "coordinates": { + "latitude": 36.963500556, + "longitude": 127.88120712 + } + }, + { + "id": 2282487808, + "name": "Chungcheongnam-do", + "translations": { + "japanese": "チュンチョンナム道", + "english": "Chungcheongnam-do", + "french": "Chungcheong du Sud", + "german": "Chungcheongnam-do", + "italian": "Chungcheong Meridionale", + "spanish": "Chungcheong del Sur", + "chinese_simple": "忠清南道", + "korean": "충청남도", + "dutch": "Zuid-Chungcheong", + "portuguese": "Chungcheong do Sul", + "russian": "Чхунчхон-Намдо", + "chinese_traditional": "忠清南道", + "unknown1": "Chungcheongnam-do", + "unknown2": "Chungcheongnam-do", + "unknown3": "Chungcheongnam-do", + "unknown4": "Chungcheongnam-do" + }, + "coordinates": { + "latitude": 36.348266188000004, + "longitude": 127.43076644199999 + } + }, + { + "id": 2282553344, + "name": "Jeollabuk-do", + "translations": { + "japanese": "チョルラブク道", + "english": "Jeollabuk-do", + "french": "Jeolla du Nord", + "german": "Jeollabuk-do", + "italian": "Jeolla Settentrionale", + "spanish": "Jeolla del Norte", + "chinese_simple": "全罗北道", + "korean": "전라북도", + "dutch": "Noord-Jeolla", + "portuguese": "Jeolla do Norte", + "russian": "Чолла-Пукто", + "chinese_traditional": "全羅北道", + "unknown1": "Jeollabuk-do", + "unknown2": "Jeollabuk-do", + "unknown3": "Jeollabuk-do", + "unknown4": "Jeollabuk-do" + }, + "coordinates": { + "latitude": 35.848388264, + "longitude": 127.079202986 + } + }, + { + "id": 2282618880, + "name": "Jeollanam-do", + "translations": { + "japanese": "チョルラナム道", + "english": "Jeollanam-do", + "french": "Jeolla du Sud", + "german": "Jeollanam-do", + "italian": "Jeolla Meridionale", + "spanish": "Jeolla del Sur", + "chinese_simple": "全罗南道", + "korean": "전라남도", + "dutch": "Zuid-Jeolla", + "portuguese": "Jeolla do Sul", + "russian": "Чолла-Намдо", + "chinese_traditional": "全羅南道", + "unknown1": "Jeollanam-do", + "unknown2": "Jeollanam-do", + "unknown3": "Jeollanam-do", + "unknown4": "Jeollanam-do" + }, + "coordinates": { + "latitude": 35.112304288, + "longitude": 126.86496900499999 + } + }, + { + "id": 2282684416, + "name": "Gyeongsangbuk-do", + "translations": { + "japanese": "キョンサンブク道", + "english": "Gyeongsangbuk-do", + "french": "Gyeongsang du Nord", + "german": "Gyeongsangbuk-do", + "italian": "Gyeongsang Settentrionale", + "spanish": "Gyeongsang del Norte", + "chinese_simple": "庆尚北道", + "korean": "경상북도", + "dutch": "Noord-Gyeongsang", + "portuguese": "Gyeongsang do Norte", + "russian": "Кёнсан-Пукто", + "chinese_traditional": "慶尚北道", + "unknown1": "Gyeongsangbuk-do", + "unknown2": "Gyeongsangbuk-do", + "unknown3": "Gyeongsangbuk-do", + "unknown4": "Gyeongsangbuk-do" + }, + "coordinates": { + "latitude": 35.848388264, + "longitude": 128.578840853 + } + }, + { + "id": 2282749952, + "name": "Gyeongsangnam-do", + "translations": { + "japanese": "キョンサンナム道", + "english": "Gyeongsangnam-do", + "french": "Gyeongsang du Sud", + "german": "Gyeongsangnam-do", + "italian": "Gyeongsang Meridionale", + "spanish": "Gyeongsang del Sur", + "chinese_simple": "庆尚南道", + "korean": "경상남도", + "dutch": "Zuid-Gyeongsang", + "portuguese": "Gyeongsang do Sul", + "russian": "Кёнсан-Намдо", + "chinese_traditional": "慶尚南道", + "unknown1": "Gyeongsangnam-do", + "unknown2": "Gyeongsangnam-do", + "unknown3": "Gyeongsangnam-do", + "unknown4": "Gyeongsangnam-do" + }, + "coordinates": { + "latitude": 35.299071864, + "longitude": 128.65025218 + } + }, + { + "id": 2282815488, + "name": "Jeju-teukbyeoljachido", + "translations": { + "japanese": "チェジュ特別自治道", + "english": "Jeju-teukbyeoljachido", + "french": "Jeju", + "german": "Jeju-do", + "italian": "Jeju", + "spanish": "Jeju", + "chinese_simple": "济州特别自治道", + "korean": "제주특별자치도", + "dutch": "Jeju-do", + "portuguese": "Jeju", + "russian": "Чеджудо", + "chinese_traditional": "濟州特別自治道", + "unknown1": "Jeju-teukbyeoljachido", + "unknown2": "Jeju-teukbyeoljachido", + "unknown3": "Jeju-teukbyeoljachido", + "unknown4": "Jeju-teukbyeoljachido" + }, + "coordinates": { + "latitude": 33.513793564, + "longitude": 126.529885086 + } + } + ] + }, + { + "id": 144, + "iso_code": "HK", + "name": "Hong Kong", + "translations": { + "japanese": "ホンコン", + "english": "Hong Kong", + "french": "Hong Kong", + "german": "Hongkong", + "italian": "Hong Kong", + "spanish": "Hong Kong", + "chinese_simple": "中国 香港", + "korean": "홍콩", + "dutch": "Hongkong", + "portuguese": "Hong Kong", + "russian": "Гонконг", + "chinese_traditional": "香港", + "unknown1": "Hong Kong", + "unknown2": "Hong Kong", + "unknown3": "Hong Kong", + "unknown4": "Hong Kong" + }, + "regions": [ + { + "id": 2415919104, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": 22.461547596, + "longitude": 114.296575453 + } + }, + { + "id": 2415984640, + "name": "Hong Kong", + "translations": { + "japanese": "ホンコン", + "english": "Hong Kong", + "french": "Hong Kong", + "german": "Hongkong", + "italian": "Hong Kong", + "spanish": "Hong Kong", + "chinese_simple": "中国 香港", + "korean": "홍콩", + "dutch": "Hongkong", + "portuguese": "Hong Kong", + "russian": "Гонконг", + "chinese_traditional": "香港", + "unknown1": "Hong Kong", + "unknown2": "Hong Kong", + "unknown3": "Hong Kong", + "unknown4": "Hong Kong" + }, + "coordinates": { + "latitude": 22.461547596, + "longitude": 114.296575453 + } + } + ] + }, + { + "id": 145, + "iso_code": "MO", + "name": "Macao", + "translations": { + "japanese": "マカオ", + "english": "Macao", + "french": "Macao", + "german": "Macau", + "italian": "Macao", + "spanish": "Macao", + "chinese_simple": "中国 澳门", + "korean": "마카오", + "dutch": "Macau", + "portuguese": "Macau", + "russian": "Макао", + "chinese_traditional": "澳門", + "unknown1": "Macao", + "unknown2": "Macao", + "unknown3": "Macao", + "unknown4": "Macao" + }, + "regions": [ + { + "id": 2432696320, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": 22.214355216, + "longitude": 113.59894172 + } + }, + { + "id": 2432761856, + "name": "Macao", + "translations": { + "japanese": "マカオ", + "english": "Macao", + "french": "Macao", + "german": "Macau", + "italian": "Macao", + "spanish": "Macao", + "chinese_simple": "中国 澳门", + "korean": "마카오", + "dutch": "Macau", + "portuguese": "Macau", + "russian": "Макао", + "chinese_traditional": "澳門", + "unknown1": "Macao", + "unknown2": "Macao", + "unknown3": "Macao", + "unknown4": "Macao" + }, + "coordinates": { + "latitude": 22.214355216, + "longitude": 113.59894172 + } + } + ] + }, + { + "id": 153, + "iso_code": "SG", + "name": "Singapore", + "translations": { + "japanese": "シンガポール", + "english": "Singapore", + "french": "Singapour", + "german": "Singapur", + "italian": "Singapore", + "spanish": "Singapur", + "chinese_simple": "新加坡", + "korean": "싱가포르", + "dutch": "Singapore", + "portuguese": "Singapura", + "russian": "Сингапур", + "chinese_traditional": "Singapore", + "unknown1": "Singapore", + "unknown2": "Singapore", + "unknown3": "Singapore", + "unknown4": "Singapore" + }, + "regions": [ + { + "id": 2566914048, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": 1.2908935400000001, + "longitude": 103.854042174 + } + }, + { + "id": 2566979584, + "name": "Singapore", + "translations": { + "japanese": "シンガポール", + "english": "Singapore", + "french": "Singapour", + "german": "Singapur", + "italian": "Singapore", + "spanish": "Singapur", + "chinese_simple": "新加坡", + "korean": "싱가포르", + "dutch": "Singapore", + "portuguese": "Singapura", + "russian": "Сингапур", + "chinese_traditional": "Singapore", + "unknown1": "Singapore", + "unknown2": "Singapore", + "unknown3": "Singapore", + "unknown4": "Singapore" + }, + "coordinates": { + "latitude": 1.2908935400000001, + "longitude": 103.854042174 + } + } + ] + }, + { + "id": 156, + "iso_code": "MY", + "name": "Malaysia", + "translations": { + "japanese": "マレーシア", + "english": "Malaysia", + "french": "Malaisie", + "german": "Malaysia", + "italian": "Malesia", + "spanish": "Malasia", + "chinese_simple": "马来西亚", + "korean": "말레이시아", + "dutch": "Maleisië", + "portuguese": "Malásia", + "russian": "Малайзия", + "chinese_traditional": "Malaysia", + "unknown1": "Malaysia", + "unknown2": "Malaysia", + "unknown3": "Malaysia", + "unknown4": "Malaysia" + }, + "regions": [ + { + "id": 2617245696, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": 3.164062464, + "longitude": 101.695222827 + } + }, + { + "id": 2617376768, + "name": "Kuala Lumpur", + "translations": { + "japanese": "クアラ・ルンプール", + "english": "Kuala Lumpur", + "french": "Kuala Lumpur", + "german": "Kuala Lumpur", + "italian": "Kuala Lumpur", + "spanish": "Kuala Lumpur", + "chinese_simple": "吉隆坡联邦直辖区", + "korean": "쿠알라룸푸르", + "dutch": "Kuala Lumpur", + "portuguese": "Kuala Lumpur", + "russian": "Куала-Лумпур", + "chinese_traditional": "Kuala Lumpur", + "unknown1": "Kuala Lumpur", + "unknown2": "Kuala Lumpur", + "unknown3": "Kuala Lumpur", + "unknown4": "Kuala Lumpur" + }, + "coordinates": { + "latitude": 3.164062464, + "longitude": 101.695222827 + } + }, + { + "id": 2617442304, + "name": "Johor", + "translations": { + "japanese": "ジョホール州", + "english": "Johor", + "french": "Johor", + "german": "Johor", + "italian": "Johor", + "spanish": "Johor", + "chinese_simple": "柔佛州", + "korean": "조호르 주", + "dutch": "Johor", + "portuguese": "Johor", + "russian": "Джохор", + "chinese_traditional": "Johor", + "unknown1": "Johor", + "unknown2": "Johor", + "unknown3": "Johor", + "unknown4": "Johor" + }, + "coordinates": { + "latitude": 1.461181624, + "longitude": 103.749671773 + } + }, + { + "id": 2617507840, + "name": "Kedah", + "translations": { + "japanese": "ケダ州", + "english": "Kedah", + "french": "Kedah", + "german": "Kedah", + "italian": "Kedah", + "spanish": "Kedah", + "chinese_simple": "吉打州", + "korean": "케다 주", + "dutch": "Kedah", + "portuguese": "Kedah", + "russian": "Кедах", + "chinese_traditional": "Kedah", + "unknown1": "Kedah", + "unknown2": "Kedah", + "unknown3": "Kedah", + "unknown4": "Kedah" + }, + "coordinates": { + "latitude": 6.113891532, + "longitude": 100.365873509 + } + }, + { + "id": 2617573376, + "name": "Kelantan", + "translations": { + "japanese": "ケランタン州", + "english": "Kelantan", + "french": "Kelantan", + "german": "Kelantan", + "italian": "Kelantan", + "spanish": "Kelantan", + "chinese_simple": "吉兰丹州", + "korean": "켈란탄 주", + "dutch": "Kelantan", + "portuguese": "Kelantan", + "russian": "Келантан", + "chinese_traditional": "Kelantan", + "unknown1": "Kelantan", + "unknown2": "Kelantan", + "unknown3": "Kelantan", + "unknown4": "Kelantan" + }, + "coordinates": { + "latitude": 6.1303710240000004, + "longitude": 102.250033906 + } + }, + { + "id": 2617638912, + "name": "Melaka", + "translations": { + "japanese": "マラッカ州", + "english": "Melaka", + "french": "Malacca", + "german": "Malakka", + "italian": "Malacca", + "spanish": "Melaka", + "chinese_simple": "马六甲州", + "korean": "믈라카 주", + "dutch": "Malakka", + "portuguese": "Malacca", + "russian": "Малакка", + "chinese_traditional": "Melaka", + "unknown1": "Melaka", + "unknown2": "Melaka", + "unknown3": "Melaka", + "unknown4": "Melaka" + }, + "coordinates": { + "latitude": 2.191772436, + "longitude": 102.244540727 + } + }, + { + "id": 2617704448, + "name": "Negeri Sembilan", + "translations": { + "japanese": "ヌグリ・センビラン州", + "english": "Negeri Sembilan", + "french": "Negeri Sembilan", + "german": "Negeri Sembilan", + "italian": "Negeri Sembilan", + "spanish": "Negeri Sembilan", + "chinese_simple": "森美兰州", + "korean": "느그리슴빌란 주", + "dutch": "Negeri Sembilan", + "portuguese": "Negeri Sembilan", + "russian": "Негери-Сембелан", + "chinese_traditional": "Negeri Sembilan", + "unknown1": "Negeri Sembilan", + "unknown2": "Negeri Sembilan", + "unknown3": "Negeri Sembilan", + "unknown4": "Negeri Sembilan" + }, + "coordinates": { + "latitude": 2.713623016, + "longitude": 101.931429524 + } + }, + { + "id": 2617769984, + "name": "Pahang", + "translations": { + "japanese": "パハン州", + "english": "Pahang", + "french": "Pahang", + "german": "Pahang", + "italian": "Pahang", + "spanish": "Pahang", + "chinese_simple": "彭亨州", + "korean": "파항 주", + "dutch": "Pahang", + "portuguese": "Pahang", + "russian": "Паханг", + "chinese_traditional": "Pahang", + "unknown1": "Pahang", + "unknown2": "Pahang", + "unknown3": "Pahang", + "unknown4": "Pahang" + }, + "coordinates": { + "latitude": 3.795776324, + "longitude": 103.332190169 + } + }, + { + "id": 2617835520, + "name": "Perak", + "translations": { + "japanese": "ペラ州", + "english": "Perak", + "french": "Perak", + "german": "Perak", + "italian": "Perak", + "spanish": "Perak", + "chinese_simple": "霹雳州", + "korean": "페라크 주", + "dutch": "Perak", + "portuguese": "Perak", + "russian": "Перак", + "chinese_traditional": "Perak", + "unknown1": "Perak", + "unknown2": "Perak", + "unknown3": "Perak", + "unknown4": "Perak" + }, + "coordinates": { + "latitude": 4.581298776, + "longitude": 101.079986779 + } + }, + { + "id": 2617901056, + "name": "Perlis", + "translations": { + "japanese": "ペルリス州", + "english": "Perlis", + "french": "Perlis", + "german": "Perlis", + "italian": "Perlis", + "spanish": "Perlis", + "chinese_simple": "玻璃市州", + "korean": "페를리스 주", + "dutch": "Perlis", + "portuguese": "Perlis", + "russian": "Перлис", + "chinese_traditional": "Perlis", + "unknown1": "Perlis", + "unknown2": "Perlis", + "unknown3": "Perlis", + "unknown4": "Perlis" + }, + "coordinates": { + "latitude": 6.432495044, + "longitude": 100.19558496 + } + }, + { + "id": 2617966592, + "name": "Penang", + "translations": { + "japanese": "ピナン州", + "english": "Penang", + "french": "Penang", + "german": "Penang", + "italian": "Penang", + "spanish": "Penang", + "chinese_simple": "槟榔屿州", + "korean": "피낭 주", + "dutch": "Penang", + "portuguese": "Penang", + "russian": "Пулау-Пинанг", + "chinese_traditional": "Penang", + "unknown1": "Penang", + "unknown2": "Penang", + "unknown3": "Penang", + "unknown4": "Penang" + }, + "coordinates": { + "latitude": 5.416259704, + "longitude": 100.33291443499999 + } + }, + { + "id": 2618032128, + "name": "Sarawak", + "translations": { + "japanese": "サラワク州", + "english": "Sarawak", + "french": "Sarawak", + "german": "Sarawak", + "italian": "Sarawak", + "spanish": "Sarawak", + "chinese_simple": "沙捞越州", + "korean": "사라왁 주", + "dutch": "Sarawak", + "portuguese": "Sarawak", + "russian": "Саравак", + "chinese_traditional": "Sarawak", + "unknown1": "Sarawak", + "unknown2": "Sarawak", + "unknown3": "Sarawak", + "unknown4": "Sarawak" + }, + "coordinates": { + "latitude": 1.549072248, + "longitude": 110.330500215 + } + }, + { + "id": 2618097664, + "name": "Selangor", + "translations": { + "japanese": "セランゴール州", + "english": "Selangor", + "french": "Selangor", + "german": "Selangor", + "italian": "Selangor", + "spanish": "Selangor", + "chinese_simple": "雪兰莪州", + "korean": "셀랑고르 주", + "dutch": "Selangor", + "portuguese": "Selangor", + "russian": "Селангор", + "chinese_traditional": "Selangor", + "unknown1": "Selangor", + "unknown2": "Selangor", + "unknown3": "Selangor", + "unknown4": "Selangor" + }, + "coordinates": { + "latitude": 3.081665004, + "longitude": 101.530427457 + } + }, + { + "id": 2618163200, + "name": "Terengganu", + "translations": { + "japanese": "トレンガヌ州", + "english": "Terengganu", + "french": "Terengganu", + "german": "Terengganu", + "italian": "Terengganu", + "spanish": "Terengganu", + "chinese_simple": "丁加奴州", + "korean": "트렝가누 주", + "dutch": "Terengganu", + "portuguese": "Terengganu", + "russian": "Тренгану", + "chinese_traditional": "Terengganu", + "unknown1": "Terengganu", + "unknown2": "Terengganu", + "unknown3": "Terengganu", + "unknown4": "Terengganu" + }, + "coordinates": { + "latitude": 5.32836908, + "longitude": 103.128942546 + } + }, + { + "id": 2618228736, + "name": "Labuan", + "translations": { + "japanese": "ラブアン", + "english": "Labuan", + "french": "Labuan", + "german": "Labuan", + "italian": "Labuan", + "spanish": "Labuan", + "chinese_simple": "纳闽联邦直辖区", + "korean": "라부안", + "dutch": "Labuan", + "portuguese": "Labuan", + "russian": "Лабуан", + "chinese_traditional": "Labuan", + "unknown1": "Labuan", + "unknown2": "Labuan", + "unknown3": "Labuan", + "unknown4": "Labuan" + }, + "coordinates": { + "latitude": 5.27343744, + "longitude": 115.241402241 + } + }, + { + "id": 2618294272, + "name": "Sabah", + "translations": { + "japanese": "サバ州", + "english": "Sabah", + "french": "Sabah", + "german": "Sabah", + "italian": "Sabah", + "spanish": "Sabah", + "chinese_simple": "沙巴州", + "korean": "사바 주", + "dutch": "Sabah", + "portuguese": "Sabah", + "russian": "Сабах", + "chinese_traditional": "Sabah", + "unknown1": "Sabah", + "unknown2": "Sabah", + "unknown3": "Sabah", + "unknown4": "Sabah" + }, + "coordinates": { + "latitude": 5.982055596, + "longitude": 116.065379091 + } + }, + { + "id": 2618359808, + "name": "Putrajaya", + "translations": { + "japanese": "プトラジャヤ", + "english": "Putrajaya", + "french": "Putrajaya", + "german": "Putrajaya", + "italian": "Putrajaya", + "spanish": "Putrajaya", + "chinese_simple": "布城联邦直辖区", + "korean": "푸트라자야", + "dutch": "Putrajaya", + "portuguese": "Putrajaya", + "russian": "Путраджая", + "chinese_traditional": "Putrajaya", + "unknown1": "Putrajaya", + "unknown2": "Putrajaya", + "unknown3": "Putrajaya", + "unknown4": "Putrajaya" + }, + "coordinates": { + "latitude": 2.91137692, + "longitude": 101.662263753 + } + } + ] + }, + { + "id": 160, + "iso_code": "CN", + "name": "China", + "translations": { + "japanese": "中国", + "english": "China", + "french": "Chine", + "german": "China", + "italian": "Cina", + "spanish": "China", + "chinese_simple": "中国", + "korean": "중국", + "dutch": "China", + "portuguese": "China", + "russian": "Китай", + "chinese_traditional": "中國", + "unknown1": "China", + "unknown2": "China", + "unknown3": "China", + "unknown4": "China" + }, + "regions": [ + { + "id": 2684354560, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": 39.913329624, + "longitude": 116.433422084 + } + }, + { + "id": 2684485632, + "name": "Beijing", + "translations": { + "japanese": "北京市", + "english": "Beijing", + "french": "Pékin", + "german": "Peking", + "italian": "Pechino", + "spanish": "Pekín", + "chinese_simple": "北京市", + "korean": "베이징", + "dutch": "Beijing", + "portuguese": "Pequim", + "russian": "Пекин", + "chinese_traditional": "北京市", + "unknown1": "Beijing", + "unknown2": "Beijing", + "unknown3": "Beijing", + "unknown4": "Beijing" + }, + "coordinates": { + "latitude": 39.913329624, + "longitude": 116.433422084 + } + }, + { + "id": 2684551168, + "name": "Chongqing", + "translations": { + "japanese": "重慶市", + "english": "Chongqing", + "french": "Chongqing", + "german": "Chongqing", + "italian": "Chongqing", + "spanish": "Chongqing", + "chinese_simple": "重庆市", + "korean": "충칭", + "dutch": "Tsjoengking", + "portuguese": "Chongqing", + "russian": "Чунцин", + "chinese_traditional": "重慶市", + "unknown1": "Chongqing", + "unknown2": "Chongqing", + "unknown3": "Chongqing", + "unknown4": "Chongqing" + }, + "coordinates": { + "latitude": 29.597167632, + "longitude": 106.364424977 + } + }, + { + "id": 2684616704, + "name": "Shanghai", + "translations": { + "japanese": "上海市", + "english": "Shanghai", + "french": "Shanghai", + "german": "Shanghai", + "italian": "Shanghai", + "spanish": "Shanghái", + "chinese_simple": "上海市", + "korean": "상하이", + "dutch": "Sjanghai", + "portuguese": "Xangai", + "russian": "Шанхай", + "chinese_traditional": "上海市", + "unknown1": "Shanghai", + "unknown2": "Shanghai", + "unknown3": "Shanghai", + "unknown4": "Shanghai" + }, + "coordinates": { + "latitude": 31.245116832, + "longitude": 121.47066722699999 + } + }, + { + "id": 2684682240, + "name": "Tianjin", + "translations": { + "japanese": "天津市", + "english": "Tianjin", + "french": "Tianjin", + "german": "Tianjin", + "italian": "Tientsin", + "spanish": "Tianjin", + "chinese_simple": "天津市", + "korean": "톈진", + "dutch": "Tianjin", + "portuguese": "Tianjin", + "russian": "Тяньцзинь", + "chinese_traditional": "天津市", + "unknown1": "Tianjin", + "unknown2": "Tianjin", + "unknown3": "Tianjin", + "unknown4": "Tianjin" + }, + "coordinates": { + "latitude": 39.149779828, + "longitude": 117.180494428 + } + }, + { + "id": 2684747776, + "name": "Anhui", + "translations": { + "japanese": "安徽省", + "english": "Anhui", + "french": "Anhui", + "german": "Anhui", + "italian": "Anhui", + "spanish": "Anhui", + "chinese_simple": "安徽省", + "korean": "안후이 성", + "dutch": "Anhui", + "portuguese": "Anhui", + "russian": "Аньхой", + "chinese_traditional": "安徽省", + "unknown1": "Anhui", + "unknown2": "Anhui", + "unknown3": "Anhui", + "unknown4": "Anhui" + }, + "coordinates": { + "latitude": 31.931762332, + "longitude": 117.31233072399999 + } + }, + { + "id": 2684813312, + "name": "Fujian", + "translations": { + "japanese": "福建省", + "english": "Fujian", + "french": "Fujian", + "german": "Fujian", + "italian": "Fujian", + "spanish": "Fujian", + "chinese_simple": "福建省", + "korean": "푸젠 성", + "dutch": "Fujian", + "portuguese": "Fujian", + "russian": "Фуцзянь", + "chinese_traditional": "福建省", + "unknown1": "Fujian", + "unknown2": "Fujian", + "unknown3": "Fujian", + "unknown4": "Fujian" + }, + "coordinates": { + "latitude": 26.081542672, + "longitude": 119.29536834299999 + } + }, + { + "id": 2684878848, + "name": "Gansu", + "translations": { + "japanese": "甘粛省", + "english": "Gansu", + "french": "Gansu", + "german": "Gansu", + "italian": "Gansu", + "spanish": "Gansu", + "chinese_simple": "甘肃省", + "korean": "간쑤 성", + "dutch": "Gansu", + "portuguese": "Gansu", + "russian": "Ганьсу", + "chinese_traditional": "甘肅省", + "unknown1": "Gansu", + "unknown2": "Gansu", + "unknown3": "Gansu", + "unknown4": "Gansu" + }, + "coordinates": { + "latitude": 36.029662676, + "longitude": 103.749671773 + } + }, + { + "id": 2684944384, + "name": "Guangdong", + "translations": { + "japanese": "広東省", + "english": "Guangdong", + "french": "Guangdong", + "german": "Guangdong", + "italian": "Guangdong", + "spanish": "Cantón", + "chinese_simple": "广东省", + "korean": "광둥 성", + "dutch": "Guangdong", + "portuguese": "Guangdong", + "russian": "Гуандун", + "chinese_traditional": "廣東省", + "unknown1": "Guangdong", + "unknown2": "Guangdong", + "unknown3": "Guangdong", + "unknown4": "Guangdong" + }, + "coordinates": { + "latitude": 23.09875462, + "longitude": 113.099062431 + } + }, + { + "id": 2685009920, + "name": "Guizhou", + "translations": { + "japanese": "貴州省", + "english": "Guizhou", + "french": "Guizhou", + "german": "Guizhou", + "italian": "Guizhou", + "spanish": "Guizhou", + "chinese_simple": "贵州省", + "korean": "구이저우 성", + "dutch": "Guizhou", + "portuguese": "Guizhou", + "russian": "Гуйчжоу", + "chinese_traditional": "貴州省", + "unknown1": "Guizhou", + "unknown2": "Guizhou", + "unknown3": "Guizhou", + "unknown4": "Guizhou" + }, + "coordinates": { + "latitude": 26.564941104, + "longitude": 106.683029359 + } + }, + { + "id": 2685075456, + "name": "Hainan", + "translations": { + "japanese": "海南省", + "english": "Hainan", + "french": "Hainan", + "german": "Hainan", + "italian": "Hainan", + "spanish": "Hainan", + "chinese_simple": "海南省", + "korean": "하이난 성", + "dutch": "Hainan", + "portuguese": "Hainan", + "russian": "Хайнань", + "chinese_traditional": "海南省", + "unknown1": "Hainan", + "unknown2": "Hainan", + "unknown3": "Hainan", + "unknown4": "Hainan" + }, + "coordinates": { + "latitude": 20.083007584, + "longitude": 110.330500215 + } + }, + { + "id": 2685140992, + "name": "Hebei", + "translations": { + "japanese": "河北省", + "english": "Hebei", + "french": "Hebei", + "german": "Hebei", + "italian": "Hebei", + "spanish": "Hebei", + "chinese_simple": "河北省", + "korean": "허베이 성", + "dutch": "Hebei", + "portuguese": "Hebei", + "russian": "Хэбэй", + "chinese_traditional": "河北省", + "unknown1": "Hebei", + "unknown2": "Hebei", + "unknown3": "Hebei", + "unknown4": "Hebei" + }, + "coordinates": { + "latitude": 38.045653864, + "longitude": 114.466864002 + } + }, + { + "id": 2685206528, + "name": "Heilongjiang", + "translations": { + "japanese": "黒龍江省", + "english": "Heilongjiang", + "french": "Heilongjiang", + "german": "Heilongjiang", + "italian": "Heilongjiang", + "spanish": "Heilongjiang", + "chinese_simple": "黑龙江省", + "korean": "헤이룽장 성", + "dutch": "Heilongjiang", + "portuguese": "Heilongjang", + "russian": "Хэйлунцзян", + "chinese_traditional": "黑龍江省", + "unknown1": "Heilongjiang", + "unknown2": "Heilongjiang", + "unknown3": "Heilongjiang", + "unknown4": "Heilongjiang" + }, + "coordinates": { + "latitude": 45.763549284, + "longitude": 126.700173635 + } + }, + { + "id": 2685272064, + "name": "Henan", + "translations": { + "japanese": "河南省", + "english": "Henan", + "french": "Henan", + "german": "Henan", + "italian": "Henan", + "spanish": "Henan", + "chinese_simple": "河南省", + "korean": "허난 성", + "dutch": "Henan", + "portuguese": "Henan", + "russian": "Хэнань", + "chinese_traditional": "河南省", + "unknown1": "Henan", + "unknown2": "Henan", + "unknown3": "Henan", + "unknown4": "Henan" + }, + "coordinates": { + "latitude": 34.595946872, + "longitude": 113.631900794 + } + }, + { + "id": 2685337600, + "name": "Hubei", + "translations": { + "japanese": "湖北省", + "english": "Hubei", + "french": "Hubei", + "german": "Hubei", + "italian": "Hubei", + "spanish": "Hubei", + "chinese_simple": "湖北省", + "korean": "후베이 성", + "dutch": "Hubei", + "portuguese": "Hubei", + "russian": "Хубэй", + "chinese_traditional": "湖北省", + "unknown1": "Hubei", + "unknown2": "Hubei", + "unknown3": "Hubei", + "unknown4": "Hubei" + }, + "coordinates": { + "latitude": 30.580443988, + "longitude": 114.31305499 + } + }, + { + "id": 2685403136, + "name": "Húnán", + "translations": { + "japanese": "湖南省", + "english": "Húnán", + "french": "Hunan", + "german": "Hunan", + "italian": "Hunan", + "spanish": "Hunan", + "chinese_simple": "湖南省", + "korean": "후난 성", + "dutch": "Hunan", + "portuguese": "Hunan", + "russian": "Хунань", + "chinese_traditional": "湖南省", + "unknown1": "Húnán", + "unknown2": "Húnán", + "unknown3": "Húnán", + "unknown4": "Húnán" + }, + "coordinates": { + "latitude": 28.17993132, + "longitude": 112.978212493 + } + }, + { + "id": 2685468672, + "name": "Jiangsu", + "translations": { + "japanese": "江蘇省", + "english": "Jiangsu", + "french": "Jiangsu", + "german": "Jiangsu", + "italian": "Jiangsu", + "spanish": "Jiangsu", + "chinese_simple": "江苏省", + "korean": "장쑤 성", + "dutch": "Jiangsu", + "portuguese": "Jiangsu", + "russian": "Цзянсу", + "chinese_traditional": "江蘇省", + "unknown1": "Jiangsu", + "unknown2": "Jiangsu", + "unknown3": "Jiangsu", + "unknown4": "Jiangsu" + }, + "coordinates": { + "latitude": 32.030639284, + "longitude": 118.828448128 + } + }, + { + "id": 2685534208, + "name": "Jiangxi", + "translations": { + "japanese": "江西省", + "english": "Jiangxi", + "french": "Jiangxi", + "german": "Jiangxi", + "italian": "Jiangxi", + "spanish": "Jiangxi", + "chinese_simple": "江西省", + "korean": "장시 성", + "dutch": "Jiangxi", + "portuguese": "Jiangxi", + "russian": "Цзянси", + "chinese_traditional": "江西省", + "unknown1": "Jiangxi", + "unknown2": "Jiangxi", + "unknown3": "Jiangxi", + "unknown4": "Jiangxi" + }, + "coordinates": { + "latitude": 28.630370768, + "longitude": 115.933542795 + } + }, + { + "id": 2685599744, + "name": "Jilin", + "translations": { + "japanese": "吉林省", + "english": "Jilin", + "french": "Jilin", + "german": "Jilin", + "italian": "Jilin", + "spanish": "Jilin", + "chinese_simple": "吉林省", + "korean": "지린 성", + "dutch": "Jilin", + "portuguese": "Jilin", + "russian": "Гирин", + "chinese_traditional": "吉林省", + "unknown1": "Jilin", + "unknown2": "Jilin", + "unknown3": "Jilin", + "unknown4": "Jilin" + }, + "coordinates": { + "latitude": 43.895873524, + "longitude": 125.282933453 + } + }, + { + "id": 2685665280, + "name": "Liaoning", + "translations": { + "japanese": "遼寧省", + "english": "Liaoning", + "french": "Liaoning", + "german": "Liaoning", + "italian": "Liaoning", + "spanish": "Liaoning", + "chinese_simple": "辽宁省", + "korean": "랴오닝 성", + "dutch": "Liaoning", + "portuguese": "Liaoning", + "russian": "Ляонин", + "chinese_traditional": "遼寧省", + "unknown1": "Liaoning", + "unknown2": "Liaoning", + "unknown3": "Liaoning", + "unknown4": "Liaoning" + }, + "coordinates": { + "latitude": 41.797484876, + "longitude": 123.382293519 + } + }, + { + "id": 2685730816, + "name": "Qinghai", + "translations": { + "japanese": "青海省", + "english": "Qinghai", + "french": "Qinghai", + "german": "Qinghai", + "italian": "Qinghai", + "spanish": "Qinghai", + "chinese_simple": "青海省", + "korean": "칭하이 성", + "dutch": "Qinghai", + "portuguese": "Qinghai", + "russian": "Цинхай", + "chinese_traditional": "青海省", + "unknown1": "Qinghai", + "unknown2": "Qinghai", + "unknown3": "Qinghai", + "unknown4": "Qinghai" + }, + "coordinates": { + "latitude": 36.595458568, + "longitude": 101.914949987 + } + }, + { + "id": 2685796352, + "name": "Shanxi", + "translations": { + "japanese": "陝西省", + "english": "Shanxi", + "french": "Shaanxi", + "german": "Shaanxi", + "italian": "Shaanxi", + "spanish": "Shaanxi", + "chinese_simple": "陕西省", + "korean": "산시 성", + "dutch": "Shaanxi", + "portuguese": "Shaanxi", + "russian": "Шэньси", + "chinese_traditional": "陝西省", + "unknown1": "Shanxi", + "unknown2": "Shanxi", + "unknown3": "Shanxi", + "unknown4": "Shanxi" + }, + "coordinates": { + "latitude": 34.266357032, + "longitude": 108.896780496 + } + }, + { + "id": 2685861888, + "name": "Shandong", + "translations": { + "japanese": "山東省", + "english": "Shandong", + "french": "Shandong", + "german": "Shandong", + "italian": "Shandong", + "spanish": "Shandong", + "chinese_simple": "山东省", + "korean": "산둥 성", + "dutch": "Shandong", + "portuguese": "Shandong", + "russian": "Шаньдун", + "chinese_traditional": "山東省", + "unknown1": "Shandong", + "unknown2": "Shandong", + "unknown3": "Shandong", + "unknown4": "Shandong" + }, + "coordinates": { + "latitude": 36.716308176, + "longitude": 117.015699058 + } + }, + { + "id": 2685927424, + "name": "Shanxi", + "translations": { + "japanese": "山西省", + "english": "Shanxi", + "french": "Shanxi", + "german": "Shanxi", + "italian": "Shanxi", + "spanish": "Shanxi", + "chinese_simple": "山西省", + "korean": "산시 성", + "dutch": "Shanxi", + "portuguese": "Shanxi", + "russian": "Шаньси", + "chinese_traditional": "山西省", + "unknown1": "Shanxi", + "unknown2": "Shanxi", + "unknown3": "Shanxi", + "unknown4": "Shanxi" + }, + "coordinates": { + "latitude": 37.831420468, + "longitude": 112.50030592 + } + }, + { + "id": 2685992960, + "name": "Sichuan", + "translations": { + "japanese": "四川省", + "english": "Sichuan", + "french": "Sichuan", + "german": "Sichuan", + "italian": "Sichuan", + "spanish": "Sichuan", + "chinese_simple": "四川省", + "korean": "쓰촨 성", + "dutch": "Sichuan", + "portuguese": "Sichuan", + "russian": "Сычуань", + "chinese_traditional": "四川省", + "unknown1": "Sichuan", + "unknown2": "Sichuan", + "unknown3": "Sichuan", + "unknown4": "Sichuan" + }, + "coordinates": { + "latitude": 30.629882464, + "longitude": 104.112221587 + } + }, + { + "id": 2686058496, + "name": "Yunnan", + "translations": { + "japanese": "雲南省", + "english": "Yunnan", + "french": "Yunnan", + "german": "Yunnan", + "italian": "Yunnan", + "spanish": "Yunnan", + "chinese_simple": "云南省", + "korean": "윈난 성", + "dutch": "Yunnan", + "portuguese": "Yunnan", + "russian": "Юньнань", + "chinese_traditional": "雲南省", + "unknown1": "Yunnan", + "unknown2": "Yunnan", + "unknown3": "Yunnan", + "unknown4": "Yunnan" + }, + "coordinates": { + "latitude": 25.065307332, + "longitude": 102.678501868 + } + }, + { + "id": 2686124032, + "name": "Zhejiang", + "translations": { + "japanese": "浙江省", + "english": "Zhejiang", + "french": "Zhejiang", + "german": "Zhejiang", + "italian": "Zhejiang", + "spanish": "Zhejiang", + "chinese_simple": "浙江省", + "korean": "저장 성", + "dutch": "Zhejiang", + "portuguese": "Zhejiang", + "russian": "Чжэцзян", + "chinese_traditional": "浙江省", + "unknown1": "Zhejiang", + "unknown2": "Zhejiang", + "unknown3": "Zhejiang", + "unknown4": "Zhejiang" + }, + "coordinates": { + "latitude": 30.29479946, + "longitude": 120.113852014 + } + }, + { + "id": 2686189568, + "name": "Taiwan", + "translations": { + "japanese": "台湾省", + "english": "Taiwan", + "french": "Taiwan", + "german": "Taiwan", + "italian": "Taiwan", + "spanish": "Taiwán", + "chinese_simple": "台湾省", + "korean": "타이완 성", + "dutch": "Taiwan", + "portuguese": "Taiwan", + "russian": "Тайвань", + "chinese_traditional": "台灣省", + "unknown1": "Taiwan", + "unknown2": "Taiwan", + "unknown3": "Taiwan", + "unknown4": "Taiwan" + }, + "coordinates": { + "latitude": 25.032348348, + "longitude": 121.503626301 + } + }, + { + "id": 2686255104, + "name": "Guangxi-Zhuangzu", + "translations": { + "japanese": "広西チワン族自治区", + "english": "Guangxi-Zhuangzu", + "french": "Guangxi", + "german": "Guangxi", + "italian": "Guangxi Zhuang", + "spanish": "Guangxi", + "chinese_simple": "广西壮族自治区", + "korean": "광시좡 족 자치구", + "dutch": "Guangxi", + "portuguese": "Guangxi", + "russian": "Гуанси-Чжуанский автономный район", + "chinese_traditional": "廣西壯族自治區", + "unknown1": "Guangxi-Zhuangzu", + "unknown2": "Guangxi-Zhuangzu", + "unknown3": "Guangxi-Zhuangzu", + "unknown4": "Guangxi-Zhuangzu" + }, + "coordinates": { + "latitude": 22.829589584, + "longitude": 108.314503522 + } + }, + { + "id": 2686320640, + "name": "Nei-Menggu", + "translations": { + "japanese": "内モンゴル自治区", + "english": "Nei-Menggu", + "french": "Mongolie Intérieure", + "german": "Innere Mongolei", + "italian": "Mongolia Interna", + "spanish": "Mongolia Interior", + "chinese_simple": "内蒙古自治区", + "korean": "네이멍구 자치구", + "dutch": "Binnen-Mongolië", + "portuguese": "Mongólia interior", + "russian": "Автономный район Внутренняя Монголия", + "chinese_traditional": "內蒙古自治區", + "unknown1": "Nei-Menggu", + "unknown2": "Nei-Menggu", + "unknown3": "Nei-Menggu", + "unknown4": "Nei-Menggu" + }, + "coordinates": { + "latitude": 40.81420852, + "longitude": 111.615904101 + } + }, + { + "id": 2686386176, + "name": "Ningxia-huizu", + "translations": { + "japanese": "寧夏回族自治区", + "english": "Ningxia-huizu", + "french": "Ningxia", + "german": "Ningxia", + "italian": "Ningxia Hui", + "spanish": "Ningxia", + "chinese_simple": "宁夏回族自治区", + "korean": "닝샤후이 족 자치구", + "dutch": "Ningxia", + "portuguese": "Ningxia", + "russian": "Нинся-Хуэйский автономный район", + "chinese_traditional": "寧夏回族自治區", + "unknown1": "Ningxia-huizu", + "unknown2": "Ningxia-huizu", + "unknown3": "Ningxia-huizu", + "unknown4": "Ningxia-huizu" + }, + "coordinates": { + "latitude": 38.496093312, + "longitude": 106.314986366 + } + }, + { + "id": 2686451712, + "name": "Xinjiang-Weiwu'er-zu", + "translations": { + "japanese": "新疆ウイグル自治区", + "english": "Xinjiang-Weiwu'er-zu", + "french": "Xinjiang", + "german": "Xinjiang", + "italian": "Xinjiang Uygur", + "spanish": "Xinjiang", + "chinese_simple": "新疆维吾尔自治区", + "korean": "신장웨이우얼 자치구", + "dutch": "Xinjiang", + "portuguese": "Xinjiang", + "russian": "Синьцзян-Уйгурский автономный район", + "chinese_traditional": "新疆維吾爾自治區", + "unknown1": "Xinjiang-Weiwu'er-zu", + "unknown2": "Xinjiang-Weiwu'er-zu", + "unknown3": "Xinjiang-Weiwu'er-zu", + "unknown4": "Xinjiang-Weiwu'er-zu" + }, + "coordinates": { + "latitude": 43.78051708, + "longitude": 87.583245976 + } + }, + { + "id": 2686517248, + "name": "Xizang", + "translations": { + "japanese": "チベット自治区", + "english": "Xizang", + "french": "Tibet", + "german": "Tibet", + "italian": "Tibet", + "spanish": "Tíbet", + "chinese_simple": "西藏自治区", + "korean": "티베트 자치구", + "dutch": "Tibet", + "portuguese": "Tibete", + "russian": "Тибетский автономный район", + "chinese_traditional": "西藏自治區", + "unknown1": "Xizang", + "unknown2": "Xizang", + "unknown3": "Xizang", + "unknown4": "Xizang" + }, + "coordinates": { + "latitude": 29.6630856, + "longitude": 91.164798684 + } + }, + { + "id": 2686582784, + "name": "Macao", + "translations": { + "japanese": "マカオ", + "english": "Macao", + "french": "Macao", + "german": "Macau", + "italian": "Macao", + "spanish": "Macao", + "chinese_simple": "澳门特别行政区", + "korean": "마카오", + "dutch": "Macau", + "portuguese": "Macau", + "russian": "Макао", + "chinese_traditional": "澳門特別行政區", + "unknown1": "Macao", + "unknown2": "Macao", + "unknown3": "Macao", + "unknown4": "Macao" + }, + "coordinates": { + "latitude": 22.214355216, + "longitude": 113.59894172 + } + }, + { + "id": 2686648320, + "name": "Hong Kong", + "translations": { + "japanese": "ホンコン", + "english": "Hong Kong", + "french": "Hong Kong", + "german": "Hongkong", + "italian": "Hong Kong", + "spanish": "Hong Kong", + "chinese_simple": "香港特别行政区", + "korean": "홍콩", + "dutch": "Hongkong", + "portuguese": "Hong Kong", + "russian": "Гонконг", + "chinese_traditional": "香港特別行政區", + "unknown1": "Hong Kong", + "unknown2": "Hong Kong", + "unknown3": "Hong Kong", + "unknown4": "Hong Kong" + }, + "coordinates": { + "latitude": 22.461547596, + "longitude": 114.296575453 + } + } + ] + }, + { + "id": 168, + "iso_code": "AE", + "name": "U.A.E.", + "translations": { + "japanese": "アラブ首長国連邦", + "english": "U.A.E.", + "french": "Émirats arabes unis", + "german": "Vereinigte Arabische Emirate", + "italian": "Emirati Arabi Uniti", + "spanish": "Emiratos Árabes Unidos", + "chinese_simple": "阿拉伯联合酋长国", + "korean": "아랍에미리트", + "dutch": "Verenigde Arabische Emiraten", + "portuguese": "Emirados Árabes Unidos", + "russian": "Объединённые Арабские Эмираты", + "chinese_traditional": "U.A.E.", + "unknown1": "U.A.E.", + "unknown2": "U.A.E.", + "unknown3": "U.A.E.", + "unknown4": "U.A.E." + }, + "regions": [ + { + "id": 2818572288, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": 24.466552456, + "longitude": 54.365992563 + } + }, + { + "id": 2818703360, + "name": "Abu Dhabi", + "translations": { + "japanese": "アブダビ", + "english": "Abu Dhabi", + "french": "Abu Dhabi", + "german": "Abu Dhabi", + "italian": "Abu Dhabi", + "spanish": "Abu Dabi", + "chinese_simple": "阿布扎比", + "korean": "아부다비", + "dutch": "Abu Dhabi", + "portuguese": "Abu Dabi", + "russian": "Абу-Даби", + "chinese_traditional": "Abu Dhabi", + "unknown1": "Abu Dhabi", + "unknown2": "Abu Dhabi", + "unknown3": "Abu Dhabi", + "unknown4": "Abu Dhabi" + }, + "coordinates": { + "latitude": 24.466552456, + "longitude": 54.365992563 + } + }, + { + "id": 2818768896, + "name": "Ajman", + "translations": { + "japanese": "アジュマン", + "english": "Ajman", + "french": "Ajman", + "german": "Adschman", + "italian": "Ajman", + "spanish": "Ajmán", + "chinese_simple": "阿治曼", + "korean": "아지만", + "dutch": "Ajman", + "portuguese": "Ajman", + "russian": "Аджман", + "chinese_traditional": "Ajman", + "unknown1": "Ajman", + "unknown2": "Ajman", + "unknown3": "Ajman", + "unknown4": "Ajman" + }, + "coordinates": { + "latitude": 25.4058835, + "longitude": 55.442655647 + } + }, + { + "id": 2818834432, + "name": "Ash Shariqah", + "translations": { + "japanese": "シャルジャ", + "english": "Ash Shariqah", + "french": "Sharjah", + "german": "Schardscha", + "italian": "Sharjah", + "spanish": "Sarja", + "chinese_simple": "沙迦", + "korean": "샤르자", + "dutch": "Sharjah", + "portuguese": "Sarja", + "russian": "Шарджа", + "chinese_traditional": "Ash Shariqah", + "unknown1": "Ash Shariqah", + "unknown2": "Ash Shariqah", + "unknown3": "Ash Shariqah", + "unknown4": "Ash Shariqah" + }, + "coordinates": { + "latitude": 24.999389364, + "longitude": 55.744780492 + } + }, + { + "id": 2818899968, + "name": "Ras al-Khaimah", + "translations": { + "japanese": "ラアス・アル・カイマー", + "english": "Ras al-Khaimah", + "french": "Ras al-Khaïmah", + "german": "Ras al-Chaima", + "italian": "Ras al-Khaimah", + "spanish": "Ras el Jaima", + "chinese_simple": "哈伊马角", + "korean": "라스알카이마", + "dutch": "Ras al-Khaimah", + "portuguese": "Ras el Jaima", + "russian": "Рас эль-Хайма", + "chinese_traditional": "Ras al-Khaimah", + "unknown1": "Ras al-Khaimah", + "unknown2": "Ras al-Khaimah", + "unknown3": "Ras al-Khaimah", + "unknown4": "Ras al-Khaimah" + }, + "coordinates": { + "latitude": 25.664062208, + "longitude": 55.997466726 + } + }, + { + "id": 2818965504, + "name": "Dubai", + "translations": { + "japanese": "ドゥバイ", + "english": "Dubai", + "french": "Dubaï", + "german": "Dubai", + "italian": "Dubai", + "spanish": "Dubái", + "chinese_simple": "迪拜", + "korean": "두바이", + "dutch": "Dubai", + "portuguese": "Dubai", + "russian": "Дубай", + "chinese_traditional": "Dubai", + "unknown1": "Dubai", + "unknown2": "Dubai", + "unknown3": "Dubai", + "unknown4": "Dubai" + }, + "coordinates": { + "latitude": 25.252074908, + "longitude": 55.277860277 + } + }, + { + "id": 2819031040, + "name": "Al Fujayrah", + "translations": { + "japanese": "フジャイラー", + "english": "Al Fujayrah", + "french": "Fujaïrah", + "german": "Fudschaira", + "italian": "Fujayrah", + "spanish": "Fujaira", + "chinese_simple": "富查伊拉", + "korean": "알푸자이라", + "dutch": "Fujairah", + "portuguese": "Fujaira", + "russian": "Фуджейра", + "chinese_traditional": "Al Fujayrah", + "unknown1": "Al Fujayrah", + "unknown2": "Al Fujayrah", + "unknown3": "Al Fujayrah", + "unknown4": "Al Fujayrah" + }, + "coordinates": { + "latitude": 25.120238972, + "longitude": 56.332550645 + } + }, + { + "id": 2819096576, + "name": "Umm al Qaywayn", + "translations": { + "japanese": "ウム・アル・カイワイン", + "english": "Umm al Qaywayn", + "french": "Umm al-Qaiwain", + "german": "Umm al-Qaiwain", + "italian": "Umm al-Qaiwain", + "spanish": "Um el Kaiwain", + "chinese_simple": "乌姆盖万", + "korean": "움알카이와인", + "dutch": "Umm al-Qaiwain", + "portuguese": "Um el Kaiwain", + "russian": "Умм эль-Кайвайн", + "chinese_traditional": "Umm al Qaywayn", + "unknown1": "Umm al Qaywayn", + "unknown2": "Umm al Qaywayn", + "unknown3": "Umm al Qaywayn", + "unknown4": "Umm al Qaywayn" + }, + "coordinates": { + "latitude": 25.499267288, + "longitude": 55.744780492 + } + } + ] + }, + { + "id": 174, + "iso_code": "SA", + "name": "Saudi Arabia", + "translations": { + "japanese": "サウジアラビア", + "english": "Saudi Arabia", + "french": "Arabie saoudite", + "german": "Saudi-Arabien", + "italian": "Arabia Saudita", + "spanish": "Arabia Saudí", + "chinese_simple": "沙特阿拉伯", + "korean": "사우디아라비아", + "dutch": "Saoedi-Arabië", + "portuguese": "Arábia Saudita", + "russian": "Саудовская Аравия", + "chinese_traditional": "Saudi Arabia", + "unknown1": "Saudi Arabia", + "unknown2": "Saudi Arabia", + "unknown3": "Saudi Arabia", + "unknown4": "Saudi Arabia" + }, + "regions": [ + { + "id": 2919235584, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": 24.63684054, + "longitude": 46.768926006 + } + }, + { + "id": 2919366656, + "name": "Ar Riyad", + "translations": { + "japanese": "リヤド州", + "english": "Ar Riyad", + "french": "Riyad", + "german": "Riad", + "italian": "Al-Riyad", + "spanish": "Riad", + "chinese_simple": "利雅得地区", + "korean": "리야드 주", + "dutch": "Ar Riyad", + "portuguese": "Riad", + "russian": "Эр-Рияд", + "chinese_traditional": "Ar Riyad", + "unknown1": "Ar Riyad", + "unknown2": "Ar Riyad", + "unknown3": "Ar Riyad", + "unknown4": "Ar Riyad" + }, + "coordinates": { + "latitude": 24.63684054, + "longitude": 46.768926006 + } + }, + { + "id": 2919432192, + "name": "Al Bahah", + "translations": { + "japanese": "バーハ州", + "english": "Al Bahah", + "french": "Al Bâhah", + "german": "Baha", + "italian": "Al-Bahah", + "spanish": "Al Bahah", + "chinese_simple": "巴哈地区", + "korean": "알바하 주", + "dutch": "Al Bahah", + "portuguese": "Baha", + "russian": "Эль-Баха", + "chinese_traditional": "Al Bahah", + "unknown1": "Al Bahah", + "unknown2": "Al Bahah", + "unknown3": "Al Bahah", + "unknown4": "Al Bahah" + }, + "coordinates": { + "latitude": 20.011596452, + "longitude": 41.462515092 + } + }, + { + "id": 2919497728, + "name": "Al Madinah", + "translations": { + "japanese": "メディナ州", + "english": "Al Madinah", + "french": "Médine", + "german": "Medina", + "italian": "Medina", + "spanish": "Medina", + "chinese_simple": "麦地那地区", + "korean": "메디나 주", + "dutch": "Medina", + "portuguese": "Medina", + "russian": "Эль-Мадина", + "chinese_traditional": "Al Madinah", + "unknown1": "Al Madinah", + "unknown2": "Al Madinah", + "unknown3": "Al Madinah", + "unknown4": "Al Madinah" + }, + "coordinates": { + "latitude": 24.082030976, + "longitude": 38.045757754 + } + }, + { + "id": 2919563264, + "name": "Ash Sharqiyah", + "translations": { + "japanese": "東部州", + "english": "Ash Sharqiyah", + "french": "Province Est", + "german": "Asch-Scharqiyya", + "italian": "Ash-Sharqiyah", + "spanish": "Región Oriental", + "chinese_simple": "东部地区", + "korean": "샤르키야 주", + "dutch": "Ash Sharqiyah", + "portuguese": "Ash Sharqiyah", + "russian": "Эш-Шаркийя", + "chinese_traditional": "Ash Sharqiyah", + "unknown1": "Ash Sharqiyah", + "unknown2": "Ash Sharqiyah", + "unknown3": "Ash Sharqiyah", + "unknown4": "Ash Sharqiyah" + }, + "coordinates": { + "latitude": 26.42211884, + "longitude": 50.114272017 + } + }, + { + "id": 2919628800, + "name": "Al Qasim", + "translations": { + "japanese": "カスィーム州", + "english": "Al Qasim", + "french": "Al Qasim", + "german": "Qasim", + "italian": "Al-Qasim", + "spanish": "Al Qasim", + "chinese_simple": "卡西姆地区", + "korean": "카심 주", + "dutch": "Al Qasim", + "portuguese": "Qasim", + "russian": "Эль-Касим", + "chinese_traditional": "Al Qasim", + "unknown1": "Al Qasim", + "unknown2": "Al Qasim", + "unknown3": "Al Qasim", + "unknown4": "Al Qasim" + }, + "coordinates": { + "latitude": 26.328735052, + "longitude": 43.967404716 + } + }, + { + "id": 2919694336, + "name": "'Asir", + "translations": { + "japanese": "アシール州", + "english": "'Asir", + "french": "Assir", + "german": "Asir", + "italian": "'Asir", + "spanish": "Asir", + "chinese_simple": "阿西尔地区", + "korean": "아시르 주", + "dutch": "Asir", + "portuguese": "Assir", + "russian": "Асир", + "chinese_traditional": "'Asir", + "unknown1": "'Asir", + "unknown2": "'Asir", + "unknown3": "'Asir", + "unknown4": "'Asir" + }, + "coordinates": { + "latitude": 18.215331824, + "longitude": 42.500725923 + } + }, + { + "id": 2919759872, + "name": "Ha'il", + "translations": { + "japanese": "ハーイル州", + "english": "Ha'il", + "french": "Haïl", + "german": "Hail", + "italian": "Ha'il", + "spanish": "Hail", + "chinese_simple": "哈伊勒地区", + "korean": "하일 주", + "dutch": "Ha'il", + "portuguese": "Hail", + "russian": "Ха’иль", + "chinese_traditional": "Ha'il", + "unknown1": "Ha'il", + "unknown2": "Ha'il", + "unknown3": "Ha'il", + "unknown4": "Ha'il" + }, + "coordinates": { + "latitude": 27.3284909, + "longitude": 41.599844567 + } + }, + { + "id": 2919825408, + "name": "Makkah", + "translations": { + "japanese": "メッカ州", + "english": "Makkah", + "french": "La Mecque", + "german": "Mekka", + "italian": "Mecca", + "spanish": "La Meca", + "chinese_simple": "麦加地区", + "korean": "메카 주", + "dutch": "Mekka", + "portuguese": "Mecca", + "russian": "Мекка", + "chinese_traditional": "Makkah", + "unknown1": "Makkah", + "unknown2": "Makkah", + "unknown3": "Makkah", + "unknown4": "Makkah" + }, + "coordinates": { + "latitude": 21.4233396, + "longitude": 39.82554775 + } + }, + { + "id": 2919890944, + "name": "Al Hudud ash Shamaliyah", + "translations": { + "japanese": "北部国境州", + "english": "Al Hudud ash Shamaliyah", + "french": "Frontière Nord", + "german": "Al-Hudud asch-schamaliyya", + "italian": "Al-Hudud ash-Shamaliyah", + "spanish": "Frontera Norte", + "chinese_simple": "北部边疆地区", + "korean": "북부 국경 주", + "dutch": "Al Hudud ash Shamaliyah", + "portuguese": "Fronteira Norte", + "russian": "Эль-Худуд эш-Шамалийя", + "chinese_traditional": "Al Hudud ash Shamaliyah", + "unknown1": "Al Hudud ash Shamaliyah", + "unknown2": "Al Hudud ash Shamaliyah", + "unknown3": "Al Hudud ash Shamaliyah", + "unknown4": "Al Hudud ash Shamaliyah" + }, + "coordinates": { + "latitude": 30.98144496, + "longitude": 41.017567593 + } + }, + { + "id": 2919956480, + "name": "Najran", + "translations": { + "japanese": "ナジュラーン州", + "english": "Najran", + "french": "Najran", + "german": "Nadschran", + "italian": "Najran", + "spanish": "Najran", + "chinese_simple": "纳季兰地区", + "korean": "나지란 주", + "dutch": "Najran", + "portuguese": "Najran", + "russian": "Наджран", + "chinese_traditional": "Najran", + "unknown1": "Najran", + "unknown2": "Najran", + "unknown3": "Najran", + "unknown4": "Najran" + }, + "coordinates": { + "latitude": 17.501220504, + "longitude": 44.181638697 + } + }, + { + "id": 2920022016, + "name": "Jizan", + "translations": { + "japanese": "ジーザーン州", + "english": "Jizan", + "french": "Jizan", + "german": "Dschaizan", + "italian": "Jizan", + "spanish": "Jizan", + "chinese_simple": "吉赞地区", + "korean": "지잔 주", + "dutch": "Jizan", + "portuguese": "Jizan", + "russian": "Джизан", + "chinese_traditional": "Jizan", + "unknown1": "Jizan", + "unknown2": "Jizan", + "unknown3": "Jizan", + "unknown4": "Jizan" + }, + "coordinates": { + "latitude": 16.885986136, + "longitude": 42.550164534 + } + }, + { + "id": 2920087552, + "name": "Tabuk", + "translations": { + "japanese": "タブーク州", + "english": "Tabuk", + "french": "Tabouk", + "german": "Tabuk", + "italian": "Tabuk", + "spanish": "Tabuk", + "chinese_simple": "塔布克地区", + "korean": "타부크 주", + "dutch": "Tabuk", + "portuguese": "Tabuk", + "russian": "Табук", + "chinese_traditional": "Tabuk", + "unknown1": "Tabuk", + "unknown2": "Tabuk", + "unknown3": "Tabuk", + "unknown4": "Tabuk" + }, + "coordinates": { + "latitude": 28.383178388, + "longitude": 36.579078961 + } + }, + { + "id": 2920153088, + "name": "Al Jawf", + "translations": { + "japanese": "ジャウフ州", + "english": "Al Jawf", + "french": "Al Djôf", + "german": "Dschauf", + "italian": "Al-Jawf", + "spanish": "Al Jouf", + "chinese_simple": "朱夫地区", + "korean": "자우프 주", + "dutch": "Al Jawf", + "portuguese": "Al Jouf", + "russian": "Эль-Джауф", + "chinese_traditional": "Al Jawf", + "unknown1": "Al Jawf", + "unknown2": "Al Jawf", + "unknown3": "Al Jawf", + "unknown4": "Al Jawf" + }, + "coordinates": { + "latitude": 29.805907864, + "longitude": 39.864000003 + } + } + ] + }, + { + "id": 184, + "iso_code": "SM", + "name": "San Marino", + "translations": { + "japanese": "サンマリノ", + "english": "San Marino", + "french": "Saint-Marin", + "german": "San Marino", + "italian": "San Marino", + "spanish": "San Marino", + "chinese_simple": "圣马力诺", + "korean": "산마리노", + "dutch": "San Marino", + "portuguese": "São Marinho", + "russian": "Сан-Марино", + "chinese_traditional": "San Marino", + "unknown1": "San Marino", + "unknown2": "San Marino", + "unknown3": "San Marino", + "unknown4": "San Marino" + }, + "regions": [ + { + "id": 3087007744, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": 43.928832508, + "longitude": 12.431064077 + } + }, + { + "id": 3087073280, + "name": "San Marino", + "translations": { + "japanese": "サンマリノ", + "english": "San Marino", + "french": "Saint-Marin", + "german": "San Marino", + "italian": "San Marino", + "spanish": "San Marino", + "chinese_simple": "圣马力诺", + "korean": "산마리노", + "dutch": "San Marino", + "portuguese": "São Marinho", + "russian": "Сан-Марино", + "chinese_traditional": "San Marino", + "unknown1": "San Marino", + "unknown2": "San Marino", + "unknown3": "San Marino", + "unknown4": "San Marino" + }, + "coordinates": { + "latitude": 43.928832508, + "longitude": 12.431064077 + } + } + ] + }, + { + "id": 185, + "iso_code": "VA", + "name": "Vatican City", + "translations": { + "japanese": "バチカン", + "english": "Vatican City", + "french": "Vatican", + "german": "Vatikanstadt", + "italian": "Vaticano (Città del)", + "spanish": "Vaticano", + "chinese_simple": "梵蒂冈", + "korean": "바티칸", + "dutch": "Vaticaanstad", + "portuguese": "Vaticano (cidade)", + "russian": "Ватикан", + "chinese_traditional": "Vatican City", + "unknown1": "Vatican City", + "unknown2": "Vatican City", + "unknown3": "Vatican City", + "unknown4": "Vatican City" + }, + "regions": [ + { + "id": 3103784960, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": 41.896361828, + "longitude": 12.447543614 + } + }, + { + "id": 3103850496, + "name": "Vatican City", + "translations": { + "japanese": "バチカン", + "english": "Vatican City", + "french": "Vatican", + "german": "Vatikanstadt", + "italian": "Vaticano (Città del)", + "spanish": "Vaticano", + "chinese_simple": "梵蒂冈", + "korean": "바티칸", + "dutch": "Vaticaanstad", + "portuguese": "Vaticano (cidade)", + "russian": "Ватикан", + "chinese_traditional": "Vatican City", + "unknown1": "Vatican City", + "unknown2": "Vatican City", + "unknown3": "Vatican City", + "unknown4": "Vatican City" + }, + "coordinates": { + "latitude": 41.896361828, + "longitude": 12.447543614 + } + } + ] + }, + { + "id": 186, + "iso_code": "BM", + "name": "Bermuda", + "translations": { + "japanese": "バーミューダ", + "english": "Bermuda", + "french": "Bermudes", + "german": "Bermuda", + "italian": "Bermude", + "spanish": "Bermudas", + "chinese_simple": "百慕大", + "korean": "버뮤다", + "dutch": "Bermuda", + "portuguese": "Bermudas", + "russian": "Бермуды", + "chinese_traditional": "Bermuda", + "unknown1": "Bermuda", + "unknown2": "Bermuda", + "unknown3": "Bermuda", + "unknown4": "Bermuda" + }, + "regions": [ + { + "id": 3120562176, + "name": "Unspecified", + "translations": { + "japanese": "—", + "english": "—", + "french": "—", + "german": "—", + "italian": "—", + "spanish": "—", + "chinese_simple": "—", + "korean": "—", + "dutch": "—", + "portuguese": "—", + "russian": "—", + "chinese_traditional": "—", + "unknown1": "—", + "unknown2": "—", + "unknown3": "—", + "unknown4": "—" + }, + "coordinates": { + "latitude": 32.332763304, + "longitude": -64.747611401 + } + }, + { + "id": 3120627712, + "name": "Bermuda", + "translations": { + "japanese": "バーミューダ", + "english": "Bermuda", + "french": "Bermudes", + "german": "Bermuda", + "italian": "Bermude", + "spanish": "Bermudas", + "chinese_simple": "百慕大", + "korean": "버뮤다", + "dutch": "Bermuda", + "portuguese": "Bermudas", + "russian": "Бермуды", + "chinese_traditional": "Bermuda", + "unknown1": "Bermuda", + "unknown2": "Bermuda", + "unknown3": "Bermuda", + "unknown4": "Bermuda" + }, + "coordinates": { + "latitude": 32.332763304, + "longitude": -64.747611401 + } + } + ] + } +] \ No newline at end of file diff --git a/src/services/nnas/routes/account-settings.ts b/src/services/nnas/routes/account-settings.ts new file mode 100644 index 0000000..da19773 --- /dev/null +++ b/src/services/nnas/routes/account-settings.ts @@ -0,0 +1,212 @@ +import crypto from 'node:crypto'; +import express from 'express'; +import got from 'got'; +import { z } from 'zod'; +import { getServerByClientID, getPNIDByPID } from '@/database'; +import { LOG_ERROR } from '@/logger'; +import { decryptToken, unpackToken, getValueFromHeaders, sendConfirmationEmail } from '@/util'; +import { config } from '@/config-manager'; +import { HydratedServerDocument } from '@/types/mongoose/server'; +import { HydratedPNIDDocument } from '@/types/mongoose/pnid'; +import { AccountSettings } from '@/types/services/nnas/account-settings'; +import { Token } from '@/types/common/token'; +import { RegionLanguages } from '@/types/services/nnas/region-languages'; +import { RegionTimezone, RegionTimezones } from '@/types/services/nnas/region-timezones'; +import { Country, Region } from '@/types/services/nnas/regions'; +import timezones from '@/services/nnas/timezones.json'; +import regionsList from '@/services/nnas/regions.json'; + +const router: express.Router = express.Router(); + +const accountSettingsSchema = z.object({ + gender: z.enum(['M', 'F']), + tz_name: z.string(), + region: z.coerce.number(), + email: z.string().email(), + server_selection: z.enum(['prod', 'test', 'dev']), + marketing_flag: z.enum(['true', 'false']).transform((value) => value === 'true'), + off_device_flag: z.enum(['true', 'false']).transform((value) => value === 'true'), +}); + +/** + * [GET] + * Replacement for: https://account.nintendo.net/v1/account-settings/ui/profile + * Description: Serves the Nintendo Network ID Settings page for the Wii U + */ +router.get('/ui/profile', async function (request: express.Request, response: express.Response): Promise { + const server: HydratedServerDocument | null = await getServerByClientID('3f3928cc6f780638d360f0485cef973f', config.server_environment); + const token: string | undefined = getValueFromHeaders(request.headers, 'x-nintendo-service-token'); + if (!server || !token) { + response.sendStatus(504); + return; + } + const aes_key: string = server?.aes_key; + const decryptedToken: Buffer = decryptToken(Buffer.from(token, 'base64'), aes_key); + + const tokenContents: Token = unpackToken(decryptedToken); + + try { + const PNID: HydratedPNIDDocument | null = await getPNIDByPID(tokenContents.pid); + + if (!PNID) { + response.sendStatus(504); + return; + } + + const countryCode: string = PNID.country; + const language: string = PNID.language; + + const regionLanguages: RegionLanguages = timezones[countryCode as keyof typeof timezones]; + const regionTimezones: RegionTimezones = regionLanguages[language] ? regionLanguages[language] : Object.values(regionLanguages)[0]; + + const region: Country | undefined = regionsList.find((region) => region.iso_code === countryCode); + + const miiFaces = ['normal_face', 'smile_open_mouth', 'sorrow', 'surprise_open_mouth', 'wink_left', 'frustrated']; + const face = miiFaces[crypto.randomInt(5)]; + + const notice: string | undefined = request.query.notice ? request.query.notice.toString() : undefined; + + const accountLevel: string[] = ['Standard', 'Tester', 'Moderator', 'Developer']; + + response.render('index.ejs', { + PNID, + regionTimezones, + face, + notice, + accountLevel, + regions: region ? region.regions: [] + }); + } + catch (error: any) { + LOG_ERROR(error); + response.sendStatus(504); + return; + } +}); + +/** + * [GET] + * Description: Fetches the requested mii image from the CDN and send it to the client. + * This is required because of the strict domain whitelist in the account settings app + * on the Wii U. + */ +router.get('/mii/:pid/:face', async function (request: express.Request, response: express.Response): Promise { + if (!config.cdn.base_url) { + response.sendStatus(404); + return; + } + const miiImage: Buffer = await got(`${config.cdn.base_url}/mii/${request.params.pid}/${request.params.face}.png`).buffer(); + response.set('Content-Type', 'image/png'); + response.send(miiImage); +}); + +/** + * [POST] + * Description: Endpoint to update the PNID from the account settings app on the Wii + */ +router.post('/update', async function (request: express.Request, response: express.Response): Promise { + const server: HydratedServerDocument | null = await getServerByClientID('3f3928cc6f780638d360f0485cef973f', config.server_environment); + const token: string | undefined = getValueFromHeaders(request.headers, 'x-nintendo-service-token'); + if (!server || !token) { + response.sendStatus(504); + return; + } + + const aesKey: string = server?.aes_key; + const decryptedToken: Buffer = decryptToken(Buffer.from(token, 'base64'), aesKey); + + const tokenContents: Token = unpackToken(decryptedToken); + + try { + const pnid: HydratedPNIDDocument | null = await getPNIDByPID(tokenContents.pid); + const personBody: AccountSettings = request.body; + + if (!pnid) { + response.status(404); + response.redirect('/v1/account-settings/ui/profile'); + return; + } + + const person = accountSettingsSchema.safeParse(personBody); + + if (!person.success) { + response.status(404); + response.redirect('/v1/account-settings/ui/profile'); + return; + } + + const timezoneName: string = (person.data.tz_name && !!Object.keys(person.data.tz_name).length) ? person.data.tz_name : pnid.timezone.name; + + const regionLanguages: RegionLanguages = timezones[pnid.country as keyof typeof timezones]; + const regionTimezones: RegionTimezones = regionLanguages[pnid.language] ? regionLanguages[pnid.language] : Object.values(regionLanguages)[0]; + const timezone: RegionTimezone | undefined = regionTimezones.find(tz => tz.area === timezoneName); + const country: Country | undefined = regionsList.find((region) => region.iso_code === pnid.country); + let notice = ''; + + if (!country) { + response.status(404); + response.redirect('/v1/account-settings/ui/profile'); + return; + } + + const regionObject: Region | undefined = country.regions.find((region) => region.id === person.data.region); + const region: number = regionObject ? regionObject.id : pnid.region; + + if (!timezone) { + response.status(404); + response.redirect('/v1/account-settings/ui/profile'); + return; + } + + pnid.gender = person.data.gender; + pnid.region = region; + pnid.timezone.name = timezoneName; + pnid.timezone.offset = Number(timezone.utc_offset); + pnid.flags.marketing = person.data.marketing_flag; + pnid.flags.off_device = person.data.off_device_flag; + + if (person.data.server_selection) { + const environment: string = person.data.server_selection; + + if (environment === 'test' && pnid.access_level < 1) { + response.status(400); + notice = 'Do not have permission to enter this environment'; + response.redirect(`/v1/account-settings/ui/profile?notice=${notice}`); + return; + } + + if (environment === 'dev' && pnid.access_level < 3) { + response.status(400); + notice = 'Do not have permission to enter this environment'; + response.redirect(`/v1/account-settings/ui/profile?notice=${notice}`); + return; + } + + pnid.server_access_level = environment; + } + + if (person.data.email.trim().toLowerCase() !== pnid.email.address) { + // TODO - Better email check + pnid.email.address = person.data.email.trim().toLowerCase(); + pnid.email.reachable = false; + pnid.email.validated = false; + pnid.email.validated_date = ''; + pnid.email.id = crypto.randomBytes(4).readUInt32LE(); + + await pnid.generateEmailValidationCode(); + await pnid.generateEmailValidationToken(); + await sendConfirmationEmail(pnid); + + notice = 'A confirmation email has been sent to your inbox.'; + } + + await pnid.save(); + response.redirect(`/v1/account-settings/ui/profile?notice=${notice}`); + } catch (error: any) { + LOG_ERROR(error); + response.sendStatus(504); + return; + } +}); + +export default router; diff --git a/src/types/common/config.ts b/src/types/common/config.ts index 82606fc..d62fcb1 100644 --- a/src/types/common/config.ts +++ b/src/types/common/config.ts @@ -46,4 +46,5 @@ export interface Config { stripe?: { secret_key: string; }; + server_environment: string; } \ No newline at end of file diff --git a/src/types/common/gender-types.ts b/src/types/common/gender-types.ts new file mode 100644 index 0000000..9d4ca82 --- /dev/null +++ b/src/types/common/gender-types.ts @@ -0,0 +1 @@ +export type GenderTypes = 'M' | 'F'; \ No newline at end of file diff --git a/src/types/services/nnas/account-settings.ts b/src/types/services/nnas/account-settings.ts new file mode 100644 index 0000000..8e7e397 --- /dev/null +++ b/src/types/services/nnas/account-settings.ts @@ -0,0 +1,11 @@ +import { GenderTypes } from '@/types/common/gender-types'; + +export interface AccountSettings { + gender: GenderTypes; + tz_name: string; + region: number; + email: string; + server_selection: string; + marketing_flag: boolean; + off_device_flag: boolean; +} \ No newline at end of file diff --git a/src/types/services/nnas/region-languages.ts b/src/types/services/nnas/region-languages.ts new file mode 100644 index 0000000..fd4bbf9 --- /dev/null +++ b/src/types/services/nnas/region-languages.ts @@ -0,0 +1,5 @@ +import { RegionTimezones } from '@/types/services/nnas/region-timezones'; + +export interface RegionLanguages { + [myKey: string]: RegionTimezones +} \ No newline at end of file diff --git a/src/types/services/nnas/region-timezones.ts b/src/types/services/nnas/region-timezones.ts new file mode 100644 index 0000000..c904aaa --- /dev/null +++ b/src/types/services/nnas/region-timezones.ts @@ -0,0 +1,9 @@ +export interface RegionTimezone { + area: string; + language: string; + name: string; + utc_offset: string; + order: string; +} + +export type RegionTimezones = RegionTimezone[]; \ No newline at end of file diff --git a/src/types/services/nnas/regions.ts b/src/types/services/nnas/regions.ts new file mode 100644 index 0000000..1d77232 --- /dev/null +++ b/src/types/services/nnas/regions.ts @@ -0,0 +1,36 @@ +export interface Country { + id: number; + iso_code: string; + name: string; + translations: Translations; + regions: Region[]; +} + +export interface Translations { + japanese: string; + english: string; + french: string; + german: string; + italian: string; + spanish: string; + chinese_simple: string; + korean: string; + dutch: string; + portuguese: string; + russian: string; + chinese_traditional: string; + unknown1: string; + unknown2: string; + unknown3: string; + unknown4: string; +} + +export interface Region { + id: number; + name: string; + translations: Translations; + coordinates: { + latitude: number; + longitude: number; + }; +} diff --git a/src/util.ts b/src/util.ts index cd0da6c..b59aa15 100644 --- a/src/util.ts +++ b/src/util.ts @@ -95,7 +95,7 @@ export function generateToken(key: string, options: TokenOptions): Buffer | null return final; } -export function decryptToken(token: Buffer): Buffer { +export function decryptToken(token: Buffer, key?: string): Buffer { let encryptedBody: Buffer; let expectedChecksum = 0; @@ -107,8 +107,12 @@ export function decryptToken(token: Buffer): Buffer { encryptedBody = token.subarray(4); } + if (!key) { + key = config.aes_key; + } + const iv = Buffer.alloc(16); - const decipher = crypto.createDecipheriv('aes-256-cbc', Buffer.from(config.aes_key, 'hex'), iv); + const decipher = crypto.createDecipheriv('aes-256-cbc', Buffer.from(key, 'hex'), iv); const decrypted = Buffer.concat([ decipher.update(encryptedBody), diff --git a/src/views/index.ejs b/src/views/index.ejs new file mode 100644 index 0000000..6c53720 --- /dev/null +++ b/src/views/index.ejs @@ -0,0 +1,146 @@ + + + + + Account Settings + + + +
+
+

Pretendo Network ID Settings

+ + +
+ +
+

User Settings

+
+

Birth Date

+

<%= PNID.birthdate %>

+
+
+

Gender

+ +
+
+

Country

+

<%= PNID.country %>

+
+
+

Region

+ +
+
+

Timezone

+ +
+

Email

+
+ +
+
+ <% if(PNID.email.validated) { %> + Verified + + <% } else { %> + Email Not Verified + + <% } %> +
+

Other Settings

+
+

Server Environment

+
+ checked=""<% } %>> + + <% if(PNID.access_level >= 1) { %> + checked="" <% } %> <% if(PNID.access_level < 1) { %> disabled <% } %> > + + <% } %> + <% if(PNID.access_level === 3) { %> + checked="" <% } %> <% if(PNID.access_level < 3) { %> disabled <% } %> > + + <% } %> +
+
+
+

Email Notifications

+
+ checked=""<% } %>> + + checked=""<% } %>> + +
+
+
+

Non-Nintendo Device Setting

+
+ checked=""<% } %>> + + checked=""<% } %>> + +
+
+
+
+ <% if (notice) { %> + + <% } %> + + + \ No newline at end of file From ccfc0589b821edc13a6cd6d1a39cddb26bee7a1c Mon Sep 17 00:00:00 2001 From: Jemma Date: Sun, 21 Apr 2024 20:32:56 -0500 Subject: [PATCH 156/219] Added support for changing the PNID country on Wii U --- src/services/nnas/routes/account-settings.ts | 5 ++++- src/views/index.ejs | 16 ++++++++++++++-- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/services/nnas/routes/account-settings.ts b/src/services/nnas/routes/account-settings.ts index da19773..c9902dd 100644 --- a/src/services/nnas/routes/account-settings.ts +++ b/src/services/nnas/routes/account-settings.ts @@ -22,6 +22,7 @@ const accountSettingsSchema = z.object({ gender: z.enum(['M', 'F']), tz_name: z.string(), region: z.coerce.number(), + country: z.string(), email: z.string().email(), server_selection: z.enum(['prod', 'test', 'dev']), marketing_flag: z.enum(['true', 'false']).transform((value) => value === 'true'), @@ -74,7 +75,8 @@ router.get('/ui/profile', async function (request: express.Request, response: ex face, notice, accountLevel, - regions: region ? region.regions: [] + regions: region ? region.regions: [], + regionsList }); } catch (error: any) { @@ -160,6 +162,7 @@ router.post('/update', async function (request: express.Request, response: expre pnid.gender = person.data.gender; pnid.region = region; + pnid.country = person.data.country; pnid.timezone.name = timezoneName; pnid.timezone.offset = Number(timezone.utc_offset); pnid.flags.marketing = person.data.marketing_flag; diff --git a/src/views/index.ejs b/src/views/index.ejs index 6c53720..a42d8f3 100644 --- a/src/views/index.ejs +++ b/src/views/index.ejs @@ -41,7 +41,11 @@

Country

-

<%= PNID.country %>

+

Region

@@ -138,9 +142,17 @@ <% if (notice) { %> <% } %> + \ No newline at end of file From f8bb78fd51430785edd06c9b8fa2312498898dbd Mon Sep 17 00:00:00 2001 From: Jemma Date: Mon, 22 Apr 2024 19:27:40 -0500 Subject: [PATCH 157/219] Added missing fields in the /v1/api/people/@me/ endpoint. Added PN_ACT_CONFIG_SERVER_ENVIRONMENT to example.env and the setup guide. --- SETUP.md | 51 +++++++++++++++--------------- example.env | 3 +- src/services/nnas/routes/people.ts | 6 ++-- 3 files changed, 32 insertions(+), 28 deletions(-) diff --git a/SETUP.md b/SETUP.md index 1aeaba2..8bb83c2 100644 --- a/SETUP.md +++ b/SETUP.md @@ -54,28 +54,29 @@ The Pretendo Network website uses this server as an API for querying user inform Configurations are loaded through environment variables. `.env` files are supported. All configuration options will be gone over, both required and optional. There also exists an example `.env` file -| Name | Description | Optional | -|-----------------------------------------------|-------------------------------------------------------------------|----------| -| `PN_ACT_CONFIG_HTTP_PORT` | The HTTP port the server listens on | No | -| `PN_ACT_CONFIG_MONGO_CONNECTION_STRING` | MongoDB connection string | No | -| `PN_ACT_CONFIG_MONGOOSE_CONNECT_OPTIONS_PATH` | Path to a `.json` file containing Mongoose connection options | Yes | -| `PN_ACT_CONFIG_REDIS_URL` | Redis URL | Yes | -| `PN_ACT_CONFIG_EMAIL_HOST` | SMTP host | Yes | -| `PN_ACT_CONFIG_EMAIL_PORT` | SMTP port | Yes | -| `PN_ACT_CONFIG_EMAIL_SECURE` | Is the email server secure | Yes | -| `PN_ACT_CONFIG_EMAIL_USERNAME` | Email account username | Yes | -| `PN_ACT_CONFIG_EMAIL_PASSWORD` | Email account password | Yes | -| `PN_ACT_CONFIG_EMAIL_FROM` | Email "from" address | Yes | -| `PN_ACT_CONFIG_S3_ENDPOINT` | s3 server endpoint | Yes | -| `PN_ACT_CONFIG_S3_ACCESS_KEY` | s3 secret key | Yes | -| `PN_ACT_CONFIG_S3_ACCESS_SECRET` | s3 secret | Yes | -| `PN_ACT_CONFIG_HCAPTCHA_SECRET` | hCaptcha secret (in the form `0x...`) | Yes | -| `PN_ACT_CONFIG_CDN_SUBDOMAIN` | Subdomain used to serve CDN contents if s3 is disabled | Yes | -| `PN_ACT_CONFIG_CDN_DISK_PATH` | File system path used to store CDN contents if s3 is disabled | Yes | -| `PN_ACT_CONFIG_CDN_BASE_URL` | URL for serving CDN contents (usually the same as s3 endpoint) | No | -| `PN_ACT_CONFIG_WEBSITE_BASE` | Website URL | Yes | -| `PN_ACT_CONFIG_AES_KEY` | AES-256 key used for encrypting tokens | No | -| `PN_ACT_CONFIG_GRPC_MASTER_API_KEY_ACCOUNT` | Master API key to interact with the account gRPC service | No | -| `PN_ACT_CONFIG_GRPC_MASTER_API_KEY_API` | Master API key to interact with the API gRPC service | No | -| `PN_ACT_CONFIG_GRPC_PORT` | gRPC server port | No | -| `PN_ACT_CONFIG_STRIPE_SECRET_KEY` | Stripe API key. Used to cancel subscriptions when scrubbing PNIDs | Yes | \ No newline at end of file +| Name | Description | Optional | +|-----------------------------------------------|--------------------------------------------------------------------------------------------------|----------| +| `PN_ACT_CONFIG_HTTP_PORT` | The HTTP port the server listens on | No | +| `PN_ACT_CONFIG_MONGO_CONNECTION_STRING` | MongoDB connection string | No | +| `PN_ACT_CONFIG_MONGOOSE_CONNECT_OPTIONS_PATH` | Path to a `.json` file containing Mongoose connection options | Yes | +| `PN_ACT_CONFIG_REDIS_URL` | Redis URL | Yes | +| `PN_ACT_CONFIG_EMAIL_HOST` | SMTP host | Yes | +| `PN_ACT_CONFIG_EMAIL_PORT` | SMTP port | Yes | +| `PN_ACT_CONFIG_EMAIL_SECURE` | Is the email server secure | Yes | +| `PN_ACT_CONFIG_EMAIL_USERNAME` | Email account username | Yes | +| `PN_ACT_CONFIG_EMAIL_PASSWORD` | Email account password | Yes | +| `PN_ACT_CONFIG_EMAIL_FROM` | Email "from" address | Yes | +| `PN_ACT_CONFIG_S3_ENDPOINT` | s3 server endpoint | Yes | +| `PN_ACT_CONFIG_S3_ACCESS_KEY` | s3 secret key | Yes | +| `PN_ACT_CONFIG_S3_ACCESS_SECRET` | s3 secret | Yes | +| `PN_ACT_CONFIG_HCAPTCHA_SECRET` | hCaptcha secret (in the form `0x...`) | Yes | +| `PN_ACT_CONFIG_CDN_SUBDOMAIN` | Subdomain used to serve CDN contents if s3 is disabled | Yes | +| `PN_ACT_CONFIG_CDN_DISK_PATH` | File system path used to store CDN contents if s3 is disabled | Yes | +| `PN_ACT_CONFIG_CDN_BASE_URL` | URL for serving CDN contents (usually the same as s3 endpoint) | No | +| `PN_ACT_CONFIG_WEBSITE_BASE` | Website URL | Yes | +| `PN_ACT_CONFIG_AES_KEY` | AES-256 key used for encrypting tokens | No | +| `PN_ACT_CONFIG_GRPC_MASTER_API_KEY_ACCOUNT` | Master API key to interact with the account gRPC service | No | +| `PN_ACT_CONFIG_GRPC_MASTER_API_KEY_API` | Master API key to interact with the API gRPC service | No | +| `PN_ACT_CONFIG_GRPC_PORT` | gRPC server port | No | +| `PN_ACT_CONFIG_STRIPE_SECRET_KEY` | Stripe API key. Used to cancel subscriptions when scrubbing PNIDs | Yes | +| `PN_ACT_CONFIG_SERVER_ENVIRONMENT` | Server environment. Currently only used by the Wii U Account Settings app. `prod`/`test`/`dev` | Yes | \ No newline at end of file diff --git a/example.env b/example.env index 14423b9..e572b64 100644 --- a/example.env +++ b/example.env @@ -19,4 +19,5 @@ PN_ACT_CONFIG_CDN_SUBDOMAIN=local-cdn PN_ACT_CONFIG_CDN_DISK_PATH=/home/jon/pretend-cdn PN_ACT_CONFIG_WEBSITE_BASE=https://example.com PN_ACT_CONFIG_AES_KEY=abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789 -PN_ACT_CONFIG_GRPC_API_KEY=apikey \ No newline at end of file +PN_ACT_CONFIG_GRPC_API_KEY=apikey +PN_ACT_CONFIG_SERVER_ENVIRONMENT=prod \ No newline at end of file diff --git a/src/services/nnas/routes/people.ts b/src/services/nnas/routes/people.ts index 4e7549b..2172b53 100644 --- a/src/services/nnas/routes/people.ts +++ b/src/services/nnas/routes/people.ts @@ -611,10 +611,12 @@ router.put('/@me', async (request: express.Request, response: express.Response): pnid.gender = gender; pnid.region = region; + pnid.country = countryCode; + pnid.language = language; pnid.timezone.name = timezoneName; pnid.timezone.offset = Number(timezone.utc_offset); - pnid.timezone.marketing = marketingFlag; - pnid.timezone.off_device = offDeviceFlag; + pnid.flags.marketing = marketingFlag; + pnid.flags.off_device = offDeviceFlag; await pnid.save(); From 96d220cb39b5979127d26eaf608150a5a8f12bd4 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Sat, 27 Apr 2024 09:16:08 -0400 Subject: [PATCH 158/219] chore: remove reuse of ban error code in NASC --- src/middleware/nasc.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/middleware/nasc.ts b/src/middleware/nasc.ts index 64fc2bd..7ef7053 100644 --- a/src/middleware/nasc.ts +++ b/src/middleware/nasc.ts @@ -96,6 +96,7 @@ async function NASCMiddleware(request: express.Request, response: express.Respon if (pid) { nexAccount = await NEXAccount.findOne({ pid }); + // TODO - 102 is a DEVICE ban. Is there an error for ACCOUNT bans? if (!nexAccount || nexAccount.access_level < 0) { response.status(200).send(nascError('102').toString()); return; @@ -132,7 +133,8 @@ async function NASCMiddleware(request: express.Request, response: express.Respon } if (device.serial !== serialNumber) { - response.status(200).send(nascError('102').toString()); + // * 150 is a custom error code + response.status(200).send(nascError('150').toString()); return; } } @@ -214,8 +216,8 @@ async function NASCMiddleware(request: express.Request, response: express.Respon await session.abortTransaction(); - // * 3DS expects 200 even on error - response.status(200).send(nascError('102').toString()); + // * 151 is a custom error code + response.status(200).send(nascError('151').toString()); return; } finally { // * This runs regardless of failure From af54670fbc82b80f4500c943600c5019860cbbd3 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Tue, 7 May 2024 18:10:52 -0400 Subject: [PATCH 159/219] fix: add some missing deleted checks --- src/services/api/routes/v1/login.ts | 10 ++++++++++ src/services/nnas/routes/oauth.ts | 14 ++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/src/services/api/routes/v1/login.ts b/src/services/api/routes/v1/login.ts index 21a92b6..7d778f2 100644 --- a/src/services/api/routes/v1/login.ts +++ b/src/services/api/routes/v1/login.ts @@ -99,6 +99,16 @@ router.post('/', async (request: express.Request, response: express.Response): P } } + if (pnid.deleted) { + response.status(400).json({ + app: 'api', + status: 400, + error: 'User not found' + }); + + return; + } + const accessTokenOptions = { system_type: 0x3, // * API token_type: 0x1, // * OAuth Access diff --git a/src/services/nnas/routes/oauth.ts b/src/services/nnas/routes/oauth.ts index 7d25b75..106e766 100644 --- a/src/services/nnas/routes/oauth.ts +++ b/src/services/nnas/routes/oauth.ts @@ -114,6 +114,20 @@ router.post('/access_token/generate', deviceCertificateMiddleware, consoleStatus } } + if (pnid.deleted) { + // * 0112 is the "account deleted" error, but unsure if this unlinks the PNID from the user? + // * 0143 is the "The link to this Nintendo Network ID has been temporarliy removed" error, + // * maybe that is a better error to use here? + response.status(400).send(xmlbuilder.create({ + error: { + code: '0112', + message: pnid.username + } + }).end()); + + return; + } + // * This are set/validated in consoleStatusVerificationMiddleware // * It is always set, despite what Express might think if (request.device?.model === 'wup') { From da0a913aa2a027fae726c1384564d78f7c868976 Mon Sep 17 00:00:00 2001 From: Daniloch Date: Fri, 31 May 2024 03:59:35 -0300 Subject: [PATCH 160/219] feat: add captcha to forgot password requests --- src/services/api/routes/v1/forgotPassword.ts | 32 +++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/src/services/api/routes/v1/forgotPassword.ts b/src/services/api/routes/v1/forgotPassword.ts index 58838bb..26260ba 100644 --- a/src/services/api/routes/v1/forgotPassword.ts +++ b/src/services/api/routes/v1/forgotPassword.ts @@ -1,13 +1,43 @@ import express from 'express'; import validator from 'validator'; +import hcaptcha from "hcaptcha"; import { getPNIDByEmailAddress, getPNIDByUsername } from '@/database'; import { sendForgotPasswordEmail } from '@/util'; +import { config, disabledFeatures } from "@/config-manager"; import { HydratedPNIDDocument } from '@/types/mongoose/pnid'; const router = express.Router(); router.post('/', async (request: express.Request, response: express.Response): Promise => { - const input = request.body?.input; + const input = request.body?.input; + const hCaptchaResponse = request.body.hCaptchaResponse?.trim(); + + if (!disabledFeatures.captcha) { + if (!hCaptchaResponse || hCaptchaResponse === "") { + response.status(400).json({ + app: "api", + status: 400, + error: "Must fill in captcha", + }); + + return; + } + + const captchaVerify = await hcaptcha.verify( + config.hcaptcha.secret, + hCaptchaResponse + ); + + if (!captchaVerify.success) { + response.status(400).json({ + app: "api", + status: 400, + error: "Captcha verification failed", + }); + + return; + } + } if (!input || input.trim() === '') { response.status(400).json({ From 84b900caaa14ee3d12f63c51d62ea840e5c5bc89 Mon Sep 17 00:00:00 2001 From: Daniloch Date: Fri, 31 May 2024 11:31:32 -0300 Subject: [PATCH 161/219] fix: indentation issues --- src/services/api/routes/v1/forgotPassword.ts | 44 ++++++++++---------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/src/services/api/routes/v1/forgotPassword.ts b/src/services/api/routes/v1/forgotPassword.ts index 26260ba..b6fd50d 100644 --- a/src/services/api/routes/v1/forgotPassword.ts +++ b/src/services/api/routes/v1/forgotPassword.ts @@ -11,33 +11,33 @@ const router = express.Router(); router.post('/', async (request: express.Request, response: express.Response): Promise => { const input = request.body?.input; const hCaptchaResponse = request.body.hCaptchaResponse?.trim(); - + if (!disabledFeatures.captcha) { - if (!hCaptchaResponse || hCaptchaResponse === "") { - response.status(400).json({ - app: "api", - status: 400, - error: "Must fill in captcha", - }); + if (!hCaptchaResponse || hCaptchaResponse === "") { + response.status(400).json({ + app: "api", + status: 400, + error: "Must fill in captcha", + }); - return; - } + return; + } - const captchaVerify = await hcaptcha.verify( - config.hcaptcha.secret, - hCaptchaResponse - ); + const captchaVerify = await hcaptcha.verify( + config.hcaptcha.secret, + hCaptchaResponse + ); - if (!captchaVerify.success) { - response.status(400).json({ - app: "api", - status: 400, - error: "Captcha verification failed", - }); + if (!captchaVerify.success) { + response.status(400).json({ + app: "api", + status: 400, + error: "Captcha verification failed", + }); - return; - } - } + return; + } + } if (!input || input.trim() === '') { response.status(400).json({ From b79e2e0cbf143cf5f838a20980a999b11b019334 Mon Sep 17 00:00:00 2001 From: Daniloch Date: Fri, 31 May 2024 11:33:47 -0300 Subject: [PATCH 162/219] fix: indentation issues (again) --- src/services/api/routes/v1/forgotPassword.ts | 42 ++++++++++---------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/src/services/api/routes/v1/forgotPassword.ts b/src/services/api/routes/v1/forgotPassword.ts index b6fd50d..3330abc 100644 --- a/src/services/api/routes/v1/forgotPassword.ts +++ b/src/services/api/routes/v1/forgotPassword.ts @@ -39,32 +39,32 @@ router.post('/', async (request: express.Request, response: express.Response): P } } - if (!input || input.trim() === '') { - response.status(400).json({ - app: 'api', - status: 400, - error: 'Invalid or missing input' - }); + if (!input || input.trim() === '') { + response.status(400).json({ + app: 'api', + status: 400, + error: 'Invalid or missing input' + }); - return; - } + return; + } - let pnid: HydratedPNIDDocument | null; + let pnid: HydratedPNIDDocument | null; - if (validator.isEmail(input)) { - pnid = await getPNIDByEmailAddress(input); - } else { - pnid = await getPNIDByUsername(input); - } + if (validator.isEmail(input)) { + pnid = await getPNIDByEmailAddress(input); + } else { + pnid = await getPNIDByUsername(input); + } - if (pnid) { - await sendForgotPasswordEmail(pnid); - } + if (pnid) { + await sendForgotPasswordEmail(pnid); + } - response.json({ - app: 'api', - status: 200 - }); + response.json({ + app: 'api', + status: 200 + }); }); export default router; \ No newline at end of file From a02f976891cbb1cbdc83731c01dee13e812f3717 Mon Sep 17 00:00:00 2001 From: Daniloch Date: Fri, 31 May 2024 14:13:29 -0300 Subject: [PATCH 163/219] fix: indentation problems (again and again) --- src/services/api/routes/v1/forgotPassword.ts | 96 ++++++++++---------- 1 file changed, 48 insertions(+), 48 deletions(-) diff --git a/src/services/api/routes/v1/forgotPassword.ts b/src/services/api/routes/v1/forgotPassword.ts index 3330abc..a45f189 100644 --- a/src/services/api/routes/v1/forgotPassword.ts +++ b/src/services/api/routes/v1/forgotPassword.ts @@ -9,62 +9,62 @@ import { HydratedPNIDDocument } from '@/types/mongoose/pnid'; const router = express.Router(); router.post('/', async (request: express.Request, response: express.Response): Promise => { - const input = request.body?.input; - const hCaptchaResponse = request.body.hCaptchaResponse?.trim(); + const input = request.body?.input; + const hCaptchaResponse = request.body.hCaptchaResponse?.trim(); + + if (!disabledFeatures.captcha) { + if (!hCaptchaResponse || hCaptchaResponse === "") { + response.status(400).json({ + app: "api", + status: 400, + error: "Must fill in captcha", + }); + + return; + } - if (!disabledFeatures.captcha) { - if (!hCaptchaResponse || hCaptchaResponse === "") { - response.status(400).json({ - app: "api", - status: 400, - error: "Must fill in captcha", - }); + const captchaVerify = await hcaptcha.verify( + config.hcaptcha.secret, + hCaptchaResponse + ); - return; - } + if (!captchaVerify.success) { + response.status(400).json({ + app: "api", + status: 400, + error: "Captcha verification failed", + }); - const captchaVerify = await hcaptcha.verify( - config.hcaptcha.secret, - hCaptchaResponse - ); + return; + } + } - if (!captchaVerify.success) { - response.status(400).json({ - app: "api", - status: 400, - error: "Captcha verification failed", - }); + if (!input || input.trim() === '') { + response.status(400).json({ + app: 'api', + status: 400, + error: 'Invalid or missing input' + }); + + return; + } - return; - } - } + let pnid: HydratedPNIDDocument | null; - if (!input || input.trim() === '') { - response.status(400).json({ - app: 'api', - status: 400, - error: 'Invalid or missing input' - }); + if (validator.isEmail(input)) { + pnid = await getPNIDByEmailAddress(input); + } else { + pnid = await getPNIDByUsername(input); + } - return; - } + if (pnid) { + await sendForgotPasswordEmail(pnid); + } - let pnid: HydratedPNIDDocument | null; - - if (validator.isEmail(input)) { - pnid = await getPNIDByEmailAddress(input); - } else { - pnid = await getPNIDByUsername(input); - } - - if (pnid) { - await sendForgotPasswordEmail(pnid); - } - - response.json({ - app: 'api', - status: 200 - }); + response.json({ + app: 'api', + status: 200 + }); }); export default router; \ No newline at end of file From 94904be12dbf4328b692c99c9aa8a635cef96492 Mon Sep 17 00:00:00 2001 From: Jemma Date: Sat, 1 Jun 2024 16:29:36 -0500 Subject: [PATCH 164/219] chore: fix indents and address nitpicks --- src/assets/user-info-settings/index.css | 15 +- src/services/nnas/index.ts | 2 +- src/services/nnas/routes/account-settings.ts | 37 +-- src/types/common/config.ts | 2 +- src/views/index.ejs | 252 +++++++++---------- 5 files changed, 155 insertions(+), 153 deletions(-) diff --git a/src/assets/user-info-settings/index.css b/src/assets/user-info-settings/index.css index 7eb8f3b..6be48d3 100644 --- a/src/assets/user-info-settings/index.css +++ b/src/assets/user-info-settings/index.css @@ -49,7 +49,7 @@ input[type="submit"] { min-height: 60px; text-align: center; font-size: 28px; - background: -webkit-gradient(linear, left top, left bottom, from(#FFF), color-stop(0.5, #FFF), color-stop(0.8, #F6F6F6), color-stop(0.95, #F5F5F5), to(#BBB))0 0; + background: -webkit-gradient(linear, left top, left bottom, from(#FFF), color-stop(0.5, #FFF), color-stop(0.8, #F6F6F6), color-stop(0.95, #F5F5F5), to(#BBB)) 0 0; border-radius: 12px; cursor: pointer; border: 0; @@ -117,11 +117,11 @@ div.group > select { cursor: pointer; } - div.group > span { color: #000; - } - div.group > svg { +} + +div.group > svg { fill: #9D6FF3; border: none; height: 40px; @@ -131,7 +131,7 @@ div.group > span { padding: 0px; margin-bottom: -10px; margin-left: 5px; - } +} .account-info { float: left; @@ -247,8 +247,7 @@ div.radio label { -webkit-box-align: center; -webkit-box-pack: center; text-align: center; - background: -webkit-gradient(linear, left top, left bottom, from(#FFF), color-stop(0.5, #FFF), color-stop(0.8, #F6F6F6), color-stop(0.95, #F5F5F5), to(#BBB))0 0; - border-radius: 12px; + background: -webkit-gradient(linear, left top, left bottom, from(#FFF), color-stop(0.5, #FFF), color-stop(0.8, #F6F6F6), color-stop(0.95, #F5F5F5), to(#BBB)) 0 0; cursor: pointer; border: 0; box-shadow: 0 1px 5px rgba(0, 0, 0, 0.4); @@ -289,7 +288,7 @@ input[type="submit"] { height: 85px; line-height: 100px; padding: 0 40px 0 60px; - background: -webkit-gradient(linear, left top, left bottom, from(#FFF), color-stop(0.5, #FFF), to(#E6E6E6))0 0; + background: -webkit-gradient(linear, left top, left bottom, from(#FFF), color-stop(0.5, #FFF), to(#E6E6E6)) 0 0; font-size: 28px; z-index: 20; border-radius: 40px 0 0 0; diff --git a/src/services/nnas/index.ts b/src/services/nnas/index.ts index 7f88efa..0849261 100644 --- a/src/services/nnas/index.ts +++ b/src/services/nnas/index.ts @@ -21,7 +21,7 @@ import settings from '@/services/nnas/routes/account-settings'; // * Router to handle the subdomain restriction const nnas = express.Router(); -// Static routes for the user information app +// * Static routes for the user information app async function setCSSHeader(request: express.Request, response: express.Response, next: express.NextFunction): Promise { response.set('Content-Type', 'text/css'); return next(); diff --git a/src/services/nnas/routes/account-settings.ts b/src/services/nnas/routes/account-settings.ts index c9902dd..58fa669 100644 --- a/src/services/nnas/routes/account-settings.ts +++ b/src/services/nnas/routes/account-settings.ts @@ -16,7 +16,7 @@ import { Country, Region } from '@/types/services/nnas/regions'; import timezones from '@/services/nnas/timezones.json'; import regionsList from '@/services/nnas/regions.json'; -const router: express.Router = express.Router(); +const router = express.Router(); const accountSettingsSchema = z.object({ gender: z.enum(['M', 'F']), @@ -36,13 +36,15 @@ const accountSettingsSchema = z.object({ */ router.get('/ui/profile', async function (request: express.Request, response: express.Response): Promise { const server: HydratedServerDocument | null = await getServerByClientID('3f3928cc6f780638d360f0485cef973f', config.server_environment); - const token: string | undefined = getValueFromHeaders(request.headers, 'x-nintendo-service-token'); + const token = getValueFromHeaders(request.headers, 'x-nintendo-service-token'); + if (!server || !token) { response.sendStatus(504); return; } + const aes_key: string = server?.aes_key; - const decryptedToken: Buffer = decryptToken(Buffer.from(token, 'base64'), aes_key); + const decryptedToken = decryptToken(Buffer.from(token, 'base64'), aes_key); const tokenContents: Token = unpackToken(decryptedToken); @@ -54,8 +56,8 @@ router.get('/ui/profile', async function (request: express.Request, response: ex return; } - const countryCode: string = PNID.country; - const language: string = PNID.language; + const countryCode = PNID.country; + const language = PNID.language; const regionLanguages: RegionLanguages = timezones[countryCode as keyof typeof timezones]; const regionTimezones: RegionTimezones = regionLanguages[language] ? regionLanguages[language] : Object.values(regionLanguages)[0]; @@ -65,9 +67,9 @@ router.get('/ui/profile', async function (request: express.Request, response: ex const miiFaces = ['normal_face', 'smile_open_mouth', 'sorrow', 'surprise_open_mouth', 'wink_left', 'frustrated']; const face = miiFaces[crypto.randomInt(5)]; - const notice: string | undefined = request.query.notice ? request.query.notice.toString() : undefined; + const notice = request.query.notice ? request.query.notice.toString() : undefined; - const accountLevel: string[] = ['Standard', 'Tester', 'Moderator', 'Developer']; + const accountLevel = ['Standard', 'Tester', 'Moderator', 'Developer']; response.render('index.ejs', { PNID, @@ -78,8 +80,7 @@ router.get('/ui/profile', async function (request: express.Request, response: ex regions: region ? region.regions: [], regionsList }); - } - catch (error: any) { + } catch (error: any) { LOG_ERROR(error); response.sendStatus(504); return; @@ -97,7 +98,9 @@ router.get('/mii/:pid/:face', async function (request: express.Request, response response.sendStatus(404); return; } - const miiImage: Buffer = await got(`${config.cdn.base_url}/mii/${request.params.pid}/${request.params.face}.png`).buffer(); + + const miiImage = await got(`${config.cdn.base_url}/mii/${request.params.pid}/${request.params.face}.png`).buffer(); + response.set('Content-Type', 'image/png'); response.send(miiImage); }); @@ -108,15 +111,15 @@ router.get('/mii/:pid/:face', async function (request: express.Request, response */ router.post('/update', async function (request: express.Request, response: express.Response): Promise { const server: HydratedServerDocument | null = await getServerByClientID('3f3928cc6f780638d360f0485cef973f', config.server_environment); - const token: string | undefined = getValueFromHeaders(request.headers, 'x-nintendo-service-token'); + const token = getValueFromHeaders(request.headers, 'x-nintendo-service-token'); + if (!server || !token) { response.sendStatus(504); return; } - const aesKey: string = server?.aes_key; - const decryptedToken: Buffer = decryptToken(Buffer.from(token, 'base64'), aesKey); - + const aesKey = server?.aes_key; + const decryptedToken = decryptToken(Buffer.from(token, 'base64'), aesKey); const tokenContents: Token = unpackToken(decryptedToken); try { @@ -137,7 +140,7 @@ router.post('/update', async function (request: express.Request, response: expre return; } - const timezoneName: string = (person.data.tz_name && !!Object.keys(person.data.tz_name).length) ? person.data.tz_name : pnid.timezone.name; + const timezoneName = (person.data.tz_name && !!Object.keys(person.data.tz_name).length) ? person.data.tz_name : pnid.timezone.name; const regionLanguages: RegionLanguages = timezones[pnid.country as keyof typeof timezones]; const regionTimezones: RegionTimezones = regionLanguages[pnid.language] ? regionLanguages[pnid.language] : Object.values(regionLanguages)[0]; @@ -152,7 +155,7 @@ router.post('/update', async function (request: express.Request, response: expre } const regionObject: Region | undefined = country.regions.find((region) => region.id === person.data.region); - const region: number = regionObject ? regionObject.id : pnid.region; + const region = regionObject ? regionObject.id : pnid.region; if (!timezone) { response.status(404); @@ -169,7 +172,7 @@ router.post('/update', async function (request: express.Request, response: expre pnid.flags.off_device = person.data.off_device_flag; if (person.data.server_selection) { - const environment: string = person.data.server_selection; + const environment = person.data.server_selection; if (environment === 'test' && pnid.access_level < 1) { response.status(400); diff --git a/src/types/common/config.ts b/src/types/common/config.ts index d62fcb1..0b27a16 100644 --- a/src/types/common/config.ts +++ b/src/types/common/config.ts @@ -47,4 +47,4 @@ export interface Config { secret_key: string; }; server_environment: string; -} \ No newline at end of file +} diff --git a/src/views/index.ejs b/src/views/index.ejs index a42d8f3..e306534 100644 --- a/src/views/index.ejs +++ b/src/views/index.ejs @@ -2,96 +2,96 @@ - Account Settings - - - -
-
-

Pretendo Network ID Settings

- - -
- -
-

User Settings

-
-

Birth Date

-

<%= PNID.birthdate %>

-
-
-

Gender

- -
-
-

Country

+ Account Settings + + + + +
+

Pretendo Network ID Settings

+ + +
+ +
+

User Settings

+
+

Birth Date

+

<%= PNID.birthdate %>

+
+
+

Gender

+ +
+
+

Country

-
-
-

Region

- -
-
-

Timezone

- -
-

Email

-
- -
-
- <% if(PNID.email.validated) { %> - Verified - - <% } else { %> - Email Not Verified - - <% } %> -
-

Other Settings

-
-

Server Environment

-
- checked=""<% } %>> - + <% for (let region of regionsList) { %> + + <% } %> + +
+
+

Region

+ +
+
+

Timezone

+ +
+

Email

+
+ +
+
+ <% if(PNID.email.validated) { %> + Verified + + <% } else { %> + Email Not Verified + + <% } %> +
+

Other Settings

+
+

Server Environment

+
+ checked=""<% } %>> + <% if(PNID.access_level >= 1) { %> checked="" <% } %> <% if(PNID.access_level < 1) { %> disabled <% } %> > <% } %> -
-
-
-

Email Notifications

-
- checked=""<% } %>> - - checked=""<% } %>> - -
-
-
-

Non-Nintendo Device Setting

-
- checked=""<% } %>> - - checked=""<% } %>> - -
-
-
- - <% if (notice) { %> - - <% } %> +
+
+
+

Email Notifications

+
+ checked=""<% } %>> + + checked=""<% } %>> + +
+
+
+

Non-Nintendo Device Setting

+
+ checked=""<% } %>> + + checked=""<% } %>> + +
+
+
+ + <% if (notice) { %> + + <% } %> - + \ No newline at end of file From 68bcd1f8a6559d85ddf267660e15e022d4d870a5 Mon Sep 17 00:00:00 2001 From: Jemma Date: Sat, 1 Jun 2024 17:08:32 -0500 Subject: [PATCH 165/219] chore: add newline to end of files --- src/assets/user-info-settings/index.css | 2 +- src/cache.ts | 2 +- src/config-manager.ts | 2 +- src/database.ts | 2 +- src/logger.ts | 2 +- src/nintendo-certificate.ts | 2 +- src/services/api/index.ts | 2 +- src/services/api/routes/index.ts | 2 +- src/services/api/routes/v1/connections.ts | 2 +- src/services/api/routes/v1/email.ts | 2 +- src/services/api/routes/v1/forgotPassword.ts | 2 +- src/services/api/routes/v1/login.ts | 2 +- src/services/api/routes/v1/register.ts | 2 +- src/services/api/routes/v1/resetPassword.ts | 2 +- src/services/api/routes/v1/user.ts | 2 +- src/services/assets/index.ts | 2 +- src/services/conntest/index.ts | 2 +- src/services/datastore/index.ts | 2 +- src/services/datastore/routes/upload.ts | 2 +- src/services/grpc/account/api-key-middleware.ts | 2 +- src/services/grpc/account/exchange-token-for-user-data.ts | 2 +- src/services/grpc/account/get-user-data.ts | 2 +- src/services/grpc/account/update-pnid-permissions.ts | 2 +- src/services/grpc/api/api-key-middleware.ts | 2 +- src/services/grpc/api/authentication-middleware.ts | 2 +- src/services/grpc/api/forgot-password.ts | 2 +- src/services/grpc/api/implementation.ts | 2 +- src/services/grpc/api/login.ts | 2 +- src/services/grpc/api/register.ts | 2 +- src/services/grpc/api/reset-password.ts | 2 +- src/services/grpc/api/set-discord-connection-data.ts | 2 +- src/services/grpc/api/set-stripe-connection-data.ts | 2 +- src/services/grpc/server.ts | 2 +- src/services/local-cdn/index.ts | 2 +- src/services/local-cdn/routes/get.ts | 2 +- src/services/nasc/index.ts | 2 +- src/services/nasc/routes/ac.ts | 2 +- src/services/nnas/index.ts | 2 +- src/services/nnas/routes/admin.ts | 2 +- src/services/nnas/routes/content.ts | 2 +- src/services/nnas/routes/devices.ts | 2 +- src/services/nnas/routes/miis.ts | 2 +- src/services/nnas/routes/oauth.ts | 2 +- src/services/nnas/routes/provider.ts | 2 +- src/services/nnas/routes/support.ts | 2 +- src/types/common/gender-types.ts | 2 +- src/types/common/mailer-options.ts | 2 +- src/types/common/permission-flags.ts | 2 +- src/types/common/safe-qs.ts | 2 +- src/types/common/signature-size.ts | 2 +- src/types/common/token-options.ts | 2 +- src/types/common/token.ts | 2 +- src/types/common/yes-no-bool-string.ts | 2 +- src/types/express-subdomain.d.ts | 2 +- src/types/express.d.ts | 2 +- src/types/image-pixels.d.ts | 2 +- src/types/mii-js.d.ts | 2 +- src/types/mongoose-unique-validator.d.ts | 2 +- src/types/mongoose/device-attribute.ts | 2 +- src/types/mongoose/device.ts | 2 +- src/types/mongoose/nex-account.ts | 2 +- src/types/mongoose/pnid.ts | 2 +- src/types/mongoose/server.ts | 2 +- src/types/services/api/connection-data.ts | 2 +- src/types/services/api/connection-response.ts | 2 +- src/types/services/api/discord-connection-data.ts | 2 +- src/types/services/api/update-user-request.ts | 2 +- src/types/services/nasc/request-params.ts | 2 +- src/types/services/nnas/account-settings.ts | 2 +- src/types/services/nnas/person.ts | 2 +- src/types/services/nnas/pnid-profile.ts | 2 +- src/types/services/nnas/region-languages.ts | 2 +- src/types/services/nnas/region-timezones.ts | 2 +- src/types/tga.d.ts | 2 +- src/util.ts | 2 +- src/views/index.ejs | 2 +- 76 files changed, 76 insertions(+), 76 deletions(-) diff --git a/src/assets/user-info-settings/index.css b/src/assets/user-info-settings/index.css index 6be48d3..09ceb5b 100644 --- a/src/assets/user-info-settings/index.css +++ b/src/assets/user-info-settings/index.css @@ -294,4 +294,4 @@ input[type="submit"] { border-radius: 40px 0 0 0; margin: 0; border: none; -} \ No newline at end of file +} diff --git a/src/cache.ts b/src/cache.ts index e354732..fcd6845 100644 --- a/src/cache.ts +++ b/src/cache.ts @@ -58,4 +58,4 @@ export async function getLocalCDNFile(name: string, encoding?: BufferEncoding): export async function setLocalCDNFile(name: string, value: Buffer): Promise { await setCachedFile(`local_cdn:${name}`, value); -} \ No newline at end of file +} diff --git a/src/config-manager.ts b/src/config-manager.ts index 9f5289b..89a006f 100644 --- a/src/config-manager.ts +++ b/src/config-manager.ts @@ -194,4 +194,4 @@ if (!config.grpc.port) { if (!config.stripe?.secret_key) { LOG_WARN('Failed to find Stripe api key! If a PNID is deleted with an active subscription, the subscription will *NOT* be canceled! Set the PN_ACT_CONFIG_STRIPE_SECRET_KEY environment variable to enable'); -} \ No newline at end of file +} diff --git a/src/database.ts b/src/database.ts index b6d443e..35056f8 100644 --- a/src/database.ts +++ b/src/database.ts @@ -274,4 +274,4 @@ export async function removePNIDConnectionDiscord(pnid: HydratedPNIDDocument): P app: 'api', status: 200 }; -} \ No newline at end of file +} diff --git a/src/logger.ts b/src/logger.ts index d3114ee..c3fdb4d 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -44,4 +44,4 @@ export function LOG_INFO(input: string): void { streams.info.write(`${input}\n`); console.log(`${input}`.cyan.bold); -} \ No newline at end of file +} diff --git a/src/nintendo-certificate.ts b/src/nintendo-certificate.ts index 38e7a1a..6af0b75 100644 --- a/src/nintendo-certificate.ts +++ b/src/nintendo-certificate.ts @@ -227,4 +227,4 @@ class NintendoCertificate { } } -export default NintendoCertificate; \ No newline at end of file +export default NintendoCertificate; diff --git a/src/services/api/index.ts b/src/services/api/index.ts index f699f79..d86cdd8 100644 --- a/src/services/api/index.ts +++ b/src/services/api/index.ts @@ -32,4 +32,4 @@ const router = express.Router(); LOG_INFO('[USER API] Creating \'api\' subdomain'); router.use(subdomain('api', api)); -export default router; \ No newline at end of file +export default router; diff --git a/src/services/api/routes/index.ts b/src/services/api/routes/index.ts index 6f25c45..de8208b 100644 --- a/src/services/api/routes/index.ts +++ b/src/services/api/routes/index.ts @@ -14,4 +14,4 @@ export const V1 = { REGISTER: register_v1, RESET_PASSWORD: resetPassword_v1, USER: user_v1 -}; \ No newline at end of file +}; diff --git a/src/services/api/routes/v1/connections.ts b/src/services/api/routes/v1/connections.ts index 2a0612a..2ddf36d 100644 --- a/src/services/api/routes/v1/connections.ts +++ b/src/services/api/routes/v1/connections.ts @@ -102,4 +102,4 @@ router.delete('/remove/:type', async (request: express.Request, response: expres response.status(result.status).json(result); }); -export default router; \ No newline at end of file +export default router; diff --git a/src/services/api/routes/v1/email.ts b/src/services/api/routes/v1/email.ts index fa306c5..9c70934 100644 --- a/src/services/api/routes/v1/email.ts +++ b/src/services/api/routes/v1/email.ts @@ -45,4 +45,4 @@ router.get('/verify', async (request: express.Request, response: express.Respons response.status(200).send('Email validated. You may close this window'); }); -export default router; \ No newline at end of file +export default router; diff --git a/src/services/api/routes/v1/forgotPassword.ts b/src/services/api/routes/v1/forgotPassword.ts index 58838bb..fb2e25d 100644 --- a/src/services/api/routes/v1/forgotPassword.ts +++ b/src/services/api/routes/v1/forgotPassword.ts @@ -37,4 +37,4 @@ router.post('/', async (request: express.Request, response: express.Response): P }); }); -export default router; \ No newline at end of file +export default router; diff --git a/src/services/api/routes/v1/login.ts b/src/services/api/routes/v1/login.ts index 21a92b6..3017fa1 100644 --- a/src/services/api/routes/v1/login.ts +++ b/src/services/api/routes/v1/login.ts @@ -133,4 +133,4 @@ router.post('/', async (request: express.Request, response: express.Response): P }); }); -export default router; \ No newline at end of file +export default router; diff --git a/src/services/api/routes/v1/register.ts b/src/services/api/routes/v1/register.ts index 2b1928b..2e2b4b5 100644 --- a/src/services/api/routes/v1/register.ts +++ b/src/services/api/routes/v1/register.ts @@ -399,4 +399,4 @@ router.post('/', async (request: express.Request, response: express.Response): P }); }); -export default router; \ No newline at end of file +export default router; diff --git a/src/services/api/routes/v1/resetPassword.ts b/src/services/api/routes/v1/resetPassword.ts index ad401fb..c50688d 100644 --- a/src/services/api/routes/v1/resetPassword.ts +++ b/src/services/api/routes/v1/resetPassword.ts @@ -147,4 +147,4 @@ router.post('/', async (request: express.Request, response: express.Response): P }); }); -export default router; \ No newline at end of file +export default router; diff --git a/src/services/api/routes/v1/user.ts b/src/services/api/routes/v1/user.ts index 0c27e88..a24abd3 100644 --- a/src/services/api/routes/v1/user.ts +++ b/src/services/api/routes/v1/user.ts @@ -225,4 +225,4 @@ router.post('/', async (request: express.Request, response: express.Response): P }); }); -export default router; \ No newline at end of file +export default router; diff --git a/src/services/assets/index.ts b/src/services/assets/index.ts index a20b710..a609d33 100644 --- a/src/services/assets/index.ts +++ b/src/services/assets/index.ts @@ -19,4 +19,4 @@ const router = express.Router(); LOG_INFO('[conntest] Creating \'assets\' subdomain'); router.use(subdomain('assets', assets)); -export default router; \ No newline at end of file +export default router; diff --git a/src/services/conntest/index.ts b/src/services/conntest/index.ts index 47c353e..4d72706 100644 --- a/src/services/conntest/index.ts +++ b/src/services/conntest/index.ts @@ -33,4 +33,4 @@ const router = express.Router(); LOG_INFO('[conntest] Creating \'conntest\' subdomain'); router.use(subdomain('conntest', conntest)); -export default router; \ No newline at end of file +export default router; diff --git a/src/services/datastore/index.ts b/src/services/datastore/index.ts index c8c3d01..6e989fe 100644 --- a/src/services/datastore/index.ts +++ b/src/services/datastore/index.ts @@ -18,4 +18,4 @@ const router = express.Router(); LOG_INFO('[DATASTORE] Creating \'datastore\' subdomain'); router.use(subdomain('datastore', datastore)); -export default router; \ No newline at end of file +export default router; diff --git a/src/services/datastore/routes/upload.ts b/src/services/datastore/routes/upload.ts index 6e3585e..b0f9591 100644 --- a/src/services/datastore/routes/upload.ts +++ b/src/services/datastore/routes/upload.ts @@ -99,4 +99,4 @@ router.post('/upload', multipartParser, async (request: express.Request, respons response.sendStatus(200); }); -export default router; \ No newline at end of file +export default router; diff --git a/src/services/grpc/account/api-key-middleware.ts b/src/services/grpc/account/api-key-middleware.ts index 2a6158d..dde9288 100644 --- a/src/services/grpc/account/api-key-middleware.ts +++ b/src/services/grpc/account/api-key-middleware.ts @@ -12,4 +12,4 @@ export async function* apiKeyMiddleware( } return yield* call.next(call.request, context); -} \ No newline at end of file +} diff --git a/src/services/grpc/account/exchange-token-for-user-data.ts b/src/services/grpc/account/exchange-token-for-user-data.ts index 347c4da..dc24fe6 100644 --- a/src/services/grpc/account/exchange-token-for-user-data.ts +++ b/src/services/grpc/account/exchange-token-for-user-data.ts @@ -59,4 +59,4 @@ export async function exchangeTokenForUserData(request: ExchangeTokenForUserData updatePnidPermissions: pnid.hasPermission(PNID_PERMISSION_FLAGS.UPDATE_PNID_PERMISSIONS) } }; -} \ No newline at end of file +} diff --git a/src/services/grpc/account/get-user-data.ts b/src/services/grpc/account/get-user-data.ts index 80d2704..40931d8 100644 --- a/src/services/grpc/account/get-user-data.ts +++ b/src/services/grpc/account/get-user-data.ts @@ -57,4 +57,4 @@ export async function getUserData(request: GetUserDataRequest): Promise( } return yield* call.next(call.request, context); -} \ No newline at end of file +} diff --git a/src/services/grpc/api/authentication-middleware.ts b/src/services/grpc/api/authentication-middleware.ts index c466c9e..90b39b1 100644 --- a/src/services/grpc/api/authentication-middleware.ts +++ b/src/services/grpc/api/authentication-middleware.ts @@ -52,4 +52,4 @@ export async function* authenticationMiddleware( throw new ServerError(Status.INVALID_ARGUMENT, message); } -} \ No newline at end of file +} diff --git a/src/services/grpc/api/forgot-password.ts b/src/services/grpc/api/forgot-password.ts index 45b9b6e..093e7b0 100644 --- a/src/services/grpc/api/forgot-password.ts +++ b/src/services/grpc/api/forgot-password.ts @@ -26,4 +26,4 @@ export async function forgotPassword(request: ForgotPasswordRequest): Promise { server.with(apiApiKeyMiddleware).with(apiAuthenticationMiddleware).add(APIDefinition, apiServiceImplementation); await server.listen(`0.0.0.0:${config.grpc.port}`); -} \ No newline at end of file +} diff --git a/src/services/local-cdn/index.ts b/src/services/local-cdn/index.ts index 7cd59d3..319d6fc 100644 --- a/src/services/local-cdn/index.ts +++ b/src/services/local-cdn/index.ts @@ -24,4 +24,4 @@ if (disabledFeatures.s3) { LOG_INFO('[LOCAL-CDN] s3 enabled, skipping local CDN'); } -export default router; \ No newline at end of file +export default router; diff --git a/src/services/local-cdn/routes/get.ts b/src/services/local-cdn/routes/get.ts index c98b586..af6e1e8 100644 --- a/src/services/local-cdn/routes/get.ts +++ b/src/services/local-cdn/routes/get.ts @@ -15,4 +15,4 @@ router.get('/*', async (request: express.Request, response: express.Response): P } }); -export default router; \ No newline at end of file +export default router; diff --git a/src/services/nasc/index.ts b/src/services/nasc/index.ts index f24e28f..c45bce4 100644 --- a/src/services/nasc/index.ts +++ b/src/services/nasc/index.ts @@ -24,4 +24,4 @@ const router = express.Router(); LOG_INFO('[NASC] Creating \'nasc\' subdomain'); router.use(subdomain('nasc', nasc)); -export default router; \ No newline at end of file +export default router; diff --git a/src/services/nasc/routes/ac.ts b/src/services/nasc/routes/ac.ts index 1a8c616..3138f46 100644 --- a/src/services/nasc/routes/ac.ts +++ b/src/services/nasc/routes/ac.ts @@ -112,4 +112,4 @@ async function processServiceTokenRequest(server: HydratedServerDocument, pid: n }); } -export default router; \ No newline at end of file +export default router; diff --git a/src/services/nnas/index.ts b/src/services/nnas/index.ts index 0849261..8ce64e2 100644 --- a/src/services/nnas/index.ts +++ b/src/services/nnas/index.ts @@ -68,4 +68,4 @@ router.use(subdomain('account', nnas)); LOG_INFO('[NNAS] Creating \'c.account\' subdomain'); router.use(subdomain('c.account', nnas)); -export default router; \ No newline at end of file +export default router; diff --git a/src/services/nnas/routes/admin.ts b/src/services/nnas/routes/admin.ts index c08c356..5a19e1b 100644 --- a/src/services/nnas/routes/admin.ts +++ b/src/services/nnas/routes/admin.ts @@ -118,4 +118,4 @@ router.get('/time', async (request: express.Request, response: express.Response) }); -export default router; \ No newline at end of file +export default router; diff --git a/src/services/nnas/routes/content.ts b/src/services/nnas/routes/content.ts index a60cc30..eef28eb 100644 --- a/src/services/nnas/routes/content.ts +++ b/src/services/nnas/routes/content.ts @@ -162,4 +162,4 @@ router.get('/time_zones/:countryCode/:language', (request: express.Request, resp }).end()); }); -export default router; \ No newline at end of file +export default router; diff --git a/src/services/nnas/routes/devices.ts b/src/services/nnas/routes/devices.ts index f46e6a4..ecd7b2f 100644 --- a/src/services/nnas/routes/devices.ts +++ b/src/services/nnas/routes/devices.ts @@ -15,4 +15,4 @@ router.get('/@current/status', async (request: express.Request, response: expres }).end()); }); -export default router; \ No newline at end of file +export default router; diff --git a/src/services/nnas/routes/miis.ts b/src/services/nnas/routes/miis.ts index 85487bc..d0c027a 100644 --- a/src/services/nnas/routes/miis.ts +++ b/src/services/nnas/routes/miis.ts @@ -127,4 +127,4 @@ router.get('/', async (request: express.Request, response: express.Response): Pr } }); -export default router; \ No newline at end of file +export default router; diff --git a/src/services/nnas/routes/oauth.ts b/src/services/nnas/routes/oauth.ts index 7d25b75..164b63d 100644 --- a/src/services/nnas/routes/oauth.ts +++ b/src/services/nnas/routes/oauth.ts @@ -172,4 +172,4 @@ router.post('/access_token/generate', deviceCertificateMiddleware, consoleStatus }).end()); }); -export default router; \ No newline at end of file +export default router; diff --git a/src/services/nnas/routes/provider.ts b/src/services/nnas/routes/provider.ts index 1f87e8d..f60b071 100644 --- a/src/services/nnas/routes/provider.ts +++ b/src/services/nnas/routes/provider.ts @@ -239,4 +239,4 @@ router.get('/nex_token/@me', async (request: express.Request, response: express. }).end()); }); -export default router; \ No newline at end of file +export default router; diff --git a/src/services/nnas/routes/support.ts b/src/services/nnas/routes/support.ts index 46522a8..3b73118 100644 --- a/src/services/nnas/routes/support.ts +++ b/src/services/nnas/routes/support.ts @@ -156,4 +156,4 @@ router.get('/forgotten_password/:pid', async (request: express.Request, response response.status(200).send(''); }); -export default router; \ No newline at end of file +export default router; diff --git a/src/types/common/gender-types.ts b/src/types/common/gender-types.ts index 9d4ca82..0cd06a8 100644 --- a/src/types/common/gender-types.ts +++ b/src/types/common/gender-types.ts @@ -1 +1 @@ -export type GenderTypes = 'M' | 'F'; \ No newline at end of file +export type GenderTypes = 'M' | 'F'; diff --git a/src/types/common/mailer-options.ts b/src/types/common/mailer-options.ts index 265b42a..d3999f7 100644 --- a/src/types/common/mailer-options.ts +++ b/src/types/common/mailer-options.ts @@ -13,4 +13,4 @@ export interface MailerOptions { href: string; code: string; }; -} \ No newline at end of file +} diff --git a/src/types/common/permission-flags.ts b/src/types/common/permission-flags.ts index 7c22211..22e5d2e 100644 --- a/src/types/common/permission-flags.ts +++ b/src/types/common/permission-flags.ts @@ -23,4 +23,4 @@ export const PNID_PERMISSION_FLAGS = { UPDATE_PNID_PERMISSIONS: 1n << 20n } as const; -export type PNIDPermissionFlag = (typeof PNID_PERMISSION_FLAGS)[keyof typeof PNID_PERMISSION_FLAGS]; \ No newline at end of file +export type PNIDPermissionFlag = (typeof PNID_PERMISSION_FLAGS)[keyof typeof PNID_PERMISSION_FLAGS]; diff --git a/src/types/common/safe-qs.ts b/src/types/common/safe-qs.ts index a17df29..46acab9 100644 --- a/src/types/common/safe-qs.ts +++ b/src/types/common/safe-qs.ts @@ -1,3 +1,3 @@ export interface SafeQs { [key: string]: string | undefined -} \ No newline at end of file +} diff --git a/src/types/common/signature-size.ts b/src/types/common/signature-size.ts index c9f9bbf..19a3a83 100644 --- a/src/types/common/signature-size.ts +++ b/src/types/common/signature-size.ts @@ -1,4 +1,4 @@ export interface SignatureSize { SIZE: number; PADDING_SIZE: number; -} \ No newline at end of file +} diff --git a/src/types/common/token-options.ts b/src/types/common/token-options.ts index 6dcc85e..114a669 100644 --- a/src/types/common/token-options.ts +++ b/src/types/common/token-options.ts @@ -5,4 +5,4 @@ export interface TokenOptions { access_level?: number; title_id?: bigint; expire_time: bigint; -} \ No newline at end of file +} diff --git a/src/types/common/token.ts b/src/types/common/token.ts index 4c7c558..daf0eef 100644 --- a/src/types/common/token.ts +++ b/src/types/common/token.ts @@ -5,4 +5,4 @@ export interface Token { access_level?: number; title_id?: bigint; expire_time: bigint; -} \ No newline at end of file +} diff --git a/src/types/common/yes-no-bool-string.ts b/src/types/common/yes-no-bool-string.ts index 474aa6e..0088e31 100644 --- a/src/types/common/yes-no-bool-string.ts +++ b/src/types/common/yes-no-bool-string.ts @@ -1 +1 @@ -export type YesNoBoolString = 'Y' | 'N'; \ No newline at end of file +export type YesNoBoolString = 'Y' | 'N'; diff --git a/src/types/express-subdomain.d.ts b/src/types/express-subdomain.d.ts index 933d1f0..9c57184 100644 --- a/src/types/express-subdomain.d.ts +++ b/src/types/express-subdomain.d.ts @@ -13,4 +13,4 @@ declare module 'express-subdomain'{ subdomain: string, fn: Router | ((req: Request, res: Response) => void | any) ): (req: Request, res: Response, next: () => void) => void | typeof fn; -} \ No newline at end of file +} diff --git a/src/types/express.d.ts b/src/types/express.d.ts index d6980f3..335d128 100644 --- a/src/types/express.d.ts +++ b/src/types/express.d.ts @@ -14,4 +14,4 @@ declare global { device?: HydratedDeviceDocument; } } -} \ No newline at end of file +} diff --git a/src/types/image-pixels.d.ts b/src/types/image-pixels.d.ts index a738a3f..f29ece7 100644 --- a/src/types/image-pixels.d.ts +++ b/src/types/image-pixels.d.ts @@ -29,4 +29,4 @@ declare module 'image-pixels' { export default function pixels( src: ImageSource | Promise ): ImageData; -} \ No newline at end of file +} diff --git a/src/types/mii-js.d.ts b/src/types/mii-js.d.ts index 55cc57f..65ffca6 100644 --- a/src/types/mii-js.d.ts +++ b/src/types/mii-js.d.ts @@ -97,4 +97,4 @@ type Mii = { studioUrl: (options: MiiStudioURLOptions) => string; encode: () => Buffer; validate: () => void; -}; \ No newline at end of file +}; diff --git a/src/types/mongoose-unique-validator.d.ts b/src/types/mongoose-unique-validator.d.ts index 0909028..f5b788f 100644 --- a/src/types/mongoose-unique-validator.d.ts +++ b/src/types/mongoose-unique-validator.d.ts @@ -1 +1 @@ -declare module 'mongoose-unique-validator'; \ No newline at end of file +declare module 'mongoose-unique-validator'; diff --git a/src/types/mongoose/device-attribute.ts b/src/types/mongoose/device-attribute.ts index e9eb87f..c42289e 100644 --- a/src/types/mongoose/device-attribute.ts +++ b/src/types/mongoose/device-attribute.ts @@ -10,4 +10,4 @@ export interface IDeviceAttributeMethods {} interface IDeviceAttributeQueryHelpers {} -export interface DeviceAttributeModel extends Model {} \ No newline at end of file +export interface DeviceAttributeModel extends Model {} diff --git a/src/types/mongoose/device.ts b/src/types/mongoose/device.ts index dfc9d89..d45016b 100644 --- a/src/types/mongoose/device.ts +++ b/src/types/mongoose/device.ts @@ -30,4 +30,4 @@ interface IDeviceQueryHelpers {} export interface DeviceModel extends Model {} -export type HydratedDeviceDocument = HydratedDocument \ No newline at end of file +export type HydratedDeviceDocument = HydratedDocument diff --git a/src/types/mongoose/nex-account.ts b/src/types/mongoose/nex-account.ts index e7fc997..dc9e26f 100644 --- a/src/types/mongoose/nex-account.ts +++ b/src/types/mongoose/nex-account.ts @@ -27,4 +27,4 @@ interface INEXAccountQueryHelpers {} export interface NEXAccountModel extends Model {} -export type HydratedNEXAccountDocument = HydratedDocument \ No newline at end of file +export type HydratedNEXAccountDocument = HydratedDocument diff --git a/src/types/mongoose/pnid.ts b/src/types/mongoose/pnid.ts index 3252c44..a6a7737 100644 --- a/src/types/mongoose/pnid.ts +++ b/src/types/mongoose/pnid.ts @@ -91,4 +91,4 @@ interface IPNIDQueryHelpers {} export interface PNIDModel extends Model {} -export type HydratedPNIDDocument = HydratedDocument \ No newline at end of file +export type HydratedPNIDDocument = HydratedDocument diff --git a/src/types/mongoose/server.ts b/src/types/mongoose/server.ts index a7c8f6e..02e8e85 100644 --- a/src/types/mongoose/server.ts +++ b/src/types/mongoose/server.ts @@ -20,4 +20,4 @@ interface IServerQueryHelpers {} export interface ServerModel extends Model {} -export type HydratedServerDocument = HydratedDocument \ No newline at end of file +export type HydratedServerDocument = HydratedDocument diff --git a/src/types/services/api/connection-data.ts b/src/types/services/api/connection-data.ts index 54fe992..6f1ab8a 100644 --- a/src/types/services/api/connection-data.ts +++ b/src/types/services/api/connection-data.ts @@ -1,4 +1,4 @@ import { DiscordConnectionData } from '@/types/services/api/discord-connection-data'; // TODO - This will be a union of all ConnectionData types when more connections are added -export type ConnectionData = DiscordConnectionData; \ No newline at end of file +export type ConnectionData = DiscordConnectionData; diff --git a/src/types/services/api/connection-response.ts b/src/types/services/api/connection-response.ts index 6baa9b0..80e2cab 100644 --- a/src/types/services/api/connection-response.ts +++ b/src/types/services/api/connection-response.ts @@ -2,4 +2,4 @@ export interface ConnectionResponse { app: string; status: number; error?: string; -} \ No newline at end of file +} diff --git a/src/types/services/api/discord-connection-data.ts b/src/types/services/api/discord-connection-data.ts index 0ee459a..8d08c84 100644 --- a/src/types/services/api/discord-connection-data.ts +++ b/src/types/services/api/discord-connection-data.ts @@ -1,3 +1,3 @@ export interface DiscordConnectionData { id: string; -} \ No newline at end of file +} diff --git a/src/types/services/api/update-user-request.ts b/src/types/services/api/update-user-request.ts index a525e98..f743d1e 100644 --- a/src/types/services/api/update-user-request.ts +++ b/src/types/services/api/update-user-request.ts @@ -4,4 +4,4 @@ export interface UpdateUserRequest { primary: string; data: string; } -} \ No newline at end of file +} diff --git a/src/types/services/nasc/request-params.ts b/src/types/services/nasc/request-params.ts index aafcda6..76bfd11 100644 --- a/src/types/services/nasc/request-params.ts +++ b/src/types/services/nasc/request-params.ts @@ -8,4 +8,4 @@ export interface NASCRequestParams { userid?: string; uidhmac?: string; passwd?: string; -} \ No newline at end of file +} diff --git a/src/types/services/nnas/account-settings.ts b/src/types/services/nnas/account-settings.ts index 8e7e397..167a08d 100644 --- a/src/types/services/nnas/account-settings.ts +++ b/src/types/services/nnas/account-settings.ts @@ -8,4 +8,4 @@ export interface AccountSettings { server_selection: string; marketing_flag: boolean; off_device_flag: boolean; -} \ No newline at end of file +} diff --git a/src/types/services/nnas/person.ts b/src/types/services/nnas/person.ts index 00958e5..066a546 100644 --- a/src/types/services/nnas/person.ts +++ b/src/types/services/nnas/person.ts @@ -41,4 +41,4 @@ export interface Person { device_attribute: IDeviceAttribute[] }; off_device_flag: YesNoBoolString; -} \ No newline at end of file +} diff --git a/src/types/services/nnas/pnid-profile.ts b/src/types/services/nnas/pnid-profile.ts index 70adea5..85a20e3 100644 --- a/src/types/services/nnas/pnid-profile.ts +++ b/src/types/services/nnas/pnid-profile.ts @@ -50,4 +50,4 @@ export interface PNIDProfile { tz_name: string; user_id: string; utc_offset: number; -} \ No newline at end of file +} diff --git a/src/types/services/nnas/region-languages.ts b/src/types/services/nnas/region-languages.ts index fd4bbf9..515dc4f 100644 --- a/src/types/services/nnas/region-languages.ts +++ b/src/types/services/nnas/region-languages.ts @@ -2,4 +2,4 @@ import { RegionTimezones } from '@/types/services/nnas/region-timezones'; export interface RegionLanguages { [myKey: string]: RegionTimezones -} \ No newline at end of file +} diff --git a/src/types/services/nnas/region-timezones.ts b/src/types/services/nnas/region-timezones.ts index c904aaa..33e5ca6 100644 --- a/src/types/services/nnas/region-timezones.ts +++ b/src/types/services/nnas/region-timezones.ts @@ -6,4 +6,4 @@ export interface RegionTimezone { order: string; } -export type RegionTimezones = RegionTimezone[]; \ No newline at end of file +export type RegionTimezones = RegionTimezone[]; diff --git a/src/types/tga.d.ts b/src/types/tga.d.ts index 16eb4a2..365a31e 100644 --- a/src/types/tga.d.ts +++ b/src/types/tga.d.ts @@ -55,4 +55,4 @@ declare module 'tga' { public pixels: Uint8Array; } -} \ No newline at end of file +} diff --git a/src/util.ts b/src/util.ts index b59aa15..64e46d2 100644 --- a/src/util.ts +++ b/src/util.ts @@ -317,4 +317,4 @@ export function getValueFromHeaders(headers: IncomingHttpHeaders, key: string): export function mapToObject(map: Map): object { return Object.fromEntries(Array.from(map.entries(), ([ k, v ]) => v instanceof Map ? [ k, mapToObject(v) ] : [ k, v ])); -} \ No newline at end of file +} diff --git a/src/views/index.ejs b/src/views/index.ejs index e306534..ef35723 100644 --- a/src/views/index.ejs +++ b/src/views/index.ejs @@ -155,4 +155,4 @@ - \ No newline at end of file + From c5658dc6b313d54228c122d60fcfb791d4b29b19 Mon Sep 17 00:00:00 2001 From: Jemma Date: Sat, 1 Jun 2024 21:35:25 -0500 Subject: [PATCH 166/219] feat: allow updating birthday in user account settings --- src/assets/user-info-settings/index.css | 44 +++++++++------- src/services/nnas/routes/account-settings.ts | 16 ++++-- src/types/services/nnas/account-settings.ts | 1 + src/views/index.ejs | 53 ++++++++++++++++---- 4 files changed, 84 insertions(+), 30 deletions(-) diff --git a/src/assets/user-info-settings/index.css b/src/assets/user-info-settings/index.css index 09ceb5b..cb95c0d 100644 --- a/src/assets/user-info-settings/index.css +++ b/src/assets/user-info-settings/index.css @@ -20,7 +20,6 @@ button:active, header { position: relative; - background: #37A985; color: #FFF; height: 70px; line-height: 70px; @@ -42,8 +41,8 @@ input[type="submit"] { font-family: Poppins, Arial, Helvetica, sans-serif; color: #45297A; min-width: 200px; - box-sizing: content-box; -webkit-box-sizing: content-box; + box-sizing: content-box; -webkit-box-align: center; -webkit-box-pack: center; min-height: 60px; @@ -58,7 +57,7 @@ input[type="submit"] { } .body-content { - margin: 30px 60px 0 200px; + margin: 30px 60px 0 220px; } div.group h2 { @@ -82,6 +81,8 @@ div.group > * { div.group > p.content, div.group > input[type="text"], +div.group > input[type="date"], +div.group > textarea, div.group > select { background: #FFF; color: #6A6C75; @@ -93,17 +94,18 @@ div.group > select { width: 100%; height: 58px; box-sizing: border-box; - appearance: none; - -webkit-appearance: none; -moz-appearance: none; + -webkit-appearance: none; + appearance: none; text-indent: 1px; text-overflow: ''; line-height: 38px; - user-select: none; + resize: none; } -div.group > input[type="text"] { - color: #000; +div.group > input[type="text"], +div.group > input[type="date"] { + color: #9D6FF3; } div.group > select { @@ -126,20 +128,22 @@ div.group > svg { border: none; height: 40px; width: 40px; - box-sizing: border-box; - user-select: none; padding: 0px; margin-bottom: -10px; margin-left: 5px; + -webkit-box-sizing: border-box; + -webkit-user-select: none; + box-sizing: border-box; + user-select: none; } .account-info { float: left; - width: 200px; - box-sizing: content-box; + width: 220px; -webkit-box-sizing: content-box; -webkit-box-align: center; -webkit-box-pack: center; + box-sizing: content-box; } .account-info > img { @@ -149,7 +153,7 @@ div.group > svg { overflow: hidden; border-radius: 100%; background: #E4DBF2; - margin: 30px 30px 0 35px; + margin: 30px 30px 0 46px; box-shadow: 0-1px 2px rgba(0, 0, 0, 0.4); } @@ -208,7 +212,7 @@ div.group > svg { } .account-info .tier-name { - margin: 12px auto; + margin: 5px auto; line-height: 1.2em; border-radius: 1.2em; border-width: 2px; @@ -233,7 +237,7 @@ div.radio { .server-selection input { opacity: 0; position: absolute; - width: 200px; + width: 130px; height: 70px; cursor: pointer; } @@ -241,9 +245,10 @@ div.radio { div.radio label { display: inline-block; color: #6A6C75; - min-width: 200px; - box-sizing: content-box; + padding: 0 10px; + min-width: 110px; -webkit-box-sizing: content-box; + box-sizing: content-box; -webkit-box-align: center; -webkit-box-pack: center; text-align: center; @@ -254,6 +259,11 @@ div.radio label { margin: 5px; font-weight: normal; font-size: 15px; + -o-border-radius: 10px; + -webkit-border-radius: 10px; + -moz-border-radius: 10px; + -ms-border-radius: 10px; + border-radius: 10px; } div.radio label h2 { diff --git a/src/services/nnas/routes/account-settings.ts b/src/services/nnas/routes/account-settings.ts index 58fa669..dc80fb2 100644 --- a/src/services/nnas/routes/account-settings.ts +++ b/src/services/nnas/routes/account-settings.ts @@ -19,6 +19,7 @@ import regionsList from '@/services/nnas/regions.json'; const router = express.Router(); const accountSettingsSchema = z.object({ + birthdate: z.string().regex(/^\d{4}-\d{2}-\d{2}$/), gender: z.enum(['M', 'F']), tz_name: z.string(), region: z.coerce.number(), @@ -99,10 +100,18 @@ router.get('/mii/:pid/:face', async function (request: express.Request, response return; } - const miiImage = await got(`${config.cdn.base_url}/mii/${request.params.pid}/${request.params.face}.png`).buffer(); + try { + const url = `${config.cdn.base_url}/mii/${request.params.pid}/${request.params.face}.png`; + console.log(url); + const miiImage = await got(url).buffer(); - response.set('Content-Type', 'image/png'); - response.send(miiImage); + response.set('Content-Type', 'image/png'); + response.send(miiImage); + } catch (error: any) { + LOG_ERROR(error); + response.sendStatus(404); + return; + } }); /** @@ -163,6 +172,7 @@ router.post('/update', async function (request: express.Request, response: expre return; } + pnid.birthdate = person.data.birthdate; pnid.gender = person.data.gender; pnid.region = region; pnid.country = person.data.country; diff --git a/src/types/services/nnas/account-settings.ts b/src/types/services/nnas/account-settings.ts index 167a08d..7ec832a 100644 --- a/src/types/services/nnas/account-settings.ts +++ b/src/types/services/nnas/account-settings.ts @@ -1,6 +1,7 @@ import { GenderTypes } from '@/types/common/gender-types'; export interface AccountSettings { + birthdate: string; gender: GenderTypes; tz_name: string; region: number; diff --git a/src/views/index.ejs b/src/views/index.ejs index ef35723..16e0795 100644 --- a/src/views/index.ejs +++ b/src/views/index.ejs @@ -30,7 +30,7 @@

User Settings

Birth Date

-

<%= PNID.birthdate %>

+

Gender

@@ -41,7 +41,7 @@

Country

- <% for (let region of regionsList) { %> <% } %> @@ -90,7 +90,7 @@ -

Production

+

Prod

<% if(PNID.access_level >= 1) { %> checked="" <% } %> <% if(PNID.access_level < 1) { %> disabled <% } %> > @@ -104,8 +104,11 @@ <% if(PNID.access_level === 3) { %> checked="" <% } %> <% if(PNID.access_level < 3) { %> disabled <% } %> > @@ -119,7 +122,7 @@ - checked=""<% } %>> + checked=""<% } %>> @@ -146,12 +149,42 @@ <% } %> From bfc0210d7cc3709ff01a463c80282083fe6e21e1 Mon Sep 17 00:00:00 2001 From: Daniloch Date: Sun, 2 Jun 2024 05:54:03 -0300 Subject: [PATCH 167/219] fix: indentation and quotes --- src/services/api/routes/v1/forgotPassword.ts | 72 ++++++++++---------- 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/src/services/api/routes/v1/forgotPassword.ts b/src/services/api/routes/v1/forgotPassword.ts index a45f189..99f127d 100644 --- a/src/services/api/routes/v1/forgotPassword.ts +++ b/src/services/api/routes/v1/forgotPassword.ts @@ -1,9 +1,9 @@ import express from 'express'; import validator from 'validator'; -import hcaptcha from "hcaptcha"; +import hcaptcha from 'hcaptcha'; import { getPNIDByEmailAddress, getPNIDByUsername } from '@/database'; import { sendForgotPasswordEmail } from '@/util'; -import { config, disabledFeatures } from "@/config-manager"; +import { config, disabledFeatures } from '@/config-manager'; import { HydratedPNIDDocument } from '@/types/mongoose/pnid'; const router = express.Router(); @@ -11,59 +11,59 @@ const router = express.Router(); router.post('/', async (request: express.Request, response: express.Response): Promise => { const input = request.body?.input; const hCaptchaResponse = request.body.hCaptchaResponse?.trim(); - + if (!disabledFeatures.captcha) { - if (!hCaptchaResponse || hCaptchaResponse === "") { - response.status(400).json({ - app: "api", - status: 400, - error: "Must fill in captcha", - }); - - return; - } + if (!hCaptchaResponse || hCaptchaResponse === '') { + response.status(400).json({ + app: 'api', + status: 400, + error: 'Must fill in captcha', + }); - const captchaVerify = await hcaptcha.verify( - config.hcaptcha.secret, - hCaptchaResponse - ); + return; + } - if (!captchaVerify.success) { - response.status(400).json({ - app: "api", - status: 400, - error: "Captcha verification failed", - }); + const captchaVerify = await hcaptcha.verify( + config.hcaptcha.secret, + hCaptchaResponse + ); - return; - } + if (!captchaVerify.success) { + response.status(400).json({ + app: 'api', + status: 400, + error: 'Captcha verification failed', + }); + + return; + } } if (!input || input.trim() === '') { - response.status(400).json({ - app: 'api', - status: 400, - error: 'Invalid or missing input' - }); - - return; + response.status(400).json({ + app: 'api', + status: 400, + error: 'Invalid or missing input' + }); + + return; } let pnid: HydratedPNIDDocument | null; if (validator.isEmail(input)) { - pnid = await getPNIDByEmailAddress(input); + pnid = await getPNIDByEmailAddress(input); } else { - pnid = await getPNIDByUsername(input); + pnid = await getPNIDByUsername(input); } if (pnid) { - await sendForgotPasswordEmail(pnid); + await sendForgotPasswordEmail(pnid); } response.json({ - app: 'api', - status: 200 + app: 'api', + status: 200 }); }); From 410f8c142134dcbb818a8d8efad1555cf024032e Mon Sep 17 00:00:00 2001 From: Jemma Date: Sun, 2 Jun 2024 18:42:24 -0500 Subject: [PATCH 168/219] chore: update links to match new error code format --- src/views/index.ejs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/views/index.ejs b/src/views/index.ejs index 16e0795..d5b5445 100644 --- a/src/views/index.ejs +++ b/src/views/index.ejs @@ -153,13 +153,13 @@ function countryChangeWarning() { if(seenCountry) return; seenCountry = true; - wiiuDialog.alert('Warning: Changing your country may cause unexpected results. You may need to re-link your PNID for the changes to apply. \n\nFor more information, see:\nhttps://preten.do/PAS-727-0001', 'I Understand'); + wiiuDialog.alert('Warning: Changing your country may cause unexpected results. You may need to re-link your PNID for the changes to apply. \n\nFor more information, see:\nhttps://preten.do/727-0001', 'I Understand'); } function birthdayChangeWarning() { if(seenBirthday) return; seenBirthday = true; - wiiuDialog.alert('Warning: Changing your birthday may cause unexpected results. You may need to re-link your PNID for the changes to apply. \n\nFor more information, see:\nhttps://preten.do/PAS-727-0002', 'I Understand'); + wiiuDialog.alert('Warning: Changing your birthday may cause unexpected results. You may need to re-link your PNID for the changes to apply. \n\nFor more information, see:\nhttps://preten.do/727-0002', 'I Understand'); } function dateValidation(element) { From f7a71d7b6d68e2007777968850c708496d27d132 Mon Sep 17 00:00:00 2001 From: Jemma Date: Tue, 4 Jun 2024 21:12:06 -0500 Subject: [PATCH 169/219] fix: check that access level is valid before allowing updates to server selection --- src/services/nnas/routes/account-settings.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/nnas/routes/account-settings.ts b/src/services/nnas/routes/account-settings.ts index dc80fb2..9cd74f1 100644 --- a/src/services/nnas/routes/account-settings.ts +++ b/src/services/nnas/routes/account-settings.ts @@ -181,7 +181,7 @@ router.post('/update', async function (request: express.Request, response: expre pnid.flags.marketing = person.data.marketing_flag; pnid.flags.off_device = person.data.off_device_flag; - if (person.data.server_selection) { + if (person.data.server_selection && pnid.access_level > 0 && pnid.access_level < 4) { const environment = person.data.server_selection; if (environment === 'test' && pnid.access_level < 1) { From 97b594cfeedc2e730086ce0e2eb96403cb5d031c Mon Sep 17 00:00:00 2001 From: Jemma Date: Tue, 4 Jun 2024 21:14:30 -0500 Subject: [PATCH 170/219] chore: warn that server default environment is prod --- src/config-manager.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config-manager.ts b/src/config-manager.ts index 89a006f..8beae9e 100644 --- a/src/config-manager.ts +++ b/src/config-manager.ts @@ -150,7 +150,7 @@ if (!config.s3.secret) { } if (!config.server_environment) { - LOG_WARN('Failed to find server environment. To change the environment, set the PN_ACT_CONFIG_SERVER_ENVIRONMENT environment variable'); + LOG_WARN('Failed to find server environment. To change the environment, set the PN_ACT_CONFIG_SERVER_ENVIRONMENT environment variable. Defaulting to prod'); config.server_environment = 'prod'; } From bafb22ce8bf7956a22504333ac74120bb7998118 Mon Sep 17 00:00:00 2001 From: Jemma Date: Thu, 6 Jun 2024 18:37:52 -0500 Subject: [PATCH 171/219] Revert "chore: add newline to end of files" This reverts commit 68bcd1f8a6559d85ddf267660e15e022d4d870a5. --- src/assets/user-info-settings/index.css | 2 +- src/cache.ts | 2 +- src/config-manager.ts | 2 +- src/database.ts | 2 +- src/logger.ts | 2 +- src/nintendo-certificate.ts | 2 +- src/services/api/index.ts | 2 +- src/services/api/routes/index.ts | 2 +- src/services/api/routes/v1/connections.ts | 2 +- src/services/api/routes/v1/email.ts | 2 +- src/services/api/routes/v1/forgotPassword.ts | 2 +- src/services/api/routes/v1/login.ts | 2 +- src/services/api/routes/v1/register.ts | 2 +- src/services/api/routes/v1/resetPassword.ts | 2 +- src/services/api/routes/v1/user.ts | 2 +- src/services/assets/index.ts | 2 +- src/services/conntest/index.ts | 2 +- src/services/datastore/index.ts | 2 +- src/services/datastore/routes/upload.ts | 2 +- src/services/grpc/account/api-key-middleware.ts | 2 +- src/services/grpc/account/exchange-token-for-user-data.ts | 2 +- src/services/grpc/account/get-user-data.ts | 2 +- src/services/grpc/account/update-pnid-permissions.ts | 2 +- src/services/grpc/api/api-key-middleware.ts | 2 +- src/services/grpc/api/authentication-middleware.ts | 2 +- src/services/grpc/api/forgot-password.ts | 2 +- src/services/grpc/api/implementation.ts | 2 +- src/services/grpc/api/login.ts | 2 +- src/services/grpc/api/register.ts | 2 +- src/services/grpc/api/reset-password.ts | 2 +- src/services/grpc/api/set-discord-connection-data.ts | 2 +- src/services/grpc/api/set-stripe-connection-data.ts | 2 +- src/services/grpc/server.ts | 2 +- src/services/local-cdn/index.ts | 2 +- src/services/local-cdn/routes/get.ts | 2 +- src/services/nasc/index.ts | 2 +- src/services/nasc/routes/ac.ts | 2 +- src/services/nnas/index.ts | 2 +- src/services/nnas/routes/admin.ts | 2 +- src/services/nnas/routes/content.ts | 2 +- src/services/nnas/routes/devices.ts | 2 +- src/services/nnas/routes/miis.ts | 2 +- src/services/nnas/routes/oauth.ts | 2 +- src/services/nnas/routes/provider.ts | 2 +- src/services/nnas/routes/support.ts | 2 +- src/types/common/gender-types.ts | 2 +- src/types/common/mailer-options.ts | 2 +- src/types/common/permission-flags.ts | 2 +- src/types/common/safe-qs.ts | 2 +- src/types/common/signature-size.ts | 2 +- src/types/common/token-options.ts | 2 +- src/types/common/token.ts | 2 +- src/types/common/yes-no-bool-string.ts | 2 +- src/types/express-subdomain.d.ts | 2 +- src/types/express.d.ts | 2 +- src/types/image-pixels.d.ts | 2 +- src/types/mii-js.d.ts | 2 +- src/types/mongoose-unique-validator.d.ts | 2 +- src/types/mongoose/device-attribute.ts | 2 +- src/types/mongoose/device.ts | 2 +- src/types/mongoose/nex-account.ts | 2 +- src/types/mongoose/pnid.ts | 2 +- src/types/mongoose/server.ts | 2 +- src/types/services/api/connection-data.ts | 2 +- src/types/services/api/connection-response.ts | 2 +- src/types/services/api/discord-connection-data.ts | 2 +- src/types/services/api/update-user-request.ts | 2 +- src/types/services/nasc/request-params.ts | 2 +- src/types/services/nnas/account-settings.ts | 2 +- src/types/services/nnas/person.ts | 2 +- src/types/services/nnas/pnid-profile.ts | 2 +- src/types/services/nnas/region-languages.ts | 2 +- src/types/services/nnas/region-timezones.ts | 2 +- src/types/tga.d.ts | 2 +- src/util.ts | 2 +- src/views/index.ejs | 2 +- 76 files changed, 76 insertions(+), 76 deletions(-) diff --git a/src/assets/user-info-settings/index.css b/src/assets/user-info-settings/index.css index cb95c0d..9d9440c 100644 --- a/src/assets/user-info-settings/index.css +++ b/src/assets/user-info-settings/index.css @@ -304,4 +304,4 @@ input[type="submit"] { border-radius: 40px 0 0 0; margin: 0; border: none; -} +} \ No newline at end of file diff --git a/src/cache.ts b/src/cache.ts index fcd6845..e354732 100644 --- a/src/cache.ts +++ b/src/cache.ts @@ -58,4 +58,4 @@ export async function getLocalCDNFile(name: string, encoding?: BufferEncoding): export async function setLocalCDNFile(name: string, value: Buffer): Promise { await setCachedFile(`local_cdn:${name}`, value); -} +} \ No newline at end of file diff --git a/src/config-manager.ts b/src/config-manager.ts index 8beae9e..28a081c 100644 --- a/src/config-manager.ts +++ b/src/config-manager.ts @@ -194,4 +194,4 @@ if (!config.grpc.port) { if (!config.stripe?.secret_key) { LOG_WARN('Failed to find Stripe api key! If a PNID is deleted with an active subscription, the subscription will *NOT* be canceled! Set the PN_ACT_CONFIG_STRIPE_SECRET_KEY environment variable to enable'); -} +} \ No newline at end of file diff --git a/src/database.ts b/src/database.ts index 35056f8..b6d443e 100644 --- a/src/database.ts +++ b/src/database.ts @@ -274,4 +274,4 @@ export async function removePNIDConnectionDiscord(pnid: HydratedPNIDDocument): P app: 'api', status: 200 }; -} +} \ No newline at end of file diff --git a/src/logger.ts b/src/logger.ts index c3fdb4d..d3114ee 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -44,4 +44,4 @@ export function LOG_INFO(input: string): void { streams.info.write(`${input}\n`); console.log(`${input}`.cyan.bold); -} +} \ No newline at end of file diff --git a/src/nintendo-certificate.ts b/src/nintendo-certificate.ts index 6af0b75..38e7a1a 100644 --- a/src/nintendo-certificate.ts +++ b/src/nintendo-certificate.ts @@ -227,4 +227,4 @@ class NintendoCertificate { } } -export default NintendoCertificate; +export default NintendoCertificate; \ No newline at end of file diff --git a/src/services/api/index.ts b/src/services/api/index.ts index d86cdd8..f699f79 100644 --- a/src/services/api/index.ts +++ b/src/services/api/index.ts @@ -32,4 +32,4 @@ const router = express.Router(); LOG_INFO('[USER API] Creating \'api\' subdomain'); router.use(subdomain('api', api)); -export default router; +export default router; \ No newline at end of file diff --git a/src/services/api/routes/index.ts b/src/services/api/routes/index.ts index de8208b..6f25c45 100644 --- a/src/services/api/routes/index.ts +++ b/src/services/api/routes/index.ts @@ -14,4 +14,4 @@ export const V1 = { REGISTER: register_v1, RESET_PASSWORD: resetPassword_v1, USER: user_v1 -}; +}; \ No newline at end of file diff --git a/src/services/api/routes/v1/connections.ts b/src/services/api/routes/v1/connections.ts index 2ddf36d..2a0612a 100644 --- a/src/services/api/routes/v1/connections.ts +++ b/src/services/api/routes/v1/connections.ts @@ -102,4 +102,4 @@ router.delete('/remove/:type', async (request: express.Request, response: expres response.status(result.status).json(result); }); -export default router; +export default router; \ No newline at end of file diff --git a/src/services/api/routes/v1/email.ts b/src/services/api/routes/v1/email.ts index 9c70934..fa306c5 100644 --- a/src/services/api/routes/v1/email.ts +++ b/src/services/api/routes/v1/email.ts @@ -45,4 +45,4 @@ router.get('/verify', async (request: express.Request, response: express.Respons response.status(200).send('Email validated. You may close this window'); }); -export default router; +export default router; \ No newline at end of file diff --git a/src/services/api/routes/v1/forgotPassword.ts b/src/services/api/routes/v1/forgotPassword.ts index fb2e25d..58838bb 100644 --- a/src/services/api/routes/v1/forgotPassword.ts +++ b/src/services/api/routes/v1/forgotPassword.ts @@ -37,4 +37,4 @@ router.post('/', async (request: express.Request, response: express.Response): P }); }); -export default router; +export default router; \ No newline at end of file diff --git a/src/services/api/routes/v1/login.ts b/src/services/api/routes/v1/login.ts index 3017fa1..21a92b6 100644 --- a/src/services/api/routes/v1/login.ts +++ b/src/services/api/routes/v1/login.ts @@ -133,4 +133,4 @@ router.post('/', async (request: express.Request, response: express.Response): P }); }); -export default router; +export default router; \ No newline at end of file diff --git a/src/services/api/routes/v1/register.ts b/src/services/api/routes/v1/register.ts index 2e2b4b5..2b1928b 100644 --- a/src/services/api/routes/v1/register.ts +++ b/src/services/api/routes/v1/register.ts @@ -399,4 +399,4 @@ router.post('/', async (request: express.Request, response: express.Response): P }); }); -export default router; +export default router; \ No newline at end of file diff --git a/src/services/api/routes/v1/resetPassword.ts b/src/services/api/routes/v1/resetPassword.ts index c50688d..ad401fb 100644 --- a/src/services/api/routes/v1/resetPassword.ts +++ b/src/services/api/routes/v1/resetPassword.ts @@ -147,4 +147,4 @@ router.post('/', async (request: express.Request, response: express.Response): P }); }); -export default router; +export default router; \ No newline at end of file diff --git a/src/services/api/routes/v1/user.ts b/src/services/api/routes/v1/user.ts index a24abd3..0c27e88 100644 --- a/src/services/api/routes/v1/user.ts +++ b/src/services/api/routes/v1/user.ts @@ -225,4 +225,4 @@ router.post('/', async (request: express.Request, response: express.Response): P }); }); -export default router; +export default router; \ No newline at end of file diff --git a/src/services/assets/index.ts b/src/services/assets/index.ts index a609d33..a20b710 100644 --- a/src/services/assets/index.ts +++ b/src/services/assets/index.ts @@ -19,4 +19,4 @@ const router = express.Router(); LOG_INFO('[conntest] Creating \'assets\' subdomain'); router.use(subdomain('assets', assets)); -export default router; +export default router; \ No newline at end of file diff --git a/src/services/conntest/index.ts b/src/services/conntest/index.ts index 4d72706..47c353e 100644 --- a/src/services/conntest/index.ts +++ b/src/services/conntest/index.ts @@ -33,4 +33,4 @@ const router = express.Router(); LOG_INFO('[conntest] Creating \'conntest\' subdomain'); router.use(subdomain('conntest', conntest)); -export default router; +export default router; \ No newline at end of file diff --git a/src/services/datastore/index.ts b/src/services/datastore/index.ts index 6e989fe..c8c3d01 100644 --- a/src/services/datastore/index.ts +++ b/src/services/datastore/index.ts @@ -18,4 +18,4 @@ const router = express.Router(); LOG_INFO('[DATASTORE] Creating \'datastore\' subdomain'); router.use(subdomain('datastore', datastore)); -export default router; +export default router; \ No newline at end of file diff --git a/src/services/datastore/routes/upload.ts b/src/services/datastore/routes/upload.ts index b0f9591..6e3585e 100644 --- a/src/services/datastore/routes/upload.ts +++ b/src/services/datastore/routes/upload.ts @@ -99,4 +99,4 @@ router.post('/upload', multipartParser, async (request: express.Request, respons response.sendStatus(200); }); -export default router; +export default router; \ No newline at end of file diff --git a/src/services/grpc/account/api-key-middleware.ts b/src/services/grpc/account/api-key-middleware.ts index dde9288..2a6158d 100644 --- a/src/services/grpc/account/api-key-middleware.ts +++ b/src/services/grpc/account/api-key-middleware.ts @@ -12,4 +12,4 @@ export async function* apiKeyMiddleware( } return yield* call.next(call.request, context); -} +} \ No newline at end of file diff --git a/src/services/grpc/account/exchange-token-for-user-data.ts b/src/services/grpc/account/exchange-token-for-user-data.ts index dc24fe6..347c4da 100644 --- a/src/services/grpc/account/exchange-token-for-user-data.ts +++ b/src/services/grpc/account/exchange-token-for-user-data.ts @@ -59,4 +59,4 @@ export async function exchangeTokenForUserData(request: ExchangeTokenForUserData updatePnidPermissions: pnid.hasPermission(PNID_PERMISSION_FLAGS.UPDATE_PNID_PERMISSIONS) } }; -} +} \ No newline at end of file diff --git a/src/services/grpc/account/get-user-data.ts b/src/services/grpc/account/get-user-data.ts index 40931d8..80d2704 100644 --- a/src/services/grpc/account/get-user-data.ts +++ b/src/services/grpc/account/get-user-data.ts @@ -57,4 +57,4 @@ export async function getUserData(request: GetUserDataRequest): Promise( } return yield* call.next(call.request, context); -} +} \ No newline at end of file diff --git a/src/services/grpc/api/authentication-middleware.ts b/src/services/grpc/api/authentication-middleware.ts index 90b39b1..c466c9e 100644 --- a/src/services/grpc/api/authentication-middleware.ts +++ b/src/services/grpc/api/authentication-middleware.ts @@ -52,4 +52,4 @@ export async function* authenticationMiddleware( throw new ServerError(Status.INVALID_ARGUMENT, message); } -} +} \ No newline at end of file diff --git a/src/services/grpc/api/forgot-password.ts b/src/services/grpc/api/forgot-password.ts index 093e7b0..45b9b6e 100644 --- a/src/services/grpc/api/forgot-password.ts +++ b/src/services/grpc/api/forgot-password.ts @@ -26,4 +26,4 @@ export async function forgotPassword(request: ForgotPasswordRequest): Promise { server.with(apiApiKeyMiddleware).with(apiAuthenticationMiddleware).add(APIDefinition, apiServiceImplementation); await server.listen(`0.0.0.0:${config.grpc.port}`); -} +} \ No newline at end of file diff --git a/src/services/local-cdn/index.ts b/src/services/local-cdn/index.ts index 319d6fc..7cd59d3 100644 --- a/src/services/local-cdn/index.ts +++ b/src/services/local-cdn/index.ts @@ -24,4 +24,4 @@ if (disabledFeatures.s3) { LOG_INFO('[LOCAL-CDN] s3 enabled, skipping local CDN'); } -export default router; +export default router; \ No newline at end of file diff --git a/src/services/local-cdn/routes/get.ts b/src/services/local-cdn/routes/get.ts index af6e1e8..c98b586 100644 --- a/src/services/local-cdn/routes/get.ts +++ b/src/services/local-cdn/routes/get.ts @@ -15,4 +15,4 @@ router.get('/*', async (request: express.Request, response: express.Response): P } }); -export default router; +export default router; \ No newline at end of file diff --git a/src/services/nasc/index.ts b/src/services/nasc/index.ts index c45bce4..f24e28f 100644 --- a/src/services/nasc/index.ts +++ b/src/services/nasc/index.ts @@ -24,4 +24,4 @@ const router = express.Router(); LOG_INFO('[NASC] Creating \'nasc\' subdomain'); router.use(subdomain('nasc', nasc)); -export default router; +export default router; \ No newline at end of file diff --git a/src/services/nasc/routes/ac.ts b/src/services/nasc/routes/ac.ts index 3138f46..1a8c616 100644 --- a/src/services/nasc/routes/ac.ts +++ b/src/services/nasc/routes/ac.ts @@ -112,4 +112,4 @@ async function processServiceTokenRequest(server: HydratedServerDocument, pid: n }); } -export default router; +export default router; \ No newline at end of file diff --git a/src/services/nnas/index.ts b/src/services/nnas/index.ts index 8ce64e2..0849261 100644 --- a/src/services/nnas/index.ts +++ b/src/services/nnas/index.ts @@ -68,4 +68,4 @@ router.use(subdomain('account', nnas)); LOG_INFO('[NNAS] Creating \'c.account\' subdomain'); router.use(subdomain('c.account', nnas)); -export default router; +export default router; \ No newline at end of file diff --git a/src/services/nnas/routes/admin.ts b/src/services/nnas/routes/admin.ts index 5a19e1b..c08c356 100644 --- a/src/services/nnas/routes/admin.ts +++ b/src/services/nnas/routes/admin.ts @@ -118,4 +118,4 @@ router.get('/time', async (request: express.Request, response: express.Response) }); -export default router; +export default router; \ No newline at end of file diff --git a/src/services/nnas/routes/content.ts b/src/services/nnas/routes/content.ts index eef28eb..a60cc30 100644 --- a/src/services/nnas/routes/content.ts +++ b/src/services/nnas/routes/content.ts @@ -162,4 +162,4 @@ router.get('/time_zones/:countryCode/:language', (request: express.Request, resp }).end()); }); -export default router; +export default router; \ No newline at end of file diff --git a/src/services/nnas/routes/devices.ts b/src/services/nnas/routes/devices.ts index ecd7b2f..f46e6a4 100644 --- a/src/services/nnas/routes/devices.ts +++ b/src/services/nnas/routes/devices.ts @@ -15,4 +15,4 @@ router.get('/@current/status', async (request: express.Request, response: expres }).end()); }); -export default router; +export default router; \ No newline at end of file diff --git a/src/services/nnas/routes/miis.ts b/src/services/nnas/routes/miis.ts index d0c027a..85487bc 100644 --- a/src/services/nnas/routes/miis.ts +++ b/src/services/nnas/routes/miis.ts @@ -127,4 +127,4 @@ router.get('/', async (request: express.Request, response: express.Response): Pr } }); -export default router; +export default router; \ No newline at end of file diff --git a/src/services/nnas/routes/oauth.ts b/src/services/nnas/routes/oauth.ts index 164b63d..7d25b75 100644 --- a/src/services/nnas/routes/oauth.ts +++ b/src/services/nnas/routes/oauth.ts @@ -172,4 +172,4 @@ router.post('/access_token/generate', deviceCertificateMiddleware, consoleStatus }).end()); }); -export default router; +export default router; \ No newline at end of file diff --git a/src/services/nnas/routes/provider.ts b/src/services/nnas/routes/provider.ts index f60b071..1f87e8d 100644 --- a/src/services/nnas/routes/provider.ts +++ b/src/services/nnas/routes/provider.ts @@ -239,4 +239,4 @@ router.get('/nex_token/@me', async (request: express.Request, response: express. }).end()); }); -export default router; +export default router; \ No newline at end of file diff --git a/src/services/nnas/routes/support.ts b/src/services/nnas/routes/support.ts index 3b73118..46522a8 100644 --- a/src/services/nnas/routes/support.ts +++ b/src/services/nnas/routes/support.ts @@ -156,4 +156,4 @@ router.get('/forgotten_password/:pid', async (request: express.Request, response response.status(200).send(''); }); -export default router; +export default router; \ No newline at end of file diff --git a/src/types/common/gender-types.ts b/src/types/common/gender-types.ts index 0cd06a8..9d4ca82 100644 --- a/src/types/common/gender-types.ts +++ b/src/types/common/gender-types.ts @@ -1 +1 @@ -export type GenderTypes = 'M' | 'F'; +export type GenderTypes = 'M' | 'F'; \ No newline at end of file diff --git a/src/types/common/mailer-options.ts b/src/types/common/mailer-options.ts index d3999f7..265b42a 100644 --- a/src/types/common/mailer-options.ts +++ b/src/types/common/mailer-options.ts @@ -13,4 +13,4 @@ export interface MailerOptions { href: string; code: string; }; -} +} \ No newline at end of file diff --git a/src/types/common/permission-flags.ts b/src/types/common/permission-flags.ts index 22e5d2e..7c22211 100644 --- a/src/types/common/permission-flags.ts +++ b/src/types/common/permission-flags.ts @@ -23,4 +23,4 @@ export const PNID_PERMISSION_FLAGS = { UPDATE_PNID_PERMISSIONS: 1n << 20n } as const; -export type PNIDPermissionFlag = (typeof PNID_PERMISSION_FLAGS)[keyof typeof PNID_PERMISSION_FLAGS]; +export type PNIDPermissionFlag = (typeof PNID_PERMISSION_FLAGS)[keyof typeof PNID_PERMISSION_FLAGS]; \ No newline at end of file diff --git a/src/types/common/safe-qs.ts b/src/types/common/safe-qs.ts index 46acab9..a17df29 100644 --- a/src/types/common/safe-qs.ts +++ b/src/types/common/safe-qs.ts @@ -1,3 +1,3 @@ export interface SafeQs { [key: string]: string | undefined -} +} \ No newline at end of file diff --git a/src/types/common/signature-size.ts b/src/types/common/signature-size.ts index 19a3a83..c9f9bbf 100644 --- a/src/types/common/signature-size.ts +++ b/src/types/common/signature-size.ts @@ -1,4 +1,4 @@ export interface SignatureSize { SIZE: number; PADDING_SIZE: number; -} +} \ No newline at end of file diff --git a/src/types/common/token-options.ts b/src/types/common/token-options.ts index 114a669..6dcc85e 100644 --- a/src/types/common/token-options.ts +++ b/src/types/common/token-options.ts @@ -5,4 +5,4 @@ export interface TokenOptions { access_level?: number; title_id?: bigint; expire_time: bigint; -} +} \ No newline at end of file diff --git a/src/types/common/token.ts b/src/types/common/token.ts index daf0eef..4c7c558 100644 --- a/src/types/common/token.ts +++ b/src/types/common/token.ts @@ -5,4 +5,4 @@ export interface Token { access_level?: number; title_id?: bigint; expire_time: bigint; -} +} \ No newline at end of file diff --git a/src/types/common/yes-no-bool-string.ts b/src/types/common/yes-no-bool-string.ts index 0088e31..474aa6e 100644 --- a/src/types/common/yes-no-bool-string.ts +++ b/src/types/common/yes-no-bool-string.ts @@ -1 +1 @@ -export type YesNoBoolString = 'Y' | 'N'; +export type YesNoBoolString = 'Y' | 'N'; \ No newline at end of file diff --git a/src/types/express-subdomain.d.ts b/src/types/express-subdomain.d.ts index 9c57184..933d1f0 100644 --- a/src/types/express-subdomain.d.ts +++ b/src/types/express-subdomain.d.ts @@ -13,4 +13,4 @@ declare module 'express-subdomain'{ subdomain: string, fn: Router | ((req: Request, res: Response) => void | any) ): (req: Request, res: Response, next: () => void) => void | typeof fn; -} +} \ No newline at end of file diff --git a/src/types/express.d.ts b/src/types/express.d.ts index 335d128..d6980f3 100644 --- a/src/types/express.d.ts +++ b/src/types/express.d.ts @@ -14,4 +14,4 @@ declare global { device?: HydratedDeviceDocument; } } -} +} \ No newline at end of file diff --git a/src/types/image-pixels.d.ts b/src/types/image-pixels.d.ts index f29ece7..a738a3f 100644 --- a/src/types/image-pixels.d.ts +++ b/src/types/image-pixels.d.ts @@ -29,4 +29,4 @@ declare module 'image-pixels' { export default function pixels( src: ImageSource | Promise ): ImageData; -} +} \ No newline at end of file diff --git a/src/types/mii-js.d.ts b/src/types/mii-js.d.ts index 65ffca6..55cc57f 100644 --- a/src/types/mii-js.d.ts +++ b/src/types/mii-js.d.ts @@ -97,4 +97,4 @@ type Mii = { studioUrl: (options: MiiStudioURLOptions) => string; encode: () => Buffer; validate: () => void; -}; +}; \ No newline at end of file diff --git a/src/types/mongoose-unique-validator.d.ts b/src/types/mongoose-unique-validator.d.ts index f5b788f..0909028 100644 --- a/src/types/mongoose-unique-validator.d.ts +++ b/src/types/mongoose-unique-validator.d.ts @@ -1 +1 @@ -declare module 'mongoose-unique-validator'; +declare module 'mongoose-unique-validator'; \ No newline at end of file diff --git a/src/types/mongoose/device-attribute.ts b/src/types/mongoose/device-attribute.ts index c42289e..e9eb87f 100644 --- a/src/types/mongoose/device-attribute.ts +++ b/src/types/mongoose/device-attribute.ts @@ -10,4 +10,4 @@ export interface IDeviceAttributeMethods {} interface IDeviceAttributeQueryHelpers {} -export interface DeviceAttributeModel extends Model {} +export interface DeviceAttributeModel extends Model {} \ No newline at end of file diff --git a/src/types/mongoose/device.ts b/src/types/mongoose/device.ts index d45016b..dfc9d89 100644 --- a/src/types/mongoose/device.ts +++ b/src/types/mongoose/device.ts @@ -30,4 +30,4 @@ interface IDeviceQueryHelpers {} export interface DeviceModel extends Model {} -export type HydratedDeviceDocument = HydratedDocument +export type HydratedDeviceDocument = HydratedDocument \ No newline at end of file diff --git a/src/types/mongoose/nex-account.ts b/src/types/mongoose/nex-account.ts index dc9e26f..e7fc997 100644 --- a/src/types/mongoose/nex-account.ts +++ b/src/types/mongoose/nex-account.ts @@ -27,4 +27,4 @@ interface INEXAccountQueryHelpers {} export interface NEXAccountModel extends Model {} -export type HydratedNEXAccountDocument = HydratedDocument +export type HydratedNEXAccountDocument = HydratedDocument \ No newline at end of file diff --git a/src/types/mongoose/pnid.ts b/src/types/mongoose/pnid.ts index a6a7737..3252c44 100644 --- a/src/types/mongoose/pnid.ts +++ b/src/types/mongoose/pnid.ts @@ -91,4 +91,4 @@ interface IPNIDQueryHelpers {} export interface PNIDModel extends Model {} -export type HydratedPNIDDocument = HydratedDocument +export type HydratedPNIDDocument = HydratedDocument \ No newline at end of file diff --git a/src/types/mongoose/server.ts b/src/types/mongoose/server.ts index 02e8e85..a7c8f6e 100644 --- a/src/types/mongoose/server.ts +++ b/src/types/mongoose/server.ts @@ -20,4 +20,4 @@ interface IServerQueryHelpers {} export interface ServerModel extends Model {} -export type HydratedServerDocument = HydratedDocument +export type HydratedServerDocument = HydratedDocument \ No newline at end of file diff --git a/src/types/services/api/connection-data.ts b/src/types/services/api/connection-data.ts index 6f1ab8a..54fe992 100644 --- a/src/types/services/api/connection-data.ts +++ b/src/types/services/api/connection-data.ts @@ -1,4 +1,4 @@ import { DiscordConnectionData } from '@/types/services/api/discord-connection-data'; // TODO - This will be a union of all ConnectionData types when more connections are added -export type ConnectionData = DiscordConnectionData; +export type ConnectionData = DiscordConnectionData; \ No newline at end of file diff --git a/src/types/services/api/connection-response.ts b/src/types/services/api/connection-response.ts index 80e2cab..6baa9b0 100644 --- a/src/types/services/api/connection-response.ts +++ b/src/types/services/api/connection-response.ts @@ -2,4 +2,4 @@ export interface ConnectionResponse { app: string; status: number; error?: string; -} +} \ No newline at end of file diff --git a/src/types/services/api/discord-connection-data.ts b/src/types/services/api/discord-connection-data.ts index 8d08c84..0ee459a 100644 --- a/src/types/services/api/discord-connection-data.ts +++ b/src/types/services/api/discord-connection-data.ts @@ -1,3 +1,3 @@ export interface DiscordConnectionData { id: string; -} +} \ No newline at end of file diff --git a/src/types/services/api/update-user-request.ts b/src/types/services/api/update-user-request.ts index f743d1e..a525e98 100644 --- a/src/types/services/api/update-user-request.ts +++ b/src/types/services/api/update-user-request.ts @@ -4,4 +4,4 @@ export interface UpdateUserRequest { primary: string; data: string; } -} +} \ No newline at end of file diff --git a/src/types/services/nasc/request-params.ts b/src/types/services/nasc/request-params.ts index 76bfd11..aafcda6 100644 --- a/src/types/services/nasc/request-params.ts +++ b/src/types/services/nasc/request-params.ts @@ -8,4 +8,4 @@ export interface NASCRequestParams { userid?: string; uidhmac?: string; passwd?: string; -} +} \ No newline at end of file diff --git a/src/types/services/nnas/account-settings.ts b/src/types/services/nnas/account-settings.ts index 7ec832a..5cef6aa 100644 --- a/src/types/services/nnas/account-settings.ts +++ b/src/types/services/nnas/account-settings.ts @@ -9,4 +9,4 @@ export interface AccountSettings { server_selection: string; marketing_flag: boolean; off_device_flag: boolean; -} +} \ No newline at end of file diff --git a/src/types/services/nnas/person.ts b/src/types/services/nnas/person.ts index 066a546..00958e5 100644 --- a/src/types/services/nnas/person.ts +++ b/src/types/services/nnas/person.ts @@ -41,4 +41,4 @@ export interface Person { device_attribute: IDeviceAttribute[] }; off_device_flag: YesNoBoolString; -} +} \ No newline at end of file diff --git a/src/types/services/nnas/pnid-profile.ts b/src/types/services/nnas/pnid-profile.ts index 85a20e3..70adea5 100644 --- a/src/types/services/nnas/pnid-profile.ts +++ b/src/types/services/nnas/pnid-profile.ts @@ -50,4 +50,4 @@ export interface PNIDProfile { tz_name: string; user_id: string; utc_offset: number; -} +} \ No newline at end of file diff --git a/src/types/services/nnas/region-languages.ts b/src/types/services/nnas/region-languages.ts index 515dc4f..fd4bbf9 100644 --- a/src/types/services/nnas/region-languages.ts +++ b/src/types/services/nnas/region-languages.ts @@ -2,4 +2,4 @@ import { RegionTimezones } from '@/types/services/nnas/region-timezones'; export interface RegionLanguages { [myKey: string]: RegionTimezones -} +} \ No newline at end of file diff --git a/src/types/services/nnas/region-timezones.ts b/src/types/services/nnas/region-timezones.ts index 33e5ca6..c904aaa 100644 --- a/src/types/services/nnas/region-timezones.ts +++ b/src/types/services/nnas/region-timezones.ts @@ -6,4 +6,4 @@ export interface RegionTimezone { order: string; } -export type RegionTimezones = RegionTimezone[]; +export type RegionTimezones = RegionTimezone[]; \ No newline at end of file diff --git a/src/types/tga.d.ts b/src/types/tga.d.ts index 365a31e..16eb4a2 100644 --- a/src/types/tga.d.ts +++ b/src/types/tga.d.ts @@ -55,4 +55,4 @@ declare module 'tga' { public pixels: Uint8Array; } -} +} \ No newline at end of file diff --git a/src/util.ts b/src/util.ts index 64e46d2..b59aa15 100644 --- a/src/util.ts +++ b/src/util.ts @@ -317,4 +317,4 @@ export function getValueFromHeaders(headers: IncomingHttpHeaders, key: string): export function mapToObject(map: Map): object { return Object.fromEntries(Array.from(map.entries(), ([ k, v ]) => v instanceof Map ? [ k, mapToObject(v) ] : [ k, v ])); -} +} \ No newline at end of file diff --git a/src/views/index.ejs b/src/views/index.ejs index d5b5445..ff443c4 100644 --- a/src/views/index.ejs +++ b/src/views/index.ejs @@ -188,4 +188,4 @@ - + \ No newline at end of file From 14e60a6bb1f1ffb39cdf4bfb7830cfee3cb1484e Mon Sep 17 00:00:00 2001 From: Jemma Poffinbarger Date: Thu, 6 Jun 2024 18:45:26 -0500 Subject: [PATCH 172/219] fix: typo in server selection svg for dev MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Daniel López Guimaraes <112760654+DaniElectra@users.noreply.github.com> --- src/views/index.ejs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/views/index.ejs b/src/views/index.ejs index ff443c4..a83781c 100644 --- a/src/views/index.ejs +++ b/src/views/index.ejs @@ -104,7 +104,7 @@ <% if(PNID.access_level === 3) { %> checked="" <% } %> <% if(PNID.access_level < 3) { %> disabled <% } %> >