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 .yarn/install-state.gz
.pnp.* .pnp.*
# galvanic corrosion test builds # galvanic corrosion
build/ build/
# galvanic corrosion local config
config.json config.json
rooms.json rooms.json
/data/
firstrun

View File

@@ -1,7 +1,7 @@
# Galvanic Corrosion # Galvanic Corrosion
delectable acids yum yum delectable yum yum
Rec Room server for communities. Fast runtime and easy setup. Rec Room custom server for communities. Fast runtime and easy setup.
Built for Rec Room version September 7th, 2018 (manifest 7763898423339170417) Built for Rec Room build 526 (Timestamp: 637098805133024772, Version: 20191120)
<img src="galv4.jpg" alt="drawing" width="200"/> <img src="galv4.jpg" alt="drawing" width="200"/>

View File

@@ -1,18 +1,24 @@
{ {
"tasks": { "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-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 --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 -A src/main.ts",
"cross-compile": "deno run compile-win && deno run compile-linux", "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": { "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", "@proxnet/undead-logging": "jsr:@proxnet/undead-logging@^1.2.0",
"@std/assert": "jsr:@std/assert@1", "@std/assert": "jsr:@std/assert@1",
"@types/express": "npm:@types/express@^5.0.0", "@types/express": "npm:@types/express@^5.0.0",
"discord.js": "npm:discord.js@^14.16.3", "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", "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", "version": "4",
"specifiers": { "specifiers": {
"jsr:@oak/commons@1": "1.0.0", "jsr:@gz/jwt@0.1": "0.1.0",
"jsr:@oak/oak@^17.1.3": "17.1.3",
"jsr:@proxnet/undead-logging@^1.2.0": "1.2.0", "jsr:@proxnet/undead-logging@^1.2.0": "1.2.0",
"jsr:@std/assert@1": "1.0.7", "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/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/express@5": "5.0.0",
"npm:@types/node@*": "22.5.4", "npm:@types/node@*": "22.5.4",
"npm:chalk@^5.3.0": "5.3.0", "npm:chalk@^5.3.0": "5.3.0",
"npm:discord.js@^14.16.3": "14.16.3", "npm:discord.js@^14.16.3": "14.16.3",
"npm:ioredis@^5.4.1": "5.4.1", "npm:express@^4.21.2": "4.21.2",
"npm:path-to-regexp@6.2.1": "6.2.1", "npm:ioredis@^5.5.0": "5.5.0",
"npm:validator@^13.12.0": "13.12.0", "npm:validator@^13.12.0": "13.12.0"
"npm:why-is-node-running@^3.2.1": "3.2.1"
}, },
"jsr": { "jsr": {
"@oak/commons@1.0.0": { "@gz/jwt@0.1.0": {
"integrity": "49805b55603c3627a9d6235c0655aa2b6222d3036b3a13ff0380c16368f607ac", "integrity": "32b0235cebcb85d363459b20ccaab0d8424fab89883c9f65caa1e2ad37e78e8f"
"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"
]
}, },
"@proxnet/undead-logging@1.2.0": { "@proxnet/undead-logging@1.2.0": {
"integrity": "59a4db428b5b848b7f51189b173b100ddabf7d86bb9de1a095e5d97b4a867e2c", "integrity": "59a4db428b5b848b7f51189b173b100ddabf7d86bb9de1a095e5d97b4a867e2c",
@@ -62,35 +29,8 @@
"jsr:@std/internal" "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": { "@std/internal@1.0.5": {
"integrity": "54a546004f769c1ac9e025abd15a76b6671ddc9687e2313b67376125650dc7ba" "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": { "npm": {
@@ -238,21 +178,92 @@
"@vladfrangu/async_event_emitter@2.4.6": { "@vladfrangu/async_event_emitter@2.4.6": {
"integrity": "sha512-RaI5qZo6D2CVS6sTHFKg1v5Ohq/+Bo2LZ5gzUEwZ/WkHhwtGTCB/sVLw8ijOkAUxasZ+WshN/Rzj4ywsABJ5ZA==" "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": { "chalk@5.3.0": {
"integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==" "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w=="
}, },
"cluster-key-slot@1.1.2": { "cluster-key-slot@1.1.2": {
"integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==" "integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA=="
}, },
"debug@4.3.7": { "content-disposition@0.5.4": {
"integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
"dependencies": [ "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": { "denque@2.1.0": {
"integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==" "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": { "discord-api-types@0.37.100": {
"integrity": "sha512-a8zvUI0GYYwDtScfRd/TtaNBDTXwP5DiDVX7K5OmE+DRT57gBqKnwtOC5Ol8z0mRW8KQfETIgiB8U0YZ9NXiCA==" "integrity": "sha512-a8zvUI0GYYwDtScfRd/TtaNBDTXwP5DiDVX7K5OmE+DRT57gBqKnwtOC5Ol8z0mRW8KQfETIgiB8U0YZ9NXiCA=="
}, },
@@ -279,15 +290,160 @@
"undici" "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": { "fast-deep-equal@3.1.3": {
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
}, },
"ioredis@5.4.1": { "finalhandler@1.3.1": {
"integrity": "sha512-2YZsvl7jopIa1gaePkeMtd9rAcSjOOjPtpcLlOeusyO+XH2SK5ZcT+UCrElPP+WVIInh2TzeI4XW9ENaSLVVHA==", "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": [ "dependencies": [
"@ioredis/commands", "@ioredis/commands",
"cluster-key-slot", "cluster-key-slot",
"debug", "debug@4.4.0",
"denque", "denque",
"lodash.defaults", "lodash.defaults",
"lodash.isarguments", "lodash.isarguments",
@@ -296,6 +452,9 @@
"standard-as-callback" "standard-as-callback"
] ]
}, },
"ipaddr.js@1.9.1": {
"integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="
},
"lodash.defaults@4.2.0": { "lodash.defaults@4.2.0": {
"integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==" "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ=="
}, },
@@ -311,11 +470,78 @@
"magic-bytes.js@1.10.0": { "magic-bytes.js@1.10.0": {
"integrity": "sha512-/k20Lg2q8LE5xiaaSkMXk4sfvI+9EGEykFS4b0CHHGWqDYU0bGUFSwchNOMA56D7TCs9GwVTkqe9als1/ns8UQ==" "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": { "ms@2.1.3": {
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
}, },
"path-to-regexp@6.2.1": { "negotiator@0.6.3": {
"integrity": "sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==" "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": { "redis-errors@1.2.0": {
"integrity": "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==" "integrity": "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w=="
@@ -326,26 +552,117 @@
"redis-errors" "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": { "standard-as-callback@2.1.0": {
"integrity": "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==" "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": { "ts-mixer@6.0.4": {
"integrity": "sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA==" "integrity": "sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA=="
}, },
"tslib@2.8.1": { "tslib@2.8.1": {
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" "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": { "undici-types@6.19.8": {
"integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==" "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw=="
}, },
"undici@6.19.8": { "undici@6.19.8": {
"integrity": "sha512-U8uCCl2x9TK3WANvmBavymRzxbfFYG+tAu+fgx3zxQy3qdagQqBLwJVrdyO1TBfUXvfKveMKJZhpvUYoOjM+4g==" "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": { "validator@13.12.0": {
"integrity": "sha512-c1Q0mCiPlgdTVVVIJIrBuxNicYE+t/7oKeI9MWLj3fh/uq2Pxh/3eeWbVZ4OcGW1TUf53At0njHw5SMdA3tmMg==" "integrity": "sha512-c1Q0mCiPlgdTVVVIJIrBuxNicYE+t/7oKeI9MWLj3fh/uq2Pxh/3eeWbVZ4OcGW1TUf53At0njHw5SMdA3tmMg=="
}, },
"why-is-node-running@3.2.1": { "vary@1.1.2": {
"integrity": "sha512-Tb2FUhB4vUsGQlfSquQLYkApkuPAFQXGFzxWKHHumVz2dK+X1RUm/HnID4+TfIGYJ1kTcwOaCk/buYCEJr6YjQ==" "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="
}, },
"ws@8.18.0": { "ws@8.18.0": {
"integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==" "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw=="
@@ -353,14 +670,14 @@
}, },
"workspace": { "workspace": {
"dependencies": [ "dependencies": [
"jsr:@oak/oak@^17.1.3", "jsr:@gz/jwt@0.1",
"jsr:@proxnet/undead-logging@^1.2.0", "jsr:@proxnet/undead-logging@^1.2.0",
"jsr:@std/assert@1", "jsr:@std/assert@1",
"npm:@types/express@5", "npm:@types/express@5",
"npm:discord.js@^14.16.3", "npm:discord.js@^14.16.3",
"npm:ioredis@^5.4.1", "npm:express@^4.21.2",
"npm:validator@^13.12.0", "npm:ioredis@^5.5.0",
"npm:why-is-node-running@^3.2.1" "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"; import Logging from "@proxnet/undead-logging";
const log = new Logging('APIUtils'); 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) { export function generateRandomString(length: number) {
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
let randomString = ''; let randomString = '';
@@ -18,12 +32,11 @@ export function generateRandomString(length: number) {
const instanceId = generateRandomString(128); const instanceId = generateRandomString(128);
export function checkQueryTypes<T>(typeDef: T) { 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) { for (const key in typeDef) {
if (typeof Object.fromEntries(ctx.request.url.searchParams)[key] !== typeof (typeDef)[key]) { if (typeof rq.query[key] !== typeof (typeDef)[key]) {
ctx.response.status = 400; rs.statusCode = 400;
setContentType(ctx, 'application/json'); rs.json(genericResponseFormat(true, "One or more query parameters were invalid or not found."));
ctx.response.body = JSON.stringify(genericResponseFormat(true, "One or more query parameters were invalid or not found."));
return; return;
} }
} }
@@ -31,13 +44,12 @@ export function checkQueryTypes<T>(typeDef: T) {
}; };
} }
export function checkBodyTypes<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) { 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.`); log.e(`Body check for key '${key}' failed.`);
ctx.response.status = 400; rs.statusCode = 400;
setContentType(ctx, 'application/json'); rs.json(genericResponseFormat(true, "One or more body values were invalid or not found."));
ctx.response.body = JSON.stringify(genericResponseFormat(true, "One or more body values were invalid or not found."));
return; return;
} }
} }
@@ -49,9 +61,8 @@ export function genericResponseFormat(failure: boolean, msg: string | null = nul
return { failed: failure, instance: instanceId, message: msg, data: data }; return { failed: failure, instance: instanceId, message: msg, data: data };
} }
export function genericResponse(failure: boolean, msg: string | null = null, data = null) { export function genericResponse(failure: boolean, msg: string | null = null, data = null) {
return (ctx: Context) => { return (_rq: express.Request, rs: express.Response) => {
setContentType(ctx, 'application/json'); rs.json({ failed: failure, instance: instanceId, message: msg, data: data });
ctx.response.body = JSON.stringify({ failed: failure, instance: instanceId, message: msg, data: data });
}; };
} }
type RecNetResponse = { type RecNetResponse = {
@@ -60,29 +71,35 @@ type RecNetResponse = {
}; };
export function RecNetResponse(success: boolean, message: string) { export function RecNetResponse(success: boolean, message: string) {
const msg: RecNetResponse = { Success: success, Message: message }; const msg: RecNetResponse = { Success: success, Message: message };
return (ctx: Context) => { return (_rq: express.Request, rs: express.Response) => {
setContentType(ctx, 'application/json'); rs.json(msg);
ctx.response.body = JSON.stringify(msg);
} }
} }
export async function logBody(ctx: Context, nxt: Next) { export function logBody(rq: express.Request, _rs: express.Response, nxt: express.NextFunction) {
nxt(); 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) { export function emptyArrayResponse(_rq: express.Request, rs: express.Response) {
setContentType(ctx, 'application/json'); rs.json([]);
ctx.response.body = JSON.stringify([]);
} }
export function setJSONBody(ctx: Context, obj: object) { export function getSrcIpDefault(rq: express.Request) {
ctx.response.type = 'json'; const cfIp = rq.header('cf-connecting-ip');
ctx.response.body = JSON.stringify(obj); 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) { export function statusResponse(code: number) {
ctx.response.headers.set('Content-Type', type); return (_rq: express.Request, rs: express.Response) => {
rs.sendStatus(code);
}
} }
export * as APIUtils from "./apiutils.ts" export * as APIUtils from "./apiutils.ts"

View File

@@ -1,6 +1,5 @@
import Logging from "@proxnet/undead-logging"; import Logging from "@proxnet/undead-logging";
import * as fs from "node:fs"; import * as fs from "node:fs";
import process from "node:process";
const log = new Logging("Config"); const log = new Logging("Config");
@@ -14,13 +13,17 @@ type RedisConfiguration = {
type WebConfiguration = { type WebConfiguration = {
port: number, port: number,
host: string host: string,
nameserverHost: string,
secureNameserverHost: boolean
} }
type PublicConfiguration = { type PublicConfiguration = {
serverName: string, serverName: string,
owner: string, owner: string,
motd: string motd: string,
levelScale: number,
maxLevels: number
} }
type LoggingConfiguration = { type LoggingConfiguration = {
@@ -34,15 +37,20 @@ type DiscordConfiguration = {
guildId: string guildId: string
} }
type GalvanicConfiguration = { type SecretConfiguration = {
authSecret: string
}
export type GalvanicConfiguration = {
redis: RedisConfiguration, redis: RedisConfiguration,
web: WebConfiguration, web: WebConfiguration,
public: PublicConfiguration, public: PublicConfiguration,
logging: LoggingConfiguration, logging: LoggingConfiguration,
discord: DiscordConfiguration discord: DiscordConfiguration,
secrets: SecretConfiguration
} }
const defaultConfig: GalvanicConfiguration = { export const defaultConfig: GalvanicConfiguration = {
redis: { redis: {
host: "127.0.0.1", host: "127.0.0.1",
port: 6379, port: 6379,
@@ -52,12 +60,16 @@ const defaultConfig: GalvanicConfiguration = {
}, },
web: { web: {
port: 3000, port: 3000,
host: "127.0.0.1" host: "127.0.0.1",
nameserverHost: "127.0.0.1:3000",
secureNameserverHost: false
}, },
public: { public: {
serverName: "Galvanic Corrosion", serverName: "Galvanic Corrosion",
owner: "John Doe", owner: "John Doe",
motd: "The narwhal bacons at midnight" motd: "The narwhal bacons at midnight",
levelScale: 1,
maxLevels: 30
}, },
logging: { logging: {
debug: false, debug: false,
@@ -67,6 +79,9 @@ const defaultConfig: GalvanicConfiguration = {
token: "replace-me", token: "replace-me",
guildId: "replace-me", guildId: "replace-me",
clientId: "replace-me" clientId: "replace-me"
},
secrets: {
authSecret: "CHANGE-ME-PLEASE"
} }
} }
@@ -77,19 +92,7 @@ try {
config = JSON.parse(fs.readFileSync('./config.json').toString()); config = JSON.parse(fs.readFileSync('./config.json').toString());
} catch (err) { } catch (err) {
log.e(`Could not get config: ${err}`); log.e(`Could not get config: ${err}`);
process.exit(1); Deno.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;
}
} }
/** Does the configuration file exist on the disk? */ /** 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 { Redis } from "ioredis";
import * as Config from "./config.ts"; import * as Config from "./config.ts";
import Logging from "@proxnet/undead-logging"; import Logging from "@proxnet/undead-logging";
import process from "node:process";
const log = new Logging("RedisDB"); const log = new Logging("RedisDB");
const config = Config.getConfig(); const config = Config.getConfig();
if (typeof config == 'undefined') { if (typeof config == 'undefined') {
log.e(`Cannot start: Redis configuration failed`); log.e(`Cannot start: Redis configuration failed`);
process.exit(1); Deno.exit(1);
} }
let shuttingDown = false; let shuttingDown = false;
@@ -16,18 +15,19 @@ Deno.addSignalListener('SIGINT', () => {
if (shuttingDown) return; if (shuttingDown) return;
shuttingDown = true; shuttingDown = true;
log.n('Disconnecting from Redis'); 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() { export function connectToRedis() {
Database = new Redis({ Database.connect();
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
});
log.i(`Connected to Redis`); log.i(`Connected to Redis`);
} }
@@ -35,13 +35,18 @@ export function buildKey(...args: string[]) {
return args.join(':'); return args.join(':');
} }
export const KeyGroups = { export const KeyGroups = {
Config: {
Root: "config",
Dynamic: "dynamic"
},
Accounts: { Accounts: {
Ids: "account-ids", Root: "accounts",
Usernames: "account-usernames", Ids: "ids",
DisplayNames: "account-displaynames", Usernames: "usernames",
XP: "account-scores", DisplayNames: "displaynames",
Developers: "account-developers", XP: "scores",
ProfileImages: "account-images" Developers: "developers",
ProfileImages: "images"
} }
} }
export * as Redis from "./db.ts"; export * as Redis from "./db.ts";

View File

@@ -1,14 +1,13 @@
import * as discord from "discord.js"; import * as discord from "discord.js";
import { Config } from "./config.ts"; import { Config } from "./config.ts";
import Logging from "@proxnet/undead-logging"; import Logging from "@proxnet/undead-logging";
import process from "node:process";
const log = new Logging("Discord"); const log = new Logging("Discord");
const config = Config.getConfig(); const config = Config.getConfig();
if (typeof config == 'undefined') { if (typeof config == 'undefined') {
log.e(`Cannot start: Discord configuration is unavailable`); 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] }); 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 * 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 { Redis } from "./db.ts";
import { Discord } from "./discord.ts";
import { APIUtils } from "./apiutils.ts"; import { APIUtils } from "./apiutils.ts";
const log = new Logging("Main"); const log = new Log.default("Main");
log.i(`Starting Galvanic Corrosion..`); log.i(`Starting Galvanic Corrosion..`);
@@ -15,16 +15,44 @@ if (typeof config == 'undefined') {
log.e('Cannot start: Configuration is undefined'); log.e('Cannot start: Configuration is undefined');
Deno.exit(1); 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 port = config.web.port;
const host = config.web.host; const host = config.web.host;
log.i(`Starting HTTP server on http://${host}:${port}`); log.i(`Starting HTTP server on http://${host}:${port}`);
const abortController = new AbortController(); const app = express();
const app = new Application();
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 { try {
log.i(`Connecting to Redis..`); log.i(`Connecting to Redis..`);
@@ -35,18 +63,22 @@ try {
} }
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) { } catch (err) {
log.e(`Cannot start: Network could not be initalized. ${err}`); log.e(`Cannot start: Network could not be initalized. ${err}`);
Deno.exit(1); Deno.exit(1);
} }
Discord.login(); //Discord.login(); do not use for now
let shuttingDown = false;
Deno.addSignalListener('SIGINT', () => {
if (shuttingDown) return;
shuttingDown = true;
log.n(`Shutting down`);
abortController.abort();
});

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