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)
\ 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