From bc3443b1dc2273ea28edc3c79bfd51e82046e335 Mon Sep 17 00:00:00 2001 From: zombieb Date: Fri, 7 Feb 2025 23:18:04 -0500 Subject: [PATCH] I can't be bothered to even explain what happene here --- .gitignore | 8 +- README.md | 6 +- deno.json | 18 +- deno.lock | 477 +++++++++++++++++++++++++++------ src/apiutils.ts | 71 +++-- src/config.ts | 45 ++-- src/data/auth.ts | 48 ++++ src/data/config.ts | 65 +++++ src/data/content/avatar.ts | 17 ++ src/data/content/comsumable.ts | 61 +++++ src/data/objectives.ts | 96 +++++++ src/data/users.ts | 12 + src/db.ts | 39 +-- src/discord.ts | 3 +- src/dynamicconfig.ts | 48 ++++ src/main.ts | 66 +++-- src/routes/api.ts | 8 + src/routes/api/config.ts | 10 + src/routes/api/versioncheck.ts | 24 ++ src/routes/nameserver.ts | 40 +++ src/types/express.ts | 9 + 21 files changed, 994 insertions(+), 177 deletions(-) create mode 100644 src/data/auth.ts create mode 100644 src/data/config.ts create mode 100644 src/data/content/avatar.ts create mode 100644 src/data/content/comsumable.ts create mode 100644 src/data/objectives.ts create mode 100644 src/data/users.ts create mode 100644 src/dynamicconfig.ts create mode 100644 src/routes/api.ts create mode 100644 src/routes/api/config.ts create mode 100644 src/routes/api/versioncheck.ts create mode 100644 src/routes/nameserver.ts create mode 100644 src/types/express.ts diff --git a/.gitignore b/.gitignore index 3d65a9f..1ede7e3 100644 --- a/.gitignore +++ b/.gitignore @@ -129,9 +129,9 @@ dist .yarn/install-state.gz .pnp.* -# galvanic corrosion test builds +# galvanic corrosion build/ - -# galvanic corrosion local config config.json -rooms.json \ No newline at end of file +rooms.json +/data/ +firstrun \ No newline at end of file diff --git a/README.md b/README.md index 9a33692..1382def 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Galvanic Corrosion -delectable acids yum yum +delectable yum yum -Rec Room server for communities. Fast runtime and easy setup. -Built for Rec Room version September 7th, 2018 (manifest 7763898423339170417) +Rec Room custom server for communities. Fast runtime and easy setup. +Built for Rec Room build 526 (Timestamp: 637098805133024772, Version: 20191120) drawing \ No newline at end of file diff --git a/deno.json b/deno.json index ef3c2ab..f0403d1 100644 --- a/deno.json +++ b/deno.json @@ -1,18 +1,24 @@ { "tasks": { - "compile-win": "deno compile --target x86_64-pc-windows-msvc -o build/GalvanicCorrosion.exe --allow-env --allow-sys --allow-net --allow-write --allow-read src/main.ts", - "compile-linux": "deno compile --target x86_64-unknown-linux-gnu -o build/GalvanicCorrosion --allow-env --allow-sys --allow-net --allow-write --allow-read src/main.ts", + "compile-win": "deno compile --target x86_64-pc-windows-msvc -o build/GalvanicCorrosion.exe -A src/main.ts", + "compile-linux": "deno compile --target x86_64-unknown-linux-gnu -o build/GalvanicCorrosion -A src/main.ts", "cross-compile": "deno run compile-win && deno run compile-linux", - "start": "deno run --allow-env --allow-sys --allow-net --allow-write --allow-read src/main.ts" + "start": "deno run -A src/main.ts" }, "imports": { - "@oak/oak": "jsr:@oak/oak@^17.1.3", + "@gz/jwt": "jsr:@gz/jwt@^0.1.0", "@proxnet/undead-logging": "jsr:@proxnet/undead-logging@^1.2.0", "@std/assert": "jsr:@std/assert@1", "@types/express": "npm:@types/express@^5.0.0", "discord.js": "npm:discord.js@^14.16.3", - "ioredis": "npm:ioredis@^5.4.1", + "express": "npm:express@^4.21.2", + "ioredis": "npm:ioredis@^5.5.0", "validator": "npm:validator@^13.12.0", - "why-is-node-running": "npm:why-is-node-running@^3.2.1" + "bcrypt": "https://deno.land/x/bcrypt@v0.3.0/mod.ts" + }, + "compilerOptions": { + "types": [ + "./src/types" + ] } } diff --git a/deno.lock b/deno.lock index cce1ab5..65675e2 100644 --- a/deno.lock +++ b/deno.lock @@ -1,54 +1,21 @@ { "version": "4", "specifiers": { - "jsr:@oak/commons@1": "1.0.0", - "jsr:@oak/oak@^17.1.3": "17.1.3", + "jsr:@gz/jwt@0.1": "0.1.0", "jsr:@proxnet/undead-logging@^1.2.0": "1.2.0", "jsr:@std/assert@1": "1.0.7", - "jsr:@std/bytes@1": "1.0.4", - "jsr:@std/bytes@^1.0.2": "1.0.4", - "jsr:@std/crypto@1": "1.0.3", - "jsr:@std/encoding@1": "1.0.5", - "jsr:@std/encoding@^1.0.5": "1.0.5", - "jsr:@std/http@1": "1.0.10", "jsr:@std/internal@^1.0.5": "1.0.5", - "jsr:@std/io@0.224": "0.224.9", - "jsr:@std/media-types@1": "1.1.0", - "jsr:@std/path@1": "1.0.8", "npm:@types/express@5": "5.0.0", "npm:@types/node@*": "22.5.4", "npm:chalk@^5.3.0": "5.3.0", "npm:discord.js@^14.16.3": "14.16.3", - "npm:ioredis@^5.4.1": "5.4.1", - "npm:path-to-regexp@6.2.1": "6.2.1", - "npm:validator@^13.12.0": "13.12.0", - "npm:why-is-node-running@^3.2.1": "3.2.1" + "npm:express@^4.21.2": "4.21.2", + "npm:ioredis@^5.5.0": "5.5.0", + "npm:validator@^13.12.0": "13.12.0" }, "jsr": { - "@oak/commons@1.0.0": { - "integrity": "49805b55603c3627a9d6235c0655aa2b6222d3036b3a13ff0380c16368f607ac", - "dependencies": [ - "jsr:@std/assert", - "jsr:@std/bytes@1", - "jsr:@std/crypto", - "jsr:@std/encoding@1", - "jsr:@std/http", - "jsr:@std/media-types" - ] - }, - "@oak/oak@17.1.3": { - "integrity": "d89296c22db91681dd3a2a1e1fd14e258d0d5a9654de55637aee5b661c159f33", - "dependencies": [ - "jsr:@oak/commons", - "jsr:@std/assert", - "jsr:@std/bytes@1", - "jsr:@std/crypto", - "jsr:@std/http", - "jsr:@std/io", - "jsr:@std/media-types", - "jsr:@std/path", - "npm:path-to-regexp" - ] + "@gz/jwt@0.1.0": { + "integrity": "32b0235cebcb85d363459b20ccaab0d8424fab89883c9f65caa1e2ad37e78e8f" }, "@proxnet/undead-logging@1.2.0": { "integrity": "59a4db428b5b848b7f51189b173b100ddabf7d86bb9de1a095e5d97b4a867e2c", @@ -62,35 +29,8 @@ "jsr:@std/internal" ] }, - "@std/bytes@1.0.4": { - "integrity": "11a0debe522707c95c7b7ef89b478c13fb1583a7cfb9a85674cd2cc2e3a28abc" - }, - "@std/crypto@1.0.3": { - "integrity": "a2a32f51ddef632d299e3879cd027c630dcd4d1d9a5285d6e6788072f4e51e7f" - }, - "@std/encoding@1.0.5": { - "integrity": "ecf363d4fc25bd85bd915ff6733a7e79b67e0e7806334af15f4645c569fefc04" - }, - "@std/http@1.0.10": { - "integrity": "4e32d11493ab04e3ef09f104f0cb9beb4228b1d4b47c5469573c2c294c0d3692", - "dependencies": [ - "jsr:@std/encoding@^1.0.5" - ] - }, "@std/internal@1.0.5": { "integrity": "54a546004f769c1ac9e025abd15a76b6671ddc9687e2313b67376125650dc7ba" - }, - "@std/io@0.224.9": { - "integrity": "4414664b6926f665102e73c969cfda06d2c4c59bd5d0c603fd4f1b1c840d6ee3", - "dependencies": [ - "jsr:@std/bytes@^1.0.2" - ] - }, - "@std/media-types@1.1.0": { - "integrity": "c9d093f0c05c3512932b330e3cc1fe1d627b301db33a4c2c2185c02471d6eaa4" - }, - "@std/path@1.0.8": { - "integrity": "548fa456bb6a04d3c1a1e7477986b6cffbce95102d0bb447c67c4ee70e0364be" } }, "npm": { @@ -238,21 +178,92 @@ "@vladfrangu/async_event_emitter@2.4.6": { "integrity": "sha512-RaI5qZo6D2CVS6sTHFKg1v5Ohq/+Bo2LZ5gzUEwZ/WkHhwtGTCB/sVLw8ijOkAUxasZ+WshN/Rzj4ywsABJ5ZA==" }, + "accepts@1.3.8": { + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dependencies": [ + "mime-types", + "negotiator" + ] + }, + "array-flatten@1.1.1": { + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" + }, + "body-parser@1.20.3": { + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "dependencies": [ + "bytes", + "content-type", + "debug@2.6.9", + "depd", + "destroy", + "http-errors", + "iconv-lite", + "on-finished", + "qs", + "raw-body", + "type-is", + "unpipe" + ] + }, + "bytes@3.1.2": { + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==" + }, + "call-bind-apply-helpers@1.0.1": { + "integrity": "sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==", + "dependencies": [ + "es-errors", + "function-bind" + ] + }, + "call-bound@1.0.3": { + "integrity": "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==", + "dependencies": [ + "call-bind-apply-helpers", + "get-intrinsic" + ] + }, "chalk@5.3.0": { "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==" }, "cluster-key-slot@1.1.2": { "integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==" }, - "debug@4.3.7": { - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "content-disposition@0.5.4": { + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", "dependencies": [ - "ms" + "safe-buffer" + ] + }, + "content-type@1.0.5": { + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==" + }, + "cookie-signature@1.0.6": { + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" + }, + "cookie@0.7.1": { + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==" + }, + "debug@2.6.9": { + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": [ + "ms@2.0.0" + ] + }, + "debug@4.4.0": { + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dependencies": [ + "ms@2.1.3" ] }, "denque@2.1.0": { "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==" }, + "depd@2.0.0": { + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" + }, + "destroy@1.2.0": { + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==" + }, "discord-api-types@0.37.100": { "integrity": "sha512-a8zvUI0GYYwDtScfRd/TtaNBDTXwP5DiDVX7K5OmE+DRT57gBqKnwtOC5Ol8z0mRW8KQfETIgiB8U0YZ9NXiCA==" }, @@ -279,15 +290,160 @@ "undici" ] }, + "dunder-proto@1.0.1": { + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dependencies": [ + "call-bind-apply-helpers", + "es-errors", + "gopd" + ] + }, + "ee-first@1.1.1": { + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + }, + "encodeurl@1.0.2": { + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==" + }, + "encodeurl@2.0.0": { + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==" + }, + "es-define-property@1.0.1": { + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==" + }, + "es-errors@1.3.0": { + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==" + }, + "es-object-atoms@1.1.1": { + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dependencies": [ + "es-errors" + ] + }, + "escape-html@1.0.3": { + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, + "etag@1.8.1": { + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==" + }, + "express@4.21.2": { + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "dependencies": [ + "accepts", + "array-flatten", + "body-parser", + "content-disposition", + "content-type", + "cookie", + "cookie-signature", + "debug@2.6.9", + "depd", + "encodeurl@2.0.0", + "escape-html", + "etag", + "finalhandler", + "fresh", + "http-errors", + "merge-descriptors", + "methods", + "on-finished", + "parseurl", + "path-to-regexp", + "proxy-addr", + "qs", + "range-parser", + "safe-buffer", + "send", + "serve-static", + "setprototypeof", + "statuses", + "type-is", + "utils-merge", + "vary" + ] + }, "fast-deep-equal@3.1.3": { "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, - "ioredis@5.4.1": { - "integrity": "sha512-2YZsvl7jopIa1gaePkeMtd9rAcSjOOjPtpcLlOeusyO+XH2SK5ZcT+UCrElPP+WVIInh2TzeI4XW9ENaSLVVHA==", + "finalhandler@1.3.1": { + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "dependencies": [ + "debug@2.6.9", + "encodeurl@2.0.0", + "escape-html", + "on-finished", + "parseurl", + "statuses", + "unpipe" + ] + }, + "forwarded@0.2.0": { + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==" + }, + "fresh@0.5.2": { + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==" + }, + "function-bind@1.1.2": { + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==" + }, + "get-intrinsic@1.2.7": { + "integrity": "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA==", + "dependencies": [ + "call-bind-apply-helpers", + "es-define-property", + "es-errors", + "es-object-atoms", + "function-bind", + "get-proto", + "gopd", + "has-symbols", + "hasown", + "math-intrinsics" + ] + }, + "get-proto@1.0.1": { + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dependencies": [ + "dunder-proto", + "es-object-atoms" + ] + }, + "gopd@1.2.0": { + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==" + }, + "has-symbols@1.1.0": { + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==" + }, + "hasown@2.0.2": { + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dependencies": [ + "function-bind" + ] + }, + "http-errors@2.0.0": { + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dependencies": [ + "depd", + "inherits", + "setprototypeof", + "statuses", + "toidentifier" + ] + }, + "iconv-lite@0.4.24": { + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": [ + "safer-buffer" + ] + }, + "inherits@2.0.4": { + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "ioredis@5.5.0": { + "integrity": "sha512-7CutT89g23FfSa8MDoIFs2GYYa0PaNiW/OrT+nRyjRXHDZd17HmIgy+reOQ/yhh72NznNjGuS8kbCAcA4Ro4mw==", "dependencies": [ "@ioredis/commands", "cluster-key-slot", - "debug", + "debug@4.4.0", "denque", "lodash.defaults", "lodash.isarguments", @@ -296,6 +452,9 @@ "standard-as-callback" ] }, + "ipaddr.js@1.9.1": { + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" + }, "lodash.defaults@4.2.0": { "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==" }, @@ -311,11 +470,78 @@ "magic-bytes.js@1.10.0": { "integrity": "sha512-/k20Lg2q8LE5xiaaSkMXk4sfvI+9EGEykFS4b0CHHGWqDYU0bGUFSwchNOMA56D7TCs9GwVTkqe9als1/ns8UQ==" }, + "math-intrinsics@1.1.0": { + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==" + }, + "media-typer@0.3.0": { + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==" + }, + "merge-descriptors@1.0.3": { + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==" + }, + "methods@1.1.2": { + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==" + }, + "mime-db@1.52.0": { + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" + }, + "mime-types@2.1.35": { + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": [ + "mime-db" + ] + }, + "mime@1.6.0": { + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" + }, + "ms@2.0.0": { + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, "ms@2.1.3": { "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, - "path-to-regexp@6.2.1": { - "integrity": "sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==" + "negotiator@0.6.3": { + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==" + }, + "object-inspect@1.13.3": { + "integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==" + }, + "on-finished@2.4.1": { + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dependencies": [ + "ee-first" + ] + }, + "parseurl@1.3.3": { + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" + }, + "path-to-regexp@0.1.12": { + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==" + }, + "proxy-addr@2.0.7": { + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dependencies": [ + "forwarded", + "ipaddr.js" + ] + }, + "qs@6.13.0": { + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "dependencies": [ + "side-channel" + ] + }, + "range-parser@1.2.1": { + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" + }, + "raw-body@2.5.2": { + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "dependencies": [ + "bytes", + "http-errors", + "iconv-lite", + "unpipe" + ] }, "redis-errors@1.2.0": { "integrity": "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==" @@ -326,26 +552,117 @@ "redis-errors" ] }, + "safe-buffer@5.2.1": { + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + }, + "safer-buffer@2.1.2": { + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "send@0.19.0": { + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "dependencies": [ + "debug@2.6.9", + "depd", + "destroy", + "encodeurl@1.0.2", + "escape-html", + "etag", + "fresh", + "http-errors", + "mime", + "ms@2.1.3", + "on-finished", + "range-parser", + "statuses" + ] + }, + "serve-static@1.16.2": { + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "dependencies": [ + "encodeurl@2.0.0", + "escape-html", + "parseurl", + "send" + ] + }, + "setprototypeof@1.2.0": { + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "side-channel-list@1.0.0": { + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dependencies": [ + "es-errors", + "object-inspect" + ] + }, + "side-channel-map@1.0.1": { + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dependencies": [ + "call-bound", + "es-errors", + "get-intrinsic", + "object-inspect" + ] + }, + "side-channel-weakmap@1.0.2": { + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dependencies": [ + "call-bound", + "es-errors", + "get-intrinsic", + "object-inspect", + "side-channel-map" + ] + }, + "side-channel@1.1.0": { + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dependencies": [ + "es-errors", + "object-inspect", + "side-channel-list", + "side-channel-map", + "side-channel-weakmap" + ] + }, "standard-as-callback@2.1.0": { "integrity": "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==" }, + "statuses@2.0.1": { + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" + }, + "toidentifier@1.0.1": { + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" + }, "ts-mixer@6.0.4": { "integrity": "sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA==" }, "tslib@2.8.1": { "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" }, + "type-is@1.6.18": { + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dependencies": [ + "media-typer", + "mime-types" + ] + }, "undici-types@6.19.8": { "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==" }, "undici@6.19.8": { "integrity": "sha512-U8uCCl2x9TK3WANvmBavymRzxbfFYG+tAu+fgx3zxQy3qdagQqBLwJVrdyO1TBfUXvfKveMKJZhpvUYoOjM+4g==" }, + "unpipe@1.0.0": { + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==" + }, + "utils-merge@1.0.1": { + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==" + }, "validator@13.12.0": { "integrity": "sha512-c1Q0mCiPlgdTVVVIJIrBuxNicYE+t/7oKeI9MWLj3fh/uq2Pxh/3eeWbVZ4OcGW1TUf53At0njHw5SMdA3tmMg==" }, - "why-is-node-running@3.2.1": { - "integrity": "sha512-Tb2FUhB4vUsGQlfSquQLYkApkuPAFQXGFzxWKHHumVz2dK+X1RUm/HnID4+TfIGYJ1kTcwOaCk/buYCEJr6YjQ==" + "vary@1.1.2": { + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==" }, "ws@8.18.0": { "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==" @@ -353,14 +670,14 @@ }, "workspace": { "dependencies": [ - "jsr:@oak/oak@^17.1.3", + "jsr:@gz/jwt@0.1", "jsr:@proxnet/undead-logging@^1.2.0", "jsr:@std/assert@1", "npm:@types/express@5", "npm:discord.js@^14.16.3", - "npm:ioredis@^5.4.1", - "npm:validator@^13.12.0", - "npm:why-is-node-running@^3.2.1" + "npm:express@^4.21.2", + "npm:ioredis@^5.5.0", + "npm:validator@^13.12.0" ] } } diff --git a/src/apiutils.ts b/src/apiutils.ts index de603b2..7a02b1b 100644 --- a/src/apiutils.ts +++ b/src/apiutils.ts @@ -1,8 +1,22 @@ -import { Context, Next } from "@oak/oak"; +// @ts-types = "npm:@types/express" +import express from "express"; import Logging from "@proxnet/undead-logging"; const log = new Logging('APIUtils'); +interface AppRouter { + path: string, + router: express.Router +} + +export function createRouter(path: string) { + const router: AppRouter = { + path: path, + router: express.Router() + } + return router; +} + export function generateRandomString(length: number) { const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; let randomString = ''; @@ -18,12 +32,11 @@ export function generateRandomString(length: number) { const instanceId = generateRandomString(128); export function checkQueryTypes(typeDef: T) { - return (ctx: Context, nxt: Next) => { + return (rq: express.Request, rs: express.Response, nxt: express.NextFunction) => { for (const key in typeDef) { - if (typeof Object.fromEntries(ctx.request.url.searchParams)[key] !== typeof (typeDef)[key]) { - ctx.response.status = 400; - setContentType(ctx, 'application/json'); - ctx.response.body = JSON.stringify(genericResponseFormat(true, "One or more query parameters were invalid or not found.")); + if (typeof rq.query[key] !== typeof (typeDef)[key]) { + rs.statusCode = 400; + rs.json(genericResponseFormat(true, "One or more query parameters were invalid or not found.")); return; } } @@ -31,13 +44,12 @@ export function checkQueryTypes(typeDef: T) { }; } export function checkBodyTypes(typeDef: T) { - return async (ctx: Context, nxt: Next) => { + return (rq: express.Request, rs: express.Response, nxt: express.NextFunction) => { for (const key in typeDef) { - if (typeof (await ctx.request.body.json())[key] !== typeof (typeDef)[key]) { + if (typeof rq.body[key] !== typeof (typeDef)[key]) { log.e(`Body check for key '${key}' failed.`); - ctx.response.status = 400; - setContentType(ctx, 'application/json'); - ctx.response.body = JSON.stringify(genericResponseFormat(true, "One or more body values were invalid or not found.")); + rs.statusCode = 400; + rs.json(genericResponseFormat(true, "One or more body values were invalid or not found.")); return; } } @@ -49,9 +61,8 @@ export function genericResponseFormat(failure: boolean, msg: string | null = nul return { failed: failure, instance: instanceId, message: msg, data: data }; } export function genericResponse(failure: boolean, msg: string | null = null, data = null) { - return (ctx: Context) => { - setContentType(ctx, 'application/json'); - ctx.response.body = JSON.stringify({ failed: failure, instance: instanceId, message: msg, data: data }); + return (_rq: express.Request, rs: express.Response) => { + rs.json({ failed: failure, instance: instanceId, message: msg, data: data }); }; } type RecNetResponse = { @@ -60,29 +71,35 @@ type RecNetResponse = { }; export function RecNetResponse(success: boolean, message: string) { const msg: RecNetResponse = { Success: success, Message: message }; - return (ctx: Context) => { - setContentType(ctx, 'application/json'); - ctx.response.body = JSON.stringify(msg); + return (_rq: express.Request, rs: express.Response) => { + rs.json(msg); } } -export async function logBody(ctx: Context, nxt: Next) { +export function logBody(rq: express.Request, _rs: express.Response, nxt: express.NextFunction) { nxt(); - log.d(`Request body: ${JSON.stringify(await ctx.request.body.text())}`); + log.d(`Request body: ${JSON.stringify(rq.body)}`); } -export function emptyArrayResponse(ctx: Context) { - setContentType(ctx, 'application/json'); - ctx.response.body = JSON.stringify([]); +export function emptyArrayResponse(_rq: express.Request, rs: express.Response) { + rs.json([]); } -export function setJSONBody(ctx: Context, obj: object) { - ctx.response.type = 'json'; - ctx.response.body = JSON.stringify(obj); +export function getSrcIpDefault(rq: express.Request) { + const cfIp = rq.header('cf-connecting-ip'); + if (cfIp !== undefined) return cfIp; + + const xrIp = rq.header('x-real-ip'); + if (xrIp !== undefined) return xrIp; + + const ip = typeof rq.ip === 'undefined' ? '(unknown source)' : rq.ip; + return ip; } -export function setContentType(ctx: Context, type: string) { - ctx.response.headers.set('Content-Type', type); +export function statusResponse(code: number) { + return (_rq: express.Request, rs: express.Response) => { + rs.sendStatus(code); + } } export * as APIUtils from "./apiutils.ts" \ No newline at end of file diff --git a/src/config.ts b/src/config.ts index 3d07aeb..65a2ee3 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,6 +1,5 @@ import Logging from "@proxnet/undead-logging"; import * as fs from "node:fs"; -import process from "node:process"; const log = new Logging("Config"); @@ -14,13 +13,17 @@ type RedisConfiguration = { type WebConfiguration = { port: number, - host: string + host: string, + nameserverHost: string, + secureNameserverHost: boolean } type PublicConfiguration = { serverName: string, owner: string, - motd: string + motd: string, + levelScale: number, + maxLevels: number } type LoggingConfiguration = { @@ -34,15 +37,20 @@ type DiscordConfiguration = { guildId: string } -type GalvanicConfiguration = { +type SecretConfiguration = { + authSecret: string +} + +export type GalvanicConfiguration = { redis: RedisConfiguration, web: WebConfiguration, public: PublicConfiguration, logging: LoggingConfiguration, - discord: DiscordConfiguration + discord: DiscordConfiguration, + secrets: SecretConfiguration } -const defaultConfig: GalvanicConfiguration = { +export const defaultConfig: GalvanicConfiguration = { redis: { host: "127.0.0.1", port: 6379, @@ -52,12 +60,16 @@ const defaultConfig: GalvanicConfiguration = { }, web: { port: 3000, - host: "127.0.0.1" + host: "127.0.0.1", + nameserverHost: "127.0.0.1:3000", + secureNameserverHost: false }, public: { serverName: "Galvanic Corrosion", owner: "John Doe", - motd: "The narwhal bacons at midnight" + motd: "The narwhal bacons at midnight", + levelScale: 1, + maxLevels: 30 }, logging: { debug: false, @@ -67,6 +79,9 @@ const defaultConfig: GalvanicConfiguration = { token: "replace-me", guildId: "replace-me", clientId: "replace-me" + }, + secrets: { + authSecret: "CHANGE-ME-PLEASE" } } @@ -77,19 +92,7 @@ try { config = JSON.parse(fs.readFileSync('./config.json').toString()); } catch (err) { log.e(`Could not get config: ${err}`); - process.exit(1); -} - -/** - * Looks for a certain file in the current directory that shouldn't exist on the first run. - * Returns `false` when GC has ran at least once - */ -export function firstRun() { - if (!fs.existsSync('./firstrun')) return true; - else { - fs.writeFile('./firstrun', "", () => {}); - return false; - } + Deno.exit(1); } /** Does the configuration file exist on the disk? */ diff --git a/src/data/auth.ts b/src/data/auth.ts new file mode 100644 index 0000000..d51fbbf --- /dev/null +++ b/src/data/auth.ts @@ -0,0 +1,48 @@ +import { encode, decode } from "@gz/jwt"; +import { Config, GalvanicConfiguration } from "../config.ts"; +import Logging from "@proxnet/undead-logging"; + +const log = new Logging("Auth"); + +const config = Config.getConfig() as GalvanicConfiguration; + +type TokenFormat = { + iss: string; + sub: number; + nbf: number; + iat: number; + exp: number; +} + +export class GameAuthContext { + + valid: boolean | null = null; + + #rawToken: string + + playerId: number | null = null; + + constructor(token: string) { + + this.#rawToken = token; + + } + + async decode() { + try { + const decoded = await decode(this.#rawToken, config.secrets.authSecret) as TokenFormat; + this.playerId = decoded.sub || null; + const now = Math.round(Date.now() / 1000); + + this.valid = true; + if (decoded.exp < now) this.valid = false; + if (decoded.nbf > now) this.valid = false; + } catch (e) { + this.valid = false; + log.w(`Token decode failed: ${(e as Error).stack}`); + } + } + +} + +export * as Authentication from "./auth.ts"; \ No newline at end of file diff --git a/src/data/config.ts b/src/data/config.ts new file mode 100644 index 0000000..10bce51 --- /dev/null +++ b/src/data/config.ts @@ -0,0 +1,65 @@ +import { Config } from "../config.ts"; +import { Objectives } from "./objectives.ts"; + +export type Config = { + Key: string, + Value: string +} +export type LevelProgressionItem = { + Level: number, + RequiredXp: number +} +export type PublicConfig = { + MessageOfTheDay: string, + CdnBaseUri: string, + MatchmakingParams: { + PreferFullRoomsFrequency: number, + PreferEmptyRoomsFrequency: number + }, + ServerMaintenance: { + StartsInMinutes: number + }, + LevelProgressionMaps: LevelProgressionItem[], + DailyObjectives: Objectives.Objective[][], + ConfigTable: Config[], + PhotonConfig: { + CrcCheckEnabled: boolean, + EnableServerTracingAfterDisconnect: boolean + } +} + +export function getConfig() { + const c = Config.getConfig(); + if (typeof c == 'undefined') return null; + const config = c as Config.GalvanicConfiguration; + + function generateLevelProgressionMap() { + const m: LevelProgressionItem[] = []; + for (let i = 0; i < config.public.maxLevels + 1; i++) + m.push({Level: i, RequiredXp: Math.round(i * config.public.levelScale * 20)}); + return m; + } + + const conf: PublicConfig = { + MessageOfTheDay: config.public.motd, + CdnBaseUri: `${config.web.secureNameserverHost ? 'https' : 'http'}://${c.web.nameserverHost}/{0}`, + MatchmakingParams: { + PreferFullRoomsFrequency: 1, + PreferEmptyRoomsFrequency: 0 + }, + ServerMaintenance: { + StartsInMinutes: 0 + }, + LevelProgressionMaps: generateLevelProgressionMap(), + DailyObjectives: [], + ConfigTable: [], + PhotonConfig: { + CrcCheckEnabled: false, + EnableServerTracingAfterDisconnect: false + } + } + + return conf; +} + +export * as GameConfigs from "./config.ts"; \ No newline at end of file diff --git a/src/data/content/avatar.ts b/src/data/content/avatar.ts new file mode 100644 index 0000000..939a526 --- /dev/null +++ b/src/data/content/avatar.ts @@ -0,0 +1,17 @@ +enum AvatarItemType { + None = -1, + Hat, + BackHead, + Hair, + Eye = 10, + Mouth = 20, + Neck = 100, + Shirt, + Belt, + Pocket, + TeamJersey, + Wrist = 200, + Glove, + Watch, + TeamWrist +} \ No newline at end of file diff --git a/src/data/content/comsumable.ts b/src/data/content/comsumable.ts new file mode 100644 index 0000000..53af09c --- /dev/null +++ b/src/data/content/comsumable.ts @@ -0,0 +1,61 @@ +export enum Consumable { + ASSORTED_DONUTS, + SUPREME_PIZZA, + ROOT_BEER, + CHOCOLATE_FROSTED_DONUTS, + CHEESE_PIZZA, + PEPPERONI_PIZZA, + GLAZED_DONUTS +} + +const ids = [ + "ZuvkidodzkuOfGLDnTOFyg", + "wUCIKdJSvEmiQHYMyx4X4w", + "JfnVXFmilU6ysv-VbTAe3A", + "mMCGPgK3tki5S_15q2Z81A", + "5hIAZ9wg5EyG1cILf4FS2A", + "mq23W-RSP0G8iGNLdrcpUw", + "7OZ5AE3uuUyqa0P-2W1ptg" +] as const; + +export class ConsumableSelection { + + type: Consumable; + + guid: string; + + constructor(type: Consumable) { + + this.type = type; + this.guid = ids[type]; + + } + +} + +export class ConsumableBuilder { + + Id: number; + + ConsumableItemDesc: string; + + CreatedAt: string; + + Count: number; + + UnlockedLevel: number; + + IsActive: boolean; + + constructor(selection: ConsumableSelection, id: number, createdAt: Date, count: number, active: boolean) { + + this.Id = id; + this.ConsumableItemDesc = selection.guid; + this.CreatedAt = createdAt.toUTCString(); + this.Count = count; + this.UnlockedLevel = 0; // All players have access to every consumable - avatars and equipment are different + this.IsActive = active; + + } + +} \ No newline at end of file diff --git a/src/data/objectives.ts b/src/data/objectives.ts new file mode 100644 index 0000000..b3b085c --- /dev/null +++ b/src/data/objectives.ts @@ -0,0 +1,96 @@ +export enum ObjectiveType { + Default = -1, + FirstSessionOfDay = 1, + AddAFriend, + PartyUp, + AllOtherChallenges, + LevelUp, + CheerAPlayer, + PointedAtPlayer, + CheerARoom, + SubscribeToPlayer, + DailyObjective1, + DailyObjective2, + DailyObjective3, + AllDailyObjectives, + CompleteAnyDaily, + CompleteAnyWeekly, + OOBE_GoToLockerRoom = 20, + OOBE_GoToActivity, + OOBE_FinishActivity, + NUX_PunchcardObjective = 25, + NUX_AllPunchcardObjectives, + GoToRecCenter = 30, + FinishActivity, + VisitACustomRoom, + CreateACustomRoom, + ScoreBasketInRecCenter = 35, + UploadPhotoToRecNet, + UpdatePlayerBio, + SaveOutfitSlot, + PurchaseClothingItem, + PurchaseNonClothingItem, + CharadesGames = 100, + CharadesWinsPerformer, + CharadesWinsGuesser, + DiscGolfWins = 200, + DiscGolfGames, + DiscGolfHolesUnderPar, + DodgeballWins = 300, + DodgeballGames, + DodgeballHits, + PaddleballGames = 400, + PaddleballWins, + PaddleballScores, + PaintballAnyModeGames = 500, + PaintballAnyModeWins, + PaintballAnyModeHits, + PaintballCTFWins = 600, + PaintballCTFGames, + PaintballCTFHits, + PaintballFlagCaptures, + PaintballTeamBattleWins = 700, + PaintballTeamBattleGames, + PaintballTeamBattleHits, + PaintballFreeForAllWins = 710, + PaintballFreeForAllGames, + PaintballFreeForAllHits, + SoccerWins = 800, + SoccerGames, + SoccerGoals, + QuestGames = 1000, + QuestWins, + QuestPlayerRevives, + QuestEnemyKills, + QuestGames_Goblin1 = 1010, + QuestWins_Goblin1, + QuestPlayerRevives_Goblin1, + QuestEnemyKills_Goblin1, + QuestGames_Goblin2 = 1020, + QuestWins_Goblin2, + QuestPlayerRevives_Goblin2, + QuestEnemyKills_Goblin2, + QuestGames_Scifi1 = 1030, + QuestWins_Scifi1, + QuestPlayerRevives_Scifi1, + QuestEnemyKills_Scifi1, + QuestGames_Pirate1 = 1040, + QuestWins_Pirate1, + QuestPlayerRevives_Pirate1, + QuestEnemyKills_Pirate1, + ArenaGames = 2000, + ArenaWins, + ArenaPlayerRevives, + ArenaHeroTags, + ArenaBotTags, + RecRoyaleGames = 3000, + RecRoyaleWins, + RecRoyaleTags +} + +export type Objective = { + type: ObjectiveType, + score: number +} + +export * as Objectives from "./objectives.ts"; \ No newline at end of file diff --git a/src/data/users.ts b/src/data/users.ts new file mode 100644 index 0000000..d36af41 --- /dev/null +++ b/src/data/users.ts @@ -0,0 +1,12 @@ +interface UserInitOptions { + username: string, + password: string, +} + +export class User { + + static init() { + + } + +} \ No newline at end of file diff --git a/src/db.ts b/src/db.ts index b3a9c66..c56880c 100644 --- a/src/db.ts +++ b/src/db.ts @@ -1,14 +1,13 @@ import { Redis } from "ioredis"; import * as Config from "./config.ts"; import Logging from "@proxnet/undead-logging"; -import process from "node:process"; const log = new Logging("RedisDB"); const config = Config.getConfig(); if (typeof config == 'undefined') { log.e(`Cannot start: Redis configuration failed`); - process.exit(1); + Deno.exit(1); } let shuttingDown = false; @@ -16,18 +15,19 @@ Deno.addSignalListener('SIGINT', () => { if (shuttingDown) return; shuttingDown = true; log.n('Disconnecting from Redis'); - if (typeof Database !== 'undefined') Database.quit(); + Database.quit(); }); -export let Database: Redis | undefined; +export const Database = new Redis({ + port: config?.redis.port, + host: config?.redis.host, + username: config?.redis.username == "" ? undefined : config?.redis.username, + password: config?.redis.password == "" ? undefined : config?.redis.password, + db: config?.redis.db, + lazyConnect: true +}); export function connectToRedis() { - Database = new Redis({ - port: config?.redis.port, - host: config?.redis.host, - username: config?.redis.username == "" ? undefined : config?.redis.username, - password: config?.redis.password == "" ? undefined : config?.redis.password, - db: config?.redis.db - }); + Database.connect(); log.i(`Connected to Redis`); } @@ -35,13 +35,18 @@ export function buildKey(...args: string[]) { return args.join(':'); } export const KeyGroups = { + Config: { + Root: "config", + Dynamic: "dynamic" + }, Accounts: { - Ids: "account-ids", - Usernames: "account-usernames", - DisplayNames: "account-displaynames", - XP: "account-scores", - Developers: "account-developers", - ProfileImages: "account-images" + Root: "accounts", + Ids: "ids", + Usernames: "usernames", + DisplayNames: "displaynames", + XP: "scores", + Developers: "developers", + ProfileImages: "images" } } export * as Redis from "./db.ts"; \ No newline at end of file diff --git a/src/discord.ts b/src/discord.ts index 91bfe7b..eebe1b7 100644 --- a/src/discord.ts +++ b/src/discord.ts @@ -1,14 +1,13 @@ import * as discord from "discord.js"; import { Config } from "./config.ts"; import Logging from "@proxnet/undead-logging"; -import process from "node:process"; const log = new Logging("Discord"); const config = Config.getConfig(); if (typeof config == 'undefined') { log.e(`Cannot start: Discord configuration is unavailable`); - process.exit(1); + Deno.exit(1); } export const client = new discord.Client({ intents: [discord.GatewayIntentBits.Guilds, discord.GatewayIntentBits.GuildPresences] }); diff --git a/src/dynamicconfig.ts b/src/dynamicconfig.ts new file mode 100644 index 0000000..0e9d3da --- /dev/null +++ b/src/dynamicconfig.ts @@ -0,0 +1,48 @@ +import { KeyGroups, Redis } from "./db.ts"; + +export enum ResultType { + Found, + NotFound +} + +interface ConfigResult { + Status: ResultType, + Data: string | null +} +interface ConfigMResult { + Status: ResultType, + Data: (string | null)[] | null +} + +/** Get a dyamic config. */ +export async function getConfig(key: string) { + const res: ConfigResult = { + Status: ResultType.Found, + Data: null + } + + const data = await Redis.Database.get(`${KeyGroups.Config.Root}:${KeyGroups.Config.Dynamic}:${key}`); + if (data == null) res.Status = ResultType.NotFound; + else res.Data = data; + + return res; +} + +export async function mgetConfig(...keys: string[]) { + const res: ConfigMResult = { + Status: ResultType.Found, + Data: null + } + + const data = await Redis.Database.mget(...keys); + res.Data = data; + + return res; +} + +/** Set a dynamic config. */ +export async function setConfig(key: string, value: string) { + await Redis.Database.set(`${KeyGroups.Config.Root}:${KeyGroups.Config.Dynamic}:${key}`, value); +} + +export * as DynamicConfig from "./dynamicconfig.ts"; \ No newline at end of file diff --git a/src/main.ts b/src/main.ts index 579067d..3a4e1dc 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,11 +1,11 @@ -import Logging from "@proxnet/undead-logging"; +import * as Log from "@proxnet/undead-logging"; import * as Config from "./config.ts"; -import { Application, Router } from "@oak/oak"; +// @ts-types = 'npm:@types/express' +import express from "express"; import { Redis } from "./db.ts"; -import { Discord } from "./discord.ts"; import { APIUtils } from "./apiutils.ts"; -const log = new Logging("Main"); +const log = new Log.default("Main"); log.i(`Starting Galvanic Corrosion..`); @@ -15,16 +15,44 @@ if (typeof config == 'undefined') { log.e('Cannot start: Configuration is undefined'); Deno.exit(1); } +if (config.secrets.authSecret == Config.defaultConfig.secrets.authSecret) { + log.e(`Cannot start: Auth secret is default. Please change 'secrets.authSecret' in 'config.json'`); + Deno.exit(1); +} + +Log.MessageTypeVisibility.Network = config.logging.network; +Log.MessageTypeVisibility.Debug = config.logging.debug; const port = config.web.port; const host = config.web.host; log.i(`Starting HTTP server on http://${host}:${port}`); -const abortController = new AbortController(); -const app = new Application(); +const app = express(); -app.use(new Router().all('/', APIUtils.genericResponse(false, `${config?.public.serverName} - ${config?.public.motd}`)).routes()); +app.disable('etag'); +app.disable('x-powered-by'); + +app.use((rq: express.Request, rs: express.Response, nxt: express.NextFunction) => { + rs.locals.auth = null; + log.n(`${APIUtils.getSrcIpDefault(rq)} ${rq.method} ${rq.originalUrl}`); + nxt(); +}); + +app.use('/', APIUtils.genericResponse(false, `${config?.public.serverName} - ${config?.public.motd}`)); + +// content routes +const nameserverRouter = await import('./routes/nameserver.ts'); +const apiRouter = await import('./routes/api.ts'); + +app.use(nameserverRouter.route.path, nameserverRouter.route.router); +app.use(apiRouter.route.path, apiRouter.route.router); + +app.use((rq: express.Request, rs: express.Response) => { + log.e(`${APIUtils.getSrcIpDefault(rq)} 404 ${rq.method} ${rq.url.toString()}`); + rs.statusCode = 404; + rs.json(APIUtils.genericResponseFormat(true, 'Endpoint not found. Check your syntax and/or method.')); +}); try { log.i(`Connecting to Redis..`); @@ -35,18 +63,22 @@ try { } try { - app.listen({port: port, hostname: host, signal: abortController.signal }); + const http = app.listen(config.web.port, config.web.host, () => { + log.n(`Listening on http://${config.web.host}`); + + let shuttingDown = false; + Deno.addSignalListener('SIGINT', () => { + if (shuttingDown) return; + shuttingDown = true; + log.i(`Shutting down`); + + http.close(); + http.removeAllListeners(); + }); + }); } catch (err) { log.e(`Cannot start: Network could not be initalized. ${err}`); Deno.exit(1); } -Discord.login(); - -let shuttingDown = false; -Deno.addSignalListener('SIGINT', () => { - if (shuttingDown) return; - shuttingDown = true; - log.n(`Shutting down`); - abortController.abort(); -}); \ No newline at end of file +//Discord.login(); do not use for now \ No newline at end of file diff --git a/src/routes/api.ts b/src/routes/api.ts new file mode 100644 index 0000000..9536843 --- /dev/null +++ b/src/routes/api.ts @@ -0,0 +1,8 @@ +import { route as VersionCheckRoute } from "./api/versioncheck.ts"; +import { route as ConfigRoute } from "./api/config.ts"; +import { APIUtils } from "../apiutils.ts"; + +export const route = APIUtils.createRouter('/api'); + +route.router.use(VersionCheckRoute.router); +route.router.use(ConfigRoute.router); \ No newline at end of file diff --git a/src/routes/api/config.ts b/src/routes/api/config.ts new file mode 100644 index 0000000..64cc12f --- /dev/null +++ b/src/routes/api/config.ts @@ -0,0 +1,10 @@ +import { APIUtils } from "../../apiutils.ts"; +import { GameConfigs } from "../../data/config.ts"; + +export const route = APIUtils.createRouter('/config'); + +route.router.get('/v2', (rq, rs) => { + const config = GameConfigs.getConfig(); + if (config == null) rs.sendStatus(500); + else rs.json(config); +}); \ No newline at end of file diff --git a/src/routes/api/versioncheck.ts b/src/routes/api/versioncheck.ts new file mode 100644 index 0000000..0fcde4b --- /dev/null +++ b/src/routes/api/versioncheck.ts @@ -0,0 +1,24 @@ +import { APIUtils } from "../../apiutils.ts"; + +export const route = APIUtils.createRouter('/versioncheck'); + +const validVersion = '20191120'; + +type ValidVersionResponse = { + ValidVersion: boolean +} + +route.router.get('/v3', (rq, rs) => { + const requestedVer = rq.query['v']; + if (typeof requestedVer !== 'string' || requestedVer !== validVersion) { + const res: ValidVersionResponse = { + ValidVersion: false + } + rs.json(res); + } else { + const res: ValidVersionResponse = { + ValidVersion: true + } + rs.json(res); + } +}); \ No newline at end of file diff --git a/src/routes/nameserver.ts b/src/routes/nameserver.ts new file mode 100644 index 0000000..54604e4 --- /dev/null +++ b/src/routes/nameserver.ts @@ -0,0 +1,40 @@ +import { APIUtils } from "../apiutils.ts"; +import { Config } from "../config.ts"; + +const config = Config.getConfig() as Config.GalvanicConfiguration; +const protocol = config.web.secureNameserverHost ? 'https' : 'http'; + +export const route = APIUtils.createRouter('/ns'); + +type NameserverHosts = { + Auth: string, + API: string, + WWW: string, + Notifications: string, + Images: string, + CDN: string, + Commerce: string, + Matchmaking: string, + Storage: string, + Chat: string, + Leaderboard: string +} + +const path = `${protocol}://${config.web.nameserverHost}`; +const nameserver: NameserverHosts = { + Auth: path, + API: path, + WWW: path, + Notifications: path, + Images: path, + CDN: path, + Commerce: path, + Matchmaking: path, + Storage: path, + Chat: path, + Leaderboard: path +} + +route.router.get('*', (_rq, rs) => { + rs.json(nameserver); +}); \ No newline at end of file diff --git a/src/types/express.ts b/src/types/express.ts new file mode 100644 index 0000000..0246f82 --- /dev/null +++ b/src/types/express.ts @@ -0,0 +1,9 @@ +import { Authentication } from "../data/auth.ts"; + +declare global { + namespace Express { + interface Locals { + auth: Authentication.GameAuthContext | null + } + } +} \ No newline at end of file