I can't be bothered to even explain what happene here

This commit is contained in:
2025-02-07 23:18:04 -05:00
parent d982567c7b
commit bc3443b1dc
21 changed files with 994 additions and 177 deletions

8
.gitignore vendored
View File

@@ -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
rooms.json
/data/
firstrun

View File

@@ -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)
<img src="galv4.jpg" alt="drawing" width="200"/>

View File

@@ -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"
]
}
}

477
deno.lock generated
View File

@@ -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"
]
}
}

View File

@@ -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<T>(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<T>(typeDef: T) {
};
}
export function checkBodyTypes<T>(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"

View File

@@ -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? */

48
src/data/auth.ts Normal file
View File

@@ -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";

65
src/data/config.ts Normal file
View File

@@ -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";

View File

@@ -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
}

View File

@@ -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;
}
}

96
src/data/objectives.ts Normal file
View File

@@ -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";

12
src/data/users.ts Normal file
View File

@@ -0,0 +1,12 @@
interface UserInitOptions {
username: string,
password: string,
}
export class User {
static init() {
}
}

View File

@@ -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";

View File

@@ -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] });

48
src/dynamicconfig.ts Normal file
View File

@@ -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";

View File

@@ -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();
});
//Discord.login(); do not use for now

8
src/routes/api.ts Normal file
View File

@@ -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);

10
src/routes/api/config.ts Normal file
View File

@@ -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);
});

View File

@@ -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);
}
});

40
src/routes/nameserver.ts Normal file
View File

@@ -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);
});

9
src/types/express.ts Normal file
View File

@@ -0,0 +1,9 @@
import { Authentication } from "../data/auth.ts";
declare global {
namespace Express {
interface Locals {
auth: Authentication.GameAuthContext | null
}
}
}