diff --git a/.gitignore b/.gitignore
index 6ad5f79..3d65a9f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -130,4 +130,8 @@ dist
.pnp.*
# galvanic corrosion test builds
-build/
\ No newline at end of file
+build/
+
+# galvanic corrosion local config
+config.json
+rooms.json
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..9a33692
--- /dev/null
+++ b/README.md
@@ -0,0 +1,7 @@
+# Galvanic Corrosion
+delectable acids yum yum
+
+Rec Room server for communities. Fast runtime and easy setup.
+Built for Rec Room version September 7th, 2018 (manifest 7763898423339170417)
+
+
\ No newline at end of file
diff --git a/deno.json b/deno.json
index 42d5491..ef3c2ab 100644
--- a/deno.json
+++ b/deno.json
@@ -1,12 +1,18 @@
{
"tasks": {
- "compile-win": "deno compile --target x86_64-pc-windows-msvc -o build/GalvanicCorrosion.exe --allow-env --allow-sys --allow-net --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-read=. src/main.ts",
- "cross-compile": "deno run compile-win && deno run compile-linux"
+ "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",
+ "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"
},
"imports": {
+ "@oak/oak": "jsr:@oak/oak@^17.1.3",
+ "@proxnet/undead-logging": "jsr:@proxnet/undead-logging@^1.2.0",
"@std/assert": "jsr:@std/assert@1",
- "express": "npm:express@^4.21.1",
- "log-like-a-zombie": "npm:log-like-a-zombie@1.4.0"
+ "@types/express": "npm:@types/express@^5.0.0",
+ "discord.js": "npm:discord.js@^14.16.3",
+ "ioredis": "npm:ioredis@^5.4.1",
+ "validator": "npm:validator@^13.12.0",
+ "why-is-node-running": "npm:why-is-node-running@^3.2.1"
}
}
diff --git a/deno.lock b/deno.lock
index 6be443f..cce1ab5 100644
--- a/deno.lock
+++ b/deno.lock
@@ -1,398 +1,366 @@
{
"version": "4",
"specifiers": {
+ "jsr:@oak/commons@1": "1.0.0",
+ "jsr:@oak/oak@^17.1.3": "17.1.3",
+ "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",
- "npm:express@^4.21.1": "4.21.1",
- "npm:log-like-a-zombie@1.4.0": "1.4.0"
+ "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"
},
"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"
+ ]
+ },
+ "@proxnet/undead-logging@1.2.0": {
+ "integrity": "59a4db428b5b848b7f51189b173b100ddabf7d86bb9de1a095e5d97b4a867e2c",
+ "dependencies": [
+ "npm:chalk"
+ ]
+ },
"@std/assert@1.0.7": {
"integrity": "64ce9fac879e0b9f3042a89b3c3f8ccfc9c984391af19e2087513a79d73e28c3",
"dependencies": [
"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": {
- "accepts@1.3.8": {
- "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
+ "@discordjs/builders@1.9.0": {
+ "integrity": "sha512-0zx8DePNVvQibh5ly5kCEei5wtPBIUbSoE9n+91Rlladz4tgtFbJ36PZMxxZrTEOQ7AHMZ/b0crT/0fCy6FTKg==",
"dependencies": [
- "mime-types",
- "negotiator"
+ "@discordjs/formatters",
+ "@discordjs/util",
+ "@sapphire/shapeshift",
+ "discord-api-types@0.37.97",
+ "fast-deep-equal",
+ "ts-mixer",
+ "tslib"
]
},
- "array-flatten@1.1.1": {
- "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg=="
+ "@discordjs/collection@1.5.3": {
+ "integrity": "sha512-SVb428OMd3WO1paV3rm6tSjM4wC+Kecaa1EUGX7vc6/fddvw/6lg90z4QtCqm21zvVe92vMMDt9+DkIvjXImQQ=="
},
- "body-parser@1.20.3": {
- "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==",
+ "@discordjs/collection@2.1.1": {
+ "integrity": "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg=="
+ },
+ "@discordjs/formatters@0.5.0": {
+ "integrity": "sha512-98b3i+Y19RFq1Xke4NkVY46x8KjJQjldHUuEbCqMvp1F5Iq9HgnGpu91jOi/Ufazhty32eRsKnnzS8n4c+L93g==",
"dependencies": [
- "bytes",
- "content-type",
- "debug",
- "depd",
- "destroy",
- "http-errors",
- "iconv-lite",
- "on-finished",
- "qs",
- "raw-body",
- "type-is",
- "unpipe"
+ "discord-api-types@0.37.97"
]
},
- "bytes@3.1.2": {
- "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="
- },
- "call-bind@1.0.7": {
- "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==",
+ "@discordjs/rest@2.4.0": {
+ "integrity": "sha512-Xb2irDqNcq+O8F0/k/NaDp7+t091p+acb51iA4bCKfIn+WFWd6HrNvcsSbMMxIR9NjcMZS6NReTKygqiQN+ntw==",
"dependencies": [
- "es-define-property",
- "es-errors",
- "function-bind",
- "get-intrinsic",
- "set-function-length"
+ "@discordjs/collection@2.1.1",
+ "@discordjs/util",
+ "@sapphire/async-queue",
+ "@sapphire/snowflake",
+ "@vladfrangu/async_event_emitter",
+ "discord-api-types@0.37.97",
+ "magic-bytes.js",
+ "tslib",
+ "undici"
]
},
+ "@discordjs/util@1.1.1": {
+ "integrity": "sha512-eddz6UnOBEB1oITPinyrB2Pttej49M9FZQY8NxgEvc3tq6ZICZ19m70RsmzRdDHk80O9NoYN/25AqJl8vPVf/g=="
+ },
+ "@discordjs/ws@1.1.1": {
+ "integrity": "sha512-PZ+vLpxGCRtmr2RMkqh8Zp+BenUaJqlS6xhgWKEZcgC/vfHLEzpHtKkB0sl3nZWpwtcKk6YWy+pU3okL2I97FA==",
+ "dependencies": [
+ "@discordjs/collection@2.1.1",
+ "@discordjs/rest",
+ "@discordjs/util",
+ "@sapphire/async-queue",
+ "@types/ws",
+ "@vladfrangu/async_event_emitter",
+ "discord-api-types@0.37.83",
+ "tslib",
+ "ws"
+ ]
+ },
+ "@ioredis/commands@1.2.0": {
+ "integrity": "sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg=="
+ },
+ "@sapphire/async-queue@1.5.5": {
+ "integrity": "sha512-cvGzxbba6sav2zZkH8GPf2oGk9yYoD5qrNWdu9fRehifgnFZJMV+nuy2nON2roRO4yQQ+v7MK/Pktl/HgfsUXg=="
+ },
+ "@sapphire/shapeshift@4.0.0": {
+ "integrity": "sha512-d9dUmWVA7MMiKobL3VpLF8P2aeanRTu6ypG2OIaEv/ZHH/SUQ2iHOVyi5wAPjQ+HmnMuL0whK9ez8I/raWbtIg==",
+ "dependencies": [
+ "fast-deep-equal",
+ "lodash"
+ ]
+ },
+ "@sapphire/snowflake@3.5.3": {
+ "integrity": "sha512-jjmJywLAFoWeBi1W7994zZyiNWPIiqRRNAmSERxyg93xRGzNYvGjlZ0gR6x0F4gPRi2+0O6S71kOZYyr3cxaIQ=="
+ },
+ "@types/body-parser@1.19.5": {
+ "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==",
+ "dependencies": [
+ "@types/connect",
+ "@types/node"
+ ]
+ },
+ "@types/connect@3.4.38": {
+ "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==",
+ "dependencies": [
+ "@types/node"
+ ]
+ },
+ "@types/express-serve-static-core@5.0.1": {
+ "integrity": "sha512-CRICJIl0N5cXDONAdlTv5ShATZ4HEwk6kDDIW2/w9qOWKg+NU/5F8wYRWCrONad0/UKkloNSmmyN/wX4rtpbVA==",
+ "dependencies": [
+ "@types/node",
+ "@types/qs",
+ "@types/range-parser",
+ "@types/send"
+ ]
+ },
+ "@types/express@5.0.0": {
+ "integrity": "sha512-DvZriSMehGHL1ZNLzi6MidnsDhUZM/x2pRdDIKdwbUNqqwHxMlRdkxtn6/EPKyqKpHqTl/4nRZsRNLpZxZRpPQ==",
+ "dependencies": [
+ "@types/body-parser",
+ "@types/express-serve-static-core",
+ "@types/qs",
+ "@types/serve-static"
+ ]
+ },
+ "@types/http-errors@2.0.4": {
+ "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA=="
+ },
+ "@types/mime@1.3.5": {
+ "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w=="
+ },
+ "@types/node@22.5.4": {
+ "integrity": "sha512-FDuKUJQm/ju9fT/SeX/6+gBzoPzlVCzfzmGkwKvRHQVxi4BntVbyIwf6a4Xn62mrvndLiml6z/UBXIdEVjQLXg==",
+ "dependencies": [
+ "undici-types"
+ ]
+ },
+ "@types/qs@6.9.17": {
+ "integrity": "sha512-rX4/bPcfmvxHDv0XjfJELTTr+iB+tn032nPILqHm5wbthUUUuVtNGGqzhya9XUxjTP8Fpr0qYgSZZKxGY++svQ=="
+ },
+ "@types/range-parser@1.2.7": {
+ "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ=="
+ },
+ "@types/send@0.17.4": {
+ "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==",
+ "dependencies": [
+ "@types/mime",
+ "@types/node"
+ ]
+ },
+ "@types/serve-static@1.15.7": {
+ "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==",
+ "dependencies": [
+ "@types/http-errors",
+ "@types/node",
+ "@types/send"
+ ]
+ },
+ "@types/ws@8.5.13": {
+ "integrity": "sha512-osM/gWBTPKgHV8XkTunnegTRIsvF6owmf5w+JtAfOw472dptdm0dlGv4xCt6GwQRcC2XVOvvRE/0bAoQcL2QkA==",
+ "dependencies": [
+ "@types/node"
+ ]
+ },
+ "@vladfrangu/async_event_emitter@2.4.6": {
+ "integrity": "sha512-RaI5qZo6D2CVS6sTHFKg1v5Ohq/+Bo2LZ5gzUEwZ/WkHhwtGTCB/sVLw8ijOkAUxasZ+WshN/Rzj4ywsABJ5ZA=="
+ },
"chalk@5.3.0": {
"integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w=="
},
- "content-disposition@0.5.4": {
- "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
+ "cluster-key-slot@1.1.2": {
+ "integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA=="
+ },
+ "debug@4.3.7": {
+ "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
"dependencies": [
- "safe-buffer"
+ "ms"
]
},
- "content-type@1.0.5": {
- "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="
+ "denque@2.1.0": {
+ "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw=="
},
- "cookie-signature@1.0.6": {
- "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ=="
+ "discord-api-types@0.37.100": {
+ "integrity": "sha512-a8zvUI0GYYwDtScfRd/TtaNBDTXwP5DiDVX7K5OmE+DRT57gBqKnwtOC5Ol8z0mRW8KQfETIgiB8U0YZ9NXiCA=="
},
- "cookie@0.7.1": {
- "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w=="
+ "discord-api-types@0.37.83": {
+ "integrity": "sha512-urGGYeWtWNYMKnYlZnOnDHm8fVRffQs3U0SpE8RHeiuLKb/u92APS8HoQnPTFbnXmY1vVnXjXO4dOxcAn3J+DA=="
},
- "debug@2.6.9": {
- "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "discord-api-types@0.37.97": {
+ "integrity": "sha512-No1BXPcVkyVD4ZVmbNgDKaBoqgeQ+FJpzZ8wqHkfmBnTZig1FcH3iPPersiK1TUIAzgClh2IvOuVUYfcWLQAOA=="
+ },
+ "discord.js@14.16.3": {
+ "integrity": "sha512-EPCWE9OkA9DnFFNrO7Kl1WHHDYFXu3CNVFJg63bfU7hVtjZGyhShwZtSBImINQRWxWP2tgo2XI+QhdXx28r0aA==",
"dependencies": [
- "ms@2.0.0"
+ "@discordjs/builders",
+ "@discordjs/collection@1.5.3",
+ "@discordjs/formatters",
+ "@discordjs/rest",
+ "@discordjs/util",
+ "@discordjs/ws",
+ "@sapphire/snowflake",
+ "discord-api-types@0.37.100",
+ "fast-deep-equal",
+ "lodash.snakecase",
+ "tslib",
+ "undici"
]
},
- "define-data-property@1.1.4": {
- "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
+ "fast-deep-equal@3.1.3": {
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
+ },
+ "ioredis@5.4.1": {
+ "integrity": "sha512-2YZsvl7jopIa1gaePkeMtd9rAcSjOOjPtpcLlOeusyO+XH2SK5ZcT+UCrElPP+WVIInh2TzeI4XW9ENaSLVVHA==",
"dependencies": [
- "es-define-property",
- "es-errors",
- "gopd"
- ]
- },
- "depd@2.0.0": {
- "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="
- },
- "destroy@1.2.0": {
- "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg=="
- },
- "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.0": {
- "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==",
- "dependencies": [
- "get-intrinsic"
- ]
- },
- "es-errors@1.3.0": {
- "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="
- },
- "escape-html@1.0.3": {
- "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="
- },
- "etag@1.8.1": {
- "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="
- },
- "express@4.21.1": {
- "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==",
- "dependencies": [
- "accepts",
- "array-flatten",
- "body-parser",
- "content-disposition",
- "content-type",
- "cookie",
- "cookie-signature",
+ "@ioredis/commands",
+ "cluster-key-slot",
"debug",
- "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"
+ "denque",
+ "lodash.defaults",
+ "lodash.isarguments",
+ "redis-errors",
+ "redis-parser",
+ "standard-as-callback"
]
},
- "finalhandler@1.3.1": {
- "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==",
- "dependencies": [
- "debug",
- "encodeurl@2.0.0",
- "escape-html",
- "on-finished",
- "parseurl",
- "statuses",
- "unpipe"
- ]
+ "lodash.defaults@4.2.0": {
+ "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ=="
},
- "forwarded@0.2.0": {
- "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="
+ "lodash.isarguments@3.1.0": {
+ "integrity": "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg=="
},
- "fresh@0.5.2": {
- "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q=="
+ "lodash.snakecase@4.1.1": {
+ "integrity": "sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw=="
},
- "function-bind@1.1.2": {
- "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="
+ "lodash@4.17.21": {
+ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
},
- "get-intrinsic@1.2.4": {
- "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==",
- "dependencies": [
- "es-errors",
- "function-bind",
- "has-proto",
- "has-symbols",
- "hasown"
- ]
- },
- "gopd@1.0.1": {
- "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==",
- "dependencies": [
- "get-intrinsic"
- ]
- },
- "has-property-descriptors@1.0.2": {
- "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
- "dependencies": [
- "es-define-property"
- ]
- },
- "has-proto@1.0.3": {
- "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q=="
- },
- "has-symbols@1.0.3": {
- "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A=="
- },
- "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=="
- },
- "ipaddr.js@1.9.1": {
- "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="
- },
- "log-like-a-zombie@1.4.0": {
- "integrity": "sha512-XZD8eDAhKhAKHcd6aGVwNiOXMo9L/TQLY0Fg+cAKa/OhG1vNcJTsJLf4ew33N5UgNIul2Plonrw+vPXjuMFG7Q==",
- "dependencies": [
- "chalk"
- ]
- },
- "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=="
+ "magic-bytes.js@1.10.0": {
+ "integrity": "sha512-/k20Lg2q8LE5xiaaSkMXk4sfvI+9EGEykFS4b0CHHGWqDYU0bGUFSwchNOMA56D7TCs9GwVTkqe9als1/ns8UQ=="
},
"ms@2.1.3": {
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
},
- "negotiator@0.6.3": {
- "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="
+ "path-to-regexp@6.2.1": {
+ "integrity": "sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw=="
},
- "object-inspect@1.13.3": {
- "integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA=="
+ "redis-errors@1.2.0": {
+ "integrity": "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w=="
},
- "on-finished@2.4.1": {
- "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
+ "redis-parser@3.0.0": {
+ "integrity": "sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==",
"dependencies": [
- "ee-first"
+ "redis-errors"
]
},
- "parseurl@1.3.3": {
- "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="
+ "standard-as-callback@2.1.0": {
+ "integrity": "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A=="
},
- "path-to-regexp@0.1.10": {
- "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w=="
+ "ts-mixer@6.0.4": {
+ "integrity": "sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA=="
},
- "proxy-addr@2.0.7": {
- "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
- "dependencies": [
- "forwarded",
- "ipaddr.js"
- ]
+ "tslib@2.8.1": {
+ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="
},
- "qs@6.13.0": {
- "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
- "dependencies": [
- "side-channel"
- ]
+ "undici-types@6.19.8": {
+ "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw=="
},
- "range-parser@1.2.1": {
- "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="
+ "undici@6.19.8": {
+ "integrity": "sha512-U8uCCl2x9TK3WANvmBavymRzxbfFYG+tAu+fgx3zxQy3qdagQqBLwJVrdyO1TBfUXvfKveMKJZhpvUYoOjM+4g=="
},
- "raw-body@2.5.2": {
- "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==",
- "dependencies": [
- "bytes",
- "http-errors",
- "iconv-lite",
- "unpipe"
- ]
+ "validator@13.12.0": {
+ "integrity": "sha512-c1Q0mCiPlgdTVVVIJIrBuxNicYE+t/7oKeI9MWLj3fh/uq2Pxh/3eeWbVZ4OcGW1TUf53At0njHw5SMdA3tmMg=="
},
- "safe-buffer@5.2.1": {
- "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="
+ "why-is-node-running@3.2.1": {
+ "integrity": "sha512-Tb2FUhB4vUsGQlfSquQLYkApkuPAFQXGFzxWKHHumVz2dK+X1RUm/HnID4+TfIGYJ1kTcwOaCk/buYCEJr6YjQ=="
},
- "safer-buffer@2.1.2": {
- "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
- },
- "send@0.19.0": {
- "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==",
- "dependencies": [
- "debug",
- "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"
- ]
- },
- "set-function-length@1.2.2": {
- "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==",
- "dependencies": [
- "define-data-property",
- "es-errors",
- "function-bind",
- "get-intrinsic",
- "gopd",
- "has-property-descriptors"
- ]
- },
- "setprototypeof@1.2.0": {
- "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
- },
- "side-channel@1.0.6": {
- "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==",
- "dependencies": [
- "call-bind",
- "es-errors",
- "get-intrinsic",
- "object-inspect"
- ]
- },
- "statuses@2.0.1": {
- "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ=="
- },
- "toidentifier@1.0.1": {
- "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="
- },
- "type-is@1.6.18": {
- "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
- "dependencies": [
- "media-typer",
- "mime-types"
- ]
- },
- "unpipe@1.0.0": {
- "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="
- },
- "utils-merge@1.0.1": {
- "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA=="
- },
- "vary@1.1.2": {
- "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="
+ "ws@8.18.0": {
+ "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw=="
}
},
"workspace": {
"dependencies": [
+ "jsr:@oak/oak@^17.1.3",
+ "jsr:@proxnet/undead-logging@^1.2.0",
"jsr:@std/assert@1",
- "npm:express@^4.21.1",
- "npm:log-like-a-zombie@1.4.0"
+ "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"
]
}
}
diff --git a/galv4.jpg b/galv4.jpg
new file mode 100644
index 0000000..807281e
Binary files /dev/null and b/galv4.jpg differ
diff --git a/src/apiutils.ts b/src/apiutils.ts
new file mode 100644
index 0000000..de603b2
--- /dev/null
+++ b/src/apiutils.ts
@@ -0,0 +1,88 @@
+import { Context, Next } from "@oak/oak";
+import Logging from "@proxnet/undead-logging";
+
+const log = new Logging('APIUtils');
+
+export function generateRandomString(length: number) {
+ const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
+ let randomString = '';
+
+ for (let i = 0; i < length; i++) {
+ const randomIndex = Math.floor(Math.random() * characters.length);
+ randomString += characters.charAt(randomIndex);
+ }
+
+ return randomString;
+}
+
+const instanceId = generateRandomString(128);
+
+export function checkQueryTypes(typeDef: T) {
+ return (ctx: Context, nxt: Next) => {
+ 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."));
+ return;
+ }
+ }
+ nxt();
+ };
+}
+export function checkBodyTypes(typeDef: T) {
+ return async (ctx: Context, nxt: Next) => {
+ for (const key in typeDef) {
+ if (typeof (await ctx.request.body.json())[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."));
+ return;
+ }
+ }
+ nxt();
+ };
+}
+
+export function genericResponseFormat(failure: boolean, msg: string | null = null, data = null) {
+ 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 });
+ };
+}
+type RecNetResponse = {
+ Success: boolean,
+ Message: string
+};
+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);
+ }
+}
+
+export async function logBody(ctx: Context, nxt: Next) {
+ nxt();
+ log.d(`Request body: ${JSON.stringify(await ctx.request.body.text())}`);
+}
+
+export function emptyArrayResponse(ctx: Context) {
+ setContentType(ctx, 'application/json');
+ ctx.response.body = JSON.stringify([]);
+}
+
+export function setJSONBody(ctx: Context, obj: object) {
+ ctx.response.type = 'json';
+ ctx.response.body = JSON.stringify(obj);
+}
+
+export function setContentType(ctx: Context, type: string) {
+ ctx.response.headers.set('Content-Type', type);
+}
+
+export * as APIUtils from "./apiutils.ts"
\ No newline at end of file
diff --git a/src/config.ts b/src/config.ts
new file mode 100644
index 0000000..3d07aeb
--- /dev/null
+++ b/src/config.ts
@@ -0,0 +1,110 @@
+import Logging from "@proxnet/undead-logging";
+import * as fs from "node:fs";
+import process from "node:process";
+
+const log = new Logging("Config");
+
+type RedisConfiguration = {
+ host: string,
+ port: number,
+ username: string,
+ password: string,
+ db: number
+}
+
+type WebConfiguration = {
+ port: number,
+ host: string
+}
+
+type PublicConfiguration = {
+ serverName: string,
+ owner: string,
+ motd: string
+}
+
+type LoggingConfiguration = {
+ debug: boolean,
+ network: boolean
+}
+
+type DiscordConfiguration = {
+ token: string,
+ clientId: string,
+ guildId: string
+}
+
+type GalvanicConfiguration = {
+ redis: RedisConfiguration,
+ web: WebConfiguration,
+ public: PublicConfiguration,
+ logging: LoggingConfiguration,
+ discord: DiscordConfiguration
+}
+
+const defaultConfig: GalvanicConfiguration = {
+ redis: {
+ host: "127.0.0.1",
+ port: 6379,
+ username: "",
+ password: "",
+ db: 0
+ },
+ web: {
+ port: 3000,
+ host: "127.0.0.1"
+ },
+ public: {
+ serverName: "Galvanic Corrosion",
+ owner: "John Doe",
+ motd: "The narwhal bacons at midnight"
+ },
+ logging: {
+ debug: false,
+ network: false
+ },
+ discord: {
+ token: "replace-me",
+ guildId: "replace-me",
+ clientId: "replace-me"
+ }
+}
+
+/** The current configuration. Read and parsed only during startup. */
+let config: GalvanicConfiguration | undefined;
+try {
+ if (!configurationExists()) generateDefaultConfig();
+ 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;
+ }
+}
+
+/** Does the configuration file exist on the disk? */
+export function configurationExists() {
+ return fs.existsSync('./config.json');
+}
+
+/** Place the default configuration in the current directory. */
+export function generateDefaultConfig() {
+ fs.writeFileSync('./config.json', JSON.stringify(defaultConfig, undefined, ' '));
+}
+
+/** Get current server configuration */
+export function getConfig() {
+ return config;
+}
+
+export * as Config from './config.ts';
\ No newline at end of file
diff --git a/src/db.ts b/src/db.ts
new file mode 100644
index 0000000..b3a9c66
--- /dev/null
+++ b/src/db.ts
@@ -0,0 +1,47 @@
+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);
+}
+
+let shuttingDown = false;
+Deno.addSignalListener('SIGINT', () => {
+ if (shuttingDown) return;
+ shuttingDown = true;
+ log.n('Disconnecting from Redis');
+ if (typeof Database !== 'undefined') Database.quit();
+});
+
+export let Database: Redis | undefined;
+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
+ });
+ log.i(`Connected to Redis`);
+}
+
+export function buildKey(...args: string[]) {
+ return args.join(':');
+}
+export const KeyGroups = {
+ Accounts: {
+ Ids: "account-ids",
+ Usernames: "account-usernames",
+ DisplayNames: "account-displaynames",
+ XP: "account-scores",
+ Developers: "account-developers",
+ ProfileImages: "account-images"
+ }
+}
+export * as Redis from "./db.ts";
\ No newline at end of file
diff --git a/src/discord.ts b/src/discord.ts
new file mode 100644
index 0000000..91bfe7b
--- /dev/null
+++ b/src/discord.ts
@@ -0,0 +1,34 @@
+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);
+}
+
+export const client = new discord.Client({ intents: [discord.GatewayIntentBits.Guilds, discord.GatewayIntentBits.GuildPresences] });
+
+client.once(discord.Events.ClientReady, client => {
+ log.i(`Logged in to Discord as "${client.user.tag}"`);
+ client.user?.setActivity(config?.public.motd, { type: discord.ActivityType.Custom });
+});
+
+let shuttingDown = false;
+Deno.addSignalListener('SIGINT', () => {
+ if (shuttingDown) return;
+ shuttingDown = true;
+ log.n('Disconnecting from Discord');
+ client.destroy();
+});
+
+export function login() {
+ log.i(`Creating Discord connection..`);
+ client.login(config?.discord.token);
+}
+
+export * as Discord from "./discord.ts";
\ No newline at end of file
diff --git a/src/main.ts b/src/main.ts
index 92918d9..579067d 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -1,22 +1,52 @@
-import Logging from "log-like-a-zombie";
-import express from "express";
+import Logging from "@proxnet/undead-logging";
+import * as Config from "./config.ts";
+import { Application, Router } from "@oak/oak";
+import { Redis } from "./db.ts";
+import { Discord } from "./discord.ts";
+import { APIUtils } from "./apiutils.ts";
const log = new Logging("Main");
-const port = 3000;
-const address = "127.0.0.1";
+log.i(`Starting Galvanic Corrosion..`);
-log.i(`Starting HTTP server on http://${address}:${port}`);
+const config = Config.getConfig();
-const app = express();
-app.disable('etag');
-app.disable('x-powered-by');
+if (typeof config == 'undefined') {
+ log.e('Cannot start: Configuration is undefined');
+ Deno.exit(1);
+}
-app.use((rq: express.Request, rs: express.Response) => {
- log.n(`${rq.ip} ${rq.method} ${rq.originalUrl}`);
- rs.sendStatus(200);
-});
+const port = config.web.port;
+const host = config.web.host;
-app.listen(port, address, () => {
- log.i(`Listening on http://${address}:${port}`);
+log.i(`Starting HTTP server on http://${host}:${port}`);
+
+const abortController = new AbortController();
+const app = new Application();
+
+app.use(new Router().all('/', APIUtils.genericResponse(false, `${config?.public.serverName} - ${config?.public.motd}`)).routes());
+
+try {
+ log.i(`Connecting to Redis..`);
+ Redis.connectToRedis();
+} catch (err) {
+ log.e(`Cannot start: Redis could not be initialized. ${err}`);
+ Deno.exit(1);
+}
+
+try {
+ app.listen({port: port, hostname: host, signal: abortController.signal });
+} 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