diff --git a/package-lock.json b/package-lock.json index 412ae08..cc61f9d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "cli-table": "^0.3.11", "debug": "^4.3.3", "discord-rpc": "^4.0.1", + "express": "^4.17.3", "node-fetch": "^3.2.2", "node-notifier": "^10.0.1", "node-persist": "^3.1.0", @@ -25,6 +26,7 @@ "@types/cli-table": "^0.3.0", "@types/debug": "^4.1.7", "@types/discord-rpc": "^4.0.0", + "@types/express": "^4.17.13", "@types/node": "^17.0.21", "@types/node-notifier": "^8.0.2", "@types/node-persist": "^3.1.2", @@ -34,12 +36,31 @@ "typescript": "^4.7.0-dev.20220308" } }, + "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/cli-table": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/@types/cli-table/-/cli-table-0.3.0.tgz", "integrity": "sha512-QnZUISJJXyhyD6L1e5QwXDV/A5i2W1/gl6D6YMc8u0ncPepbv/B4w3S+izVvtAg60m6h+JP09+Y/0zF2mojlFQ==", "dev": true }, + "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/debug": { "version": "4.1.7", "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.7.tgz", @@ -55,6 +76,35 @@ "integrity": "sha512-a5HiKOcBkB43g/lN6fBYw8FyGc6Ue9CYucxxHxXlELXpb1CxCa2NA2pGK2Ub88pi4uY5+HQeSFbYtH6DJtV3Qw==", "dev": true }, + "node_modules/@types/express": { + "version": "4.17.13", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.13.tgz", + "integrity": "sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA==", + "dev": true, + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.18", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.17.28", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.28.tgz", + "integrity": "sha512-P1BJAEAW3E2DJUlkgq4tOL3RyMunoWXqbSCygWo5ZIWTjUgN1YnaXWW4VWl/oc8vs/XoYibEGBKP0uZyF4AHig==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*" + } + }, + "node_modules/@types/mime": { + "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/ms": { "version": "0.7.31", "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz", @@ -85,12 +135,34 @@ "@types/node": "*" } }, + "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/read": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/read/-/read-0.0.29.tgz", "integrity": "sha512-TisW3O3OhpP8/ZwaiqV7kewh9gnoH7PfqHd4hkCM9ogiqWEagu43WXpHWqgPbltXhembYJDpYB3cVwUIOweHXg==", "dev": true }, + "node_modules/@types/serve-static": { + "version": "1.13.10", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.10.tgz", + "integrity": "sha512-nCkHGI4w7ZgAdNkrEu0bv+4xNV/XDqW+DydknebMOQwkpDGx8G+HTlj7R7ABI8i8nKxVw0wtKPi1D+lPOkh4YQ==", + "dev": true, + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, "node_modules/@types/uuid": { "version": "8.3.4", "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz", @@ -112,6 +184,18 @@ "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==", "dev": true }, + "node_modules/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==", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -134,6 +218,11 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" + }, "node_modules/bindings": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", @@ -143,6 +232,47 @@ "file-uri-to-path": "1.0.0" } }, + "node_modules/body-parser": { + "version": "1.19.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.2.tgz", + "integrity": "sha512-SAAwOxgoCKMGs9uUAUFHygfLAyaniaoun6I8mFY9pRAJL9+Kec34aU+oIjDhTycub1jozEfEwx1W1IuOYxVSFw==", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.2", + "http-errors": "1.8.1", + "iconv-lite": "0.4.24", + "on-finished": "~2.3.0", + "qs": "6.9.7", + "raw-body": "2.4.3", + "type-is": "~1.6.18" + }, + "engines": { + "node": ">= 0.8" + } + }, + "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": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/cli-table": { "version": "0.3.11", "resolved": "https://registry.npmjs.org/cli-table/-/cli-table-0.3.11.tgz", @@ -188,6 +318,38 @@ "node": ">=0.1.90" } }, + "node_modules/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==", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" + }, "node_modules/data-uri-to-buffer": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.0.tgz", @@ -212,6 +374,19 @@ } } }, + "node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + }, "node_modules/discord-rpc": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/discord-rpc/-/discord-rpc-4.0.1.tgz", @@ -243,11 +418,24 @@ } } }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + }, "node_modules/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==" }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", @@ -256,6 +444,72 @@ "node": ">=6" } }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "4.17.3", + "resolved": "https://registry.npmjs.org/express/-/express-4.17.3.tgz", + "integrity": "sha512-yuSQpz5I+Ch7gFrPCk4/c+dIBKlQUxtgwqzph132bsT6qhuzss6I8cLJQz7B3rFblzd6wtcI0ZbGltH/C4LjUg==", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.19.2", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.4.2", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~1.1.2", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.1.2", + "fresh": "0.5.2", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.9.7", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.17.2", + "serve-static": "1.14.2", + "setprototypeof": "1.2.0", + "statuses": "~1.5.0", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "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": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, "node_modules/fetch-blob": { "version": "3.1.4", "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.1.4.tgz", @@ -284,6 +538,36 @@ "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", "optional": true }, + "node_modules/finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" + }, + "engines": { + "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": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, "node_modules/formdata-polyfill": { "version": "4.0.10", "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", @@ -295,6 +579,22 @@ "node": ">=12.20.0" } }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", @@ -308,6 +608,45 @@ "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz", "integrity": "sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=" }, + "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/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/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==", + "engines": { + "node": ">= 0.10" + } + }, "node_modules/is-docker": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", @@ -357,6 +696,57 @@ "node": ">=10" } }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -367,6 +757,14 @@ "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==" }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/node-addon-api": { "version": "1.7.2", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-1.7.2.tgz", @@ -429,6 +827,75 @@ "node": ">=10.12.0" } }, + "node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" + }, + "node_modules/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==", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/qs": { + "version": "6.9.7", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.7.tgz", + "integrity": "sha512-IhMFgUmuNpyRfxA90umL7ByLlgRXu6tIfKPpF5TmcfRLlLCckfP/g3IQmju6jjpu+Hh8rA+2p6A27ZSPOOHdKw==", + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/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==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.3.tgz", + "integrity": "sha512-UlTNLIcu0uzb4D2f4WltY6cVjLi+/jEN4lgEUj3E04tpMDpUlkBo/eSn6zou9hum2VMNpCCUone0O0WeJim07g==", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "1.8.1", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/read": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz", @@ -459,6 +926,30 @@ "node": ">=0.10.0" } }, + "node_modules/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==", + "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/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, "node_modules/semver": { "version": "7.3.5", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", @@ -473,11 +964,79 @@ "node": ">=10" } }, + "node_modules/send": { + "version": "0.17.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.17.2.tgz", + "integrity": "sha512-UJYB6wFSJE3G00nEivR5rgWp8c2xXvJ3OPWPhmuteU0IKj8nKbG3DrjiOmLwpnHGYWAVwA69zmTm++YG0Hmwww==", + "dependencies": { + "debug": "2.6.9", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "1.8.1", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "~2.3.0", + "range-parser": "~1.2.1", + "statuses": "~1.5.0" + }, + "engines": { + "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": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "node_modules/send/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==" + }, + "node_modules/serve-static": { + "version": "1.14.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.2.tgz", + "integrity": "sha512-+TMNA9AFxUEGuC0z2mevogSnn9MXKb4fa7ngeRMJaaGv8vTwnIEkKi+QGvPt33HSnf8pRS+WGM0EbMtCJLKMBQ==", + "dependencies": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.17.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, "node_modules/shellwords": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz", "integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==" }, + "node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", @@ -502,11 +1061,31 @@ "node": ">=8" } }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "engines": { + "node": ">=0.6" + } + }, "node_modules/tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/typescript": { "version": "4.7.0-dev.20220308", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.0-dev.20220308.tgz", @@ -520,6 +1099,22 @@ "node": ">=4.2.0" } }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/uuid": { "version": "8.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", @@ -528,6 +1123,14 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/web-streams-polyfill": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.0.tgz", @@ -640,12 +1243,31 @@ } }, "dependencies": { + "@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/cli-table": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/@types/cli-table/-/cli-table-0.3.0.tgz", "integrity": "sha512-QnZUISJJXyhyD6L1e5QwXDV/A5i2W1/gl6D6YMc8u0ncPepbv/B4w3S+izVvtAg60m6h+JP09+Y/0zF2mojlFQ==", "dev": true }, + "@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/debug": { "version": "4.1.7", "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.7.tgz", @@ -661,6 +1283,35 @@ "integrity": "sha512-a5HiKOcBkB43g/lN6fBYw8FyGc6Ue9CYucxxHxXlELXpb1CxCa2NA2pGK2Ub88pi4uY5+HQeSFbYtH6DJtV3Qw==", "dev": true }, + "@types/express": { + "version": "4.17.13", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.13.tgz", + "integrity": "sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA==", + "dev": true, + "requires": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.18", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "@types/express-serve-static-core": { + "version": "4.17.28", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.28.tgz", + "integrity": "sha512-P1BJAEAW3E2DJUlkgq4tOL3RyMunoWXqbSCygWo5ZIWTjUgN1YnaXWW4VWl/oc8vs/XoYibEGBKP0uZyF4AHig==", + "dev": true, + "requires": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*" + } + }, + "@types/mime": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", + "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==", + "dev": true + }, "@types/ms": { "version": "0.7.31", "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz", @@ -691,12 +1342,34 @@ "@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/read": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/read/-/read-0.0.29.tgz", "integrity": "sha512-TisW3O3OhpP8/ZwaiqV7kewh9gnoH7PfqHd4hkCM9ogiqWEagu43WXpHWqgPbltXhembYJDpYB3cVwUIOweHXg==", "dev": true }, + "@types/serve-static": { + "version": "1.13.10", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.10.tgz", + "integrity": "sha512-nCkHGI4w7ZgAdNkrEu0bv+4xNV/XDqW+DydknebMOQwkpDGx8G+HTlj7R7ABI8i8nKxVw0wtKPi1D+lPOkh4YQ==", + "dev": true, + "requires": { + "@types/mime": "^1", + "@types/node": "*" + } + }, "@types/uuid": { "version": "8.3.4", "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz", @@ -718,6 +1391,15 @@ "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==", "dev": true }, + "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" + } + }, "ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -731,6 +1413,11 @@ "color-convert": "^2.0.1" } }, + "array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" + }, "bindings": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", @@ -740,6 +1427,43 @@ "file-uri-to-path": "1.0.0" } }, + "body-parser": { + "version": "1.19.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.2.tgz", + "integrity": "sha512-SAAwOxgoCKMGs9uUAUFHygfLAyaniaoun6I8mFY9pRAJL9+Kec34aU+oIjDhTycub1jozEfEwx1W1IuOYxVSFw==", + "requires": { + "bytes": "3.1.2", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.2", + "http-errors": "1.8.1", + "iconv-lite": "0.4.24", + "on-finished": "~2.3.0", + "qs": "6.9.7", + "raw-body": "2.4.3", + "type-is": "~1.6.18" + }, + "dependencies": { + "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" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + } + } + }, + "bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==" + }, "cli-table": { "version": "0.3.11", "resolved": "https://registry.npmjs.org/cli-table/-/cli-table-0.3.11.tgz", @@ -776,6 +1500,29 @@ "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", "integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=" }, + "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.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" + }, + "cookie": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==" + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" + }, "data-uri-to-buffer": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.0.tgz", @@ -789,6 +1536,16 @@ "ms": "2.1.2" } }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" + }, + "destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + }, "discord-rpc": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/discord-rpc/-/discord-rpc-4.0.1.tgz", @@ -809,16 +1566,88 @@ } } }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + }, "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": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" + }, "escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==" }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" + }, + "express": { + "version": "4.17.3", + "resolved": "https://registry.npmjs.org/express/-/express-4.17.3.tgz", + "integrity": "sha512-yuSQpz5I+Ch7gFrPCk4/c+dIBKlQUxtgwqzph132bsT6qhuzss6I8cLJQz7B3rFblzd6wtcI0ZbGltH/C4LjUg==", + "requires": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.19.2", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.4.2", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~1.1.2", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.1.2", + "fresh": "0.5.2", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.9.7", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.17.2", + "serve-static": "1.14.2", + "setprototypeof": "1.2.0", + "statuses": "~1.5.0", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "dependencies": { + "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" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + } + } + }, "fetch-blob": { "version": "3.1.4", "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.1.4.tgz", @@ -834,6 +1663,35 @@ "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", "optional": true }, + "finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" + }, + "dependencies": { + "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" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + } + } + }, "formdata-polyfill": { "version": "4.0.10", "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", @@ -842,6 +1700,16 @@ "fetch-blob": "^3.1.2" } }, + "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": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" + }, "get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", @@ -852,6 +1720,36 @@ "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz", "integrity": "sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=" }, + "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" + } + }, + "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" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "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-docker": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", @@ -883,6 +1781,39 @@ "yallist": "^4.0.0" } }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" + }, + "merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" + }, + "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" + } + }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -893,6 +1824,11 @@ "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==" }, + "negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==" + }, "node-addon-api": { "version": "1.7.2", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-1.7.2.tgz", @@ -932,6 +1868,54 @@ "resolved": "https://registry.npmjs.org/node-persist/-/node-persist-3.1.0.tgz", "integrity": "sha512-/j+fd/u71wNgKf3V2bx4tnDm+3GvLnlCuvf2MXbJ3wern+67IAb6zN9Leu1tCWPlPNZ+v1hLSibVukkPK2HqJw==" }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "requires": { + "ee-first": "1.1.1" + } + }, + "parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" + }, + "path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" + }, + "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" + } + }, + "qs": { + "version": "6.9.7", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.7.tgz", + "integrity": "sha512-IhMFgUmuNpyRfxA90umL7ByLlgRXu6tIfKPpF5TmcfRLlLCckfP/g3IQmju6jjpu+Hh8rA+2p6A27ZSPOOHdKw==" + }, + "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.4.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.3.tgz", + "integrity": "sha512-UlTNLIcu0uzb4D2f4WltY6cVjLi+/jEN4lgEUj3E04tpMDpUlkBo/eSn6zou9hum2VMNpCCUone0O0WeJim07g==", + "requires": { + "bytes": "3.1.2", + "http-errors": "1.8.1", + "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", @@ -954,6 +1938,16 @@ "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" }, + "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==" + }, "semver": { "version": "7.3.5", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", @@ -962,11 +1956,74 @@ "lru-cache": "^6.0.0" } }, + "send": { + "version": "0.17.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.17.2.tgz", + "integrity": "sha512-UJYB6wFSJE3G00nEivR5rgWp8c2xXvJ3OPWPhmuteU0IKj8nKbG3DrjiOmLwpnHGYWAVwA69zmTm++YG0Hmwww==", + "requires": { + "debug": "2.6.9", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "1.8.1", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "~2.3.0", + "range-parser": "~1.2.1", + "statuses": "~1.5.0" + }, + "dependencies": { + "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" + }, + "dependencies": { + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + } + } + }, + "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.14.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.2.tgz", + "integrity": "sha512-+TMNA9AFxUEGuC0z2mevogSnn9MXKb4fa7ngeRMJaaGv8vTwnIEkKi+QGvPt33HSnf8pRS+WGM0EbMtCJLKMBQ==", + "requires": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.17.2" + } + }, + "setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, "shellwords": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz", "integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==" }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" + }, "string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", @@ -985,22 +2042,51 @@ "ansi-regex": "^5.0.1" } }, + "toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" + }, "tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" }, + "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" + } + }, "typescript": { "version": "4.7.0-dev.20220308", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.0-dev.20220308.tgz", "integrity": "sha512-dWx2ETRQadrYmrtU4XDnjQsxPNzX1u3+/TWJZ8kU0gOwdyZQ8NrO5BSzhIwp5eqAGe8xdBg+xPDT9tI4mxLfjw==", "dev": true }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" + }, + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" + }, "uuid": { "version": "8.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" + }, "web-streams-polyfill": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.0.tgz", diff --git a/package.json b/package.json index d041801..ae39364 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "cli-table": "^0.3.11", "debug": "^4.3.3", "discord-rpc": "^4.0.1", + "express": "^4.17.3", "node-fetch": "^3.2.2", "node-notifier": "^10.0.1", "node-persist": "^3.1.0", @@ -32,6 +33,7 @@ "@types/cli-table": "^0.3.0", "@types/debug": "^4.1.7", "@types/discord-rpc": "^4.0.0", + "@types/express": "^4.17.13", "@types/node": "^17.0.21", "@types/node-notifier": "^8.0.2", "@types/node-persist": "^3.1.2", diff --git a/src/api/znc-proxy.ts b/src/api/znc-proxy.ts new file mode 100644 index 0000000..234848a --- /dev/null +++ b/src/api/znc-proxy.ts @@ -0,0 +1,81 @@ +import fetch from 'node-fetch'; +import createDebug from 'debug'; +import { ActiveEvent, Announcement, CurrentUser, Friend, WebService, WebServiceToken } from './znc-types.js'; +import { ErrorResponse } from './util.js'; +import ZncApi from './znc.js'; +import { SavedToken } from '../util.js'; + +const debug = createDebug('api:znc-proxy'); + +export default class ZncProxyApi implements ZncApi { + constructor( + private url: string, + // ZncApi uses the NSO token (valid for a few hours) + // ZncProxyApi uses the Nintendo Account session token (valid for two years) + public token: string + ) {} + + async fetch(url: string, method = 'GET', body?: string, headers?: object) { + const response = await fetch(this.url + url, { + method: method, + headers: Object.assign({ + 'Authorization': 'na ' + this.token, + }, headers), + body: body, + }); + + debug('fetch %s %s, response %s', method, url, response.status); + + if (response.status !== 200) { + throw new ErrorResponse('[zncproxy] Unknown error', response); + } + + const data = await response.json() as T; + + return data; + } + + async getAnnouncements() { + const response = await this.fetch<{announcements: Announcement[]}>('/announcements'); + return {status: 0 as const, result: response.announcements, correlationId: ''}; + } + + async getFriendList() { + const response = await this.fetch<{friends: Friend[]}>('/friends'); + return {status: 0 as const, result: response, correlationId: ''}; + } + + async getWebServices() { + const response = await this.fetch<{webservices: WebService[]}>('/webservices'); + return {status: 0 as const, result: response.webservices, correlationId: ''}; + } + + async getActiveEvent() { + const response = await this.fetch<{activeevent: ActiveEvent}>('/activeevent'); + return {status: 0 as const, result: response.activeevent, correlationId: ''}; + } + + async getCurrentUser() { + const response = await this.fetch<{user: CurrentUser}>('/user'); + return {status: 0 as const, result: response.user, correlationId: ''}; + } + + async getWebServiceToken(id: string) { + const response = await this.fetch<{token: WebServiceToken}>('/webservice/' + id + '/token'); + return {status: 0 as const, result: response.token, correlationId: ''}; + } + + async renewToken() { + const data = await this.fetch('/auth'); + data.proxy_url = this.url; + return data; + } + + static async createWithSessionToken(url: string, token: string) { + const nso = new this(url, token); + const data = await nso.fetch('/auth'); + data.proxy_url = url; + + return {nso, data}; + } +} diff --git a/src/api/znc-types.ts b/src/api/znc-types.ts index ecad95a..bc083c3 100644 --- a/src/api/znc-types.ts +++ b/src/api/znc-types.ts @@ -106,10 +106,9 @@ export interface CurrentUser { links: { nintendoAccount: { membership: { - // active: { - // active: boolean; - // }; - active: boolean; + active: { + active: boolean; + } | boolean; }; }; friendCode: { diff --git a/src/api/znc.ts b/src/api/znc.ts index 402b6c1..02c85cb 100644 --- a/src/api/znc.ts +++ b/src/api/znc.ts @@ -17,10 +17,10 @@ const ZNC_URL = 'https://api-lp1.znc.srv.nintendo.net'; export default class ZncApi { constructor( - private token: string + public token: string ) {} - protected async fetch(url: string, method = 'GET', body?: string, headers?: object) { + async fetch(url: string, method = 'GET', body?: string, headers?: object) { const response = await fetch(ZNC_URL + url, { method: method, headers: Object.assign({ @@ -33,6 +33,8 @@ export default class ZncApi { body: body, }); + debug('fetch %s %s, response %s', method, url, response.status); + const data = await response.json() as ZncResponse; if ('errorMessage' in data) { diff --git a/src/cli.ts b/src/cli.ts index 4f56074..503f5ae 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -10,6 +10,9 @@ const yargs = Yargs(process.argv.slice(2)).option('data-path', { describe: 'Data storage path', type: 'string', default: path.join(import.meta.url.substr(7), '..', '..', 'data'), +}).option('znc-proxy-url', { + describe: 'URL of Nintendo Switch Online app API proxy server to use', + type: 'string', }); export type Arguments = YargsArguments; diff --git a/src/cli/announcements.ts b/src/cli/announcements.ts index 925d3a6..1427bc3 100644 --- a/src/cli/announcements.ts +++ b/src/cli/announcements.ts @@ -30,7 +30,7 @@ export async function handler(argv: ArgumentsCamelCase) { const token: string = argv.token || await storage.getItem('NintendoAccountToken.' + usernsid) || await storage.getItem('SessionToken'); - const {nso, data} = await getToken(storage, token); + const {nso, data} = await getToken(storage, token, argv.zncProxyUrl); const announcements = await nso.getAnnouncements(); const friends = await nso.getFriendList(); diff --git a/src/cli/friends.ts b/src/cli/friends.ts index ed15981..d22147b 100644 --- a/src/cli/friends.ts +++ b/src/cli/friends.ts @@ -31,7 +31,7 @@ export async function handler(argv: ArgumentsCamelCase) { const token: string = argv.token || await storage.getItem('NintendoAccountToken.' + usernsid) || await storage.getItem('SessionToken'); - const {nso, data} = await getToken(storage, token); + const {nso, data} = await getToken(storage, token, argv.zncProxyUrl); const announcements = await nso.getAnnouncements(); const friends = await nso.getFriendList(); diff --git a/src/cli/http-server.ts b/src/cli/http-server.ts new file mode 100644 index 0000000..28bd7d9 --- /dev/null +++ b/src/cli/http-server.ts @@ -0,0 +1,384 @@ +import createDebug from 'debug'; +import express from 'express'; +import * as net from 'net'; +import persist from 'node-persist'; +import { ActiveEvent, Announcement, CurrentUser, Friend, Presence, WebService } from '../api/znc-types.js'; +import ZncApi from '../api/znc.js'; +import type { Arguments as ParentArguments } from '../cli.js'; +import { ArgumentsCamelCase, Argv, getToken, initStorage, SavedToken, YargsArguments } from '../util.js'; +import { ZncNotifications } from './notify.js'; + +declare global { + namespace Express { + interface Request { + znc?: ZncApi; + zncAuth?: SavedToken; + } + } +} + +const debug = createDebug('cli:http-server'); + +export const command = 'http-server'; +export const desc = 'Starts a HTTP server to access the Nintendo Switch Online app API'; + +export function builder(yargs: Argv) { + return yargs.option('listen', { + describe: 'Server address and port', + type: 'array', + default: ['[::]:0'], + }).option('require-token', { + describe: 'Require Nintendo Account session token for all requests (if disabled the user query string parameter can be used to use the last token for that user)', + type: 'boolean', + default: true, + }).option('update-interval', { + describe: 'Max. update interval in seconds', + type: 'number', + default: 30, + }); +} + +type Arguments = YargsArguments>; + +export async function handler(argv: ArgumentsCamelCase) { + const updateInterval = argv.updateInterval * 1000; + + const storage = await initStorage(argv.dataPath); + + const app = express(); + + const localAuth: express.RequestHandler = async (req, res, next) => { + if (argv.requireToken || !req.query.user) return next(); + + const token = await storage.getItem('NintendoAccountToken.' + req.query.user); + if (!token) return next(); + + req.headers['authorization'] = 'na ' + token; + + next(); + }; + + const nsoAuth: express.RequestHandler = async (req, res, next) => { + try { + const auth = req.headers['authorization']; + if (!auth || !auth.startsWith('na ')) throw new Error('Requires Nintendo Account authentication'); + const nintendoAccountSessionToken = auth.substr(3); + + const {nso, data} = await getToken(storage, nintendoAccountSessionToken, argv.zncProxyUrl); + + req.znc = nso; + req.zncAuth = data; + + const users = new Set(await storage.getItem('NintendoAccountIds') ?? []); + users.add(data.user.id); + await storage.setItem('NintendoAccountIds', [...users]); + + next(); + } catch (err) { + res.statusCode = 500; + res.setHeader('Content-Type', 'application/json'); + res.end(JSON.stringify({ + error: err, + error_message: (err as Error).message, + })); + } + }; + + app.get('/api/znc/auth', nsoAuth, (req, res) => { + res.setHeader('Content-Type', 'application/json'); + res.end(JSON.stringify(req.zncAuth)); + }); + + // + // Announcements + // This is cached for all users. + // + + let cached_announcements: Announcement[] | null = null; + app.get('/api/znc/announcements', localAuth, nsoAuth, async (req, res) => { + if (cached_announcements) { + res.setHeader('Content-Type', 'application/json'); + res.end(JSON.stringify({ + announcements: cached_announcements, + })); + return; + } + + try { + const announcements = await req.znc!.getAnnouncements(); + cached_announcements = announcements.result; + + res.setHeader('Content-Type', 'application/json'); + res.end(JSON.stringify({ + announcements: announcements.result, + })); + } catch (err) { + res.statusCode = 500; + res.setHeader('Content-Type', 'application/json'); + res.end(JSON.stringify({ + error: err, + error_message: (err as Error).message, + })); + } + }); + + // + // Nintendo Switch friends, NSO app web services, events + // + + const cached_appdata = new Map(); + + const getAppData: express.RequestHandler = async (req, res, next) => { + const cache = cached_appdata.get(req.zncAuth!.user.id); + + if (cache && ((cache[3] + updateInterval) > Date.now())) { + debug('Using cached app data for %s', req.zncAuth!.user.id); + next(); + return; + } + + try { + const friends = await req.znc!.getFriendList(); + const webservices = await req.znc!.getWebServices(); + const activeevent = await req.znc!.getActiveEvent(); + + cached_appdata.set(req.zncAuth!.user.id, [ + friends.result.friends, webservices.result, activeevent.result, Date.now(), + ]); + + next(); + } catch (err) { + res.statusCode = 500; + res.setHeader('Content-Type', 'application/json'); + res.end(JSON.stringify({ + error: err, + error_message: (err as Error).message, + })); + } + }; + + app.get('/api/znc/friends', localAuth, nsoAuth, getAppData, async (req, res) => { + const [friends, webservices, activeevent, updated] = cached_appdata.get(req.zncAuth!.user.id)!; + + res.setHeader('Content-Type', 'application/json'); + res.end(JSON.stringify({friends, updated})); + }); + + app.get('/api/znc/friends/presence', localAuth, nsoAuth, getAppData, async (req, res) => { + const [friends, webservices, activeevent, updated] = cached_appdata.get(req.zncAuth!.user.id)!; + const presence: Record = {}; + + for (const friend of friends) { + presence[friend.nsaId] = friend.presence; + } + + res.setHeader('Content-Type', 'application/json'); + res.end(JSON.stringify(presence)); + }); + + app.get('/api/znc/friend/:nsid', localAuth, nsoAuth, getAppData, async (req, res) => { + const [friends, webservices, activeevent, updated] = cached_appdata.get(req.zncAuth!.user.id)!; + const friend = friends.find(f => f.nsaId === req.params.nsid); + + if (!friend) { + res.statusCode = 404; + res.setHeader('Content-Type', 'application/json'); + res.end(JSON.stringify({ + error: 'not_found', + error_message: 'The user is not friends with the authenticated user.', + })); + return; + } + + res.setHeader('Content-Type', 'application/json'); + res.end(JSON.stringify({friend, updated})); + }); + + app.get('/api/znc/friend/:nsid/presence', localAuth, nsoAuth, getAppData, async (req, res) => { + const [friends, webservices, activeevent, updated] = cached_appdata.get(req.zncAuth!.user.id)!; + const friend = friends.find(f => f.nsaId === req.params.nsid); + + if (!friend) { + res.statusCode = 404; + res.setHeader('Content-Type', 'application/json'); + res.end(JSON.stringify({ + error: 'not_found', + error_message: 'The user is not friends with the authenticated user.', + })); + return; + } + + res.setHeader('Content-Type', 'application/json'); + res.end(JSON.stringify(friend.presence)); + }); + + app.get('/api/znc/webservices', localAuth, nsoAuth, getAppData, async (req, res) => { + const [friends, webservices, activeevent, updated] = cached_appdata.get(req.zncAuth!.user.id)!; + + res.setHeader('Content-Type', 'application/json'); + res.end(JSON.stringify({webservices, updated})); + }); + + app.get('/api/znc/webservice/:id/token', nsoAuth, async (req, res) => { + try { + const response = await req.znc!.getWebServiceToken(req.params.id, req.zncAuth!.credential.accessToken); + + res.setHeader('Content-Type', 'application/json'); + res.end(JSON.stringify({ + token: response.result, + })); + } catch (err) { + res.statusCode = 500; + res.setHeader('Content-Type', 'application/json'); + res.end(JSON.stringify({ + error: err, + error_message: (err as Error).message, + })); + } + }); + + app.get('/api/znc/activeevent', localAuth, nsoAuth, getAppData, async (req, res) => { + const [friends, webservices, activeevent, updated] = cached_appdata.get(req.zncAuth!.user.id)!; + + res.setHeader('Content-Type', 'application/json'); + res.end(JSON.stringify({activeevent, updated})); + }); + + // + // Nintendo Switch user data + // + + const cached_userdata = new Map(); + + const getUserData: express.RequestHandler = async (req, res, next) => { + const cache = cached_userdata.get(req.zncAuth!.user.id); + + if (cache && ((cache[1] + updateInterval) > Date.now())) { + debug('Using cached user data for %s', req.zncAuth!.user.id); + next(); + return; + } + + try { + const user = await req.znc!.getCurrentUser(); + cached_userdata.set(req.zncAuth!.user.id, [user.result, Date.now()]); + + next(); + } catch (err) { + res.statusCode = 500; + res.setHeader('Content-Type', 'application/json'); + res.end(JSON.stringify({ + error: err, + error_message: (err as Error).message, + })); + } + }; + + app.get('/api/znc/user', localAuth, nsoAuth, getUserData, async (req, res) => { + const [user, updated] = cached_userdata.get(req.zncAuth!.user.id)!; + + res.setHeader('Content-Type', 'application/json'); + res.end(JSON.stringify({user, updated})); + }); + + app.get('/api/znc/user/presence', localAuth, nsoAuth, getUserData, async (req, res) => { + const [user, updated] = cached_userdata.get(req.zncAuth!.user.id)!; + + res.setHeader('Content-Type', 'application/json'); + res.end(JSON.stringify(user.presence)); + }); + + // + // Event stream + // + + app.get('/api/znc/presence/events', localAuth, nsoAuth, async (req, res) => { + res.setHeader('Cache-Control', 'no-store'); + res.setHeader('Content-Type', 'text/event-stream'); + + const nintendoAccountSessionToken = req.headers['authorization']!.substr(3); + const i = new ZncPresenceEventStream( + argv as any, storage, nintendoAccountSessionToken, req.znc!, req.zncAuth!, + req, res, + true, true, + ); + + await i.init(); + + while (true) { + await i.loop(); + } + }); + + for (const address of argv.listen) { + const match = address.match(/^(?:((?:\d+\.){3}\d+)|\[(.*)\]):(\d+)$/); + if (!match || !net.isIP(match[1] || match[2])) throw new Error('Not a valid address/port'); + + const server = app.listen(parseInt(match[3]), match[1] || match[2]); + server.on('listening', () => { + const address = server.address() as net.AddressInfo; + console.log('Listening on %s, port %d', address.address, address.port); + }); + } +} + +export enum ZncPresenceEventStreamEvent { + FRIEND_ONLINE = '0', + FRIEND_OFFLINE = '1', + FRIEND_TITLE_CHANGE = '2', + FRIEND_TITLE_STATECHANGE = '3', +} + +class ZncPresenceEventStream extends ZncNotifications { + constructor( + argv: ArgumentsCamelCase, + storage: persist.LocalStorage, + token: string, + nso: ZncApi, + data: Omit, + public req: express.Request, + public res: express.Response, + user: boolean, + friend: boolean, + ) { + super({ + ...argv, + user: undefined, + token: undefined, + 'user-notifications': user, + userNotifications: user, + 'friend-notifications': friend, + friendNotifications: friend, + }, storage, token, nso, data); + } + + sendEvent(event: string | null, ...data: unknown[]) { + if (event) this.res.write('event: ' + event + '\n'); + for (const d of data) this.res.write('data: ' + JSON.stringify(d) + '\n'); + this.res.write('\n'); + } + + onFriendOnline(friend: CurrentUser | Friend, prev?: CurrentUser | Friend, ir?: boolean) { + this.sendEvent(ZncPresenceEventStreamEvent.FRIEND_ONLINE, { + id: friend.nsaId, presence: friend.presence, prev: prev?.presence, + }); + } + + onFriendOffline(friend: CurrentUser | Friend, prev?: CurrentUser | Friend, ir?: boolean) { + this.sendEvent(ZncPresenceEventStreamEvent.FRIEND_OFFLINE, { + id: friend.nsaId, presence: friend.presence, prev: prev?.presence, + }); + } + + onFriendPlayingChangeTitle(friend: CurrentUser | Friend, prev?: CurrentUser | Friend, ir?: boolean) { + this.sendEvent(ZncPresenceEventStreamEvent.FRIEND_TITLE_CHANGE, { + id: friend.nsaId, presence: friend.presence, prev: prev?.presence, + }); + } + + onFriendTitleStateChange(friend: CurrentUser | Friend, prev?: CurrentUser | Friend, ir?: boolean) { + this.sendEvent(ZncPresenceEventStreamEvent.FRIEND_TITLE_STATECHANGE, { + id: friend.nsaId, presence: friend.presence, prev: prev?.presence, + }); + } +} diff --git a/src/cli/index.ts b/src/cli/index.ts index 3e97361..e09d7d0 100644 --- a/src/cli/index.ts +++ b/src/cli/index.ts @@ -7,3 +7,4 @@ export * as webservicetoken from './webservicetoken.js'; export * as friends from './friends.js'; export * as presence from './presence.js'; export * as notify from './notify.js'; +export * as httpServer from './http-server.js'; diff --git a/src/cli/notify.ts b/src/cli/notify.ts index 08f333f..7664e45 100644 --- a/src/cli/notify.ts +++ b/src/cli/notify.ts @@ -5,6 +5,7 @@ import { CurrentUser, Friend, Game, PresenceState } from '../api/znc-types.js'; import ZncApi from '../api/znc.js'; import type { Arguments as ParentArguments } from '../cli.js'; import { ArgumentsCamelCase, Argv, getTitleIdFromEcUrl, getToken, initStorage, SavedToken, YargsArguments } from '../util.js'; +import ZncProxyApi from '../api/znc-proxy.js'; const debug = createDebug('cli:notify'); const debugFriends = createDebug('cli:notify:friends'); @@ -47,7 +48,7 @@ export async function handler(argv: ArgumentsCamelCase) { const token: string = argv.token || await storage.getItem('NintendoAccountToken.' + usernsid) || await storage.getItem('SessionToken'); - const {nso, data} = await getToken(storage, token); + const {nso, data} = await getToken(storage, token, argv.zncProxyUrl); const i = new ZncNotifications(argv, storage, token, nso, data); @@ -72,9 +73,12 @@ export class ZncNotifications { async init() { const announcements = await this.nso.getAnnouncements(); - const friends = await this.nso.getFriendList(); - const webservices = await this.nso.getWebServices(); - const activeevent = await this.nso.getActiveEvent(); + const friends = this.argv.friendNotifications || !(this.nso instanceof ZncProxyApi) ? + await this.nso.getFriendList() : {result: {friends: []}}; + if (!(this.nso instanceof ZncProxyApi)) { + const webservices = await this.nso.getWebServices(); + const activeevent = await this.nso.getActiveEvent(); + } if (this.argv.userNotifications) { const user = await this.nso.getCurrentUser(); @@ -88,13 +92,58 @@ export class ZncNotifications { await new Promise(rs => setTimeout(rs, this.argv.updateInterval * 1000)); } + onFriendOnline(friend: CurrentUser | Friend, prev?: CurrentUser | Friend, ir?: boolean) { + const currenttitle = friend.presence.game as Game; + + notifier.notify({ + title: friend.name, + message: 'Playing ' + currenttitle.name + + (currenttitle.sysDescription ? '\n' + currenttitle.sysDescription : ''), + // icon: currenttitle.imageUri, + icon: friend.imageUri, + }); + } + + onFriendOffline(friend: CurrentUser | Friend, prev?: CurrentUser | Friend, ir?: boolean) { + notifier.notify({ + title: friend.name, + message: 'Offline', + icon: friend.imageUri, + }); + } + + onFriendPlayingChangeTitle(friend: CurrentUser | Friend, prev?: CurrentUser | Friend, ir?: boolean) { + const currenttitle = friend.presence.game as Game; + + notifier.notify({ + title: friend.name, + message: 'Playing ' + currenttitle.name + + (currenttitle.sysDescription ? '\n' + currenttitle.sysDescription : ''), + // icon: currenttitle.imageUri, + icon: friend.imageUri, + }); + } + + onFriendTitleStateChange(friend: CurrentUser | Friend, prev?: CurrentUser | Friend, ir?: boolean) { + const currenttitle = friend.presence.game as Game; + + notifier.notify({ + title: friend.name, + message: 'Playing ' + currenttitle.name + + (currenttitle.sysDescription ? '\n' + currenttitle.sysDescription : ''), + // icon: currenttitle.imageUri, + icon: friend.imageUri, + }); + } + onlinefriends: (CurrentUser | Friend)[] = []; async updateFriendsStatusForNotifications(friends: (CurrentUser | Friend)[], initialRun?: boolean) { const newonlinefriends: (CurrentUser | Friend)[] = []; for (const friend of friends) { - const lastpresence = this.onlinefriends.find(f => f.id === friend.id)?.presence; + const prev = this.onlinefriends.find(f => f.id === friend.id); + const lastpresence = prev?.presence; const online = friend.presence.state === PresenceState.ONLINE || friend.presence.state === PresenceState.PLAYING; @@ -105,25 +154,17 @@ export class ZncNotifications { debugFriends('%s is now online, title %s %s', friend.name, currenttitle.name, JSON.stringify(currenttitle.sysDescription)); - notifier.notify({ - title: friend.name, - message: 'Playing ' + currenttitle.name + - (currenttitle.sysDescription ? '\n' + currenttitle.sysDescription : ''), - icon: currenttitle.imageUri, - }); + this.onFriendOnline(friend, prev, initialRun); newonlinefriends.push(friend); } else if (lastpresence && !online) { // Friend has gone offline const lasttitle = lastpresence.game as Game; - notifier.notify({ - title: friend.name, - message: 'Offline', - }); - debugFriends('%s is now offline, was playing title %s %s', friend.name, lasttitle.name, JSON.stringify(lasttitle.sysDescription)); + + this.onFriendOffline(friend, prev, initialRun); } else if (lastpresence && online) { // Friend is still online const lasttitle = lastpresence.game as Game; @@ -137,12 +178,7 @@ export class ZncNotifications { currenttitle.name, JSON.stringify(currenttitle.sysDescription), lasttitle.name, JSON.stringify(lasttitle.sysDescription)); - notifier.notify({ - title: friend.name, - message: 'Playing ' + currenttitle.name + - (currenttitle.sysDescription ? '\n' + currenttitle.sysDescription : ''), - icon: currenttitle.imageUri, - }); + this.onFriendPlayingChangeTitle(friend, prev, initialRun); } else if ( lastpresence.state !== friend.presence.state || lasttitle.sysDescription !== currenttitle.sysDescription @@ -154,12 +190,7 @@ export class ZncNotifications { friend.presence.state, JSON.stringify(currenttitle.sysDescription), lastpresence.state, JSON.stringify(lasttitle.sysDescription)); - notifier.notify({ - title: friend.name, - message: 'Playing ' + currenttitle.name + - (currenttitle.sysDescription ? '\n' + currenttitle.sysDescription : ''), - icon: currenttitle.imageUri, - }); + this.onFriendTitleStateChange(friend, prev, initialRun); } newonlinefriends.push(friend); @@ -173,9 +204,10 @@ export class ZncNotifications { debug('Updating presence'); if (this.argv.friendNotifications) { - const activeevent = await this.nso.getActiveEvent(); - const friends = await this.nso.getFriendList(); - const webservices = await this.nso.getWebServices(); + if (!(this.nso instanceof ZncProxyApi)) await this.nso.getActiveEvent(); + const friends = this.argv.friendNotifications || !(this.nso instanceof ZncProxyApi) ? + await this.nso.getFriendList() : {result: {friends: []}}; + if (!(this.nso instanceof ZncProxyApi)) await this.nso.getWebServices(); if (this.argv.userNotifications) { const user = await this.nso.getCurrentUser(); diff --git a/src/cli/presence.ts b/src/cli/presence.ts index 326ba3a..faeaff7 100644 --- a/src/cli/presence.ts +++ b/src/cli/presence.ts @@ -1,14 +1,16 @@ import createDebug from 'debug'; import persist from 'node-persist'; import DiscordRPC from 'discord-rpc'; -import { CurrentUser, Presence, PresenceState } from '../api/znc-types.js'; +import fetch from 'node-fetch'; +import { CurrentUser, Friend, Presence, PresenceState } from '../api/znc-types.js'; import ZncApi from '../api/znc.js'; import type { Arguments as ParentArguments } from '../cli.js'; import { ArgumentsCamelCase, Argv, getDiscordPresence, getTitleIdFromEcUrl, getToken, initStorage, SavedToken, YargsArguments } from '../util.js'; import { ZncNotifications } from './notify.js'; +import ZncProxyApi from '../api/znc-proxy.js'; const debug = createDebug('cli:presence'); -const debugFriends = createDebug('cli:presence:friends'); +const debugProxy = createDebug('cli:presence:proxy'); const debugDiscord = createDebug('cli:presence:discordrpc'); export const command = 'presence'; @@ -39,19 +41,42 @@ export function builder(yargs: Argv) { describe: 'Update interval in seconds', type: 'number', default: 30, + }).option('presence-url', { + describe: 'URL to get user presence from, for use with `nintendo-znc http-server`', + type: 'string', }); } type Arguments = YargsArguments>; export async function handler(argv: ArgumentsCamelCase) { + if (argv.presenceUrl) { + if (argv.token) throw new Error('--presence-url not compatible with --user'); + if (argv.token) throw new Error('--presence-url not compatible with --token'); + if (argv.friendNaid) throw new Error('--presence-url not compatible with --friend-naid'); + if (argv.userNotifications) throw new Error('--presence-url not compatible with --user-notifications'); + if (argv.friendNotifications) throw new Error('--presence-url not compatible with --user-notifications'); + + const i = new ZncProxyDiscordPresence(argv, argv.presenceUrl); + + console.log('Not authenticated; using znc proxy'); + + await i.init(); + + while (true) { + await i.loop(); + } + + return; + } + const storage = await initStorage(argv.dataPath); const usernsid = argv.user ?? await storage.getItem('SelectedUser'); const token: string = argv.token || await storage.getItem('NintendoAccountToken.' + usernsid) || await storage.getItem('SessionToken'); - const {nso, data} = await getToken(storage, token); + const {nso, data} = await getToken(storage, token, argv.zncProxyUrl); const i = new ZncDiscordPresence(argv, storage, token, nso, data); @@ -85,9 +110,23 @@ class ZncDiscordPresence extends ZncNotifications { async init() { const announcements = await this.nso.getAnnouncements(); - const friends = await this.nso.getFriendList(); - const webservices = await this.nso.getWebServices(); - const activeevent = await this.nso.getActiveEvent(); + const friends = this.argv.friendNotifications || !(this.nso instanceof ZncProxyApi) ? + await this.nso.getFriendList() : {result: {friends: this.argv.friendNaid ? [this.argv.userNotifications ? + (await this.nso.fetch<{friend: Friend}>('/friend/' + this.argv.friendNaid)).friend : { + id: 0, + nsaId: this.argv.friendNaid, + imageUri: '', + name: '', + isFriend: true, + isFavoriteFriend: false, + isServiceUser: true, + friendCreatedAt: 0, + presence: await this.nso.fetch('/friend/' + this.argv.friendNaid + '/presence'), + }] : []}}; + if (!(this.nso instanceof ZncProxyApi)) { + await this.nso.getWebServices(); + await this.nso.getActiveEvent(); + } if (this.argv.friendNaid) { const friend = friends.result.friends.find(f => f.nsaId === this.argv.friendNaid); @@ -235,9 +274,21 @@ class ZncDiscordPresence extends ZncNotifications { async update() { if (this.argv.friendNaid) { - const activeevent = await this.nso.getActiveEvent(); - const friends = await this.nso.getFriendList(); - const webservices = await this.nso.getWebServices(); + if (!(this.nso instanceof ZncProxyApi)) await this.nso.getActiveEvent(); + const friends = this.argv.friendNotifications || !(this.nso instanceof ZncProxyApi) ? + await this.nso.getFriendList() : {result: {friends: this.argv.friendNaid ? [this.argv.userNotifications ? + (await this.nso.fetch<{friend: Friend}>('/friend/' + this.argv.friendNaid)).friend : { + id: 0, + nsaId: this.argv.friendNaid, + imageUri: '', + name: '', + isFriend: true, + isFavoriteFriend: false, + isServiceUser: true, + friendCreatedAt: 0, + presence: await this.nso.fetch('/friend/' + this.argv.friendNaid + '/presence'), + }] : []}}; + if (!(this.nso instanceof ZncProxyApi)) await this.nso.getWebServices(); const friend = friends.result.friends.find(f => f.nsaId === this.argv.friendNaid); @@ -258,13 +309,17 @@ class ZncDiscordPresence extends ZncNotifications { await this.updatePresence(friend.presence); } else { - const user = await this.nso.getCurrentUser(); + const user = !(this.nso instanceof ZncProxyApi) ? + await this.nso.getCurrentUser() : {result: { + ...this.data.nsoAccount.user, + presence: await this.nso.fetch('/user/presence'), + }}; if (this.argv.friendNotifications) { - const activeevent = await this.nso.getActiveEvent(); + if (!(this.nso instanceof ZncProxyApi)) await this.nso.getActiveEvent(); const friends = await this.nso.getFriendList(); - const webservices = await this.nso.getWebServices(); - + if (!(this.nso instanceof ZncProxyApi)) await this.nso.getWebServices(); + await this.updateFriendsStatusForNotifications(this.argv.userNotifications ? [user.result, ...friends.result.friends] : friends.result.friends); } else if (this.argv.userNotifications) { @@ -275,3 +330,30 @@ class ZncDiscordPresence extends ZncNotifications { } } } + +class ZncProxyDiscordPresence extends ZncDiscordPresence { + constructor( + readonly argv: ArgumentsCamelCase, + public presence_url: string + ) { + super(argv, null!, null!, null!, null!); + } + + async init() { + await this.update(); + + await new Promise(rs => setTimeout(rs, this.argv.updateInterval * 1000)); + } + + async update() { + const response = await fetch(this.presence_url); + debugProxy('fetch %s %s, response %s', 'GET', this.presence_url, response.status); + if (response.status !== 200) { + console.error('Non-200 status code', await response.text()); + throw new Error('Unknown error'); + } + const presence = await response.json() as Presence; + + await this.updatePresence(presence); + } +} diff --git a/src/cli/token.ts b/src/cli/token.ts index b415824..db43772 100644 --- a/src/cli/token.ts +++ b/src/cli/token.ts @@ -42,7 +42,7 @@ export async function handler(argv: ArgumentsCamelCase) { await storage.setItem('SessionToken', argv.token); if (argv.auth) { - const {nso, data} = await getToken(storage, argv.token); + const {nso, data} = await getToken(storage, argv.token, argv.zncProxyUrl); console.log('Authenticated as Nintendo Account %s (NA %s, NSO %s)', data.user.screenName, data.user.nickname, data.nsoAccount.user.name); diff --git a/src/cli/user.ts b/src/cli/user.ts index b21714e..27dd0f5 100644 --- a/src/cli/user.ts +++ b/src/cli/user.ts @@ -30,7 +30,7 @@ export async function handler(argv: ArgumentsCamelCase) { const token: string = argv.token || await storage.getItem('NintendoAccountToken.' + usernsid) || await storage.getItem('SessionToken'); - const {nso, data} = await getToken(storage, token); + const {nso, data} = await getToken(storage, token, argv.zncProxyUrl); if (argv.forceRefresh && 'expires_at' in data) { const user = await nso.getCurrentUser(); diff --git a/src/cli/users.ts b/src/cli/users.ts index fdc96a8..f479368 100644 --- a/src/cli/users.ts +++ b/src/cli/users.ts @@ -22,6 +22,7 @@ export function builder(yargs: Argv) { 'Screen name', 'Nickname', 'Country', + 'Nintendo Switch ID', 'Nintendo Switch username', ], }); @@ -37,6 +38,7 @@ export function builder(yargs: Argv) { cache.user.screenName, cache.user.nickname, cache.user.country, + cache.nsoAccount.user.nsaId, cache.nsoAccount.user.name, ]); } @@ -64,6 +66,10 @@ export function builder(yargs: Argv) { } await storage.setItem('SelectedUser', argv.user); + + const users = new Set(await storage.getItem('NintendoAccountIds') ?? []); + users.add(argv.user); + await storage.setItem('NintendoAccountIds', [...users]); }).command('forget ', 'Removes all data for a Nintendo Account', yargs => { return yargs.positional('user', { describe: 'Nintendo Account ID', diff --git a/src/cli/webservices.ts b/src/cli/webservices.ts index d6199cd..085b0c9 100644 --- a/src/cli/webservices.ts +++ b/src/cli/webservices.ts @@ -30,7 +30,7 @@ export async function handler(argv: ArgumentsCamelCase) { const token: string = argv.token || await storage.getItem('NintendoAccountToken.' + usernsid) || await storage.getItem('SessionToken'); - const {nso, data} = await getToken(storage, token); + const {nso, data} = await getToken(storage, token, argv.zncProxyUrl); const announcements = await nso.getAnnouncements(); const friends = await nso.getFriendList(); diff --git a/src/cli/webservicetoken.ts b/src/cli/webservicetoken.ts index fc8faf9..a314cfd 100644 --- a/src/cli/webservicetoken.ts +++ b/src/cli/webservicetoken.ts @@ -31,7 +31,7 @@ export async function handler(argv: ArgumentsCamelCase) { const token: string = argv.token || await storage.getItem('NintendoAccountToken.' + usernsid) || await storage.getItem('SessionToken'); - const {nso, data} = await getToken(storage, token); + const {nso, data} = await getToken(storage, token, argv.zncProxyUrl); const announcements = await nso.getAnnouncements(); const friends = await nso.getFriendList(); diff --git a/src/util.ts b/src/util.ts index 9038059..f289960 100644 --- a/src/util.ts +++ b/src/util.ts @@ -9,6 +9,7 @@ import { NintendoAccountToken, NintendoAccountUser } from './api/na.js'; import { AccountLogin, CurrentUser, Game } from './api/znc-types.js'; import ZncApi from './api/znc.js'; import titles, { defaultTitle } from './titles.js'; +import ZncProxyApi from './api/znc-proxy.js'; const debug = createDebug('cli'); @@ -26,6 +27,7 @@ export interface SavedToken { credential: AccountLogin['webApiServerCredential']; expires_at: number; + proxy_url?: string; } export async function initStorage(dir = path.join(import.meta.url.substr(7), '..', '..', 'data')) { @@ -37,7 +39,7 @@ export async function initStorage(dir = path.join(import.meta.url.substr(7), '.. return storage; } -export async function getToken(storage: persist.LocalStorage, token: string) { +export async function getToken(storage: persist.LocalStorage, token: string, proxy_url?: string) { if (!token) { console.error('No token set. Set a Nintendo Account session token using the `--token` option or by running `nintendo-znc token`.'); throw new Error('Invalid token'); @@ -49,23 +51,28 @@ export async function getToken(storage: persist.LocalStorage, token: string) { console.log('Authenticating to Nintendo Switch Online app'); debug('Authenticating to znc with session token'); - const data = await ZncApi.createWithSessionToken(token); + const {nso, data} = proxy_url ? + await ZncProxyApi.createWithSessionToken(proxy_url, token) : + await ZncApi.createWithSessionToken(token); const existingToken: SavedToken = { - ...data.data, - expires_at: Date.now() + (data.data.credential.expiresIn * 1000), + ...data, + expires_at: Date.now() + (data.credential.expiresIn * 1000), }; await storage.setItem('NsoToken.' + token, existingToken); - await storage.setItem('NintendoAccountToken.' + data.data.user.id, token); + await storage.setItem('NintendoAccountToken.' + data.user.id, token); - return data; + return {nso, data: existingToken}; } debug('Using existing token'); + await storage.setItem('NintendoAccountToken.' + existingToken.user.id, token); return { - nso: new ZncApi(existingToken.credential.accessToken), + nso: proxy_url ? + new ZncProxyApi(proxy_url, token) : + new ZncApi(existingToken.credential.accessToken), data: existingToken, }; }